Merge "Prepare to restrict PI sender BAL privileges."
diff --git a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
new file mode 100644
index 0000000..3d447ac
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 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.input
+
+import android.content.Context
+import android.content.res.Resources
+import android.os.SystemProperties
+import android.perftests.utils.PerfStatusReporter
+import android.view.InputDevice
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.PointerCoords
+import android.view.MotionEvent.PointerProperties
+import android.view.MotionPredictor
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.filters.LargeTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+
+import java.time.Duration
+
+private fun getStylusMotionEvent(
+        eventTime: Duration,
+        action: Int,
+        x: Float,
+        y: Float,
+        ): MotionEvent{
+    val pointerCount = 1
+    val properties = arrayOfNulls<MotionEvent.PointerProperties>(pointerCount)
+    val coords = arrayOfNulls<MotionEvent.PointerCoords>(pointerCount)
+
+    for (i in 0 until pointerCount) {
+        properties[i] = PointerProperties()
+        properties[i]!!.id = i
+        properties[i]!!.toolType = MotionEvent.TOOL_TYPE_STYLUS
+        coords[i] = PointerCoords()
+        coords[i]!!.x = x
+        coords[i]!!.y = y
+    }
+
+    return MotionEvent.obtain(/*downTime=*/0, eventTime.toMillis(), action, properties.size,
+                properties, coords, /*metaState=*/0, /*buttonState=*/0,
+                /*xPrecision=*/0f, /*yPrecision=*/0f, /*deviceId=*/0, /*edgeFlags=*/0,
+                InputDevice.SOURCE_STYLUS, /*flags=*/0)
+}
+
+private fun getPredictionContext(offset: Duration, enablePrediction: Boolean): Context {
+    val context = mock(Context::class.java)
+    val resources: Resources = mock(Resources::class.java)
+    `when`(context.getResources()).thenReturn(resources)
+    `when`(resources.getInteger(
+            com.android.internal.R.integer.config_motionPredictionOffsetNanos)).thenReturn(
+                offset.toNanos().toInt())
+    `when`(resources.getBoolean(
+            com.android.internal.R.bool.config_enableMotionPrediction)).thenReturn(enablePrediction)
+    return context
+}
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class MotionPredictorBenchmark {
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+    @get:Rule
+    val perfStatusReporter = PerfStatusReporter()
+    private val initialPropertyValue =
+            SystemProperties.get("persist.input.enable_motion_prediction")
+
+    private var eventTime = Duration.ofMillis(1)
+
+    @Before
+    fun setUp() {
+        instrumentation.uiAutomation.executeShellCommand(
+            "setprop persist.input.enable_motion_prediction true")
+    }
+
+    @After
+    fun tearDown() {
+        instrumentation.uiAutomation.executeShellCommand(
+            "setprop persist.input.enable_motion_prediction $initialPropertyValue")
+    }
+
+    /**
+     * In a typical usage, app will send the event to the predictor and then call .predict to draw
+     * a prediction. In a loop, we keep sending MOVE and then calling .predict to simulate this.
+     */
+    @Test
+    fun timeRecordAndPredict() {
+        val offset = Duration.ofMillis(1)
+        val predictor = MotionPredictor(getPredictionContext(offset, /*enablePrediction=*/true))
+        // ACTION_DOWN t=0 x=0 y=0
+        predictor.record(getStylusMotionEvent(eventTime, ACTION_DOWN, /*x=*/0f, /*y=*/0f))
+
+        val state = perfStatusReporter.getBenchmarkState()
+        while (state.keepRunning()) {
+            eventTime += Duration.ofMillis(1)
+
+            // Send MOVE event and then call .predict
+            val moveEvent = getStylusMotionEvent(eventTime, ACTION_MOVE, /*x=*/1f, /*y=*/2f)
+            predictor.record(moveEvent)
+            val predictionTime = eventTime + Duration.ofMillis(2)
+            val predicted = predictor.predict(predictionTime.toNanos())
+            assertEquals(1, predicted.size)
+            assertEquals((predictionTime + offset).toMillis(), predicted[0].eventTime)
+        }
+    }
+
+    /**
+     * The creation of the predictor should happen infrequently. However, we still want to be
+     * mindful of the load times.
+     */
+    @Test
+    fun timeCreatePredictor() {
+        val context = getPredictionContext(
+                /*offset=*/Duration.ofMillis(1), /*enablePrediction=*/true)
+
+        val state = perfStatusReporter.getBenchmarkState()
+        while (state.keepRunning()) {
+            MotionPredictor(context)
+        }
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/input/OWNERS b/apct-tests/perftests/core/src/android/input/OWNERS
new file mode 100644
index 0000000..95e3f02
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/input/OWNERS
@@ -0,0 +1,3 @@
+include platform/frameworks/base:/INPUT_OWNERS
+
+# Bug component: 136048
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index 1cd5d96..a976de3 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -19,6 +19,8 @@
 import android.graphics.Canvas;
 import android.graphics.RecordingCanvas;
 import android.graphics.RenderNode;
+import android.graphics.text.LineBreakConfig;
+import android.os.LocaleList;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 
@@ -43,6 +45,19 @@
 
     public StaticLayoutPerfTest() {}
 
+    public static final String JP_TEXT_SHORT = "日本語でのパフォーマンス計測のための例文です。";
+    // About 350 chars
+    public static final String JP_TEXT_LONG = "日本語でのパフォーマンス計測のための文章ですが、長いです。"
+            + "長い文章が必要なのですが、特に書くことが思いつかないので、コロッケの作り方でも書こうと思います。"
+            + "じゃがいもを茹でて潰しておきます。私は少し形が残っているほうが好きなので、ある程度のところで潰すのを"
+            + "やめます。別のフライパンで軽く塩をして玉ねぎのみじん切りを炒め、透き通ったら、一度取り出します。"
+            + "きれいにしたフライパンに、豚ひき肉を入れてあまりイジらずに豚肉を炒めます。"
+            + "しっかり火が通ったら炒めた玉ねぎを戻し入れ、塩コショウで味を決めます。"
+            + "炒めた肉玉ねぎとじゃがいもをよく混ぜて、1個あたり100gになるように整形します。"
+            + "整形したタネに小麦粉、卵、パン粉をつけて揚げます。"
+            + "180℃で揚げ、衣がきつね色になったら引き上げて、油を切る。"
+            + "盛り付けて出来上がり。";
+
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
@@ -432,4 +447,117 @@
         }
     }
 
+    @Test
+    public void testCreate_JPText_Phrase_Short() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final String text = JP_TEXT_SHORT;
+        final LineBreakConfig config = new LineBreakConfig.Builder()
+                .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+                .build();
+        final TextPaint paint = new TextPaint(PAINT);
+        paint.setTextLocales(LocaleList.forLanguageTags("ja-JP"));
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+            StaticLayout.Builder.obtain(text, 0, text.length(), paint, TEXT_WIDTH)
+                    .setLineBreakConfig(config)
+                    .build();
+        }
+    }
+
+    @Test
+    public void testCreate_JPText_Phrase_Long() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final String text = JP_TEXT_LONG;
+        final LineBreakConfig config = new LineBreakConfig.Builder()
+                .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+                .build();
+        final TextPaint paint = new TextPaint(PAINT);
+        paint.setTextLocales(LocaleList.forLanguageTags("ja-JP"));
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+            StaticLayout.Builder.obtain(text, 0, text.length(), paint, TEXT_WIDTH)
+                    .setLineBreakConfig(config)
+                    .build();
+        }
+    }
+
+    @Test
+    public void testCreate_JPText_Phrase_LongLong() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final String text = JP_TEXT_LONG.repeat(20);  // 250 * 20 = 7000 chars
+        final LineBreakConfig config = new LineBreakConfig.Builder()
+                .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+                .build();
+        final TextPaint paint = new TextPaint(PAINT);
+        paint.setTextLocales(LocaleList.forLanguageTags("ja-JP"));
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+            StaticLayout.Builder.obtain(text, 0, text.length(), paint, TEXT_WIDTH)
+                    .setLineBreakConfig(config)
+                    .build();
+        }
+    }
+
+    @Test
+    public void testCreate_JPText_NoPhrase_Short() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final String text = JP_TEXT_SHORT;
+        final LineBreakConfig config = new LineBreakConfig.Builder()
+                .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+                .build();
+        final TextPaint paint = new TextPaint(PAINT);
+        paint.setTextLocales(LocaleList.forLanguageTags("ja-JP"));
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+            StaticLayout.Builder.obtain(text, 0, text.length(), paint, TEXT_WIDTH)
+                    .setLineBreakConfig(config)
+                    .build();
+        }
+    }
+
+    @Test
+    public void testCreate_JPText_NoPhrase_Long() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final String text = JP_TEXT_LONG;
+        final LineBreakConfig config = new LineBreakConfig.Builder()
+                .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+                .build();
+        final TextPaint paint = new TextPaint(PAINT);
+        paint.setTextLocales(LocaleList.forLanguageTags("ja-JP"));
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+            StaticLayout.Builder.obtain(text, 0, text.length(), paint, TEXT_WIDTH)
+                    .setLineBreakConfig(config)
+                    .build();
+        }
+    }
+
+    @Test
+    public void testCreate_JPText_NoPhrase_LongLong() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final String text = JP_TEXT_LONG.repeat(20);  // 250 * 20 = 7000 chars
+        final LineBreakConfig config = new LineBreakConfig.Builder()
+                .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+                .build();
+        final TextPaint paint = new TextPaint(PAINT);
+        paint.setTextLocales(LocaleList.forLanguageTags("ja-JP"));
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+            StaticLayout.Builder.obtain(text, 0, text.length(), paint, TEXT_WIDTH)
+                    .setLineBreakConfig(config)
+                    .build();
+        }
+    }
 }
diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml
index 5befa1f..424d784 100644
--- a/apct-tests/perftests/multiuser/AndroidManifest.xml
+++ b/apct-tests/perftests/multiuser/AndroidManifest.xml
@@ -15,8 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.perftests.multiuser">
-
+    package="com.android.perftests.multiuser">
 
     <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
     <uses-permission android:name="android.permission.DEVICE_POWER" />
@@ -27,6 +26,7 @@
     <uses-permission android:name="android.permission.REAL_GET_TASKS" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.SET_WALLPAPER" />
 
     <application>
         <uses-library android:name="android.test.runner" />
@@ -38,5 +38,4 @@
     <queries>
         <package android:name="perftests.multiuser.apps.dummyapp" />
     </queries>
-
 </manifest>
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index b24076a..64ba6d1 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -15,7 +15,11 @@
  */
 package android.multiuser;
 
+import static android.app.WallpaperManager.FLAG_LOCK;
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import android.annotation.NonNull;
@@ -25,6 +29,7 @@
 import android.app.IActivityManager;
 import android.app.IStopUserCallback;
 import android.app.WaitResult;
+import android.app.WallpaperManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -34,6 +39,7 @@
 import android.content.pm.IPackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IProgressListener;
@@ -59,6 +65,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -114,6 +121,7 @@
     private ActivityManager mAm;
     private IActivityManager mIam;
     private PackageManager mPm;
+    private WallpaperManager mWm;
     private ArrayList<Integer> mUsersToRemove;
     private boolean mHasManagedUserFeature;
     private BroadcastWaiter mBroadcastWaiter;
@@ -132,6 +140,7 @@
         mIam = ActivityManager.getService();
         mUsersToRemove = new ArrayList<>();
         mPm = context.getPackageManager();
+        mWm = WallpaperManager.getInstance(context);
         mHasManagedUserFeature = mPm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
         mBroadcastWaiter = new BroadcastWaiter(context, TAG, TIMEOUT_IN_SECOND,
                 Intent.ACTION_USER_STARTED,
@@ -241,24 +250,27 @@
 
     /**
      * Tests starting an uninitialized user, with wait times in between iterations.
-     * Measures the time until ACTION_USER_STARTED is received.
+     * Measures the time until the ProgressListener callback.
      */
     @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
     public void startUser_realistic() throws RemoteException {
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
             final int userId = createUserNoFlags();
+            final ProgressWaiter waiter = new ProgressWaiter();
 
             waitForBroadcastIdle();
-            runThenWaitForBroadcasts(userId, () -> {
-                mRunner.resumeTiming();
-                Log.i(TAG, "Starting timer");
+            mRunner.resumeTiming();
+            Log.i(TAG, "Starting timer");
 
-                mIam.startUserInBackground(userId);
-            }, Intent.ACTION_USER_STARTED);
+            final boolean success = mIam.startUserInBackgroundWithListener(userId, waiter)
+                    && waiter.waitForFinish(TIMEOUT_IN_SECOND * 1000);
 
             mRunner.pauseTiming();
             Log.i(TAG, "Stopping timer");
+
+            assertTrue("Error: could not start user " + userId, success);
+
             removeUser(userId);
             waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
@@ -372,6 +384,32 @@
         removeUser(testUser);
     }
 
+    /** Tests switching to a previously-started, but no-longer-running, user with wait
+     * times between iterations and using a static wallpaper */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void switchUser_stopped_staticWallpaper() throws RemoteException {
+        assumeTrue(mWm.isWallpaperSupported() && mWm.isSetWallpaperAllowed());
+        final int startUser = ActivityManager.getCurrentUser();
+        final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true,
+                /* useStaticWallpaper */true);
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            waitCoolDownPeriod();
+            Log.d(TAG, "Starting timer");
+            mRunner.resumeTiming();
+
+            switchUser(testUser);
+
+            mRunner.pauseTiming();
+            Log.d(TAG, "Stopping timer");
+            switchUserNoCheck(startUser);
+            stopUserAfterWaitingForBroadcastIdle(testUser, true);
+            attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
+            mRunner.resumeTimingForNextIteration();
+        }
+        removeUser(testUser);
+    }
+
     /** Tests switching to an already-created already-running non-owner background user. */
     @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
     public void switchUser_running() throws RemoteException {
@@ -415,6 +453,31 @@
         removeUser(testUser);
     }
 
+    /** Tests switching to an already-created already-running non-owner background user, with wait
+     * times between iterations and using a default static wallpaper */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void switchUser_running_staticWallpaper() throws RemoteException {
+        assumeTrue(mWm.isWallpaperSupported() && mWm.isSetWallpaperAllowed());
+        final int startUser = ActivityManager.getCurrentUser();
+        final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ false,
+                /* useStaticWallpaper */ true);
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            waitCoolDownPeriod();
+            Log.d(TAG, "Starting timer");
+            mRunner.resumeTiming();
+
+            switchUser(testUser);
+
+            mRunner.pauseTiming();
+            Log.d(TAG, "Stopping timer");
+            waitForBroadcastIdle();
+            switchUserNoCheck(startUser);
+            mRunner.resumeTimingForNextIteration();
+        }
+        removeUser(testUser);
+    }
+
     /** Tests stopping a background user. */
     @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
     public void stopUser() throws RemoteException {
@@ -843,14 +906,20 @@
         waitForLatch("Failed to properly stop user " + userId, latch);
     }
 
+    private int initializeNewUserAndSwitchBack(boolean stopNewUser) throws RemoteException {
+        return initializeNewUserAndSwitchBack(stopNewUser, /* useStaticWallpaper */ false);
+    }
+
     /**
      * Creates a user and waits for its ACTION_USER_UNLOCKED.
      * Then switches to back to the original user and waits for its switchUser() to finish.
      *
      * @param stopNewUser whether to stop the new user after switching to otherUser.
+     * @param useStaticWallpaper whether to switch the wallpaper of the default user to a static.
      * @return userId of the newly created user.
      */
-    private int initializeNewUserAndSwitchBack(boolean stopNewUser) throws RemoteException {
+    private int initializeNewUserAndSwitchBack(boolean stopNewUser, boolean useStaticWallpaper)
+            throws RemoteException {
         final int origUser = mAm.getCurrentUser();
         // First, create and switch to testUser, waiting for its ACTION_USER_UNLOCKED
         final int testUser = createUserNoFlags();
@@ -858,6 +927,17 @@
             mAm.switchUser(testUser);
         }, Intent.ACTION_USER_UNLOCKED, Intent.ACTION_MEDIA_MOUNTED);
 
+        if (useStaticWallpaper) {
+            assertTrue(mWm.isWallpaperSupported() && mWm.isSetWallpaperAllowed());
+            try {
+                Bitmap blank = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+                mWm.setBitmap(blank, /* visibleCropHint */ null, /* allowBackup */ true,
+                        /* which */ FLAG_SYSTEM | FLAG_LOCK, testUser);
+            } catch (IOException exception) {
+                fail("Unable to set static wallpaper.");
+            }
+        }
+
         // Second, switch back to origUser, waiting merely for switchUser() to finish
         switchUser(origUser);
         attestTrue("Didn't switch back to user, " + origUser, origUser == mAm.getCurrentUser());
diff --git a/boot/Android.bp b/boot/Android.bp
index c9a3bd0..d4a6500 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -23,6 +23,18 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+soong_config_module_type {
+    name: "custom_platform_bootclasspath",
+    module_type: "platform_bootclasspath",
+    config_namespace: "AUTO",
+    bool_variables: [
+        "car_bootclasspath_fragment",
+    ],
+    properties: [
+        "fragments",
+    ],
+}
+
 // This module provides access to information Soong has related to the
 // whole platform bootclasspath. Currently, that information is provided solely
 // through configuration but additional information will be added here.
@@ -41,7 +53,7 @@
 //
 // This module needs to be present in the build for the above processing to be
 // done correctly.
-platform_bootclasspath {
+custom_platform_bootclasspath {
     name: "platform-bootclasspath",
 
     // The bootclasspath_fragments that contribute to the platform
@@ -127,17 +139,24 @@
             apex: "com.android.wifi",
             module: "com.android.wifi-bootclasspath-fragment",
         },
-        // only used for auto
-        {
-            apex: "com.android.car.framework",
-            module: "com.android.car.framework-bootclasspath-fragment",
-        },
         {
             apex: "com.android.virt",
             module: "com.android.virt-bootclasspath-fragment",
         },
     ],
 
+    soong_config_variables: {
+        car_bootclasspath_fragment: {
+            fragments: [
+                // only used for auto
+                {
+                    apex: "com.android.car.framework",
+                    module: "com.android.car.framework-bootclasspath-fragment",
+                },
+            ],
+        },
+    },
+
     // Additional information needed by hidden api processing.
     hidden_api: {
         unsupported: [
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 6998081..ed717c4 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -195,35 +195,10 @@
             return;
         }
 
-        if ("scheduling".equals(op)) {
-            setSchedulingEnabled(userId);
-            return;
-        }
-
         System.err.println("Unknown command");
         showUsage();
     }
 
-    private void setSchedulingEnabled(int userId) {
-        String arg = nextArg();
-        if (arg == null) {
-            showUsage();
-            return;
-        }
-
-        try {
-            boolean enable = Boolean.parseBoolean(arg);
-            mBmgr.setFrameworkSchedulingEnabledForUser(userId, enable);
-            System.out.println(
-                    "Backup scheduling is now "
-                            + (enable ? "enabled" : "disabled")
-                            + " for user "
-                            + userId);
-        } catch (RemoteException e) {
-            handleRemoteException(e);
-        }
-    }
-
     private void handleRemoteException(RemoteException e) {
         System.err.println(e.toString());
         System.err.println(BMGR_NOT_RUNNING_ERR);
@@ -969,7 +944,6 @@
         System.err.println("       bmgr activate BOOL");
         System.err.println("       bmgr activated");
         System.err.println("       bmgr autorestore BOOL");
-        System.err.println("       bmgr scheduling BOOL");
         System.err.println("");
         System.err.println("The '--user' option specifies the user on which the operation is run.");
         System.err.println("It must be the first argument before the operation.");
@@ -1047,9 +1021,6 @@
         System.err.println("");
         System.err.println("The 'autorestore' command enables or disables automatic restore when");
         System.err.println("a new package is installed.");
-        System.err.println("");
-        System.err.println("The 'scheduling' command enables or disables backup scheduling in the");
-        System.err.println("framework.");
     }
 
     private static class BackupMonitor extends IBackupManagerMonitor.Stub {
diff --git a/core/api/current.txt b/core/api/current.txt
index ba1a358..a6446ff 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6138,7 +6138,7 @@
   }
 
   public static class Notification.Action implements android.os.Parcelable {
-    ctor @Deprecated public Notification.Action(int, CharSequence, android.app.PendingIntent);
+    ctor @Deprecated public Notification.Action(int, CharSequence, @Nullable android.app.PendingIntent);
     method public android.app.Notification.Action clone();
     method public int describeContents();
     method public boolean getAllowGeneratedReplies();
@@ -6168,8 +6168,8 @@
   }
 
   public static final class Notification.Action.Builder {
-    ctor @Deprecated public Notification.Action.Builder(int, CharSequence, android.app.PendingIntent);
-    ctor public Notification.Action.Builder(android.graphics.drawable.Icon, CharSequence, android.app.PendingIntent);
+    ctor @Deprecated public Notification.Action.Builder(int, CharSequence, @Nullable android.app.PendingIntent);
+    ctor public Notification.Action.Builder(android.graphics.drawable.Icon, CharSequence, @Nullable android.app.PendingIntent);
     ctor public Notification.Action.Builder(android.app.Notification.Action);
     method @NonNull public android.app.Notification.Action.Builder addExtras(android.os.Bundle);
     method @NonNull public android.app.Notification.Action.Builder addRemoteInput(android.app.RemoteInput);
@@ -8070,6 +8070,7 @@
     method public int getNetworkId();
     method public boolean isEnabled();
     method public boolean isFallbackToDefaultConnectionAllowed();
+    method public boolean shouldBlockNonMatchingNetworks();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.PreferentialNetworkServiceConfig> CREATOR;
     field public static final int PREFERENTIAL_NETWORK_ID_1 = 1; // 0x1
@@ -8087,6 +8088,7 @@
     method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setFallbackToDefaultConnectionAllowed(boolean);
     method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setIncludedUids(@NonNull int[]);
     method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setNetworkId(int);
+    method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setShouldBlockNonMatchingNetworks(boolean);
   }
 
   public class SecurityLog {
@@ -31684,7 +31686,8 @@
     method public static void scaleM(float[], int, float, float, float);
     method public static void setIdentityM(float[], int);
     method public static void setLookAtM(float[], int, float, float, float, float, float, float, float, float, float);
-    method public static void setRotateEulerM(float[], int, float, float, float);
+    method @Deprecated public static void setRotateEulerM(float[], int, float, float, float);
+    method public static void setRotateEulerM2(@NonNull float[], int, float, float, float);
     method public static void setRotateM(float[], int, float, float, float, float);
     method public static void translateM(float[], int, float[], int, float, float, float);
     method public static void translateM(float[], int, float, float, float);
@@ -39239,9 +39242,13 @@
     method @NonNull public android.service.autofill.FillResponse.Builder setFlags(int);
     method @NonNull public android.service.autofill.FillResponse.Builder setFooter(@NonNull android.widget.RemoteViews);
     method @NonNull public android.service.autofill.FillResponse.Builder setHeader(@NonNull android.widget.RemoteViews);
+    method @NonNull public android.service.autofill.FillResponse.Builder setIconResourceId(@DrawableRes int);
     method @NonNull public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
     method @NonNull public android.service.autofill.FillResponse.Builder setPresentationCancelIds(@Nullable int[]);
     method @NonNull public android.service.autofill.FillResponse.Builder setSaveInfo(@NonNull android.service.autofill.SaveInfo);
+    method @NonNull public android.service.autofill.FillResponse.Builder setServiceDisplayNameResourceId(@StringRes int);
+    method @NonNull public android.service.autofill.FillResponse.Builder setShowFillDialogIcon(boolean);
+    method @NonNull public android.service.autofill.FillResponse.Builder setShowSaveDialogIcon(boolean);
     method @NonNull public android.service.autofill.FillResponse.Builder setUserData(@NonNull android.service.autofill.UserData);
   }
 
@@ -40528,11 +40535,12 @@
     method public void onLaunchVoiceAssistFromKeyguard();
     method public void onPrepareToShowSession(@NonNull android.os.Bundle, int);
     method public void onReady();
-    method public void onShowSessionFailed();
+    method public void onShowSessionFailed(@NonNull android.os.Bundle);
     method public void onShutdown();
     method public void setDisabledShowContext(int);
     method public final void setUiHints(@NonNull android.os.Bundle);
     method public void showSession(android.os.Bundle, int);
+    field public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
     field public static final String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
     field public static final String SERVICE_META_DATA = "android.voice_interaction";
   }
@@ -50656,6 +50664,13 @@
     field public int toolType;
   }
 
+  public final class MotionPredictor {
+    ctor public MotionPredictor(@NonNull android.content.Context);
+    method public boolean isPredictionAvailable(int, int);
+    method @NonNull public java.util.List<android.view.MotionEvent> predict(long);
+    method public void record(@NonNull android.view.MotionEvent);
+  }
+
   public interface OnReceiveContentListener {
     method @Nullable public android.view.ContentInfo onReceiveContent(@NonNull android.view.View, @NonNull android.view.ContentInfo);
   }
@@ -54835,6 +54850,7 @@
   public final class InputMethodManager {
     method public void dispatchKeyEventFromInputMethod(@Nullable android.view.View, @NonNull android.view.KeyEvent);
     method public void displayCompletions(android.view.View, android.view.inputmethod.CompletionInfo[]);
+    method @Nullable public android.view.inputmethod.InputMethodInfo getCurrentInputMethodInfo();
     method @Nullable public android.view.inputmethod.InputMethodSubtype getCurrentInputMethodSubtype();
     method @NonNull public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodList();
     method @NonNull public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeList(@Nullable android.view.inputmethod.InputMethodInfo, boolean);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f91122c..4e0e4b9 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -78,6 +78,7 @@
     field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
     field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
     field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
+    field public static final String BIND_VISUAL_QUERY_DETECTION_SERVICE = "android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE";
     field public static final String BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE = "android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE";
     field public static final String BIND_WEARABLE_SENSING_SERVICE = "android.permission.BIND_WEARABLE_SENSING_SERVICE";
     field public static final String BLUETOOTH_MAP = "android.permission.BLUETOOTH_MAP";
@@ -208,6 +209,7 @@
     field public static final String MIGRATE_HEALTH_CONNECT_DATA = "android.permission.MIGRATE_HEALTH_CONNECT_DATA";
     field public static final String MODIFY_APPWIDGET_BIND_PERMISSIONS = "android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS";
     field public static final String MODIFY_AUDIO_ROUTING = "android.permission.MODIFY_AUDIO_ROUTING";
+    field public static final String MODIFY_AUDIO_SYSTEM_SETTINGS = "android.permission.MODIFY_AUDIO_SYSTEM_SETTINGS";
     field public static final String MODIFY_CELL_BROADCASTS = "android.permission.MODIFY_CELL_BROADCASTS";
     field public static final String MODIFY_DAY_NIGHT_MODE = "android.permission.MODIFY_DAY_NIGHT_MODE";
     field @Deprecated public static final String MODIFY_NETWORK_ACCOUNTING = "android.permission.MODIFY_NETWORK_ACCOUNTING";
@@ -6610,8 +6612,8 @@
   }
 
   public class AudioDeviceVolumeManager {
-    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.VolumeInfo getDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS}) public android.media.VolumeInfo getDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS}) public void setDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
   }
 
   public final class AudioFocusInfo implements android.os.Parcelable {
@@ -10404,6 +10406,7 @@
     method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public boolean isUserVisible();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int removeUserWhenPossible(@NonNull android.os.UserHandle, boolean);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public void setBootUser(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean someUserHasAccount(@NonNull String, @NonNull String);
@@ -12593,11 +12596,28 @@
     method public void onQueryRejected() throws java.lang.IllegalStateException;
   }
 
+  public class VisualQueryDetector {
+    method public void destroy();
+    method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean startRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
+    method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean stopRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
+    method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
+  }
+
+  public static interface VisualQueryDetector.Callback {
+    method public void onError();
+    method public void onQueryDetected(@NonNull String);
+    method public void onQueryFinished();
+    method public void onQueryRejected();
+    method public void onVisualQueryDetectionServiceInitialized(int);
+    method public void onVisualQueryDetectionServiceRestarted();
+  }
+
   public class VoiceInteractionService extends android.app.Service {
     method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, android.service.voice.AlwaysOnHotwordDetector.Callback);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.HotwordDetector createHotwordDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.service.voice.HotwordDetector.Callback);
     method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager();
+    method @NonNull public final android.service.voice.VisualQueryDetector createVisualQueryDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.VisualQueryDetector.Callback);
   }
 
 }
@@ -16226,7 +16246,6 @@
     method public void acknowledgeSms(int, @IntRange(from=0, to=65535) int, int, @NonNull byte[]);
     method public void acknowledgeSmsReport(int, @IntRange(from=0, to=65535) int, int);
     method public String getSmsFormat();
-    method public void onMemoryAvailable(int);
     method public void onReady();
     method @Deprecated public final void onSendSmsResult(int, @IntRange(from=0, to=65535) int, int, int) throws java.lang.RuntimeException;
     method public final void onSendSmsResultError(int, @IntRange(from=0, to=65535) int, int, int, int) throws java.lang.RuntimeException;
@@ -16635,6 +16654,14 @@
 
 }
 
+package android.view.inputmethod {
+
+  public final class InputMethodManager {
+    method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.view.inputmethod.InputMethodInfo getCurrentInputMethodInfoAsUser(@NonNull android.os.UserHandle);
+  }
+
+}
+
 package android.view.translation {
 
   public final class TranslationCapability implements android.os.Parcelable {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0b4a708..8c64e40 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -528,6 +528,7 @@
     method @NonNull public static String operationSafetyReasonToString(int);
     method @NonNull public static String operationToString(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void resetDefaultCrossProfileIntentFilters(int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState();
     method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwner(@NonNull android.content.ComponentName, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, int);
@@ -897,6 +898,7 @@
     method public boolean isDemo();
     method public boolean isEnabled();
     method public boolean isEphemeral();
+    method public boolean isForTesting();
     method public boolean isFull();
     method public boolean isGuest();
     method public boolean isInitialized();
@@ -2025,6 +2027,7 @@
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createProfileForUser(@Nullable String, @NonNull String, int, int, @Nullable String[]);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle getBootUser();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
@@ -3212,6 +3215,9 @@
     field public static final String DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_IGNORE_VIEWS = "autofill_credential_manager_ignore_views";
     field public static final String DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED = "autofill_dialog_enabled";
     field public static final String DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES = "smart_suggestion_supported_modes";
+    field public static final String DEVICE_CONFIG_NON_AUTOFILLABLE_IME_ACTION_IDS = "non_autofillable_ime_action_ids";
+    field public static final String DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW = "package_deny_list_for_unimportant_view";
+    field public static final String DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_UNIMPORTANT_VIEW = "trigger_fill_request_on_unimportant_view";
   }
 
   public final class AutofillId implements android.os.Parcelable {
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index dbdee07..821a23c 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -16,6 +16,8 @@
 
 package android.accounts;
 
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+
 import android.annotation.BroadcastBehavior;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -891,15 +893,24 @@
      * @return An {@link AccountManagerFuture} which resolves to a Boolean, true if the account
      *         exists and has all of the specified features.
      */
+    @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+            requiresPermissionIfNotCaller = INTERACT_ACROSS_USERS_FULL)
     public AccountManagerFuture<Boolean> hasFeatures(final Account account,
             final String[] features,
             AccountManagerCallback<Boolean> callback, Handler handler) {
+        return hasFeaturesAsUser(account, features, callback, handler, mContext.getUserId());
+    }
+
+    private AccountManagerFuture<Boolean> hasFeaturesAsUser(
+            final Account account, final String[] features,
+            AccountManagerCallback<Boolean> callback, Handler handler, int userId) {
         if (account == null) throw new IllegalArgumentException("account is null");
         if (features == null) throw new IllegalArgumentException("features is null");
         return new Future2Task<Boolean>(handler, callback) {
             @Override
             public void doWork() throws RemoteException {
-                mService.hasFeatures(mResponse, account, features, mContext.getOpPackageName());
+                mService.hasFeatures(
+                        mResponse, account, features, userId, mContext.getOpPackageName());
             }
             @Override
             public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
@@ -3319,7 +3330,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+    @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
     public AccountManagerFuture<Bundle> finishSessionAsUser(
             final Bundle sessionBundle,
             final Activity activity,
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index a3a7b0c..08fb308 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -38,7 +38,7 @@
     Account[] getAccountsByTypeForPackage(String type, String packageName, String opPackageName);
     Account[] getAccountsAsUser(String accountType, int userId, String opPackageName);
     void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features,
-        String opPackageName);
+        int userId, String opPackageName);
     void getAccountByTypeAndFeatures(in IAccountManagerResponse response, String accountType,
         in String[] features, String opPackageName);
     void getAccountsByFeatures(in IAccountManagerResponse response, String accountType,
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index a81ef18..a9d14df 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -23,7 +23,6 @@
 import android.content.pm.ActivityInfo.Config;
 import android.content.res.ConstantState;
 import android.os.Build;
-import android.util.LongArray;
 
 import java.util.ArrayList;
 
@@ -547,6 +546,7 @@
      */
     void skipToEndValue(boolean inReverse) {}
 
+
     /**
      * Internal use only.
      *
@@ -559,36 +559,9 @@
     }
 
     /**
-     * Internal use only. Changes the value of the animator as if currentPlayTime has passed since
-     * the start of the animation. Therefore, currentPlayTime includes the start delay, and any
-     * repetition. lastPlayTime is similar and is used to calculate how many repeats have been
-     * done between the two times.
+     * Internal use only.
      */
-    void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {}
-
-    /**
-     * Internal use only. This animates any animation that has ended since lastPlayTime.
-     * If an animation hasn't been finished, no change will be made.
-     */
-    void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {}
-
-    /**
-     * Internal use only. Adds all start times (after delay) to and end times to times.
-     * The value must include offset.
-     */
-    void getStartAndEndTimes(LongArray times, long offset) {
-        long startTime = offset + getStartDelay();
-        if (times.indexOf(startTime) < 0) {
-            times.add(startTime);
-        }
-        long duration = getTotalDuration();
-        if (duration != DURATION_INFINITE) {
-            long endTime = duration + offset;
-            if (times.indexOf(endTime) < 0) {
-                times.add(endTime);
-            }
-        }
-    }
+    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {}
 
     /**
      * <p>An animation listener receives notifications from an animation.
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 257adfe..bc8db02 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -23,11 +23,9 @@
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.Log;
-import android.util.LongArray;
 import android.view.animation.Animation;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -183,16 +181,6 @@
      */
     private long mPauseTime = -1;
 
-    /**
-     * The start and stop times of all descendant animators.
-     */
-    private long[] mChildStartAndStopTimes;
-
-    /**
-     * Tracks whether we've notified listeners of the onAnimationStart() event.
-     */
-    private boolean mStartListenersCalled;
-
     // This is to work around a bug in b/34736819. This needs to be removed once app team
     // fixes their side.
     private AnimatorListenerAdapter mAnimationEndListener = new AnimatorListenerAdapter() {
@@ -741,7 +729,14 @@
             startAnimation();
         }
 
-        notifyStartListeners(inReverse);
+        if (mListeners != null) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                tmpListeners.get(i).onAnimationStart(this, inReverse);
+            }
+        }
         if (isEmptySet) {
             // In the case of empty AnimatorSet, or 0 duration scale, we will trigger the
             // onAnimationEnd() right away.
@@ -749,32 +744,6 @@
         }
     }
 
-    private void notifyStartListeners(boolean inReverse) {
-        if (mListeners != null && !mStartListenersCalled) {
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            int numListeners = tmpListeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                AnimatorListener listener = tmpListeners.get(i);
-                listener.onAnimationStart(this, inReverse);
-            }
-        }
-        mStartListenersCalled = true;
-    }
-
-    private void notifyEndListeners(boolean inReverse) {
-        if (mListeners != null && mStartListenersCalled) {
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            int numListeners = tmpListeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                AnimatorListener listener = tmpListeners.get(i);
-                listener.onAnimationEnd(this, inReverse);
-            }
-        }
-        mStartListenersCalled = false;
-    }
-
     // Returns true if set is empty or contains nothing but animator sets with no start delay.
     private static boolean isEmptySet(AnimatorSet set) {
         if (set.getStartDelay() > 0) {
@@ -810,25 +779,26 @@
 
     @Override
     void skipToEndValue(boolean inReverse) {
+        if (!isInitialized()) {
+            throw new UnsupportedOperationException("Children must be initialized.");
+        }
+
         // This makes sure the animation events are sorted an up to date.
         initAnimation();
-        initChildren();
 
         // Calling skip to the end in the sequence that they would be called in a forward/reverse
         // run, such that the sequential animations modifying the same property would have
         // the right value in the end.
         if (inReverse) {
             for (int i = mEvents.size() - 1; i >= 0; i--) {
-                AnimationEvent event = mEvents.get(i);
-                if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
-                    event.mNode.mAnimation.skipToEndValue(true);
+                if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+                    mEvents.get(i).mNode.mAnimation.skipToEndValue(true);
                 }
             }
         } else {
             for (int i = 0; i < mEvents.size(); i++) {
-                AnimationEvent event = mEvents.get(i);
-                if (event.mEvent == AnimationEvent.ANIMATION_END) {
-                    event.mNode.mAnimation.skipToEndValue(false);
+                if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_END) {
+                    mEvents.get(i).mNode.mAnimation.skipToEndValue(false);
                 }
             }
         }
@@ -844,216 +814,72 @@
      * {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)},
      * as needed, based on the last play time and current play time.
      */
-    private void animateBasedOnPlayTime(
-            long currentPlayTime,
-            long lastPlayTime,
-            boolean inReverse,
-            boolean notify
-    ) {
-        if (currentPlayTime < 0 || lastPlayTime < -1) {
+    @Override
+    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
+        if (currentPlayTime < 0 || lastPlayTime < 0) {
             throw new UnsupportedOperationException("Error: Play time should never be negative.");
         }
         // TODO: take into account repeat counts and repeat callback when repeat is implemented.
+        // Clamp currentPlayTime and lastPlayTime
 
+        // TODO: Make this more efficient
+
+        // Convert the play times to the forward direction.
         if (inReverse) {
-            long duration = getTotalDuration();
-            if (duration == DURATION_INFINITE) {
-                throw new UnsupportedOperationException(
-                        "Cannot reverse AnimatorSet with infinite duration"
-                );
+            if (getTotalDuration() == DURATION_INFINITE) {
+                throw new UnsupportedOperationException("Cannot reverse AnimatorSet with infinite"
+                        + " duration");
             }
-            // Convert the play times to the forward direction.
+            long duration = getTotalDuration() - mStartDelay;
             currentPlayTime = Math.min(currentPlayTime, duration);
             currentPlayTime = duration - currentPlayTime;
             lastPlayTime = duration - lastPlayTime;
+            inReverse = false;
         }
 
-        long[] startEndTimes = ensureChildStartAndEndTimes();
-        int index = findNextIndex(lastPlayTime, startEndTimes);
-        int endIndex = findNextIndex(currentPlayTime, startEndTimes);
-
-        // Change values at the start/end times so that values are set in the right order.
-        // We don't want an animator that would finish before another to override the value
-        // set by another animator that finishes earlier.
-        if (currentPlayTime >= lastPlayTime) {
-            while (index < endIndex) {
-                long playTime = startEndTimes[index];
-                if (lastPlayTime != playTime) {
-                    animateSkipToEnds(playTime, lastPlayTime, notify);
-                    animateValuesInRange(playTime, lastPlayTime, notify);
-                    lastPlayTime = playTime;
-                }
-                index++;
-            }
-        } else {
-            while (index > endIndex) {
-                index--;
-                long playTime = startEndTimes[index];
-                if (lastPlayTime != playTime) {
-                    animateSkipToEnds(playTime, lastPlayTime, notify);
-                    animateValuesInRange(playTime, lastPlayTime, notify);
-                    lastPlayTime = playTime;
-                }
-            }
-        }
-        if (currentPlayTime != lastPlayTime) {
-            animateSkipToEnds(currentPlayTime, lastPlayTime, notify);
-            animateValuesInRange(currentPlayTime, lastPlayTime, notify);
-        }
-    }
-
-    /**
-     * Looks through startEndTimes for playTime. If it is in startEndTimes, the index after
-     * is returned. Otherwise, it returns the index at which it would be placed if it were
-     * to be inserted.
-     */
-    private int findNextIndex(long playTime, long[] startEndTimes) {
-        int index = Arrays.binarySearch(startEndTimes, playTime);
-        if (index < 0) {
-            index = -index - 1;
-        } else {
-            index++;
-        }
-        return index;
-    }
-
-    @Override
-    void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
-        initAnimation();
-
-        if (lastPlayTime > currentPlayTime) {
-            if (notify) {
-                notifyStartListeners(true);
-            }
-            for (int i = mEvents.size() - 1; i >= 0; i--) {
-                AnimationEvent event = mEvents.get(i);
-                Node node = event.mNode;
-                if (event.mEvent == AnimationEvent.ANIMATION_END
-                        && node.mStartTime != DURATION_INFINITE
-                ) {
-                    Animator animator = node.mAnimation;
-                    long start = node.mStartTime;
-                    long end = node.mTotalDuration == DURATION_INFINITE
-                            ? Long.MAX_VALUE : node.mEndTime;
-                    if (currentPlayTime <= start && start < lastPlayTime) {
-                        animator.animateSkipToEnds(
-                                0,
-                                lastPlayTime - node.mStartTime,
-                                notify
-                        );
-                    } else if (start <= currentPlayTime && currentPlayTime <= end) {
-                        animator.animateSkipToEnds(
-                                currentPlayTime - node.mStartTime,
-                                lastPlayTime - node.mStartTime,
-                                notify
-                        );
-                    }
-                }
-            }
-            if (currentPlayTime <= 0 && notify) {
-                notifyEndListeners(true);
-            }
-        } else {
-            if (notify) {
-                notifyStartListeners(false);
-            }
-            int eventsSize = mEvents.size();
-            for (int i = 0; i < eventsSize; i++) {
-                AnimationEvent event = mEvents.get(i);
-                Node node = event.mNode;
-                if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
-                        && node.mStartTime != DURATION_INFINITE
-                ) {
-                    Animator animator = node.mAnimation;
-                    long start = node.mStartTime;
-                    long end = node.mTotalDuration == DURATION_INFINITE
-                            ? Long.MAX_VALUE : node.mEndTime;
-                    if (lastPlayTime < end && end <= currentPlayTime) {
-                        animator.animateSkipToEnds(
-                                end - node.mStartTime,
-                                lastPlayTime - node.mStartTime,
-                                notify
-                        );
-                    } else if (start <= currentPlayTime && currentPlayTime <= end) {
-                        animator.animateSkipToEnds(
-                                currentPlayTime - node.mStartTime,
-                                lastPlayTime - node.mStartTime,
-                                notify
-                        );
-                    }
-                }
-            }
-            if (currentPlayTime >= getTotalDuration() && notify) {
-                notifyEndListeners(false);
-            }
-        }
-    }
-
-    @Override
-    void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
-        initAnimation();
-
-        if (notify) {
-            if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
-                notifyStartListeners(false);
-            } else {
-                long duration = getTotalDuration();
-                if (duration >= 0
-                        && (lastPlayTime > duration || (lastPlayTime == duration
-                        && currentPlayTime < duration))
-                ) {
-                    notifyStartListeners(true);
-                }
-            }
-        }
-
-        int eventsSize = mEvents.size();
-        for (int i = 0; i < eventsSize; i++) {
+        ArrayList<Node> unfinishedNodes = new ArrayList<>();
+        // Assumes forward playing from here on.
+        for (int i = 0; i < mEvents.size(); i++) {
             AnimationEvent event = mEvents.get(i);
-            Node node = event.mNode;
-            if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
-                    && node.mStartTime != DURATION_INFINITE
-            ) {
-                Animator animator = node.mAnimation;
-                long start = node.mStartTime;
-                long end = node.mTotalDuration == DURATION_INFINITE
-                        ? Long.MAX_VALUE : node.mEndTime;
-                if ((start < currentPlayTime && currentPlayTime < end)
-                        || (start == currentPlayTime && lastPlayTime < start)
-                        || (end == currentPlayTime && lastPlayTime > end)
-                ) {
-                    animator.animateValuesInRange(
-                            currentPlayTime - node.mStartTime,
-                            Math.max(-1, lastPlayTime - node.mStartTime),
-                            notify
-                    );
+            if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) {
+                break;
+            }
+
+            // This animation started prior to the current play time, and won't finish before the
+            // play time, add to the unfinished list.
+            if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+                if (event.mNode.mEndTime == DURATION_INFINITE
+                        || event.mNode.mEndTime > currentPlayTime) {
+                    unfinishedNodes.add(event.mNode);
                 }
             }
-        }
-    }
-
-    private long[] ensureChildStartAndEndTimes() {
-        if (mChildStartAndStopTimes == null) {
-            LongArray startAndEndTimes = new LongArray();
-            getStartAndEndTimes(startAndEndTimes, 0);
-            long[] times = startAndEndTimes.toArray();
-            Arrays.sort(times);
-            mChildStartAndStopTimes = times;
-        }
-        return mChildStartAndStopTimes;
-    }
-
-    @Override
-    void getStartAndEndTimes(LongArray times, long offset) {
-        int eventsSize = mEvents.size();
-        for (int i = 0; i < eventsSize; i++) {
-            AnimationEvent event = mEvents.get(i);
-            if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
-                    && event.mNode.mStartTime != DURATION_INFINITE
-            ) {
-                event.mNode.mAnimation.getStartAndEndTimes(times, offset + event.mNode.mStartTime);
+            // For animations that do finish before the play time, end them in the sequence that
+            // they would in a normal run.
+            if (event.mEvent == AnimationEvent.ANIMATION_END) {
+                // Skip to the end of the animation.
+                event.mNode.mAnimation.skipToEndValue(false);
             }
         }
+
+        // Seek unfinished animation to the right time.
+        for (int i = 0; i < unfinishedNodes.size(); i++) {
+            Node node = unfinishedNodes.get(i);
+            long playTime = getPlayTimeForNode(currentPlayTime, node, inReverse);
+            if (!inReverse) {
+                playTime -= node.mAnimation.getStartDelay();
+            }
+            node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse);
+        }
+
+        // Seek not yet started animations.
+        for (int i = 0; i < mEvents.size(); i++) {
+            AnimationEvent event = mEvents.get(i);
+            if (event.getTime() > currentPlayTime
+                    && event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+                event.mNode.mAnimation.skipToEndValue(true);
+            }
+        }
+
     }
 
     @Override
@@ -1073,6 +899,10 @@
         return mChildrenInitialized;
     }
 
+    private void skipToStartValue(boolean inReverse) {
+        skipToEndValue(!inReverse);
+    }
+
     /**
      * Sets the position of the animation to the specified point in time. This time should
      * be between 0 and the total duration of the animation, including any repetition. If
@@ -1080,11 +910,6 @@
      * set to this time; it will simply set the time to this value and perform any appropriate
      * actions based on that time. If the animation is already running, then setCurrentPlayTime()
      * will set the current playing time to this value and continue playing from that point.
-     * On {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, an AnimatorSet
-     * that hasn't been {@link #start()}ed, will issue
-     * {@link android.animation.Animator.AnimatorListener#onAnimationStart(Animator, boolean)}
-     * and {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator, boolean)}
-     * events.
      *
      * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
      *                 Unless the animation is reversing, the playtime is considered the time since
@@ -1101,27 +926,29 @@
         if ((getTotalDuration() != DURATION_INFINITE && playTime > getTotalDuration() - mStartDelay)
                 || playTime < 0) {
             throw new UnsupportedOperationException("Error: Play time should always be in between"
-                    + " 0 and duration.");
+                    + "0 and duration.");
         }
 
         initAnimation();
 
-        long lastPlayTime = mSeekState.getPlayTime();
         if (!isStarted() || isPaused()) {
-            if (mReversing && !isStarted()) {
+            if (mReversing) {
                 throw new UnsupportedOperationException("Error: Something went wrong. mReversing"
                         + " should not be set when AnimatorSet is not started.");
             }
             if (!mSeekState.isActive()) {
                 findLatestEventIdForTime(0);
-                initChildren();
                 // Set all the values to start values.
-                skipToEndValue(!mReversing);
+                initChildren();
                 mSeekState.setPlayTime(0, mReversing);
             }
+            animateBasedOnPlayTime(playTime, 0, mReversing);
+            mSeekState.setPlayTime(playTime, mReversing);
+        } else {
+            // If the animation is running, just set the seek time and wait until the next frame
+            // (i.e. doAnimationFrame(...)) to advance the animation.
+            mSeekState.setPlayTime(playTime, mReversing);
         }
-        animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true);
-        mSeekState.setPlayTime(playTime, mReversing);
     }
 
     /**
@@ -1154,16 +981,10 @@
     private void initChildren() {
         if (!isInitialized()) {
             mChildrenInitialized = true;
-
-            // We have to initialize all the start values so that they are based on the previous
-            // values.
-            long[] times = ensureChildStartAndEndTimes();
-
-            long previousTime = -1;
-            for (long time : times) {
-                animateBasedOnPlayTime(time, previousTime, false, false);
-                previousTime = time;
-            }
+            // Forcefully initialize all children based on their end time, so that if the start
+            // value of a child is dependent on a previous animation, the animation will be
+            // initialized after the the previous animations have been advanced to the end.
+            skipToEndValue(false);
         }
     }
 
@@ -1237,7 +1058,7 @@
         for (int i = 0; i < mPlayingSet.size(); i++) {
             Node node = mPlayingSet.get(i);
             if (!node.mEnded) {
-                pulseFrame(node, getPlayTimeForNodeIncludingDelay(unscaledPlayTime, node));
+                pulseFrame(node, getPlayTimeForNode(unscaledPlayTime, node));
             }
         }
 
@@ -1308,7 +1129,7 @@
                     pulseFrame(node, 0);
                 } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED && !node.mEnded) {
                     // end event:
-                    pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
+                    pulseFrame(node, getPlayTimeForNode(playTime, node));
                 }
             }
         } else {
@@ -1329,7 +1150,7 @@
                     pulseFrame(node, 0);
                 } else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) {
                     // start event:
-                    pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
+                    pulseFrame(node, getPlayTimeForNode(playTime, node));
                 }
             }
         }
@@ -1351,15 +1172,11 @@
         }
     }
 
-    private long getPlayTimeForNodeIncludingDelay(long overallPlayTime, Node node) {
-        return getPlayTimeForNodeIncludingDelay(overallPlayTime, node, mReversing);
+    private long getPlayTimeForNode(long overallPlayTime, Node node) {
+        return getPlayTimeForNode(overallPlayTime, node, mReversing);
     }
 
-    private long getPlayTimeForNodeIncludingDelay(
-            long overallPlayTime,
-            Node node,
-            boolean inReverse
-    ) {
+    private long getPlayTimeForNode(long overallPlayTime, Node node, boolean inReverse) {
         if (inReverse) {
             overallPlayTime = getTotalDuration() - overallPlayTime;
             return node.mEndTime - overallPlayTime;
@@ -1381,8 +1198,26 @@
         }
         // Set the child animators to the right end:
         if (mShouldResetValuesAtStart) {
-            initChildren();
-            skipToEndValue(!mReversing);
+            if (isInitialized()) {
+                skipToEndValue(!mReversing);
+            } else if (mReversing) {
+                // Reversing but haven't initialized all the children yet.
+                initChildren();
+                skipToEndValue(!mReversing);
+            } else {
+                // If not all children are initialized and play direction is forward
+                for (int i = mEvents.size() - 1; i >= 0; i--) {
+                    if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+                        Animator anim = mEvents.get(i).mNode.mAnimation;
+                        // Only reset the animations that have been initialized to start value,
+                        // so that if they are defined without a start value, they will get the
+                        // values set at the right time (i.e. the next animation run)
+                        if (anim.isInitialized()) {
+                            anim.skipToEndValue(true);
+                        }
+                    }
+                }
+            }
         }
 
         if (mReversing || mStartDelay == 0 || mSeekState.isActive()) {
@@ -1457,7 +1292,15 @@
 
         // No longer receive callbacks
         removeAnimationCallback();
-        notifyEndListeners(mReversing);
+        // Call end listener
+        if (mListeners != null) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                tmpListeners.get(i).onAnimationEnd(this, mReversing);
+            }
+        }
         removeAnimationEndListener();
         mSelfPulse = true;
         mReversing = false;
@@ -2079,11 +1922,11 @@
         }
 
         void setPlayTime(long playTime, boolean inReverse) {
+            // TODO: This can be simplified.
+
             // Clamp the play time
             if (getTotalDuration() != DURATION_INFINITE) {
                 mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay);
-            } else {
-                mPlayTime = playTime;
             }
             mPlayTime = Math.max(0, mPlayTime);
             mSeekingInReverse = inReverse;
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 7009725..6ab7ae6 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -324,9 +324,8 @@
             listenerCopy = new ArrayList<>(sDurationScaleChangeListeners);
         }
 
-        int listenersSize = listenerCopy.size();
-        for (int i = 0; i < listenersSize; i++) {
-            final DurationScaleChangeListener listener = listenerCopy.get(i).get();
+        for (WeakReference<DurationScaleChangeListener> listenerRef : listenerCopy) {
+            final DurationScaleChangeListener listener = listenerRef.get();
             if (listener != null) {
                 listener.onChanged(durationScale);
             }
@@ -625,7 +624,7 @@
     public void setValues(PropertyValuesHolder... values) {
         int numValues = values.length;
         mValues = values;
-        mValuesMap = new HashMap<>(numValues);
+        mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
         for (int i = 0; i < numValues; ++i) {
             PropertyValuesHolder valuesHolder = values[i];
             mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
@@ -659,11 +658,9 @@
     @CallSuper
     void initAnimation() {
         if (!mInitialized) {
-            if (mValues != null) {
-                int numValues = mValues.length;
-                for (int i = 0; i < numValues; ++i) {
-                    mValues[i].init();
-                }
+            int numValues = mValues.length;
+            for (int i = 0; i < numValues; ++i) {
+                mValues[i].init();
             }
             mInitialized = true;
         }
@@ -1108,30 +1105,18 @@
         }
     }
 
-    private void notifyStartListeners(boolean isReversing) {
+    private void notifyStartListeners() {
         if (mListeners != null && !mStartListenersCalled) {
             ArrayList<AnimatorListener> tmpListeners =
                     (ArrayList<AnimatorListener>) mListeners.clone();
             int numListeners = tmpListeners.size();
             for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onAnimationStart(this, isReversing);
+                tmpListeners.get(i).onAnimationStart(this, mReversing);
             }
         }
         mStartListenersCalled = true;
     }
 
-    private void notifyEndListeners(boolean isReversing) {
-        if (mListeners != null && mStartListenersCalled) {
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            int numListeners = tmpListeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onAnimationEnd(this, isReversing);
-            }
-        }
-        mStartListenersCalled = false;
-    }
-
     /**
      * Start the animation playing. This version of start() takes a boolean flag that indicates
      * whether the animation should play in reverse. The flag is usually false, but may be set
@@ -1222,16 +1207,12 @@
         if ((mStarted || mRunning) && mListeners != null) {
             if (!mRunning) {
                 // If it's not yet running, then start listeners weren't called. Call them now.
-                notifyStartListeners(mReversing);
+                notifyStartListeners();
             }
-            int listenersSize = mListeners.size();
-            if (listenersSize > 0) {
-                ArrayList<AnimatorListener> tmpListeners =
-                        (ArrayList<AnimatorListener>) mListeners.clone();
-                for (int i = 0; i < listenersSize; i++) {
-                    AnimatorListener listener = tmpListeners.get(i);
-                    listener.onAnimationCancel(this);
-                }
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            for (AnimatorListener listener : tmpListeners) {
+                listener.onAnimationCancel(this);
             }
         }
         endAnimation();
@@ -1336,14 +1317,22 @@
         boolean notify = (mStarted || mRunning) && mListeners != null;
         if (notify && !mRunning) {
             // If it's not yet running, then start listeners weren't called. Call them now.
-            notifyStartListeners(mReversing);
+            notifyStartListeners();
         }
         mRunning = false;
         mStarted = false;
+        mStartListenersCalled = false;
         mLastFrameTime = -1;
         mFirstFrameTime = -1;
         mStartTime = -1;
-        notifyEndListeners(mReversing);
+        if (notify && mListeners != null) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                tmpListeners.get(i).onAnimationEnd(this, mReversing);
+            }
+        }
         // mReversing needs to be reset *after* notifying the listeners for the end callbacks.
         mReversing = false;
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
@@ -1370,8 +1359,9 @@
         } else {
             mOverallFraction = 0f;
         }
-
-        notifyStartListeners(mReversing);
+        if (mListeners != null) {
+            notifyStartListeners();
+        }
     }
 
     /**
@@ -1462,32 +1452,16 @@
      * will be called.
      */
     @Override
-    void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
-        if (currentPlayTime < 0 || lastPlayTime < -1) {
+    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
+        if (currentPlayTime < 0 || lastPlayTime < 0) {
             throw new UnsupportedOperationException("Error: Play time should never be negative.");
         }
 
         initAnimation();
-        long duration = getTotalDuration();
-        if (notify) {
-            if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
-                notifyStartListeners(false);
-            } else if (lastPlayTime > duration
-                    || (lastPlayTime == duration && currentPlayTime < duration)
-            ) {
-                notifyStartListeners(true);
-            }
-        }
-        if (duration >= 0) {
-            lastPlayTime = Math.min(duration, lastPlayTime);
-        }
-        lastPlayTime -= mStartDelay;
-        currentPlayTime -= mStartDelay;
-
         // Check whether repeat callback is needed only when repeat count is non-zero
         if (mRepeatCount > 0) {
-            int iteration = Math.max(0, (int) (currentPlayTime / mDuration));
-            int lastIteration = Math.max(0, (int) (lastPlayTime / mDuration));
+            int iteration = (int) (currentPlayTime / mDuration);
+            int lastIteration = (int) (lastPlayTime / mDuration);
 
             // Clamp iteration to [0, mRepeatCount]
             iteration = Math.min(iteration, mRepeatCount);
@@ -1503,37 +1477,16 @@
             }
         }
 
-        if (mRepeatCount != INFINITE && currentPlayTime > (mRepeatCount + 1) * mDuration) {
-            throw new IllegalStateException("Can't animate a value outside of the duration");
+        if (mRepeatCount != INFINITE && currentPlayTime >= (mRepeatCount + 1) * mDuration) {
+            skipToEndValue(inReverse);
         } else {
             // Find the current fraction:
-            float fraction = Math.max(0, currentPlayTime) / (float) mDuration;
-            fraction = getCurrentIterationFraction(fraction, false);
+            float fraction = currentPlayTime / (float) mDuration;
+            fraction = getCurrentIterationFraction(fraction, inReverse);
             animateValue(fraction);
         }
     }
 
-    @Override
-    void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
-        boolean inReverse = currentPlayTime < lastPlayTime;
-        boolean doSkip;
-        if (currentPlayTime <= 0 && lastPlayTime > 0) {
-            doSkip = true;
-        } else {
-            long duration = getTotalDuration();
-            doSkip = duration >= 0 && currentPlayTime >= duration && lastPlayTime < duration;
-        }
-        if (doSkip) {
-            if (notify) {
-                notifyStartListeners(inReverse);
-            }
-            skipToEndValue(inReverse);
-            if (notify) {
-                notifyEndListeners(inReverse);
-            }
-        }
-    }
-
     /**
      * Internal use only.
      * Skips the animation value to end/start, depending on whether the play direction is forward
@@ -1688,9 +1641,6 @@
             Trace.traceCounter(Trace.TRACE_TAG_VIEW, getNameForTrace() + hashCode(),
                     (int) (fraction * 1000));
         }
-        if (mValues == null) {
-            return;
-        }
         fraction = mInterpolator.getInterpolation(fraction);
         mCurrentFraction = fraction;
         int numValues = mValues.length;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 174c982..b626493 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -404,6 +404,13 @@
     public static final int START_FLAG_NATIVE_DEBUGGING = 1<<3;
 
     /**
+     * Flag for IActivityManaqer.startActivity: launch the app for
+     * debugging and suspend threads.
+     * @hide
+     */
+    public static final int START_FLAG_DEBUG_SUSPEND = 1 << 4;
+
+    /**
      * Result for IActivityManaqer.broadcastIntent: success!
      * @hide
      */
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 421ba7c..3761251 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -784,9 +784,10 @@
 
     static final class ReceiverData extends BroadcastReceiver.PendingResult {
         public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
-                boolean ordered, boolean sticky, IBinder token, int sendingUser) {
+                boolean ordered, boolean sticky, boolean assumeDelivered, IBinder token,
+                int sendingUser) {
             super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky,
-                    token, sendingUser, intent.getFlags());
+                    assumeDelivered, token, sendingUser, intent.getFlags());
             this.intent = intent;
         }
 
@@ -1040,10 +1041,10 @@
 
         public final void scheduleReceiver(Intent intent, ActivityInfo info,
                 CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
-                boolean sync, int sendingUser, int processState) {
+                boolean ordered, boolean assumeDelivered, int sendingUser, int processState) {
             updateProcessState(processState, false);
             ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
-                    sync, false, mAppThread.asBinder(), sendingUser);
+                    ordered, false, assumeDelivered, mAppThread.asBinder(), sendingUser);
             r.info = info;
             sendMessage(H.RECEIVER, r);
         }
@@ -1054,11 +1055,11 @@
                 if (r.registered) {
                     scheduleRegisteredReceiver(r.receiver, r.intent,
                             r.resultCode, r.data, r.extras, r.ordered, r.sticky,
-                            r.sendingUser, r.processState);
+                            r.assumeDelivered, r.sendingUser, r.processState);
                 } else {
                     scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
                             r.resultCode, r.data, r.extras, r.sync,
-                            r.sendingUser, r.processState);
+                            r.assumeDelivered, r.sendingUser, r.processState);
                 }
             }
         }
@@ -1288,10 +1289,25 @@
         // applies transaction ordering per object for such calls.
         public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
                 int resultCode, String dataStr, Bundle extras, boolean ordered,
-                boolean sticky, int sendingUser, int processState) throws RemoteException {
+                boolean sticky, boolean assumeDelivered, int sendingUser, int processState)
+                throws RemoteException {
             updateProcessState(processState, false);
-            receiver.performReceive(intent, resultCode, dataStr, extras, ordered,
-                    sticky, sendingUser);
+
+            // We can't modify IIntentReceiver due to UnsupportedAppUsage, so
+            // try our best to shortcut to known subclasses, and alert if
+            // registered using a custom IIntentReceiver that isn't able to
+            // report an expected delivery event
+            if (receiver instanceof LoadedApk.ReceiverDispatcher.InnerReceiver) {
+                ((LoadedApk.ReceiverDispatcher.InnerReceiver) receiver).performReceive(intent,
+                        resultCode, dataStr, extras, ordered, sticky, assumeDelivered, sendingUser);
+            } else {
+                if (!assumeDelivered) {
+                    Log.wtf(TAG, "scheduleRegisteredReceiver() called for " + receiver
+                            + " and " + intent + " without mechanism to finish delivery");
+                }
+                receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky,
+                        sendingUser);
+            }
         }
 
         @Override
@@ -6869,26 +6885,13 @@
         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
         final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
 
-        // Wait for debugger after we have notified the system to finish attach application
         if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
             if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) {
-                Slog.w(TAG, "Application " + data.info.getPackageName()
-                        + " is waiting for the debugger ...");
-
-                try {
-                    mgr.showWaitingForDebugger(mAppThread, true);
-                } catch (RemoteException ex) {
-                    throw ex.rethrowFromSystemServer();
-                }
-
-                Debug.waitForDebugger();
-
-                try {
-                    mgr.showWaitingForDebugger(mAppThread, false);
-                } catch (RemoteException ex) {
-                    throw ex.rethrowFromSystemServer();
-                }
+                waitForDebugger(data);
+            } else if (data.debugMode == ApplicationThreadConstants.DEBUG_SUSPEND) {
+                suspendAllAndSendVmStart(data);
             }
+            // Nothing special to do in case of DEBUG_ON.
         }
 
         try {
@@ -6972,6 +6975,49 @@
         }
     }
 
+    @UnsupportedAppUsage
+    private void waitForDebugger(AppBindData data) {
+        final IActivityManager mgr = ActivityManager.getService();
+        Slog.w(TAG, "Application " + data.info.getPackageName()
+                         + " is waiting for the debugger ...");
+
+        try {
+            mgr.showWaitingForDebugger(mAppThread, true);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+
+        Debug.waitForDebugger();
+
+        try {
+            mgr.showWaitingForDebugger(mAppThread, false);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    @UnsupportedAppUsage
+    private void suspendAllAndSendVmStart(AppBindData data) {
+        final IActivityManager mgr = ActivityManager.getService();
+        Slog.w(TAG, "Application " + data.info.getPackageName()
+                         + " is suspending. Debugger needs to resume to continue.");
+
+        try {
+            mgr.showWaitingForDebugger(mAppThread, true);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+
+        Debug.suspendAllAndSendVmStart();
+
+        try {
+            mgr.showWaitingForDebugger(mAppThread, false);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+
     private void handleSetContentCaptureOptionsCallback(String packageName) {
         if (mContentCaptureOptionsCallback != null) {
             return;
diff --git a/core/java/android/app/ApplicationThreadConstants.java b/core/java/android/app/ApplicationThreadConstants.java
index 1fa670f..c7620c3 100644
--- a/core/java/android/app/ApplicationThreadConstants.java
+++ b/core/java/android/app/ApplicationThreadConstants.java
@@ -28,6 +28,7 @@
     public static final int DEBUG_OFF = 0;
     public static final int DEBUG_ON = 1;
     public static final int DEBUG_WAIT = 2;
+    public static final int DEBUG_SUSPEND = 3;
 
     // the package has been removed, clean up internal references
     public static final int PACKAGE_REMOVED = 0;
@@ -36,4 +37,4 @@
     public static final int PACKAGE_REMOVED_DONT_KILL = 2;
     // a previously removed package was replaced with a new version [eg. upgrade, split added, ...]
     public static final int PACKAGE_REPLACED = 3;
-}
\ No newline at end of file
+}
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 3984fee..4f69d85 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -65,8 +65,8 @@
 oneway interface IApplicationThread {
     void scheduleReceiver(in Intent intent, in ActivityInfo info,
             in CompatibilityInfo compatInfo,
-            int resultCode, in String data, in Bundle extras, boolean sync,
-            int sendingUser, int processState);
+            int resultCode, in String data, in Bundle extras, boolean ordered,
+            boolean assumeDelivered, int sendingUser, int processState);
 
     void scheduleReceiverList(in List<ReceiverInfo> info);
 
@@ -102,7 +102,7 @@
             in String[] args);
     void scheduleRegisteredReceiver(IIntentReceiver receiver, in Intent intent,
             int resultCode, in String data, in Bundle extras, boolean ordered,
-            boolean sticky, int sendingUser, int processState);
+            boolean sticky, boolean assumeDelivered, int sendingUser, int processState);
     void scheduleLowMemory();
     void profilerControl(boolean start, in ProfilerInfo profilerInfo, int profileType);
     void setSchedulingGroup(int group);
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 3620a60..7c22902 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1679,6 +1679,16 @@
             @Override
             public void performReceive(Intent intent, int resultCode, String data,
                     Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
+                Log.wtf(TAG, "performReceive() called targeting raw IIntentReceiver for " + intent);
+                performReceive(intent, resultCode, data, extras, ordered, sticky,
+                        BroadcastReceiver.PendingResult.guessAssumeDelivered(
+                                BroadcastReceiver.PendingResult.TYPE_REGISTERED, ordered),
+                        sendingUser);
+            }
+
+            public void performReceive(Intent intent, int resultCode, String data,
+                    Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
+                    int sendingUser) {
                 final LoadedApk.ReceiverDispatcher rd;
                 if (intent == null) {
                     Log.wtf(TAG, "Null intent received");
@@ -1693,8 +1703,8 @@
                 }
                 if (rd != null) {
                     rd.performReceive(intent, resultCode, data, extras,
-                            ordered, sticky, sendingUser);
-                } else {
+                            ordered, sticky, assumeDelivered, sendingUser);
+                } else if (!assumeDelivered) {
                     // The activity manager dispatched a broadcast to a registered
                     // receiver in this process, but before it could be delivered the
                     // receiver was unregistered.  Acknowledge the broadcast on its
@@ -1729,30 +1739,26 @@
 
         final class Args extends BroadcastReceiver.PendingResult {
             private Intent mCurIntent;
-            private final boolean mOrdered;
             private boolean mDispatched;
             private boolean mRunCalled;
 
             public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,
-                    boolean ordered, boolean sticky, int sendingUser) {
+                    boolean ordered, boolean sticky, boolean assumeDelivered, int sendingUser) {
                 super(resultCode, resultData, resultExtras,
                         mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, ordered,
-                        sticky, mAppThread.asBinder(), sendingUser, intent.getFlags());
+                        sticky, assumeDelivered, mAppThread.asBinder(), sendingUser,
+                        intent.getFlags());
                 mCurIntent = intent;
-                mOrdered = ordered;
             }
 
             public final Runnable getRunnable() {
                 return () -> {
                     final BroadcastReceiver receiver = mReceiver;
-                    final boolean ordered = mOrdered;
 
                     if (ActivityThread.DEBUG_BROADCAST) {
                         int seq = mCurIntent.getIntExtra("seq", -1);
                         Slog.i(ActivityThread.TAG, "Dispatching broadcast " + mCurIntent.getAction()
                                 + " seq=" + seq + " to " + mReceiver);
-                        Slog.i(ActivityThread.TAG, "  mRegistered=" + mRegistered
-                                + " mOrderedHint=" + ordered);
                     }
 
                     final IActivityManager mgr = ActivityManager.getService();
@@ -1766,11 +1772,9 @@
                     mDispatched = true;
                     mRunCalled = true;
                     if (receiver == null || intent == null || mForgotten) {
-                        if (mRegistered && ordered) {
-                            if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
-                                    "Finishing null broadcast to " + mReceiver);
-                            sendFinished(mgr);
-                        }
+                        if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+                                "Finishing null broadcast to " + mReceiver);
+                        sendFinished(mgr);
                         return;
                     }
 
@@ -1790,11 +1794,9 @@
                         receiver.setPendingResult(this);
                         receiver.onReceive(mContext, intent);
                     } catch (Exception e) {
-                        if (mRegistered && ordered) {
-                            if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
-                                    "Finishing failed broadcast to " + mReceiver);
-                            sendFinished(mgr);
-                        }
+                        if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+                                "Finishing failed broadcast to " + mReceiver);
+                        sendFinished(mgr);
                         if (mInstrumentation == null ||
                                 !mInstrumentation.onException(mReceiver, e)) {
                             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -1868,9 +1870,10 @@
         }
 
         public void performReceive(Intent intent, int resultCode, String data,
-                Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
+                Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
+                int sendingUser) {
             final Args args = new Args(intent, resultCode, data, extras, ordered,
-                    sticky, sendingUser);
+                    sticky, assumeDelivered, sendingUser);
             if (intent == null) {
                 Log.wtf(TAG, "Null intent received");
             } else {
@@ -1881,12 +1884,10 @@
                 }
             }
             if (intent == null || !mActivityThread.post(args.getRunnable())) {
-                if (mRegistered && ordered) {
-                    IActivityManager mgr = ActivityManager.getService();
-                    if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
-                            "Finishing sync broadcast to " + mReceiver);
-                    args.sendFinished(mgr);
-                }
+                IActivityManager mgr = ActivityManager.getService();
+                if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+                        "Finishing sync broadcast to " + mReceiver);
+                args.sendFinished(mgr);
             }
         }
 
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index f9ef3cc..c081d82 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1782,7 +1782,7 @@
          * @deprecated Use {@link android.app.Notification.Action.Builder}.
          */
         @Deprecated
-        public Action(int icon, CharSequence title, PendingIntent intent) {
+        public Action(int icon, CharSequence title, @Nullable PendingIntent intent) {
             this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
                     SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */);
         }
@@ -1907,10 +1907,12 @@
              * which may display them in other contexts, for example on a wearable device.
              * @param icon icon to show for this action
              * @param title the title of the action
-             * @param intent the {@link PendingIntent} to fire when users trigger this action
+             * @param intent the {@link PendingIntent} to fire when users trigger this action. May
+             * be null, in which case the action may be rendered in a disabled presentation by the
+             * system UI.
              */
             @Deprecated
-            public Builder(int icon, CharSequence title, PendingIntent intent) {
+            public Builder(int icon, CharSequence title, @Nullable PendingIntent intent) {
                 this(Icon.createWithResource("", icon), title, intent);
             }
 
@@ -1939,9 +1941,11 @@
              *
              * @param icon icon to show for this action
              * @param title the title of the action
-             * @param intent the {@link PendingIntent} to fire when users trigger this action
+             * @param intent the {@link PendingIntent} to fire when users trigger this action. May
+             * be null, in which case the action may be rendered in a disabled presentation by the
+             * system UI.
              */
-            public Builder(Icon icon, CharSequence title, PendingIntent intent) {
+            public Builder(Icon icon, CharSequence title, @Nullable PendingIntent intent) {
                 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false);
             }
 
diff --git a/core/java/android/app/ReceiverInfo.aidl b/core/java/android/app/ReceiverInfo.aidl
index d90eee7..8d7e3c4 100644
--- a/core/java/android/app/ReceiverInfo.aidl
+++ b/core/java/android/app/ReceiverInfo.aidl
@@ -34,6 +34,7 @@
     Intent intent;
     String data;
     Bundle extras;
+    boolean assumeDelivered;
     int sendingUser;
     int processState;
     int resultCode;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 1633073..33721a0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.QUERY_ADMIN_POLICY;
 import static android.Manifest.permission.SET_TIME;
 import static android.Manifest.permission.SET_TIME_ZONE;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
 import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
 
@@ -4186,7 +4187,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+    @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
     public boolean packageHasActiveAdmins(String packageName) {
         return packageHasActiveAdmins(packageName, myUserId());
     }
@@ -8743,7 +8744,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @RequiresPermission(allOf = {
             android.Manifest.permission.MANAGE_DEVICE_ADMINS,
-            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
+            INTERACT_ACROSS_USERS_FULL
     })
     public void setActiveAdmin(@NonNull ComponentName policyReceiver, boolean refreshing,
             int userHandle) {
@@ -10654,7 +10655,7 @@
      */
     @UserHandleAware
     @RequiresPermission(allOf = {
-            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+            INTERACT_ACROSS_USERS_FULL,
             android.Manifest.permission.MANAGE_USERS
             }, conditional = true)
     public @Nullable List<String> getPermittedInputMethods() {
@@ -14845,7 +14846,7 @@
      * @hide
      */
     @RequiresPermission(anyOf = {
-            permission.INTERACT_ACROSS_USERS_FULL,
+            INTERACT_ACROSS_USERS_FULL,
             permission.INTERACT_ACROSS_USERS
     }, conditional = true)
     public boolean isPackageAllowedToAccessCalendar(@NonNull  String packageName) {
@@ -14877,7 +14878,7 @@
      * @hide
      */
     @RequiresPermission(anyOf = {
-            permission.INTERACT_ACROSS_USERS_FULL,
+            INTERACT_ACROSS_USERS_FULL,
             permission.INTERACT_ACROSS_USERS
     })
     public @Nullable Set<String> getCrossProfileCalendarPackages() {
@@ -14970,7 +14971,7 @@
      * @hide
      */
     @RequiresPermission(anyOf = {
-            permission.INTERACT_ACROSS_USERS_FULL,
+            INTERACT_ACROSS_USERS_FULL,
             permission.INTERACT_ACROSS_USERS,
             permission.INTERACT_ACROSS_PROFILES
     })
@@ -16154,6 +16155,23 @@
     }
 
     /**
+     * Reset cache for {@link #shouldAllowBypassingDevicePolicyManagementRoleQualification}.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS)
+    public void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState() {
+        if (mService != null) {
+            try {
+                mService.resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * @return {@code true} if bypassing the device policy management role qualification is allowed
      * with the current state of the device.
      *
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 20695ca..aebeaf0 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -575,6 +575,7 @@
     void resetStrings(in List<String> stringIds);
     ParcelableResource getString(String stringId);
 
+    void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState();
     boolean shouldAllowBypassingDevicePolicyManagementRoleQualification();
 
     List<UserHandle> getPolicyManagedProfiles(in UserHandle userHandle);
diff --git a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
index b0ea499..0513099 100644
--- a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
+++ b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
@@ -51,6 +51,7 @@
     final boolean mIsEnabled;
     final int mNetworkId;
     final boolean mAllowFallbackToDefaultConnection;
+    final boolean mShouldBlockNonMatchingNetworks;
     final int[] mIncludedUids;
     final int[] mExcludedUids;
 
@@ -64,6 +65,8 @@
             "preferential_network_service_network_id";
     private static final String TAG_ALLOW_FALLBACK_TO_DEFAULT_CONNECTION =
             "allow_fallback_to_default_connection";
+    private static final String TAG_BLOCK_NON_MATCHING_NETWORKS =
+            "block_non_matching_networks";
     private static final String TAG_INCLUDED_UIDS = "included_uids";
     private static final String TAG_EXCLUDED_UIDS = "excluded_uids";
     private static final String ATTR_VALUE = "value";
@@ -111,10 +114,12 @@
     }
 
     private PreferentialNetworkServiceConfig(boolean isEnabled,
-            boolean allowFallbackToDefaultConnection, int[] includedUids,
+            boolean allowFallbackToDefaultConnection, boolean shouldBlockNonMatchingNetworks,
+            int[] includedUids,
             int[] excludedUids, @PreferentialNetworkPreferenceId int networkId) {
         mIsEnabled = isEnabled;
         mAllowFallbackToDefaultConnection = allowFallbackToDefaultConnection;
+        mShouldBlockNonMatchingNetworks = shouldBlockNonMatchingNetworks;
         mIncludedUids = includedUids;
         mExcludedUids = excludedUids;
         mNetworkId = networkId;
@@ -123,6 +128,7 @@
     private PreferentialNetworkServiceConfig(Parcel in) {
         mIsEnabled = in.readBoolean();
         mAllowFallbackToDefaultConnection = in.readBoolean();
+        mShouldBlockNonMatchingNetworks = in.readBoolean();
         mNetworkId = in.readInt();
         mIncludedUids = in.createIntArray();
         mExcludedUids = in.createIntArray();
@@ -137,9 +143,18 @@
     }
 
     /**
-     * is fallback to default network allowed. This boolean configures whether default connection
-     * (default internet or wifi) should be used or not if a preferential network service
-     * connection is not available.
+     * Whether fallback to the device-wide default network is allowed.
+     *
+     * This boolean configures whether the default connection (e.g. general cell network or wifi)
+     * should be used if no preferential network service connection is available. If true, the
+     * default connection will be used when no preferential service is available. If false, the
+     * UIDs subject to this configuration will have no default network.
+     * Note that while this boolean determines whether the UIDs subject to this configuration have
+     * a default network in the absence of a preferential service, apps can still explicitly decide
+     * to use another network than their default network by requesting them from the system. This
+     * boolean does not determine whether the UIDs are blocked from using such other networks.
+     * See {@link #shouldBlockNonMatchingNetworks()} for that configuration.
+     *
      * @return true if fallback is allowed, else false.
      */
     public boolean isFallbackToDefaultConnectionAllowed() {
@@ -147,6 +162,21 @@
     }
 
     /**
+     * Whether to block UIDs from using other networks than the preferential service.
+     *
+     * Apps can inspect the list of available networks on the device and choose to use multiple
+     * of them concurrently for performance, privacy or other reasons.
+     * This boolean configures whether the concerned UIDs should be blocked from using
+     * networks that do not match the configured preferential network service even if these
+     * networks are otherwise open to all apps.
+     *
+     * @return true if UIDs should be blocked from using the other networks, else false.
+     */
+    public boolean shouldBlockNonMatchingNetworks() {
+        return mShouldBlockNonMatchingNetworks;
+    }
+
+    /**
      * Get the array of uids that are applicable for the profile preference.
      *
      * {@see #getExcludedUids()}
@@ -190,6 +220,7 @@
         return "PreferentialNetworkServiceConfig{"
                 + "mIsEnabled=" + isEnabled()
                 + "mAllowFallbackToDefaultConnection=" + isFallbackToDefaultConnectionAllowed()
+                + "mBlockNonMatchingNetworks=" + shouldBlockNonMatchingNetworks()
                 + "mIncludedUids=" + Arrays.toString(mIncludedUids)
                 + "mExcludedUids=" + Arrays.toString(mExcludedUids)
                 + "mNetworkId=" + mNetworkId
@@ -203,6 +234,7 @@
         final PreferentialNetworkServiceConfig that = (PreferentialNetworkServiceConfig) o;
         return mIsEnabled == that.mIsEnabled
                 && mAllowFallbackToDefaultConnection == that.mAllowFallbackToDefaultConnection
+                && mShouldBlockNonMatchingNetworks == that.mShouldBlockNonMatchingNetworks
                 && mNetworkId == that.mNetworkId
                 && Arrays.equals(mIncludedUids, that.mIncludedUids)
                 && Arrays.equals(mExcludedUids, that.mExcludedUids);
@@ -211,7 +243,8 @@
     @Override
     public int hashCode() {
         return Objects.hash(mIsEnabled, mAllowFallbackToDefaultConnection,
-                Arrays.hashCode(mIncludedUids), Arrays.hashCode(mExcludedUids), mNetworkId);
+                mShouldBlockNonMatchingNetworks, Arrays.hashCode(mIncludedUids),
+                Arrays.hashCode(mExcludedUids), mNetworkId);
     }
 
     /**
@@ -222,6 +255,7 @@
         boolean mIsEnabled = false;
         int mNetworkId = 0;
         boolean mAllowFallbackToDefaultConnection = true;
+        boolean mShouldBlockNonMatchingNetworks = false;
         int[] mIncludedUids = new int[0];
         int[] mExcludedUids = new int[0];
 
@@ -243,10 +277,21 @@
         }
 
         /**
-         * Set whether the default connection should be used as fallback.
-         * This boolean configures whether the default connection (default internet or wifi)
-         * should be used if a preferential network service connection is not available.
-         * Default value is true
+         * Set whether fallback to the device-wide default network is allowed.
+         *
+         * This boolean configures whether the default connection (e.g. general cell network or
+         * wifi) should be used if no preferential network service connection is available. If true,
+         * the default connection will be used when no preferential service is available. If false,
+         * the UIDs subject to this configuration will have no default network.
+         * Note that while this boolean determines whether the UIDs subject to this configuration
+         * have a default network in the absence of a preferential service, apps can still
+         * explicitly decide to use another network than their default network by requesting them
+         * from the system. This boolean does not determine whether the UIDs are blocked from using
+         * such other networks.
+         * Use {@link #setShouldBlockNonMatchingNetworks(boolean)} to specify this.
+         *
+         * The default value is true.
+         *
          * @param allowFallbackToDefaultConnection  true if fallback is allowed else false
          * @return The builder to facilitate chaining.
          */
@@ -259,6 +304,31 @@
         }
 
         /**
+         * Set whether to block UIDs from using other networks than the preferential service.
+         *
+         * Apps can inspect the list of available networks on the device and choose to use multiple
+         * of them concurrently for performance, privacy or other reasons.
+         * This boolean configures whether the concerned UIDs should be blocked from using
+         * networks that do not match the configured preferential network service even if these
+         * networks are otherwise open to all apps.
+         *
+         * The default value is false. This value can only be set to {@code true} if
+         * {@link #setFallbackToDefaultConnectionAllowed(boolean)} is set to {@code false}, because
+         * allowing fallback but blocking it does not make sense. Failure to comply with this
+         * constraint will throw when building the object.
+         *
+         * @param blockNonMatchingNetworks true if UIDs should be blocked from using non-matching
+         *                                 networks.
+         * @return The builder to facilitate chaining.
+         */
+        @NonNull
+        public PreferentialNetworkServiceConfig.Builder setShouldBlockNonMatchingNetworks(
+                boolean blockNonMatchingNetworks) {
+            mShouldBlockNonMatchingNetworks = blockNonMatchingNetworks;
+            return this;
+        }
+
+        /**
          * Set the array of uids whose network access will go through this preferential
          * network service.
          * {@see #setExcludedUids(int[])}
@@ -306,8 +376,13 @@
                 throw new IllegalStateException("Both includedUids and excludedUids "
                         + "cannot be nonempty");
             }
+            if (mShouldBlockNonMatchingNetworks && mAllowFallbackToDefaultConnection) {
+                throw new IllegalStateException("A config cannot both allow fallback and "
+                        + "block non-matching networks");
+            }
             return new PreferentialNetworkServiceConfig(mIsEnabled,
-                    mAllowFallbackToDefaultConnection, mIncludedUids, mExcludedUids, mNetworkId);
+                    mAllowFallbackToDefaultConnection, mShouldBlockNonMatchingNetworks,
+                    mIncludedUids, mExcludedUids, mNetworkId);
         }
 
         /**
@@ -332,6 +407,7 @@
     public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
         dest.writeBoolean(mIsEnabled);
         dest.writeBoolean(mAllowFallbackToDefaultConnection);
+        dest.writeBoolean(mShouldBlockNonMatchingNetworks);
         dest.writeInt(mNetworkId);
         dest.writeIntArray(mIncludedUids);
         dest.writeIntArray(mExcludedUids);
@@ -423,6 +499,9 @@
             } else if (TAG_ALLOW_FALLBACK_TO_DEFAULT_CONNECTION.equals(tagDAM)) {
                 resultBuilder.setFallbackToDefaultConnectionAllowed(parser.getAttributeBoolean(
                         null, ATTR_VALUE, true));
+            } else if (TAG_BLOCK_NON_MATCHING_NETWORKS.equals(tagDAM)) {
+                resultBuilder.setShouldBlockNonMatchingNetworks(parser.getAttributeBoolean(
+                        null, ATTR_VALUE, false));
             } else if (TAG_INCLUDED_UIDS.equals(tagDAM)) {
                 resultBuilder.setIncludedUids(readStringListToIntArray(parser, TAG_UID));
             } else if (TAG_EXCLUDED_UIDS.equals(tagDAM)) {
@@ -443,6 +522,8 @@
         writeAttributeValueToXml(out, TAG_NETWORK_ID, getNetworkId());
         writeAttributeValueToXml(out, TAG_ALLOW_FALLBACK_TO_DEFAULT_CONNECTION,
                 isFallbackToDefaultConnectionAllowed());
+        writeAttributeValueToXml(out, TAG_BLOCK_NON_MATCHING_NETWORKS,
+                shouldBlockNonMatchingNetworks());
         writeAttributeValuesToXml(out, TAG_INCLUDED_UIDS, TAG_UID,
                 intArrayToStringList(getIncludedUids()));
         writeAttributeValuesToXml(out, TAG_EXCLUDED_UIDS, TAG_UID,
@@ -460,6 +541,8 @@
         pw.println(mIsEnabled);
         pw.print("allowFallbackToDefaultConnection=");
         pw.println(mAllowFallbackToDefaultConnection);
+        pw.print("blockNonMatchingNetworks=");
+        pw.println(mShouldBlockNonMatchingNetworks);
         pw.print("includedUids=");
         pw.println(mIncludedUids);
         pw.print("excludedUids=");
diff --git a/core/java/android/app/admin/Provisioning_OWNERS b/core/java/android/app/admin/Provisioning_OWNERS
index c59a9dc..2e5c2df 100644
--- a/core/java/android/app/admin/Provisioning_OWNERS
+++ b/core/java/android/app/admin/Provisioning_OWNERS
@@ -1,5 +1,5 @@
 # Assign bugs to android-enterprise-triage@google.com
-petuska@google.com
-nupursn@google.com
-shreyacsingh@google.com
-alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
+mdb.ae-provisioning-reviews@google.com
+petuska@google.com #{LAST_RESORT_SUGGESTION}
+nupursn@google.com #{LAST_RESORT_SUGGESTION}
+shreyacsingh@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index ddeb2f9..bad282e 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -37,6 +38,8 @@
 import android.util.Log;
 import android.util.Pair;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -407,33 +410,6 @@
     }
 
     /**
-     * Enable/disable the framework backup scheduling entirely for the current user. When disabled,
-     * no Key/Value or Full backup jobs will be scheduled by the Android framework.
-     *
-     * <p>Note: This does not disable backups: only their scheduling is affected and backups can
-     * still be triggered manually.
-     *
-     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
-     *
-     * @hide
-     */
-    @RequiresPermission(allOf = {android.Manifest.permission.BACKUP,
-            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)
-    public void setFrameworkSchedulingEnabled(boolean isEnabled) {
-        checkServiceBinder();
-        if (sService == null) {
-            Log.e(TAG, "setFrameworkSchedulingEnabled() couldn't connect");
-            return;
-        }
-
-        try {
-            sService.setFrameworkSchedulingEnabledForUser(mContext.getUserId(), isEnabled);
-        } catch (RemoteException e) {
-            Log.e(TAG, "setFrameworkSchedulingEnabled() couldn't connect");
-        }
-    }
-
-    /**
      * Report whether the backup mechanism is currently enabled.
      *
      * @hide
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 041c2a7..aeb4987 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -155,22 +155,6 @@
      */
     void setBackupEnabledForUser(int userId, boolean isEnabled);
 
-
-    /**
-     * Enable/disable the framework backup scheduling entirely. When disabled, no Key/Value or Full
-     * backup jobs will be scheduled by the Android framework.
-     *
-     * <p>Note: This does not disable backups: only their scheduling is affected and backups can
-     * still be triggered manually.
-     *
-     * <p>Callers must hold the android.permission.BACKUP permission to use this method. If
-     * {@code userId} is different from the calling user id, then the caller must additionally hold
-     * the android.permission.INTERACT_ACROSS_USERS_FULL permission.
-     *
-     * @param userId The user for which backup scheduling should be enabled/disabled.
-     */
-    void setFrameworkSchedulingEnabledForUser(int userId, boolean isEnabled);
-
     /**
      * {@link android.app.backup.IBackupManager.setBackupEnabledForUser} for the calling user id.
      */
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index c7a3b52..64dcc4d 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -82,6 +82,7 @@
         final boolean mOrderedHint;
         @UnsupportedAppUsage
         final boolean mInitialStickyHint;
+        final boolean mAssumeDeliveredHint;
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         final IBinder mToken;
         @UnsupportedAppUsage
@@ -105,17 +106,38 @@
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         public PendingResult(int resultCode, String resultData, Bundle resultExtras, int type,
                 boolean ordered, boolean sticky, IBinder token, int userId, int flags) {
+            this(resultCode, resultData, resultExtras, type, ordered, sticky,
+                    guessAssumeDelivered(type, ordered), token, userId, flags);
+        }
+
+        /** @hide */
+        public PendingResult(int resultCode, String resultData, Bundle resultExtras, int type,
+                boolean ordered, boolean sticky, boolean assumeDelivered, IBinder token,
+                int userId, int flags) {
             mResultCode = resultCode;
             mResultData = resultData;
             mResultExtras = resultExtras;
             mType = type;
             mOrderedHint = ordered;
             mInitialStickyHint = sticky;
+            mAssumeDeliveredHint = assumeDelivered;
             mToken = token;
             mSendingUser = userId;
             mFlags = flags;
         }
 
+        /** @hide */
+        public static boolean guessAssumeDelivered(int type, boolean ordered) {
+            // When a caller didn't provide a concrete way of knowing if we need
+            // to report delivery, make a best-effort guess
+            if (type == TYPE_COMPONENT) {
+                return false;
+            } else if (ordered && type != TYPE_UNREGISTERED) {
+                return false;
+            }
+            return true;
+        }
+
         /**
          * Version of {@link BroadcastReceiver#setResultCode(int)
          * BroadcastReceiver.setResultCode(int)} for
@@ -252,7 +274,7 @@
                             "Finishing broadcast to component " + mToken);
                     sendFinished(mgr);
                 }
-            } else if (mOrderedHint && mType != TYPE_UNREGISTERED) {
+            } else {
                 if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                         "Finishing broadcast to " + mToken);
                 final IActivityManager mgr = ActivityManager.getService();
@@ -279,13 +301,16 @@
                     if (mResultExtras != null) {
                         mResultExtras.setAllowFds(false);
                     }
-                    if (mOrderedHint) {
-                        am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
-                                mAbortBroadcast, mFlags);
-                    } else {
-                        // This broadcast was sent to a component; it is not ordered,
-                        // but we still need to tell the activity manager we are done.
-                        am.finishReceiver(mToken, 0, null, null, false, mFlags);
+
+                    // When the OS didn't assume delivery, we need to inform
+                    // it that we've actually finished the delivery
+                    if (!mAssumeDeliveredHint) {
+                        if (mOrderedHint) {
+                            am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
+                                    mAbortBroadcast, mFlags);
+                        } else {
+                            am.finishReceiver(mToken, 0, null, null, false, mFlags);
+                        }
                     }
                 } catch (RemoteException ex) {
                 }
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 2be0323..44747fa 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -171,6 +171,16 @@
     public static final int FLAG_MAIN = 0x00004000;
 
     /**
+     * Indicates that this user was created for the purposes of testing.
+     *
+     * <p>These users are subject to removal during tests and should not be used on actual devices
+     * used by humans.
+     *
+     * @hide
+     */
+    public static final int FLAG_FOR_TESTING = 0x00008000;
+
+    /**
      * @hide
      */
     @IntDef(flag = true, prefix = "FLAG_", value = {
@@ -188,7 +198,8 @@
             FLAG_SYSTEM,
             FLAG_PROFILE,
             FLAG_EPHEMERAL_ON_CREATE,
-            FLAG_MAIN
+            FLAG_MAIN,
+            FLAG_FOR_TESTING
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserInfoFlag {
@@ -369,6 +380,12 @@
         return (flags & FLAG_EPHEMERAL) == FLAG_EPHEMERAL;
     }
 
+    /** @hide */
+    @TestApi
+    public boolean isForTesting() {
+        return (flags & FLAG_FOR_TESTING) == FLAG_FOR_TESTING;
+    }
+
     public boolean isInitialized() {
         return (flags & FLAG_INITIALIZED) == FLAG_INITIALIZED;
     }
diff --git a/core/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java b/core/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java
index 41f2f9c..b86b97c 100644
--- a/core/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java
+++ b/core/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java
@@ -17,7 +17,7 @@
 package android.database.sqlite;
 
 /**
- * Thrown if the the bind or column parameter index is out of range
+ * Thrown if the bind or column parameter index is out of range.
  */
 public class SQLiteBindOrColumnIndexOutOfRangeException extends SQLiteException {
     public SQLiteBindOrColumnIndexOutOfRangeException() {}
diff --git a/core/java/android/database/sqlite/SQLiteDiskIOException.java b/core/java/android/database/sqlite/SQLiteDiskIOException.java
index 01b2069..152d90a 100644
--- a/core/java/android/database/sqlite/SQLiteDiskIOException.java
+++ b/core/java/android/database/sqlite/SQLiteDiskIOException.java
@@ -17,7 +17,7 @@
 package android.database.sqlite;
 
 /**
- * An exception that indicates that an IO error occured while accessing the 
+ * Indicates that an IO error occurred while accessing the
  * SQLite database file.
  */
 public class SQLiteDiskIOException extends SQLiteException {
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index e2dedd6..da847a8 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -398,6 +398,23 @@
      * except that it uses {@link java.util.concurrent.Executor} as an argument
      * instead of {@link android.os.Handler}.</p>
      *
+     * <p>Note: If the order between some availability callbacks matters, the implementation of the
+     * executor should handle those callbacks in the same thread to maintain the callbacks' order.
+     * Some examples are:</p>
+     *
+     * <ul>
+     *
+     * <li>{@link AvailabilityCallback#onCameraAvailable} and
+     * {@link AvailabilityCallback#onCameraUnavailable} of the same camera ID.</li>
+     *
+     * <li>{@link AvailabilityCallback#onCameraAvailable} or
+     * {@link AvailabilityCallback#onCameraUnavailable} of a logical multi-camera, and {@link
+     * AvailabilityCallback#onPhysicalCameraUnavailable} or
+     * {@link AvailabilityCallback#onPhysicalCameraAvailable} of its physical
+     * cameras.</li>
+     *
+     * </ul>
+     *
      * @param executor The executor which will be used to invoke the callback.
      * @param callback the new callback to send camera availability notices to
      *
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index d55367f..ed6a88f 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -223,11 +223,13 @@
                 final SomeArgs args = (SomeArgs) msg.obj;
                 final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
                 if (isValid(inputMethod, target, "DO_SHOW_SOFT_INPUT")) {
-                    ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+                    ImeTracker.forLogging().onProgress(
+                            statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                     inputMethod.showSoftInputWithToken(
                             msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1, statsToken);
                 } else {
-                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+                    ImeTracker.forLogging().onFailed(
+                            statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                 }
                 args.recycle();
                 return;
@@ -236,11 +238,13 @@
                 final SomeArgs args = (SomeArgs) msg.obj;
                 final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
                 if (isValid(inputMethod, target, "DO_HIDE_SOFT_INPUT")) {
-                    ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+                    ImeTracker.forLogging().onProgress(
+                            statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                     inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
                             (IBinder) args.arg1, statsToken);
                 } else {
-                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+                    ImeTracker.forLogging().onFailed(
+                            statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                 }
                 args.recycle();
                 return;
@@ -428,7 +432,7 @@
     @Override
     public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
             int flags, ResultReceiver resultReceiver) {
-        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
         mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT,
                 flags, showInputToken, resultReceiver, statsToken));
     }
@@ -437,7 +441,7 @@
     @Override
     public void hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
             int flags, ResultReceiver resultReceiver) {
-        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
         mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_HIDE_SOFT_INPUT,
                 flags, hideInputToken, resultReceiver, statsToken));
     }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 872414a..ee9d8a4 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -896,7 +896,8 @@
         @MainThread
         @Override
         public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
-            ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT);
+            ImeTracker.forLogging().onProgress(
+                    mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT);
             if (DEBUG) Log.v(TAG, "hideSoftInput()");
             if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
                     && !mSystemCallingHideSoftInput) {
@@ -950,7 +951,8 @@
         @MainThread
         @Override
         public void showSoftInput(int flags, ResultReceiver resultReceiver) {
-            ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
+            ImeTracker.forLogging().onProgress(
+                    mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
             if (DEBUG) Log.v(TAG, "showSoftInput()");
             // TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods.
             if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
@@ -966,11 +968,11 @@
                     null /* icProto */);
             final boolean wasVisible = isInputViewShown();
             if (dispatchOnShowInputRequested(flags, false)) {
-                ImeTracker.get().onProgress(mCurStatsToken,
+                ImeTracker.forLogging().onProgress(mCurStatsToken,
                         ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
                 showWindow(true);
             } else {
-                ImeTracker.get().onFailed(mCurStatsToken,
+                ImeTracker.forLogging().onFailed(mCurStatsToken,
                         ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
             }
             setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
@@ -2979,7 +2981,7 @@
         ImeTracing.getInstance().triggerServiceDump(
                 "InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", mDumper,
                 null /* icProto */);
-        ImeTracker.get().onProgress(mCurStatsToken,
+        ImeTracker.forLogging().onProgress(mCurStatsToken,
                 ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
         mPrivOps.applyImeVisibilityAsync(setVisible
                 ? mCurShowInputToken : mCurHideInputToken, setVisible, mCurStatsToken);
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 3b4e8cd..d1d3315 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -142,4 +142,8 @@
     long getUserStartRealtime();
     long getUserUnlockRealtime();
     boolean setUserEphemeral(int userId, boolean enableEphemeral);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS})")
+    void setBootUser(int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS})")
+    int getBootUser();
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index b016c781..9db1233 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5671,6 +5671,40 @@
         }
     }
 
+    /**
+     * Sets the user who should be in the foreground when boot completes. This should be called
+     * during boot, and the provided user must be a full user (i.e. not a profile).
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
+    public void setBootUser(@NonNull UserHandle bootUser) {
+        try {
+            mService.setBootUser(bootUser.getIdentifier());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the user who should be in the foreground when boot completes.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
+    @SuppressWarnings("[AndroidFrameworkContextUserId]")
+    public @NonNull UserHandle getBootUser() {
+        try {
+            return UserHandle.of(mService.getBootUser());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
     /* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */
     private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props";
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3886708..9a848af 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8660,12 +8660,6 @@
         public static final String BACKUP_AUTO_RESTORE = "backup_auto_restore";
 
         /**
-         * Controls whether framework backup scheduling is enabled.
-         * @hide
-         */
-        public static final String BACKUP_SCHEDULING_ENABLED = "backup_scheduling_enabled";
-
-        /**
          * Indicates whether settings backup has been fully provisioned.
          * Type: int ( 0 = unprovisioned, 1 = fully provisioned )
          * @hide
@@ -9408,6 +9402,14 @@
         public static final int DOCK_SETUP_PROMPTED = 3;
 
         /**
+         * Indicates that the user has started dock setup but never finished it.
+         * One of the possible states for {@link #DOCK_SETUP_STATE}.
+         *
+         * @hide
+         */
+        public static final int DOCK_SETUP_INCOMPLETE = 4;
+
+        /**
          * Indicates that the user has completed dock setup.
          * One of the possible states for {@link #DOCK_SETUP_STATE}.
          *
@@ -9415,6 +9417,14 @@
          */
         public static final int DOCK_SETUP_COMPLETED = 10;
 
+        /**
+         * Indicates that dock setup timed out before the user could complete it.
+         * One of the possible states for {@link #DOCK_SETUP_STATE}.
+         *
+         * @hide
+         */
+        public static final int DOCK_SETUP_TIMED_OUT = 11;
+
         /** @hide */
         @Retention(RetentionPolicy.SOURCE)
         @IntDef({
@@ -9422,7 +9432,9 @@
                 DOCK_SETUP_STARTED,
                 DOCK_SETUP_PAUSED,
                 DOCK_SETUP_PROMPTED,
-                DOCK_SETUP_COMPLETED
+                DOCK_SETUP_INCOMPLETE,
+                DOCK_SETUP_COMPLETED,
+                DOCK_SETUP_TIMED_OUT
         })
         public @interface DockSetupState {
         }
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 0fb9f57..b0e847c 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -166,7 +166,7 @@
     }
 
     /**
-     * Description of an event that occured after the latest call to
+     * Description of an event that occurred after the latest call to
      * {@link FillCallback#onSuccess(FillResponse)}.
      */
     public static final class Event {
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 78f91ed..385b0aa 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -20,9 +20,11 @@
 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
 import static android.view.autofill.Helper.sDebug;
 
+import android.annotation.DrawableRes;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringRes;
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.Activity;
@@ -107,6 +109,10 @@
     private final @Nullable UserData mUserData;
     private final @Nullable int[] mCancelIds;
     private final boolean mSupportsInlineSuggestions;
+    private final @DrawableRes int mIconResourceId;
+    private final @StringRes int mServiceDisplayNameResourceId;
+    private final boolean mShowFillDialogIcon;
+    private final boolean mShowSaveDialogIcon;
 
     private FillResponse(@NonNull Builder builder) {
         mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
@@ -130,6 +136,10 @@
         mUserData = builder.mUserData;
         mCancelIds = builder.mCancelIds;
         mSupportsInlineSuggestions = builder.mSupportsInlineSuggestions;
+        mIconResourceId = builder.mIconResourceId;
+        mServiceDisplayNameResourceId = builder.mServiceDisplayNameResourceId;
+        mShowFillDialogIcon = builder.mShowFillDialogIcon;
+        mShowSaveDialogIcon = builder.mShowSaveDialogIcon;
     }
 
     /** @hide */
@@ -218,6 +228,26 @@
     }
 
     /** @hide */
+    public @DrawableRes int getIconResourceId() {
+        return mIconResourceId;
+    }
+
+    /** @hide */
+    public @StringRes int getServiceDisplayNameResourceId() {
+        return mServiceDisplayNameResourceId;
+    }
+
+    /** @hide */
+    public boolean getShowFillDialogIcon() {
+        return mShowFillDialogIcon;
+    }
+
+    /** @hide */
+    public boolean getShowSaveDialogIcon() {
+        return mShowSaveDialogIcon;
+    }
+
+    /** @hide */
     @TestApi
     public int getFlags() {
         return mFlags;
@@ -278,6 +308,10 @@
         private UserData mUserData;
         private int[] mCancelIds;
         private boolean mSupportsInlineSuggestions;
+        private int mIconResourceId;
+        private int mServiceDisplayNameResourceId;
+        private boolean mShowFillDialogIcon = true;
+        private boolean mShowSaveDialogIcon = true;
 
         /**
          * Triggers a custom UI before autofilling the screen with any data set in this
@@ -729,6 +763,70 @@
         }
 
         /**
+         * Overwrites Save/Fill dialog header icon with a specific one specified by resource id.
+         * The image is pulled from the package, so id should be defined in the manifest.
+         *
+         * @param id {@link android.graphics.drawable.Drawable} resource id of the icon to be used.
+         * A value of 0 indicates to use the default header icon.
+         *
+         * @return this builder
+         */
+        @NonNull
+        public Builder setIconResourceId(@DrawableRes int id) {
+            throwIfDestroyed();
+
+            mIconResourceId = id;
+            return this;
+        }
+
+        /**
+         * Overrides the service name in the Save Dialog header with a specific string defined
+         * in the service provider's manifest.xml
+         *
+         * @param id Resoure Id of the custom string defined in the provider's manifest. If set
+         * to 0, the default name will be used.
+         *
+         * @return this builder
+         */
+        @NonNull
+        public Builder setServiceDisplayNameResourceId(@StringRes int id) {
+            throwIfDestroyed();
+
+            mServiceDisplayNameResourceId = id;
+            return this;
+        }
+
+        /**
+         * Whether or not to show the Autofill provider icon inside of the Fill Dialog
+         *
+         * @param show True to show, false to hide. Defaults to true.
+         *
+         * @return this builder
+         */
+        @NonNull
+        public Builder setShowFillDialogIcon(boolean show) {
+            throwIfDestroyed();
+
+            mShowFillDialogIcon = show;
+            return this;
+        }
+
+        /**
+         * Whether or not to show the Autofill provider icon inside of the Save Dialog
+         *
+         * @param show True to show, false to hide. Defaults to true.
+         *
+         * @return this builder
+         */
+        @NonNull
+        public Builder setShowSaveDialogIcon(boolean show) {
+            throwIfDestroyed();
+
+            mShowSaveDialogIcon = show;
+            return this;
+        }
+
+        /**
          * Sets a header to be shown as the first element in the list of datasets.
          *
          * <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset},
@@ -1024,6 +1122,10 @@
         parcel.writeParcelableArray(mIgnoredIds, flags);
         parcel.writeLong(mDisableDuration);
         parcel.writeParcelableArray(mFieldClassificationIds, flags);
+        parcel.writeInt(mIconResourceId);
+        parcel.writeInt(mServiceDisplayNameResourceId);
+        parcel.writeBoolean(mShowFillDialogIcon);
+        parcel.writeBoolean(mShowSaveDialogIcon);
         parcel.writeInt(mFlags);
         parcel.writeIntArray(mCancelIds);
         parcel.writeInt(mRequestId);
@@ -1089,6 +1191,11 @@
             if (fieldClassifactionIds != null) {
                 builder.setFieldClassificationIds(fieldClassifactionIds);
             }
+
+            builder.setIconResourceId(parcel.readInt());
+            builder.setServiceDisplayNameResourceId(parcel.readInt());
+            builder.setShowFillDialogIcon(parcel.readBoolean());
+            builder.setShowSaveDialogIcon(parcel.readBoolean());
             builder.setFlags(parcel.readInt());
             final int[] cancelIds = parcel.createIntArray();
             builder.setPresentationCancelIds(cancelIds);
diff --git a/core/java/android/service/smartspace/SmartspaceService.java b/core/java/android/service/smartspace/SmartspaceService.java
index 3a148df..b13a069 100644
--- a/core/java/android/service/smartspace/SmartspaceService.java
+++ b/core/java/android/service/smartspace/SmartspaceService.java
@@ -302,7 +302,7 @@
                 Slog.e(TAG, "Callback is null, likely the binder has died.");
                 return false;
             }
-            return mCallback.equals(callback);
+            return mCallback.asBinder().equals(callback.asBinder());
         }
 
         @Override
diff --git a/core/java/android/service/voice/AbstractDetector.java b/core/java/android/service/voice/AbstractDetector.java
index db0ede5..a70f783 100644
--- a/core/java/android/service/voice/AbstractDetector.java
+++ b/core/java/android/service/voice/AbstractDetector.java
@@ -84,7 +84,7 @@
             @Nullable SharedMemory sharedMemory);
 
     /**
-     * Detect hotword from an externally supplied stream of data.
+     * Detect from an externally supplied stream of data.
      *
      * @return {@code true} if the request to start recognition succeeded
      */
@@ -114,7 +114,25 @@
         return true;
     }
 
-    /** {@inheritDoc} */
+    /**
+     * Set configuration and pass read-only data to trusted detection service.
+     *
+     * @param options Application configuration data to provide to the
+     *         {@link VisualQueryDetectionService} and {@link HotwordDetectionService}.
+     *         PersistableBundle does not allow any remotable objects or other contents that can be
+     *         used to communicate with other processes.
+     * @param sharedMemory The unrestricted data blob to provide to the
+     *        {@link VisualQueryDetectionService} and {@link HotwordDetectionService}. Use this to
+     *         provide the hotword models data or other such data to the trusted process.
+     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of
+     *         Android Tiramisu or above and attempts to start a recognition when the detector is
+     *         not able based on the state. Because the caller receives updates via an asynchronous
+     *         callback and the state of the detector can change without caller's knowledge, a
+     *         checked exception is thrown.
+     * @throws IllegalStateException if this {@link HotwordDetector} wasn't specified to use a
+     *         {@link HotwordDetectionService} or {@link VisualQueryDetectionService} when it was
+     *         created.
+     */
     @Override
     public void updateState(@Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory) throws IllegalDetectorStateException {
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index f3d4809..0384454 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -194,6 +194,12 @@
         }
 
         @Override
+        public void detectWithVisualSignals(
+                IDetectorSessionVisualQueryDetectionCallback callback) {
+            throw new UnsupportedOperationException("Not supported by HotwordDetectionService");
+        }
+
+        @Override
         public void updateAudioFlinger(IBinder audioFlinger) {
             AudioSystem.setAudioFlingerBinder(audioFlinger);
         }
@@ -382,7 +388,7 @@
      */
     @SystemApi
     public static final class Callback {
-        // TODO: need to make sure we don't store remote references, but not a high priority.
+        // TODO: consider making the constructor a test api for testing purpose
         private final IDspHotwordDetectionCallback mRemoteCallback;
 
         private Callback(IDspHotwordDetectionCallback remoteCallback) {
diff --git a/core/java/android/service/voice/HotwordDetector.java b/core/java/android/service/voice/HotwordDetector.java
index 669c22b..562277e 100644
--- a/core/java/android/service/voice/HotwordDetector.java
+++ b/core/java/android/service/voice/HotwordDetector.java
@@ -35,7 +35,8 @@
 import java.io.PrintWriter;
 
 /**
- * Basic functionality for sandboxed detectors.
+ * Basic functionality for sandboxed detectors. This interface will be used by detectors that
+ * manages their service lifecycle.
  *
  * @hide
  */
@@ -81,9 +82,20 @@
     int DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE = 2;
 
     /**
+     * Indicates that it is a visual query detector.
+     *
+     * @hide
+     */
+    int DETECTOR_TYPE_VISUAL_QUERY_DETECTOR = 3;
+
+    /**
      * Starts sandboxed detection recognition.
      * <p>
-     * On calling this, the system streams audio from the device microphone to this application's
+     * If a {@link VisualQueryDetector} calls this method, {@link VisualQueryDetectionService
+     * #onStartDetection(VisualQueryDetectionService.Callback)} will be called to start detection.
+     * <p>
+     * Otherwise if a {@link AlwaysOnHotwordDetector} or {@link SoftwareHotwordDetector} calls this,
+     * the system streams audio from the device microphone to this application's
      * {@link HotwordDetectionService}. Audio is streamed until {@link #stopRecognition()} is
      * called.
      * <p>
@@ -192,6 +204,8 @@
                 return "trusted_hotword_dsp";
             case DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE:
                 return "trusted_hotword_software";
+            case DETECTOR_TYPE_VISUAL_QUERY_DETECTOR:
+                return "visual_query_detector";
             default:
                 return Integer.toString(detectorType);
         }
@@ -244,18 +258,21 @@
         void onRejected(@NonNull HotwordRejectedResult result);
 
         /**
-         * Called when the {@link HotwordDetectionService} is created by the system and given a
-         * short amount of time to report its initialization state.
+         * Called when the {@link HotwordDetectionService} or {@link VisualQueryDetectionService} is
+         * created by the system and given a short amount of time to report their initialization
+         * state.
          *
-         * @param status Info about initialization state of {@link HotwordDetectionService}; the
-         * allowed values are {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_SUCCESS},
+         * @param status Info about initialization state of {@link HotwordDetectionService} or
+         * {@link VisualQueryDetectionService}; allowed values are
+         * {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_SUCCESS},
          * 1<->{@link SandboxedDetectionServiceBase#getMaxCustomInitializationStatus()},
          * {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_UNKNOWN}.
          */
         void onHotwordDetectionServiceInitialized(int status);
 
         /**
-         * Called with the {@link HotwordDetectionService} is restarted.
+         * Called with the {@link HotwordDetectionService} or {@link VisualQueryDetectionService} is
+         * restarted.
          *
          * Clients are expected to call {@link HotwordDetector#updateState} to share the state with
          * the newly created service.
diff --git a/core/java/android/service/voice/IDetectorSessionVisualQueryDetectionCallback.aidl b/core/java/android/service/voice/IDetectorSessionVisualQueryDetectionCallback.aidl
new file mode 100644
index 0000000..22172ed
--- /dev/null
+++ b/core/java/android/service/voice/IDetectorSessionVisualQueryDetectionCallback.aidl
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 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.voice;
+
+/**
+ * Callback for returning the detected result from the {@link VisualQueryDetectionService}.
+ *
+ * The {@link VisualQueryDetectorSession} will overrides this interface to reach query egression
+ * control within each callback methods.
+ *
+ * @hide
+ */
+oneway interface IDetectorSessionVisualQueryDetectionCallback {
+
+    /**
+     * Called when the user attention is gained and intent to show the assistant icon in SysUI.
+     */
+    void onAttentionGained();
+
+    /**
+     * Called when the user attention is lost and intent to hide the assistant icon in SysUI.
+     */
+    void onAttentionLost();
+
+    /**
+     * Called when the detected query is streamed.
+     */
+    void onQueryDetected(in String partialQuery);
+
+    /**
+     * Called when the detected result is valid.
+     */
+    void onQueryFinished();
+
+    /**
+     * Called when the detected result is invalid.
+     */
+    void onQueryRejected();
+}
diff --git a/core/java/android/service/voice/ISandboxedDetectionService.aidl b/core/java/android/service/voice/ISandboxedDetectionService.aidl
index 5537fd1..098536d 100644
--- a/core/java/android/service/voice/ISandboxedDetectionService.aidl
+++ b/core/java/android/service/voice/ISandboxedDetectionService.aidl
@@ -24,6 +24,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.SharedMemory;
+import android.service.voice.IDetectorSessionVisualQueryDetectionCallback;
 import android.service.voice.IDspHotwordDetectionCallback;
 import android.view.contentcapture.IContentCaptureManager;
 import android.speech.IRecognitionServiceManager;
@@ -47,6 +48,8 @@
         in PersistableBundle options,
         in IDspHotwordDetectionCallback callback);
 
+    void detectWithVisualSignals(in IDetectorSessionVisualQueryDetectionCallback callback);
+
     void updateState(
         in PersistableBundle options,
         in SharedMemory sharedMemory,
diff --git a/core/java/android/service/voice/IVisualQueryDetectionVoiceInteractionCallback.aidl b/core/java/android/service/voice/IVisualQueryDetectionVoiceInteractionCallback.aidl
new file mode 100644
index 0000000..2eb2470
--- /dev/null
+++ b/core/java/android/service/voice/IVisualQueryDetectionVoiceInteractionCallback.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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.voice;
+
+import android.media.AudioFormat;
+
+/**
+ * Callback for returning the detected result from the VisualQueryDetectionService.
+ *
+ * @hide
+ */
+oneway interface IVisualQueryDetectionVoiceInteractionCallback {
+
+    /**
+     * Called when the detected query is streamed
+     */
+    void onQueryDetected(in String partialQuery);
+
+    /**
+     * Called when the detected result is valid.
+     */
+    void onQueryFinished();
+
+    /**
+     * Called when the detected result is invalid.
+     */
+    void onQueryRejected();
+
+    /**
+     * Called when the detection fails due to an error.
+     */
+    void onError();
+
+}
diff --git a/core/java/android/service/voice/IVoiceInteractionService.aidl b/core/java/android/service/voice/IVoiceInteractionService.aidl
index efae5c1..6a54606 100644
--- a/core/java/android/service/voice/IVoiceInteractionService.aidl
+++ b/core/java/android/service/voice/IVoiceInteractionService.aidl
@@ -31,5 +31,5 @@
     void getActiveServiceSupportedActions(in List<String> voiceActions,
      in IVoiceActionCheckCallback callback);
     void prepareToShowSession(in Bundle args, int flags);
-    void showSessionFailed();
+    void showSessionFailed(in Bundle args);
 }
diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java
index 7926901..fde0afb 100644
--- a/core/java/android/service/voice/VisualQueryDetectionService.java
+++ b/core/java/android/service/voice/VisualQueryDetectionService.java
@@ -36,6 +36,7 @@
 import android.util.Log;
 import android.view.contentcapture.IContentCaptureManager;
 
+import java.util.Objects;
 import java.util.function.IntConsumer;
 
 /**
@@ -80,6 +81,19 @@
     private final ISandboxedDetectionService mInterface = new ISandboxedDetectionService.Stub() {
 
         @Override
+        public void detectWithVisualSignals(
+                IDetectorSessionVisualQueryDetectionCallback callback) {
+            Log.v(TAG, "#detectWithVisualSignals");
+            VisualQueryDetectionService.this.onStartDetection(new Callback(callback));
+        }
+
+        @Override
+        public void stopDetection() {
+            Log.v(TAG, "#stopDetection");
+            VisualQueryDetectionService.this.onStopDetection();
+        }
+
+        @Override
         public void updateState(PersistableBundle options, SharedMemory sharedMemory,
                 IRemoteCallback callback) throws RemoteException {
             Log.v(TAG, "#updateState" + (callback != null ? " with callback" : ""));
@@ -128,11 +142,6 @@
         public void updateRecognitionServiceManager(IRecognitionServiceManager manager) {
             Log.v(TAG, "Ignore #updateRecognitionServiceManager");
         }
-
-        @Override
-        public void stopDetection() {
-            throw new UnsupportedOperationException("Not supported by VisualQueryDetectionService");
-        }
     };
 
     /**
@@ -216,18 +225,37 @@
      */
     public static final class Callback {
 
+        // TODO: consider making the constructor a test api for testing purpose
+        public Callback() {
+            mRemoteCallback = null;
+        }
+
+        private final IDetectorSessionVisualQueryDetectionCallback mRemoteCallback;
+
+        private Callback(IDetectorSessionVisualQueryDetectionCallback remoteCallback) {
+            mRemoteCallback = remoteCallback;
+        }
+
         /**
          * Informs attention listener that the user attention is gained.
          */
         public void onAttentionGained() {
-            //TODO(b/265345361): call internal callbacks to send signal to the interactor
+            try {
+                mRemoteCallback.onAttentionGained();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
 
         /**
          * Informs attention listener that the user attention is lost.
          */
         public void onAttentionLost() {
-            //TODO(b/265345361): call internal callbacks to send signal to the interactor
+            try {
+                mRemoteCallback.onAttentionLost();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
 
         /**
@@ -241,8 +269,13 @@
          * @throws IllegalStateException if method called without attention gained.
          */
         public void onQueryDetected(@NonNull String partialQuery) throws IllegalStateException {
-            //TODO(b/265345361): call internal callbacks to send signal to the interactor
-            //TODO(b/265381651): convert callback exceptions and throw IllegalStateException.
+            Objects.requireNonNull(partialQuery);
+            try {
+                mRemoteCallback.onQueryDetected(partialQuery);
+            } catch (RemoteException e) {
+                throw new IllegalStateException("#onQueryDetected must be only be triggered after "
+                        + "calling #onAttentionGained to be in the attention gained state.");
+            }
         }
 
         /**
@@ -254,8 +287,12 @@
          * @throws IllegalStateException if method called without query streamed.
          */
         public void onQueryRejected() throws IllegalStateException {
-            //TODO(b/265345361): call internal callbacks to send signal to the interactor
-            //TODO(b/265381651): convert callback exceptions and throw IllegalStateException.
+            try {
+                mRemoteCallback.onQueryRejected();
+            } catch (RemoteException e) {
+                throw new IllegalStateException("#onQueryRejected must be only be triggered after "
+                        + "calling #onQueryDetected to be in the query streaming state.");
+            }
         }
 
         /**
@@ -267,8 +304,12 @@
          * @throws IllegalStateException if method called without query streamed.
          */
         public void onQueryFinished() throws IllegalStateException {
-            //TODO(b/265345361): call internal callbacks to send signal to the interactor
-            //TODO(b/265381651): convert callback exceptions and throw IllegalStateException.
+            try {
+                mRemoteCallback.onQueryFinished();
+            } catch (RemoteException e) {
+                throw new IllegalStateException("#onQueryFinished must be only be triggered after "
+                        + "calling #onQueryDetected to be in the query streaming state.");
+            }
         }
     }
 
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
new file mode 100644
index 0000000..6917576
--- /dev/null
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2023 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.voice;
+
+import static android.Manifest.permission.CAMERA;
+import static android.Manifest.permission.RECORD_AUDIO;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.AudioFormat;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.util.Slog;
+
+import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.internal.app.IVoiceInteractionManagerService;
+
+import java.io.PrintWriter;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Manages VisualQueryDetectionService.
+ *
+ * This detector provides necessary functionalities to initialize, start, update and destroy a
+ * {@link VisualQueryDetectionService}.
+ *
+ * @hide
+ **/
+@SystemApi
+@SuppressLint("NotCloseable")
+public class VisualQueryDetector {
+    private static final String TAG = VisualQueryDetector.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private final Callback mCallback;
+    private final Executor mExecutor;
+    private final IVoiceInteractionManagerService mManagerService;
+    private final VisualQueryDetectorInitializationDelegate mInitializationDelegate;
+
+    VisualQueryDetector(
+            IVoiceInteractionManagerService managerService,
+            @NonNull @CallbackExecutor Executor executor,
+            Callback callback) {
+        mManagerService = managerService;
+        mCallback = callback;
+        mExecutor = executor;
+        mInitializationDelegate = new VisualQueryDetectorInitializationDelegate();
+    }
+
+    /**
+     * Initialize the {@link VisualQueryDetectionService} by passing configurations and read-only
+     * data.
+     */
+    void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
+        mInitializationDelegate.initialize(options, sharedMemory);
+    }
+
+    /**
+     * Set configuration and pass read-only data to {@link VisualQueryDetectionService}.
+     *
+     * @see HotwordDetector#updateState(PersistableBundle, SharedMemory)
+     */
+    public void updateState(@Nullable PersistableBundle options,
+            @Nullable SharedMemory sharedMemory) throws
+            HotwordDetector.IllegalDetectorStateException {
+        mInitializationDelegate.updateState(options, sharedMemory);
+    }
+
+
+    /**
+     * On calling this method, {@link VisualQueryDetectionService
+     * #onStartDetection(VisualQueryDetectionService.Callback)} will be called to start using
+     * visual signals such as camera frames and microphone audio to perform detection. When user
+     * attention is captured and the {@link VisualQueryDetectionService} streams queries,
+     * {@link VisualQueryDetector.Callback#onQueryDetected(String)} is called to control the
+     * behavior of handling {@code transcribedText}. When the query streaming is finished,
+     * {@link VisualQueryDetector.Callback#onQueryFinished()} is called. If the current streamed
+     * query is invalid, {@link VisualQueryDetector.Callback#onQueryRejected()} is called to abandon
+     * the streamed query.
+     *
+     * @see HotwordDetector#startRecognition()
+     */
+    @RequiresPermission(allOf = {CAMERA, RECORD_AUDIO})
+    public boolean startRecognition() throws HotwordDetector.IllegalDetectorStateException {
+        if (DEBUG) {
+            Slog.i(TAG, "#startRecognition");
+        }
+        // check if the detector is active with the initialization delegate
+        mInitializationDelegate.startRecognition();
+
+        try {
+            mManagerService.startPerceiving(new BinderCallback(mExecutor, mCallback));
+        } catch (SecurityException e) {
+            Slog.e(TAG, "startRecognition failed: " + e);
+            return false;
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return true;
+    }
+
+    /**
+     * Stops visual query detection recognition.
+     *
+     * @see HotwordDetector#stopRecognition()
+     */
+    @RequiresPermission(allOf = {CAMERA, RECORD_AUDIO})
+    public boolean stopRecognition() throws HotwordDetector.IllegalDetectorStateException {
+        if (DEBUG) {
+            Slog.i(TAG, "#stopRecognition");
+        }
+        // check if the detector is active with the initialization delegate
+        mInitializationDelegate.startRecognition();
+
+        try {
+            mManagerService.stopPerceiving();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return true;
+    }
+
+    /**
+     * Destroy the current detector.
+     *
+     * @see HotwordDetector#destroy()
+     */
+    public void destroy() {
+        if (DEBUG) {
+            Slog.i(TAG, "#destroy");
+        }
+        mInitializationDelegate.destroy();
+    }
+
+    /** @hide */
+    public void dump(String prefix, PrintWriter pw) {
+        // TODO: implement this
+    }
+
+    /** @hide */
+    public HotwordDetector getInitializationDelegate() {
+        return mInitializationDelegate;
+    }
+
+    /** @hide */
+    void registerOnDestroyListener(Consumer<AbstractDetector> onDestroyListener) {
+        mInitializationDelegate.registerOnDestroyListener(onDestroyListener);
+    }
+
+    /**
+     * A class that lets a VoiceInteractionService implementation interact with
+     * visual query detection APIs.
+     */
+    public interface Callback {
+
+        /**
+         * Called when the {@link VisualQueryDetectionService} starts to stream partial queries.
+         *
+         * @param partialQuery The partial query in a text form being streamed.
+         */
+        void onQueryDetected(@NonNull String partialQuery);
+
+        /**
+         * Called when the {@link VisualQueryDetectionService} decides to abandon the streamed
+         * partial queries.
+         */
+        void onQueryRejected();
+
+        /**
+         *  Called when the {@link VisualQueryDetectionService} finishes streaming partial queries.
+         */
+        void onQueryFinished();
+
+        /**
+         * Called when the {@link VisualQueryDetectionService} is created by the system and given a
+         * short amount of time to report its initialization state.
+         *
+         * @param status Info about initialization state of {@link VisualQueryDetectionService}; the
+         * allowed values are {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_SUCCESS},
+         * 1<->{@link SandboxedDetectionServiceBase#getMaxCustomInitializationStatus()},
+         * {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_UNKNOWN}.
+         */
+        void onVisualQueryDetectionServiceInitialized(int status);
+
+         /**
+         * Called with the {@link VisualQueryDetectionService} is restarted.
+         *
+         * Clients are expected to call {@link HotwordDetector#updateState} to share the state with
+         * the newly created service.
+         */
+        void onVisualQueryDetectionServiceRestarted();
+
+        /**
+         * Called when the detection fails due to an error.
+         */
+        //TODO(b/265390855): Replace this callback with the new onError(DetectorError) design.
+        void onError();
+    }
+
+    private class VisualQueryDetectorInitializationDelegate extends AbstractDetector {
+
+        VisualQueryDetectorInitializationDelegate() {
+            super(mManagerService, null);
+        }
+
+        @Override
+        void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
+            initAndVerifyDetector(options, sharedMemory,
+                    new InitializationStateListener(mExecutor, mCallback),
+                    DETECTOR_TYPE_VISUAL_QUERY_DETECTOR);
+        }
+
+        @Override
+        public boolean stopRecognition() throws IllegalDetectorStateException {
+            throwIfDetectorIsNoLongerActive();
+            return true;
+        }
+
+        @Override
+        public boolean startRecognition() throws IllegalDetectorStateException {
+            throwIfDetectorIsNoLongerActive();
+            return true;
+        }
+
+        @Override
+        public final boolean startRecognition(
+                @NonNull ParcelFileDescriptor audioStream,
+                @NonNull AudioFormat audioFormat,
+                @Nullable PersistableBundle options) throws IllegalDetectorStateException {
+            //No-op, not supported by VisualQueryDetector as it should be trusted.
+            return false;
+        }
+
+        @Override
+        public boolean isUsingSandboxedDetectionService() {
+            return true;
+        }
+    }
+
+    private static class BinderCallback
+            extends IVisualQueryDetectionVoiceInteractionCallback.Stub {
+        private final Executor mExecutor;
+        private final VisualQueryDetector.Callback mCallback;
+
+        BinderCallback(Executor executor, VisualQueryDetector.Callback callback) {
+            this.mExecutor = executor;
+            this.mCallback = callback;
+        }
+
+        /** Called when the detected result is valid. */
+        @Override
+        public void onQueryDetected(@NonNull String partialQuery) {
+            Slog.v(TAG, "BinderCallback#onQueryDetected");
+            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+                    () -> mCallback.onQueryDetected(partialQuery)));
+        }
+
+        @Override
+        public void onQueryFinished() {
+            Slog.v(TAG, "BinderCallback#onQueryFinished");
+            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+                    () -> mCallback.onQueryFinished()));
+        }
+
+        @Override
+        public void onQueryRejected() {
+            Slog.v(TAG, "BinderCallback#onQueryRejected");
+            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+                    () -> mCallback.onQueryRejected()));
+        }
+
+        /** Called when the detection fails due to an error. */
+        @Override
+        public void onError() {
+            Slog.v(TAG, "BinderCallback#onError");
+            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+                    () -> mCallback.onError()));
+        }
+
+    }
+
+
+    private static class InitializationStateListener
+            extends IHotwordRecognitionStatusCallback.Stub {
+        private final Executor mExecutor;
+        private final Callback mCallback;
+
+        InitializationStateListener(Executor executor, Callback callback) {
+            this.mExecutor = executor;
+            this.mCallback = callback;
+        }
+
+        @Override
+        public void onKeyphraseDetected(
+                SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
+                HotwordDetectedResult result) {
+            if (DEBUG) {
+                Slog.i(TAG, "Ignored #onKeyphraseDetected event");
+            }
+        }
+
+        @Override
+        public void onGenericSoundTriggerDetected(
+                SoundTrigger.GenericRecognitionEvent recognitionEvent) throws RemoteException {
+            if (DEBUG) {
+                Slog.i(TAG, "Ignored #onGenericSoundTriggerDetected event");
+            }
+        }
+
+        @Override
+        public void onRejected(HotwordRejectedResult result) throws RemoteException {
+            if (DEBUG) {
+                Slog.i(TAG, "Ignored #onRejected event");
+            }
+        }
+
+        @Override
+        public void onRecognitionPaused() throws RemoteException {
+            if (DEBUG) {
+                Slog.i(TAG, "Ignored #onRecognitionPaused event");
+            }
+        }
+
+        @Override
+        public void onRecognitionResumed() throws RemoteException {
+            if (DEBUG) {
+                Slog.i(TAG, "Ignored #onRecognitionResumed event");
+            }
+        }
+
+        @Override
+        public void onStatusReported(int status) {
+            Slog.v(TAG, "onStatusReported" + (DEBUG ? "(" + status + ")" : ""));
+            //TODO: rename the target callback with a more general term
+            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+                    () -> mCallback.onVisualQueryDetectionServiceInitialized(status)));
+
+        }
+
+        @Override
+        public void onProcessRestarted() throws RemoteException {
+            Slog.v(TAG, "onProcessRestarted()");
+            //TODO: rename the target callback with a more general term
+            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+                    () -> mCallback.onVisualQueryDetectionServiceRestarted()));
+        }
+
+        @Override
+        public void onError(int status) throws RemoteException {
+            Slog.v(TAG, "Initialization Error: (" + status + ")");
+            // Do nothing
+        }
+    }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index d49b5a5..df739e3 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -17,6 +17,7 @@
 package android.service.voice;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -58,7 +59,7 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.Set;
-
+import java.util.concurrent.Executor;
 /**
  * Top-level service of the current global voice interactor, which is providing
  * support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc.
@@ -94,6 +95,20 @@
     public static final String SERVICE_META_DATA = "android.voice_interaction";
 
     /**
+     * Bundle key used to specify the id when the system prepares to show session. It increases for
+     * each request.
+     * <p>
+     * Type: int
+     * </p>
+     * @see #showSession(Bundle, int)
+     * @see #onPrepareToShowSession(Bundle, int)
+     * @see #onShowSessionFailed(Bundle)
+     * @see VoiceInteractionSession#onShow(Bundle, int)
+     * @see VoiceInteractionSession#show(Bundle, int)
+     */
+    public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
+
+    /**
      * For apps targeting Build.VERSION_CODES.TRAMISU and above, implementors of this
      * service can create multiple AlwaysOnHotwordDetector instances in parallel. They will
      * also e ale to create a single SoftwareHotwordDetector in parallel with any other
@@ -169,15 +184,17 @@
         }
 
         @Override
-        public void showSessionFailed() {
+        public void showSessionFailed(@NonNull Bundle args) {
             Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
                     VoiceInteractionService::onShowSessionFailed,
-                    VoiceInteractionService.this));
+                    VoiceInteractionService.this, args));
         }
     };
 
     IVoiceInteractionManagerService mSystemService;
 
+    private VisualQueryDetector mActiveVisualQueryDetector;
+
     private final Object mLock = new Object();
 
     private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
@@ -202,9 +219,10 @@
      * bind the session service.
      *
      * @param args  The arguments that were supplied to {@link #showSession(Bundle, int)}.
+     *              It always includes {@link #KEY_SHOW_SESSION_ID}.
      * @param flags The show flags originally provided to {@link #showSession(Bundle, int)}.
      * @see #showSession(Bundle, int)
-     * @see #onShowSessionFailed()
+     * @see #onShowSessionFailed(Bundle)
      * @see VoiceInteractionSession#onShow(Bundle, int)
      * @see VoiceInteractionSession#show(Bundle, int)
      */
@@ -214,12 +232,14 @@
     /**
      * Called when the show session failed. E.g. When the system bound the session service failed.
      *
+     * @param args Additional info about the show session attempt that failed. For now, includes
+     *             {@link #KEY_SHOW_SESSION_ID}.
      * @see #showSession(Bundle, int)
      * @see #onPrepareToShowSession(Bundle, int)
      * @see VoiceInteractionSession#onShow(Bundle, int)
      * @see VoiceInteractionSession#show(Bundle, int)
      */
-    public void onShowSessionFailed() {
+    public void onShowSessionFailed(@NonNull Bundle args) {
     }
 
     /**
@@ -583,6 +603,85 @@
     }
 
     /**
+     * Creates a {@link VisualQueryDetector} and initializes the application's
+     * {@link VisualQueryDetectionService} using {@code options} and {@code sharedMemory}.
+     *
+     * <p>To be able to call this, you need to set android:visualQueryDetectionService in the
+     * android.voice_interaction metadata file to a valid visual query detection service, and set
+     * android:isolatedProcess="true" in the service's declaration. Otherwise, this throws an
+     * {@link IllegalStateException}.
+     *
+     * <p>Using this has a noticeable impact on battery, since the microphone is kept open
+     * for the lifetime of the recognition {@link VisualQueryDetector#startRecognition() session}.
+     *
+     * @param options Application configuration data to be provided to the
+     * {@link VisualQueryDetectionService}. PersistableBundle does not allow any remotable objects
+     * or other contents that can be used to communicate with other processes.
+     * @param sharedMemory The unrestricted data blob to be provided to the
+     * {@link VisualQueryDetectionService}. Use this to provide models or other such data to the
+     * sandboxed process.
+     * @param callback The callback to notify of detection events.
+     * @return An instanece of {@link VisualQueryDetector}.
+     * @throws UnsupportedOperationException if only single detector is supported. Multiple detector
+     * is only available for apps targeting {@link Build.VERSION_CODES#TIRAMISU} and above.
+     * @throws IllegalStateException when there is an existing {@link VisualQueryDetector}, or when
+     * there is a non-trusted hotword detector running.
+     *
+     * @hide
+     */
+    // TODO: add MANAGE_HOTWORD_DETECTION permission to protect this API and update java doc.
+    @SystemApi
+    @NonNull
+    public final VisualQueryDetector createVisualQueryDetector(
+            @Nullable PersistableBundle options,
+            @Nullable SharedMemory sharedMemory,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull VisualQueryDetector.Callback callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        if (mSystemService == null) {
+            throw new IllegalStateException("Not available until onReady() is called");
+        }
+        synchronized (mLock) {
+            if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) {
+                throw new UnsupportedOperationException("VisualQueryDetector is only available if "
+                        + "multiple detectors are allowed");
+            } else {
+                if (mActiveVisualQueryDetector != null) {
+                    throw new IllegalStateException(
+                                "There is already an active VisualQueryDetector. "
+                                        + "It must be destroyed to create a new one.");
+                }
+                for (HotwordDetector detector : mActiveDetectors) {
+                    if (!detector.isUsingSandboxedDetectionService()) {
+                        throw new IllegalStateException(
+                                "It disallows to create trusted and non-trusted detectors "
+                                        + "at the same time.");
+                    }
+                }
+            }
+
+            VisualQueryDetector visualQueryDetector =
+                    new VisualQueryDetector(mSystemService, executor, callback);
+            HotwordDetector visualQueryDetectorInitializationDelegate =
+                    visualQueryDetector.getInitializationDelegate();
+            mActiveDetectors.add(visualQueryDetectorInitializationDelegate);
+
+            try {
+                visualQueryDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
+                visualQueryDetector.initialize(options, sharedMemory);
+            } catch (Exception e) {
+                mActiveDetectors.remove(visualQueryDetectorInitializationDelegate);
+                visualQueryDetector.destroy();
+                throw e;
+            }
+            mActiveVisualQueryDetector = visualQueryDetector;
+            return visualQueryDetector;
+        }
+    }
+
+    /**
      * Creates an {@link KeyphraseModelManager} to use for enrolling voice models outside of the
      * pre-bundled system voice models.
      * @hide
@@ -637,6 +736,10 @@
 
     private void onHotwordDetectorDestroyed(@NonNull HotwordDetector detector) {
         synchronized (mLock) {
+            if (mActiveVisualQueryDetector != null
+                    && detector == mActiveVisualQueryDetector.getInitializationDelegate()) {
+                mActiveVisualQueryDetector = null;
+            }
             mActiveDetectors.remove(detector);
             shutdownHotwordDetectionServiceIfRequiredLocked();
         }
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 2c70229..d55fede 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -1763,7 +1763,8 @@
      * @param args The arguments that were supplied to
      * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
      * Some example keys include : "invocation_type", "invocation_phone_state",
-     * "invocation_time_ms", Intent.EXTRA_TIME ("android.intent.extra.TIME") indicating timing
+     * {@link VoiceInteractionService#KEY_SHOW_SESSION_ID}, "invocation_time_ms",
+     * Intent.EXTRA_TIME ("android.intent.extra.TIME") indicating timing
      * in milliseconds of the KeyEvent that triggered Assistant and
      * Intent.EXTRA_ASSIST_INPUT_DEVICE_ID (android.intent.extra.ASSIST_INPUT_DEVICE_ID)
      *  referring to the device that sent the request.
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 7f45b38..196bac2 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -24,6 +24,7 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.os.Build;
+import android.text.method.OffsetMapping;
 import android.text.style.ReplacementSpan;
 import android.text.style.UpdateLayout;
 import android.text.style.WrapTogetherSpan;
@@ -1095,10 +1096,27 @@
         }
 
         public void beforeTextChanged(CharSequence s, int where, int before, int after) {
-            // Intentionally empty
+            final DynamicLayout dynamicLayout = mLayout.get();
+            if (dynamicLayout != null && dynamicLayout.mDisplay instanceof OffsetMapping) {
+                final OffsetMapping transformedText = (OffsetMapping) dynamicLayout.mDisplay;
+                if (mTransformedTextUpdate == null) {
+                    mTransformedTextUpdate = new OffsetMapping.TextUpdate(where, before, after);
+                } else {
+                    mTransformedTextUpdate.where = where;
+                    mTransformedTextUpdate.before = before;
+                    mTransformedTextUpdate.after = after;
+                }
+                transformedText.originalToTransformed(mTransformedTextUpdate);
+            }
         }
 
         public void onTextChanged(CharSequence s, int where, int before, int after) {
+            final DynamicLayout dynamicLayout = mLayout.get();
+            if (dynamicLayout != null && dynamicLayout.mDisplay instanceof OffsetMapping) {
+                where = mTransformedTextUpdate.where;
+                before = mTransformedTextUpdate.before;
+                after = mTransformedTextUpdate.after;
+            }
             reflow(s, where, before, after);
         }
 
@@ -1106,14 +1124,34 @@
             // Intentionally empty
         }
 
+        /**
+         * Reflow the {@link DynamicLayout} at the given range from {@code start} to the
+         * {@code end}.
+         * If the display text in this {@link DynamicLayout} is a {@link OffsetMapping} instance
+         * (which means it's also a transformed text), it will transform the given range first and
+         * then reflow.
+         */
+        private void transformAndReflow(Spannable s, int start, int end) {
+            final DynamicLayout dynamicLayout = mLayout.get();
+            if (dynamicLayout != null && dynamicLayout.mDisplay instanceof OffsetMapping) {
+                final OffsetMapping transformedText = (OffsetMapping) dynamicLayout.mDisplay;
+                start = transformedText.originalToTransformed(start,
+                        OffsetMapping.MAP_STRATEGY_CHARACTER);
+                end = transformedText.originalToTransformed(end,
+                        OffsetMapping.MAP_STRATEGY_CHARACTER);
+            }
+            reflow(s, start, end - start, end - start);
+        }
+
         public void onSpanAdded(Spannable s, Object o, int start, int end) {
-            if (o instanceof UpdateLayout)
-                reflow(s, start, end - start, end - start);
+            if (o instanceof UpdateLayout) {
+                transformAndReflow(s, start, end);
+            }
         }
 
         public void onSpanRemoved(Spannable s, Object o, int start, int end) {
             if (o instanceof UpdateLayout)
-                reflow(s, start, end - start, end - start);
+                transformAndReflow(s, start, end);
         }
 
         public void onSpanChanged(Spannable s, Object o, int start, int end, int nstart, int nend) {
@@ -1123,12 +1161,13 @@
                     // instead of causing an exception
                     start = 0;
                 }
-                reflow(s, start, end - start, end - start);
-                reflow(s, nstart, nend - nstart, nend - nstart);
+                transformAndReflow(s, start, end);
+                transformAndReflow(s, nstart, nend);
             }
         }
 
         private WeakReference<DynamicLayout> mLayout;
+        private OffsetMapping.TextUpdate mTransformedTextUpdate;
     }
 
     @Override
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index d3367d0..37474e5 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -68,6 +68,9 @@
 
     @Override
     protected boolean left(TextView widget, Spannable buffer) {
+        if (widget.isOffsetMappingAvailable()) {
+            return false;
+        }
         final Layout layout = widget.getLayout();
         if (isSelecting(buffer)) {
             return Selection.extendLeft(buffer, layout);
@@ -78,6 +81,9 @@
 
     @Override
     protected boolean right(TextView widget, Spannable buffer) {
+        if (widget.isOffsetMappingAvailable()) {
+            return false;
+        }
         final Layout layout = widget.getLayout();
         if (isSelecting(buffer)) {
             return Selection.extendRight(buffer, layout);
@@ -88,6 +94,9 @@
 
     @Override
     protected boolean up(TextView widget, Spannable buffer) {
+        if (widget.isOffsetMappingAvailable()) {
+            return false;
+        }
         final Layout layout = widget.getLayout();
         if (isSelecting(buffer)) {
             return Selection.extendUp(buffer, layout);
@@ -98,6 +107,9 @@
 
     @Override
     protected boolean down(TextView widget, Spannable buffer) {
+        if (widget.isOffsetMappingAvailable()) {
+            return false;
+        }
         final Layout layout = widget.getLayout();
         if (isSelecting(buffer)) {
             return Selection.extendDown(buffer, layout);
@@ -108,6 +120,9 @@
 
     @Override
     protected boolean pageUp(TextView widget, Spannable buffer) {
+        if (widget.isOffsetMappingAvailable()) {
+            return false;
+        }
         final Layout layout = widget.getLayout();
         final boolean selecting = isSelecting(buffer);
         final int targetY = getCurrentLineTop(buffer, layout) - getPageHeight(widget);
@@ -132,6 +147,9 @@
 
     @Override
     protected boolean pageDown(TextView widget, Spannable buffer) {
+        if (widget.isOffsetMappingAvailable()) {
+            return false;
+        }
         final Layout layout = widget.getLayout();
         final boolean selecting = isSelecting(buffer);
         final int targetY = getCurrentLineTop(buffer, layout) + getPageHeight(widget);
@@ -176,6 +194,9 @@
 
     @Override
     protected boolean lineStart(TextView widget, Spannable buffer) {
+        if (widget.isOffsetMappingAvailable()) {
+            return false;
+        }
         final Layout layout = widget.getLayout();
         if (isSelecting(buffer)) {
             return Selection.extendToLeftEdge(buffer, layout);
@@ -186,6 +207,9 @@
 
     @Override
     protected boolean lineEnd(TextView widget, Spannable buffer) {
+        if (widget.isOffsetMappingAvailable()) {
+            return false;
+        }
         final Layout layout = widget.getLayout();
         if (isSelecting(buffer)) {
             return Selection.extendToRightEdge(buffer, layout);
@@ -224,6 +248,9 @@
 
     @Override
     public boolean previousParagraph(@NonNull TextView widget, @NonNull Spannable buffer) {
+        if (widget.isOffsetMappingAvailable()) {
+            return false;
+        }
         final Layout layout = widget.getLayout();
         if (isSelecting(buffer)) {
             return Selection.extendToParagraphStart(buffer);
@@ -234,6 +261,9 @@
 
     @Override
     public boolean nextParagraph(@NonNull TextView widget, @NonNull  Spannable buffer) {
+        if (widget.isOffsetMappingAvailable()) {
+            return false;
+        }
         final Layout layout = widget.getLayout();
         if (isSelecting(buffer)) {
             return Selection.extendToParagraphEnd(buffer);
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index 01989d5..9a120d5 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -440,8 +440,9 @@
 
     private boolean deleteLine(View view, Editable content) {
         if (view instanceof TextView) {
-            final Layout layout = ((TextView) view).getLayout();
-            if (layout != null) {
+            final TextView textView = (TextView) view;
+            final Layout layout = textView.getLayout();
+            if (layout != null && !textView.isOffsetMappingAvailable()) {
                 final int line = layout.getLineForOffset(Selection.getSelectionStart(content));
                 final int start = layout.getLineStart(line);
                 final int end = layout.getLineEnd(line);
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
index c544c41..dae978e 100644
--- a/core/java/android/text/method/LinkMovementMethod.java
+++ b/core/java/android/text/method/LinkMovementMethod.java
@@ -100,6 +100,10 @@
 
     private boolean action(int what, TextView widget, Spannable buffer) {
         Layout layout = widget.getLayout();
+        if (widget.isOffsetMappingAvailable()) {
+            // The text in the layout is transformed and has OffsetMapping, don't do anything.
+            return false;
+        }
 
         int padding = widget.getTotalPaddingTop() +
                       widget.getTotalPaddingBottom();
diff --git a/core/java/android/text/method/OffsetMapping.java b/core/java/android/text/method/OffsetMapping.java
new file mode 100644
index 0000000..fcf3de6
--- /dev/null
+++ b/core/java/android/text/method/OffsetMapping.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 android.text.method;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The interface for the index mapping information of a transformed text returned by
+ * {@link TransformationMethod}. This class is mainly used to support the
+ * {@link TransformationMethod} that alters the text length.
+ * @hide
+ */
+public interface OffsetMapping {
+    /**
+     * The mapping strategy for a character offset.
+     *
+     * @see #originalToTransformed(int, int)
+     * @see #transformedToOriginal(int, int)
+     */
+    int MAP_STRATEGY_CHARACTER = 0;
+
+    /**
+     * The mapping strategy for a cursor position.
+     *
+     * @see #originalToTransformed(int, int)
+     * @see #transformedToOriginal(int, int)
+     */
+    int MAP_STRATEGY_CURSOR = 1;
+
+    @IntDef(prefix = { "MAP_STRATEGY" }, value = {
+            MAP_STRATEGY_CHARACTER,
+            MAP_STRATEGY_CURSOR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface MapStrategy {}
+
+    /**
+     * Map an offset at original text to the offset at transformed text. <br/>
+     *
+     * This function must be a monotonically non-decreasing function. In other words, if the offset
+     * advances at the original text, the offset at the transformed text must advance or stay there.
+     * <br/>
+     *
+     * Depending on the mapping strategy, a same offset can be mapped differently. For example,
+     * <pre>
+     * Original:       ABCDE
+     * Transformed:    ABCXDE ('X' is introduced due to the transformation.)
+     * </pre>
+     * Let's check the offset 3, which is the offset of the character 'D'.
+     * If we want to map the character offset 3, it should be mapped to index 4.
+     * If we want to map the cursor offset 3 (the offset of the character before which the cursor is
+     * placed), it's unclear if the mapped cursor is before 'X' or after 'X'.
+     * This depends on how the transformed text reacts an insertion at offset 3 in the original
+     * text. Assume character 'V' is insert at offset 3 in the original text, and the original text
+     * become "ABCVDE". The transformed text can be:
+     * <pre>
+     * 1) "ABCVXDE"
+     * or
+     * 2) "ABCXVDE"
+     * </pre>
+     * In the first case, the offset 3 should be mapped to 3 (before 'X'). And in the second case,
+     * the offset should be mapped to 4 (after 'X').<br/>
+     *
+     * In some cases, a character offset at the original text doesn't have a proper corresponding
+     * offset at the transformed text. For example:
+     * <pre>
+     * Original:    ABCDE
+     * Transformed: ABDE ('C' is deleted due to the transformation.)
+     * </pre>
+     * The character offset 2 can be mapped either to offset 2 or 3, but neither is meaningful. For
+     * convenience, it MUST map to the next offset (offset 3 in this case), or the
+     * transformedText.length() if there is no valid character to map.
+     * This is mandatory when the map strategy is {@link #MAP_STRATEGY_CHARACTER}, but doesn't
+     * apply for other map strategies.
+     *
+     * @param offset the offset at the original text. It's normally equal to or less than the
+     *               originalText.length(). When {@link #MAP_STRATEGY_CHARACTER} is passed, it must
+     *               be less than originalText.length(). For convenience, it's also allowed to be
+     *               negative, which represents an invalid offset. When the given offset is
+     *               negative, this method should return it as it is.
+     * @param strategy the mapping strategy. Depending on its value, the same offset can be mapped
+     *                 differently.
+     * @return the mapped offset at the transformed text, must be equal to or less than the
+     * transformedText.length().
+     *
+     * @see #transformedToOriginal(int, int)
+     */
+    int originalToTransformed(int offset, @MapStrategy int strategy);
+
+    /**
+     * Map an offset at transformed text to the offset at original text. This is the reverse method
+     * of {@link #originalToTransformed(int, int)}. <br/>
+     * This function must be a monotonically non-decreasing function. In other words, if the offset
+     * advances at the original text, the offset at the transformed text must advance or stay there.
+     * <br/>
+     * Similar to the {@link #originalToTransformed(int, int)} if a character offset doesn't have a
+     * corresponding offset at the transformed text, it MUST return the value as the previous
+     * offset. This is mandatory when the map strategy is {@link #MAP_STRATEGY_CHARACTER},
+     * but doesn't apply for other map strategies.
+     *
+     * @param offset the offset at the transformed text. It's normally equal to or less than the
+     *               transformedText.length(). When {@link #MAP_STRATEGY_CHARACTER} is passed, it
+     *               must be less than transformedText.length(). For convenience, it's also allowed
+     *               to be negative, which represents an invalid offset. When the given offset is
+     *               negative, this method should return it as it is.
+     * @param strategy the mapping strategy. Depending on its value, the same offset can be mapped
+     *                 differently.
+     * @return the mapped offset at the original text, must be equal to or less than the
+     * original.length().
+     *
+     * @see #originalToTransformed(int, int)
+     */
+    int transformedToOriginal(int offset, @MapStrategy int strategy);
+
+    /**
+     * Map a text update in the original text to an update the transformed text.
+     * This method used to determine how the transformed text is updated in response to an
+     * update in the original text. It is always called before the original text being changed.
+     *
+     * The main usage of this method is to update text layout incrementally. So it should report
+     * the range where text needs to be laid out again.
+     *
+     * @param textUpdate the {@link TextUpdate} object containing the text  update information on
+     *                  the original text. The transformed text update information will also be
+     *                   stored at this object.
+     */
+    void originalToTransformed(TextUpdate textUpdate);
+
+    /**
+     * The class that stores the text update information that from index <code>where</code>,
+     * <code>after</code> characters will replace the old text that has length <code>before</code>.
+     */
+    class TextUpdate {
+        /** The start index of the text update range, inclusive */
+        public int where;
+        /** The length of the replaced old text. */
+        public int before;
+        /** The length of the new text that replaces the old text. */
+        public int after;
+
+        /**
+         * Creates a {@link TextUpdate} object.
+         * @param where the start index of the text update range.
+         * @param before the length of the replaced old text.
+         * @param after the length of the new text that replaces the old text.
+         */
+        public TextUpdate(int where, int before, int after) {
+            this.where = where;
+            this.before = before;
+            this.after = after;
+        }
+    }
+}
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index e89be47..3e84153 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -123,7 +123,7 @@
 
         // TODO: ResultReceiver for IME.
         // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
-        ImeTracker.get().onProgress(statsToken,
+        ImeTracker.forLogging().onProgress(statsToken,
                 ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW);
 
         if (getControl() == null) {
@@ -164,12 +164,13 @@
         //  - we do already have one, but we have control and use the passed in token
         //      for the insets animation already.
         if (statsToken == null || getControl() != null) {
-            statsToken = ImeTracker.get().onRequestHide(null /* component */, Process.myUid(),
+            statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
+                    Process.myUid(),
                     ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
                     SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
         }
 
-        ImeTracker.get().onProgress(statsToken,
+        ImeTracker.forLogging().onProgress(statsToken,
                 ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN);
 
         getImm().notifyImeHidden(mController.getHost().getWindowToken(), statsToken);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8e8e28a..c992450 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -26,6 +26,7 @@
 import static android.view.WindowInsets.Type.LAST;
 import static android.view.WindowInsets.Type.all;
 import static android.view.WindowInsets.Type.ime;
+import static android.view.inputmethod.ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL;
 
 import android.animation.AnimationHandler;
 import android.animation.Animator;
@@ -35,6 +36,8 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.content.Context;
 import android.content.res.CompatibilityInfo;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -60,6 +63,7 @@
 import android.view.animation.LinearInterpolator;
 import android.view.animation.PathInterpolator;
 import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.ImeTracker.InputMethodJankContext;
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -186,6 +190,14 @@
         @Nullable
         String getRootViewTitle();
 
+        /**
+         * @return the context related to the rootView.
+         */
+        @Nullable
+        default Context getRootViewContext() {
+            return null;
+        }
+
         /** @see ViewRootImpl#dipToPx */
         int dipToPx(int dips);
 
@@ -306,7 +318,7 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {ANIMATION_TYPE_NONE, ANIMATION_TYPE_SHOW, ANIMATION_TYPE_HIDE,
             ANIMATION_TYPE_USER, ANIMATION_TYPE_RESIZE})
-    @interface AnimationType {
+    public @interface AnimationType {
     }
 
     /**
@@ -321,6 +333,27 @@
     /** Logging listener. */
     private WindowInsetsAnimationControlListener mLoggingListener;
 
+    /** Context for {@link android.view.inputmethod.ImeTracker.ImeJankTracker} to monitor jank. */
+    private final InputMethodJankContext mJankContext = new InputMethodJankContext() {
+        @Override
+        public Context getDisplayContext() {
+            return mHost != null ? mHost.getRootViewContext() : null;
+        }
+
+        @Override
+        public SurfaceControl getTargetSurfaceControl() {
+            final InsetsSourceConsumer imeSourceConsumer = mSourceConsumers.get(ITYPE_IME);
+            final InsetsSourceControl imeSourceControl =
+                    imeSourceConsumer != null ? imeSourceConsumer.getControl() : null;
+            return imeSourceControl != null ? imeSourceControl.getLeash() : null;
+        }
+
+        @Override
+        public String getHostPackageName() {
+            return mHost != null ? mHost.getRootViewContext().getPackageName() : null;
+        }
+    };
+
     /**
      * The default implementation of listener, to be used by InsetsController and InsetsPolicy to
      * animate insets.
@@ -338,6 +371,7 @@
         private final boolean mDisable;
         private final int mFloatingImeBottomInset;
         private final WindowInsetsAnimationControlListener mLoggingListener;
+        private final InputMethodJankContext mInputMethodJankContext;
 
         private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
                 new ThreadLocal<AnimationHandler>() {
@@ -351,7 +385,8 @@
 
         public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
                 @InsetsType int requestedTypes, @Behavior int behavior, boolean disable,
-                int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener) {
+                int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener,
+                @Nullable InputMethodJankContext jankContext) {
             mShow = show;
             mHasAnimationCallbacks = hasAnimationCallbacks;
             mRequestedTypes = requestedTypes;
@@ -360,6 +395,7 @@
             mDisable = disable;
             mFloatingImeBottomInset = floatingImeBottomInset;
             mLoggingListener = loggingListener;
+            mInputMethodJankContext = jankContext;
         }
 
         @Override
@@ -406,10 +442,26 @@
                         + insetsFraction);
             });
             mAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    if (mInputMethodJankContext == null) return;
+                    ImeTracker.forJank().onRequestAnimation(
+                            mInputMethodJankContext,
+                            mShow ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
+                            !mHasAnimationCallbacks);
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    if (mInputMethodJankContext == null) return;
+                    ImeTracker.forJank().onCancelAnimation();
+                }
 
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     onAnimationFinish();
+                    if (mInputMethodJankContext == null) return;
+                    ImeTracker.forJank().onFinishAnimation();
                 }
             });
             if (!mHasAnimationCallbacks) {
@@ -873,7 +925,7 @@
 
     /**
      * @see InsetsState#calculateInsets(Rect, InsetsState, boolean, boolean, int, int, int, int,
-     *      int, SparseIntArray)
+     *      int, android.util.SparseIntArray)
      */
     @VisibleForTesting
     public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars,
@@ -981,7 +1033,7 @@
     public void show(@InsetsType int types) {
         ImeTracker.Token statsToken = null;
         if ((types & ime()) != 0) {
-            statsToken = ImeTracker.get().onRequestShow(null /* component */,
+            statsToken = ImeTracker.forLogging().onRequestShow(null /* component */,
                     Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
                     SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
         }
@@ -1006,6 +1058,9 @@
         }
         // Handle pending request ready in case there was one set.
         if (fromIme && mPendingImeControlRequest != null) {
+            if ((types & Type.ime()) != 0) {
+                ImeTracker.forLatency().onShown(statsToken, ActivityThread::currentApplication);
+            }
             handlePendingControlRequest(statsToken);
             return;
         }
@@ -1028,7 +1083,7 @@
                         "show ignored for type: %d animType: %d requestedVisible: %s",
                         type, animationType, requestedVisible));
                 if (isImeAnimation) {
-                    ImeTracker.get().onCancelled(statsToken,
+                    ImeTracker.forLogging().onCancelled(statsToken,
                             ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
                 }
                 continue;
@@ -1036,16 +1091,21 @@
             if (fromIme && animationType == ANIMATION_TYPE_USER) {
                 // App is already controlling the IME, don't cancel it.
                 if (isImeAnimation) {
-                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+                    ImeTracker.forLogging().onFailed(
+                            statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
                 }
                 continue;
             }
             if (isImeAnimation) {
-                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+                ImeTracker.forLogging().onProgress(
+                        statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
             }
             typesReady |= type;
         }
         if (DEBUG) Log.d(TAG, "show typesReady: " + typesReady);
+        if (fromIme && (typesReady & Type.ime()) != 0) {
+            ImeTracker.forLatency().onShown(statsToken, ActivityThread::currentApplication);
+        }
         applyAnimation(typesReady, true /* show */, fromIme, statsToken);
     }
 
@@ -1074,7 +1134,7 @@
     public void hide(@InsetsType int types) {
         ImeTracker.Token statsToken = null;
         if ((types & ime()) != 0) {
-            statsToken = ImeTracker.get().onRequestHide(null /* component */,
+            statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
                     Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
                     SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
         }
@@ -1123,13 +1183,14 @@
                 // no-op: already hidden or animating out (because window visibility is
                 // applied before starting animation).
                 if (isImeAnimation) {
-                    ImeTracker.get().onCancelled(statsToken,
+                    ImeTracker.forLogging().onCancelled(statsToken,
                             ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
                 }
                 continue;
             }
             if (isImeAnimation) {
-                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+                ImeTracker.forLogging().onProgress(
+                        statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
             }
             typesReady |= type;
         }
@@ -1201,8 +1262,19 @@
             @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
             boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
-        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
+        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
         if ((types & mTypesBeingCancelled) != 0) {
+            final boolean monitoredAnimation =
+                    animationType == ANIMATION_TYPE_SHOW || animationType == ANIMATION_TYPE_HIDE;
+            if (monitoredAnimation && (types & Type.ime()) != 0) {
+                if (animationType == ANIMATION_TYPE_SHOW) {
+                    ImeTracker.forLatency().onShowCancelled(statsToken,
+                            PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication);
+                } else {
+                    ImeTracker.forLatency().onHideCancelled(statsToken,
+                            PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication);
+                }
+            }
             throw new IllegalStateException("Cannot start a new insets animation of "
                     + Type.toString(types)
                     + " while an existing " + Type.toString(mTypesBeingCancelled)
@@ -1214,7 +1286,7 @@
             types &= ~mDisabledUserAnimationInsetsTypes;
 
             if ((disabledTypes & ime()) != 0) {
-                ImeTracker.get().onFailed(statsToken,
+                ImeTracker.forLogging().onFailed(statsToken,
                         ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
 
                 if (fromIme && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
@@ -1235,7 +1307,8 @@
             Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
             return;
         }
-        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
+        ImeTracker.forLogging().onProgress(statsToken,
+                ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
         if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
         mLastStartedAnimTypes |= types;
 
@@ -1302,8 +1375,11 @@
         if ((typesReady & WindowInsets.Type.ime()) != 0) {
             ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl",
                     mHost.getInputMethodManager(), null /* icProto */);
+            if (animationType == ANIMATION_TYPE_HIDE) {
+                ImeTracker.forLatency().onHidden(statsToken, ActivityThread::currentApplication);
+            }
         }
-        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING);
+        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING);
         mRunningAnimations.add(new RunningAnimation(runner, animationType));
         if (DEBUG) Log.d(TAG, "Animation added to runner. useInsetsAnimationThread: "
                 + useInsetsAnimationThread);
@@ -1343,7 +1419,8 @@
     private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, @InsetsType int types,
             SparseArray<InsetsSourceControl> controls, @AnimationType int animationType,
             @Nullable ImeTracker.Token statsToken) {
-        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS);
+        ImeTracker.forLogging().onProgress(statsToken,
+                ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS);
 
         int typesReady = 0;
         boolean imeReady = true;
@@ -1446,13 +1523,13 @@
         }
         final ImeTracker.Token statsToken = runner.getStatsToken();
         if (shown) {
-            ImeTracker.get().onProgress(statsToken,
+            ImeTracker.forLogging().onProgress(statsToken,
                     ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW);
-            ImeTracker.get().onShown(statsToken);
+            ImeTracker.forLogging().onShown(statsToken);
         } else {
-            ImeTracker.get().onProgress(statsToken,
+            ImeTracker.forLogging().onProgress(statsToken,
                     ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_HIDE);
-            ImeTracker.get().onHidden(statsToken);
+            ImeTracker.forLogging().onHidden(statsToken);
         }
         reportRequestedVisibleTypes();
     }
@@ -1478,13 +1555,13 @@
 
     private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) {
         if (invokeCallback) {
-            ImeTracker.get().onCancelled(control.getStatsToken(),
-                    ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
+            ImeTracker.forLogging().onCancelled(control.getStatsToken(),
+                    PHASE_CLIENT_ANIMATION_CANCEL);
             control.cancel();
         } else {
             // Succeeds if invokeCallback is false (i.e. when called from notifyFinished).
-            ImeTracker.get().onProgress(control.getStatsToken(),
-                    ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
+            ImeTracker.forLogging().onProgress(control.getStatsToken(),
+                    PHASE_CLIENT_ANIMATION_CANCEL);
         }
         if (DEBUG) {
             Log.d(TAG, TextUtils.formatSimple(
@@ -1649,7 +1726,7 @@
         final InternalAnimationControlListener listener = new InternalAnimationControlListener(
                 show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(),
                 skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP),
-                mLoggingListener);
+                mLoggingListener, mJankContext);
 
         // We are about to playing the default animation (show/hide). Passing a null frame indicates
         // the controlled types should be animated regardless of the frame.
diff --git a/core/java/android/view/MotionPredictor.java b/core/java/android/view/MotionPredictor.java
index 3e58a31..fa86a4c 100644
--- a/core/java/android/view/MotionPredictor.java
+++ b/core/java/android/view/MotionPredictor.java
@@ -25,10 +25,15 @@
 import java.util.List;
 
 /**
- * Calculates motion predictions.
+ * Calculate motion predictions.
  *
- * Add motions here to get predicted events!
- * @hide
+ * Feed motion events to this class in order to generate the predicted events. The prediction
+ * functionality may not be available on all devices. Check if a specific source is supported on a
+ * given input device using #isPredictionAvailable.
+ *
+ * Send all of the events that were received from the system here in order to generate complete,
+ * accurate predictions. When processing the returned predictions, make sure to consider all of the
+ * {@link MotionEvent#getHistoricalAxisValue historical samples}.
  */
 // Acts as a pass-through to the native MotionPredictor object.
 // Do not store any state in this Java layer, or add any business logic here. All of the
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 00170cb..85aea85 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.input.InputManager;
-import android.os.Build;
 import android.util.ArrayMap;
 import android.util.Pools.SynchronizedPool;
 
@@ -190,8 +189,6 @@
     private static native void nativeAddMovement(long ptr, MotionEvent event);
     private static native void nativeComputeCurrentVelocity(long ptr, int units, float maxVelocity);
     private static native float nativeGetVelocity(long ptr, int axis, int id);
-    private static native boolean nativeGetEstimator(
-            long ptr, int axis, int id, Estimator outEstimator);
     private static native boolean nativeIsAxisSupported(int axis);
 
     static {
@@ -436,7 +433,7 @@
      * method:
      * <ul>
      *   <li> {@link MotionEvent#AXIS_SCROLL}: supported starting
-     *        {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+     *        {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
      * </ul>
      *
      * <p>Before accessing velocities of an axis using this method, check that your
@@ -467,89 +464,4 @@
     public float getAxisVelocity(@VelocityTrackableMotionEventAxis int axis) {
         return nativeGetVelocity(mPtr, axis, ACTIVE_POINTER_ID);
     }
-
-    /**
-     * Get an estimator for the movements of a pointer using past movements of the
-     * pointer to predict future movements.
-     *
-     * It is not necessary to call {@link #computeCurrentVelocity(int)} before calling
-     * this method.
-     *
-     * @param axis Which axis's velocity to return.
-     *             Should be one of the axes defined in {@link MotionEvent}.
-     * @param id Which pointer's velocity to return.
-     * @param outEstimator The estimator to populate.
-     * @return True if an estimator was obtained, false if there is no information
-     * available about the pointer.
-     *
-     * @hide For internal use only.  Not a final API.
-     */
-    public boolean getEstimator(int axis, int id, Estimator outEstimator) {
-        if (outEstimator == null) {
-            throw new IllegalArgumentException("outEstimator must not be null");
-        }
-        return nativeGetEstimator(mPtr, axis, id, outEstimator);
-    }
-
-    /**
-     * An estimator for the movements of a pointer based on a polynomial model.
-     *
-     * The last recorded position of the pointer is at time zero seconds.
-     * Past estimated positions are at negative times and future estimated positions
-     * are at positive times.
-     *
-     * First coefficient is position (in units), second is velocity (in units per second),
-     * third is acceleration (in units per second squared).
-     *
-     * @hide For internal use only.  Not a final API.
-     */
-    public static final class Estimator {
-        // Must match VelocityTracker::Estimator::MAX_DEGREE
-        private static final int MAX_DEGREE = 4;
-
-        /**
-         * Polynomial coefficients describing motion.
-         */
-        public final float[] coeff = new float[MAX_DEGREE + 1];
-
-        /**
-         * Polynomial degree, or zero if only position information is available.
-         */
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        public int degree;
-
-        /**
-         * Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit).
-         */
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        public float confidence;
-
-        /**
-         * Gets an estimate of the position of the pointer at the specified time point.
-         * @param time The time point in seconds, 0 is the last recorded time.
-         * @return The estimated axis value.
-         */
-        public float estimate(float time) {
-            return estimate(time, coeff);
-        }
-
-        /**
-         * Gets the coefficient with the specified index.
-         * @param index The index of the coefficient to return.
-         * @return The coefficient, or 0 if the index is greater than the degree.
-         */
-        public float getCoeff(int index) {
-            return index <= degree ? coeff[index] : 0;
-        }
-
-        private float estimate(float time, float[] c) {
-            float a = 0;
-            float scale = 1;
-            for (int i = 0; i <= degree; i++) {
-                a += c[i] * scale;
-                scale *= time;
-            }
-            return a;
-        }
-    }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c73cfc2..5ac9819 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -10244,6 +10244,27 @@
         return mContext.getSystemService(AutofillManager.class);
     }
 
+    /**
+     * Check whether current activity / package is in denylist.If it's in the denylist,
+     * then the views marked as not important for autofill are not eligible for autofill.
+     */
+    final boolean isActivityDeniedForAutofillForUnimportantView() {
+        final AutofillManager afm = getAutofillManager();
+        // keep behavior same with denylist feature not enabled
+        if (afm == null) return true;
+        return afm.isActivityDeniedForAutofillForUnimportantView();
+    }
+
+    /**
+     * Check whether current view matches autofillable heuristics
+     */
+    final boolean isMatchingAutofillableHeuristics() {
+        final AutofillManager afm = getAutofillManager();
+        // keep default behavior
+        if (afm == null) return false;
+        return afm.isMatchingAutofillableHeuristics(this);
+    }
+
     private boolean isAutofillable() {
         if (getAutofillType() == AUTOFILL_TYPE_NONE) return false;
 
@@ -10252,6 +10273,12 @@
                 && isCredential()) return false;
 
         if (!isImportantForAutofill()) {
+            // If view matches heuristics and is not denied, it will be treated same as view that's
+            // important for autofill
+            if (isMatchingAutofillableHeuristics()
+                    && !isActivityDeniedForAutofillForUnimportantView()) {
+                return getAutofillViewId() > LAST_APP_AUTOFILL_ID;
+            }
             // View is not important for "regular" autofill, so we must check if Augmented Autofill
             // is enabled for the activity
             final AutofillOptions options = mContext.getAutofillOptions();
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 46b2cfc..0e4ac01 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3723,7 +3723,9 @@
             final View child = (preorderedList == null)
                     ? mChildren[childIndex] : preorderedList.get(childIndex);
             if ((flags & AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
-                    || child.isImportantForAutofill()) {
+                    || child.isImportantForAutofill()
+                    || (child.isMatchingAutofillableHeuristics()
+                        && !child.isActivityDeniedForAutofillForUnimportantView())) {
                 list.add(child);
             } else if (child instanceof ViewGroup) {
                 ((ViewGroup) child).populateChildrenForAutofill(list, flags);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 74e521a..2763607 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5764,7 +5764,7 @@
                 }
                 case MSG_SHOW_INSETS: {
                     final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
-                    ImeTracker.get().onProgress(statsToken,
+                    ImeTracker.forLogging().onProgress(statsToken,
                             ImeTracker.PHASE_CLIENT_HANDLE_SHOW_INSETS);
                     if (mView == null) {
                         Log.e(TAG,
@@ -5777,7 +5777,7 @@
                 }
                 case MSG_HIDE_INSETS: {
                     final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
-                    ImeTracker.get().onProgress(statsToken,
+                    ImeTracker.forLogging().onProgress(statsToken,
                             ImeTracker.PHASE_CLIENT_HANDLE_HIDE_INSETS);
                     mInsetsController.hide(msg.arg1, msg.arg2 == 1, statsToken);
                     break;
@@ -10324,10 +10324,10 @@
                         null /* icProto */);
             }
             if (viewAncestor != null) {
-                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
+                ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
                 viewAncestor.showInsets(types, fromIme, statsToken);
             } else {
-                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
+                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
             }
         }
 
@@ -10341,10 +10341,10 @@
                         null /* icProto */);
             }
             if (viewAncestor != null) {
-                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
+                ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
                 viewAncestor.hideInsets(types, fromIme, statsToken);
             } else {
-                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
+                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
             }
         }
 
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index c59d83e..037ce87 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -21,6 +21,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
 
 import android.annotation.NonNull;
+import android.content.Context;
 import android.content.res.CompatibilityInfo;
 import android.os.Handler;
 import android.os.IBinder;
@@ -246,6 +247,11 @@
     }
 
     @Override
+    public Context getRootViewContext() {
+        return mViewRoot != null ? mViewRoot.getView().getContext() : null;
+    }
+
+    @Override
     public int dipToPx(int dips) {
         if (mViewRoot != null) {
             return mViewRoot.dipToPx(dips);
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 59ad151..cba399f 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -16,13 +16,18 @@
 
 package android.view.autofill;
 
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.provider.DeviceConfig;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.view.View;
 
 import com.android.internal.util.ArrayUtils;
 
+import java.util.Arrays;
+import java.util.Set;
+
 /**
  * Feature flags associated with autofill.
  * @hide
@@ -119,8 +124,6 @@
     public static final String DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_SUPPRESS_FILL_DIALOG =
             "autofill_credential_manager_suppress_fill_dialog";
 
-
-
     /**
      * Indicates whether credential manager tagged views should suppress save dialog.
      * This flag is further gated by {@link #DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_ENABLED}
@@ -131,6 +134,50 @@
             "autofill_credential_manager_suppress_save_dialog";
     // END CREDENTIAL MANAGER FLAGS //
 
+    // START AUTOFILL FOR ALL APPS FLAGS //
+    /**
+     * Sets the list of activities and packages denied for autofill
+     *
+     * The list is {@code ";"} colon delimited. Activities under a package is separated by
+     * {@code ","}. Each package name much be followed by a {@code ":"}. Each package entry must be
+     * ends with a {@code ";"}
+     *
+     * <p>For example, a list with only 1 package would be, {@code Package1:;}. A list with one
+     * denied activity {@code Activity1} under {@code Package1} and a full denied package
+     * {@code Package2} would be {@code Package1:Activity1;Package2:;}
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW =
+            "package_deny_list_for_unimportant_view";
+
+    /**
+     * Whether the heuristics check for view is enabled
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_UNIMPORTANT_VIEW =
+            "trigger_fill_request_on_unimportant_view";
+
+    /**
+     * Continas imeAction ids that is irrelevant for autofill. For example, ime_action_search. We
+     * use this to avoid trigger fill request on unimportant views.
+     *
+     * The list is {@code ","} delimited.
+     *
+     * <p> For example, a imeAction list could be "2,3,4", corresponding to ime_action definition
+     * in {@link android.view.inputmethod.EditorInfo.java}</p>
+     *
+     * @hide
+     */
+    @TestApi
+    @SuppressLint("IntentName")
+    public static final String DEVICE_CONFIG_NON_AUTOFILLABLE_IME_ACTION_IDS =
+            "non_autofillable_ime_action_ids";
+    // END AUTOFILL FOR ALL APPS FLAGS //
+
     /**
      * Sets a value of delay time to show up the inline tooltip view.
      *
@@ -221,4 +268,38 @@
                 DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_SUPPRESS_FILL_DIALOG,
                 DEFAULT_CREDENTIAL_MANAGER_SUPPRESS_FILL_DIALOG);
     }
+
+    /**
+     * Whether triggering fill request on unimportant view is enabled.
+     *
+     * @hide
+     */
+    public static boolean isTriggerFillRequestOnUnimportantViewEnabled() {
+        return DeviceConfig.getBoolean(
+            DeviceConfig.NAMESPACE_AUTOFILL,
+            DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_UNIMPORTANT_VIEW, false);
+    }
+
+    /**
+     * Get the non-autofillable ime actions from flag. This will be used in filtering
+     * condition to trigger fill request.
+     *
+     * @hide
+     */
+    public static Set<String> getNonAutofillableImeActionIdSetFromFlag() {
+        final String mNonAutofillableImeActions = DeviceConfig.getString(
+                DeviceConfig.NAMESPACE_AUTOFILL, DEVICE_CONFIG_NON_AUTOFILLABLE_IME_ACTION_IDS, "");
+        return new ArraySet<>(Arrays.asList(mNonAutofillableImeActions.split(",")));
+    }
+
+    /**
+     * Get denylist string from flag
+     *
+     * @hide
+     */
+    public static String getDenylistStringFromFlag() {
+        return DeviceConfig.getString(
+            DeviceConfig.NAMESPACE_AUTOFILL,
+            DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW, "");
+    }
 }
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 58e7a70..2ad01ed 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -86,8 +86,13 @@
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InputMethodManager;
+import android.widget.CheckBox;
+import android.widget.DatePicker;
 import android.widget.EditText;
+import android.widget.RadioGroup;
+import android.widget.Spinner;
 import android.widget.TextView;
+import android.widget.TimePicker;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
@@ -657,6 +662,23 @@
 
     private final boolean mIsFillDialogEnabled;
 
+    // Indicate whether trigger fill request on unimportant views is enabled
+    private boolean mIsTriggerFillRequestOnUnimportantViewEnabled = false;
+
+    // A set containing all non-autofillable ime actions passed by flag
+    private Set<String> mNonAutofillableImeActionIdSet = new ArraySet<>();
+
+    // If a package is fully denied, then all views that marked as not
+    // important for autofill will not trigger fill request
+    private boolean mIsPackageFullyDeniedForAutofillForUnimportantView = false;
+
+    // If a package is partially denied, autofill manager will check whether
+    // current activity is in deny set to decide whether to trigger fill request
+    private boolean mIsPackagePartiallyDeniedForAutofillForUnimportantView = false;
+
+    // A deny set read from device config
+    private Set<String> mDeniedActivitiySet = new ArraySet<>();
+
     // Indicates whether called the showAutofillDialog() method.
     private boolean mShowAutofillDialogCalled = false;
 
@@ -816,8 +838,133 @@
             sDebug = (mOptions.loggingLevel & FLAG_ADD_CLIENT_DEBUG) != 0;
             sVerbose = (mOptions.loggingLevel & FLAG_ADD_CLIENT_VERBOSE) != 0;
         }
+
+        mIsTriggerFillRequestOnUnimportantViewEnabled =
+            AutofillFeatureFlags.isTriggerFillRequestOnUnimportantViewEnabled();
+
+        mNonAutofillableImeActionIdSet =
+            AutofillFeatureFlags.getNonAutofillableImeActionIdSetFromFlag();
+
+        final String denyListString = AutofillFeatureFlags.getDenylistStringFromFlag();
+
+        final String packageName = mContext.getPackageName();
+
+        mIsPackageFullyDeniedForAutofillForUnimportantView =
+            isPackageFullyDeniedForAutofillForUnimportantView(denyListString, packageName);
+
+        mIsPackagePartiallyDeniedForAutofillForUnimportantView =
+            isPackagePartiallyDeniedForAutofillForUnimportantView(denyListString, packageName);
+
+        if (mIsPackagePartiallyDeniedForAutofillForUnimportantView) {
+            setDeniedActivitySetWithDenyList(denyListString, packageName);
+        }
     }
 
+    private boolean isPackageFullyDeniedForAutofillForUnimportantView(
+            @NonNull String denyListString, @NonNull String packageName) {
+        // If "PackageName:;" is in the string, then it means the package name is in denylist
+        // and there are no activities specified under it. That means the package is fully
+        // denied for autofill
+        return denyListString.indexOf(packageName + ":;") != -1;
+    }
+
+    private boolean isPackagePartiallyDeniedForAutofillForUnimportantView(
+            @NonNull String denyListString, @NonNull String packageName) {
+        // This check happens after checking package is not fully denied. If "PackageName:" instead
+        // is in denylist, then it means there are specific activities to be denied. So the package
+        // is partially denied for autofill
+        return denyListString.indexOf(packageName + ":") != -1;
+    }
+
+    /**
+     * Get the denied activitiy names under specified package from denylist and set it in field
+     * mDeniedActivitiySet
+     *
+     * If using parameter as the example below, the denied activity set would be set to
+     * Set{Activity1,Activity2}.
+     *
+     * @param denyListString Denylist that is got from device config. For example,
+     *        "Package1:Activity1,Activity2;Package2:;"
+     * @param packageName Specify to extract activities under which package.For example,
+     *        "Package1:;"
+     */
+    private void setDeniedActivitySetWithDenyList(
+            @NonNull String denyListString, @NonNull String packageName) {
+        // 1. Get the index of where the Package name starts
+        final int packageInStringIndex = denyListString.indexOf(packageName + ":");
+
+        // 2. Get the ";" index after this index of package
+        final int firstNextSemicolonIndex = denyListString.indexOf(";", packageInStringIndex);
+
+        // 3. Get the activity names substring between the indexes
+        final int activityStringStartIndex = packageInStringIndex + packageName.length() + 1;
+        if (activityStringStartIndex < firstNextSemicolonIndex) {
+            Log.e(TAG, "Failed to get denied activity names from denylist because it's wrongly "
+                    + "formatted");
+        }
+        final String activitySubstring =
+                denyListString.substring(activityStringStartIndex, firstNextSemicolonIndex);
+
+        // 4. Split the activity name substring
+        final String[] activityStringArray = activitySubstring.split(",");
+
+        // 5. Set the denied activity set
+        mDeniedActivitiySet = new ArraySet<>(Arrays.asList(activityStringArray));
+
+        return;
+    }
+
+    /**
+     * Check whether autofill is denied for current activity or package. Used when a view is marked
+     * as not important for autofill, if current activity or package is denied, then the view won't
+     * trigger fill request.
+     *
+     * @hide
+     */
+    public final boolean isActivityDeniedForAutofillForUnimportantView() {
+        if (mIsPackageFullyDeniedForAutofillForUnimportantView) {
+            return true;
+        }
+        if (mIsPackagePartiallyDeniedForAutofillForUnimportantView) {
+            final AutofillClient client = getClient();
+            if (client == null) {
+                return false;
+            }
+            final ComponentName clientActivity = client.autofillClientGetComponentName();
+            if (mDeniedActivitiySet.contains(clientActivity.flattenToShortString())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check whether view matches autofill-able heuristics
+     *
+     * @hide
+     */
+    public final boolean isMatchingAutofillableHeuristics(@NonNull View view) {
+        if (!mIsTriggerFillRequestOnUnimportantViewEnabled) {
+            return false;
+        }
+        if (view instanceof EditText) {
+            final int actionId = ((EditText) view).getImeOptions();
+            if (mNonAutofillableImeActionIdSet.contains(String.valueOf(actionId))) {
+                return false;
+            }
+            return true;
+        }
+        if (view instanceof CheckBox
+                || view instanceof Spinner
+                || view instanceof DatePicker
+                || view instanceof TimePicker
+                || view instanceof RadioGroup) {
+            return true;
+        }
+        return false;
+    }
+
+
     /**
      * @hide
      */
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index f08f61f..96602619 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -215,6 +215,21 @@
     }
 
     @AnyThread
+    @Nullable
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+    static InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return null;
+        }
+        try {
+            return service.getCurrentInputMethodInfoAsUser(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
     @NonNull
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     static List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 3b6ec80..a07dedc 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -16,24 +16,36 @@
 
 package android.view.inputmethod;
 
+import static com.android.internal.inputmethod.InputMethodDebug.softInputDisplayReasonToString;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_ANIMATION;
+import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_HIDDEN;
+import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
+import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemProperties;
 import android.util.Log;
+import android.view.InsetsController.AnimationType;
+import android.view.SurfaceControl;
 
 import com.android.internal.inputmethod.InputMethodDebug;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.jank.InteractionJankMonitor.Configuration;
+import com.android.internal.util.LatencyTracker;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Field;
 import java.util.Arrays;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Random;
 import java.util.stream.Collectors;
@@ -385,15 +397,35 @@
     void onHidden(@Nullable Token token);
 
     /**
-     * Get the singleton instance of this class.
+     * Get the singleton request tracker instance.
      *
-     * @return the singleton instance of this class
+     * @return the singleton request tracker instance
      */
     @NonNull
-    static ImeTracker get() {
+    static ImeTracker forLogging() {
         return LOGGER;
     }
 
+    /**
+     * Get the singleton jank tracker instance.
+     *
+     * @return the singleton jank tracker instance
+     */
+    @NonNull
+    static ImeJankTracker forJank() {
+        return JANK_TRACKER;
+    }
+
+    /**
+     * Get the singleton latency tracker instance.
+     *
+     * @return the singleton latency tracker instance
+     */
+    @NonNull
+    static ImeLatencyTracker forLatency() {
+        return LATENCY_TRACKER;
+    }
+
     /** The singleton IME tracker instance. */
     @NonNull
     ImeTracker LOGGER = new ImeTracker() {
@@ -489,6 +521,12 @@
         }
     };
 
+    /** The singleton IME tracker instance for instrumenting jank metrics. */
+    ImeJankTracker JANK_TRACKER = new ImeJankTracker();
+
+    /** The singleton IME tracker instance for instrumenting latency metrics. */
+    ImeLatencyTracker LATENCY_TRACKER = new ImeLatencyTracker();
+
     /** A token that tracks the progress of an IME request. */
     class Token implements Parcelable {
 
@@ -592,4 +630,153 @@
             }
         }
     }
+
+    /**
+     * Context related to {@link InteractionJankMonitor}.
+     */
+    interface InputMethodJankContext {
+        /**
+         * @return a context associated with a display
+         */
+        Context getDisplayContext();
+
+        /**
+         * @return a SurfaceControl is going to be monitored
+         */
+        SurfaceControl getTargetSurfaceControl();
+
+        /**
+         * @return the package name of the host
+         */
+        String getHostPackageName();
+    }
+
+    /**
+     * Context related to {@link LatencyTracker}.
+     */
+    interface InputMethodLatencyContext {
+        /**
+         * @return a context associated with current application
+         */
+        Context getAppContext();
+    }
+
+    /**
+     * A tracker instance which is in charge of communicating with {@link InteractionJankMonitor}.
+     */
+    final class ImeJankTracker {
+
+        private ImeJankTracker() {
+        }
+
+        /**
+         * Called when the animation, which is going to be monitored, starts.
+         *
+         * @param jankContext context which is needed by {@link InteractionJankMonitor}
+         * @param animType {@link AnimationType}
+         * @param useSeparatedThread {@code true} if the animation is handled by the app,
+         *                           {@code false} if the animation will be scheduled on the
+         *                           {@link android.view.InsetsAnimationThread}
+         */
+        public void onRequestAnimation(@NonNull InputMethodJankContext jankContext,
+                @AnimationType int animType, boolean useSeparatedThread) {
+            if (jankContext.getDisplayContext() == null
+                    || jankContext.getTargetSurfaceControl() == null) {
+                return;
+            }
+            final Configuration.Builder builder = Configuration.Builder.withSurface(
+                            CUJ_IME_INSETS_ANIMATION,
+                            jankContext.getDisplayContext(),
+                            jankContext.getTargetSurfaceControl())
+                    .setTag(String.format(Locale.US, "%d@%d@%s", animType,
+                            useSeparatedThread ? 0 : 1, jankContext.getHostPackageName()));
+            InteractionJankMonitor.getInstance().begin(builder);
+        }
+
+        /**
+         * Called when the animation, which is going to be monitored, cancels.
+         */
+        public void onCancelAnimation() {
+            InteractionJankMonitor.getInstance().cancel(CUJ_IME_INSETS_ANIMATION);
+        }
+
+        /**
+         * Called when the animation, which is going to be monitored, ends.
+         */
+        public void onFinishAnimation() {
+            InteractionJankMonitor.getInstance().end(CUJ_IME_INSETS_ANIMATION);
+        }
+    }
+
+    /**
+     * A tracker instance which is in charge of communicating with {@link LatencyTracker}.
+     */
+    final class ImeLatencyTracker {
+
+        private ImeLatencyTracker() {
+        }
+
+        private boolean shouldMonitorLatency(@SoftInputShowHideReason int reason) {
+            return reason == SoftInputShowHideReason.SHOW_SOFT_INPUT
+                    || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT
+                    || reason == SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API
+                    || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API
+                    || reason == SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME
+                    || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_IME;
+        }
+
+        public void onRequestShow(@Nullable Token token, @Origin int origin,
+                @SoftInputShowHideReason int reason,
+                @NonNull InputMethodLatencyContext latencyContext) {
+            if (!shouldMonitorLatency(reason)) return;
+            LatencyTracker.getInstance(latencyContext.getAppContext())
+                    .onActionStart(
+                            ACTION_REQUEST_IME_SHOWN,
+                            softInputDisplayReasonToString(reason));
+        }
+
+        public void onRequestHide(@Nullable Token token, @Origin int origin,
+                @SoftInputShowHideReason int reason,
+                @NonNull InputMethodLatencyContext latencyContext) {
+            if (!shouldMonitorLatency(reason)) return;
+            LatencyTracker.getInstance(latencyContext.getAppContext())
+                    .onActionStart(
+                            ACTION_REQUEST_IME_HIDDEN,
+                            softInputDisplayReasonToString(reason));
+        }
+
+        public void onShowFailed(@Nullable Token token, @Phase int phase,
+                @NonNull InputMethodLatencyContext latencyContext) {
+            onShowCancelled(token, phase, latencyContext);
+        }
+
+        public void onHideFailed(@Nullable Token token, @Phase int phase,
+                @NonNull InputMethodLatencyContext latencyContext) {
+            onHideCancelled(token, phase, latencyContext);
+        }
+
+        public void onShowCancelled(@Nullable Token token, @Phase int phase,
+                @NonNull InputMethodLatencyContext latencyContext) {
+            LatencyTracker.getInstance(latencyContext.getAppContext())
+                    .onActionCancel(ACTION_REQUEST_IME_SHOWN);
+        }
+
+        public void onHideCancelled(@Nullable Token token, @Phase int phase,
+                @NonNull InputMethodLatencyContext latencyContext) {
+            LatencyTracker.getInstance(latencyContext.getAppContext())
+                    .onActionCancel(ACTION_REQUEST_IME_HIDDEN);
+        }
+
+        public void onShown(@Nullable Token token,
+                @NonNull InputMethodLatencyContext latencyContext) {
+            LatencyTracker.getInstance(latencyContext.getAppContext())
+                    .onActionEnd(ACTION_REQUEST_IME_SHOWN);
+        }
+
+        public void onHidden(@Nullable Token token,
+                @NonNull InputMethodLatencyContext latencyContext) {
+            LatencyTracker.getInstance(latencyContext.getAppContext())
+                    .onActionEnd(ACTION_REQUEST_IME_HIDDEN);
+        }
+    }
 }
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index b7da732..229cc02 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -339,6 +339,28 @@
         mIsVrOnly = isVrOnly;
     }
 
+    /**
+     * @hide
+     */
+    public InputMethodInfo(InputMethodInfo source) {
+        mId = source.mId;
+        mSettingsActivityName = source.mSettingsActivityName;
+        mIsDefaultResId = source.mIsDefaultResId;
+        mIsAuxIme = source.mIsAuxIme;
+        mSupportsSwitchingToNextInputMethod = source.mSupportsSwitchingToNextInputMethod;
+        mInlineSuggestionsEnabled = source.mInlineSuggestionsEnabled;
+        mSupportsInlineSuggestionsWithTouchExploration =
+                source.mSupportsInlineSuggestionsWithTouchExploration;
+        mSuppressesSpellChecker = source.mSuppressesSpellChecker;
+        mShowInInputMethodPicker = source.mShowInInputMethodPicker;
+        mIsVrOnly = source.mIsVrOnly;
+        mService = source.mService;
+        mSubtypes = source.mSubtypes;
+        mHandledConfigChanges = source.mHandledConfigChanges;
+        mSupportsStylusHandwriting = source.mSupportsStylusHandwriting;
+        mForceDefault = source.mForceDefault;
+    }
+
     InputMethodInfo(Parcel source) {
         mId = source.readString();
         mSettingsActivityName = source.readString();
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index c1b6cda..642182b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -43,6 +43,8 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.annotation.UiThread;
@@ -1598,6 +1600,37 @@
     }
 
     /**
+     * Returns the {@link InputMethodInfo} of the currently selected input method (for the process's
+     * user).
+     *
+     * <p>On multi user environment, this API returns a result for the calling process user.</p>
+     */
+    @Nullable
+    public InputMethodInfo getCurrentInputMethodInfo() {
+        // We intentionally do not use UserHandle.getCallingUserId() here because for system
+        // services InputMethodManagerInternal.getCurrentInputMethodInfoForUser() should be used
+        // instead.
+        return IInputMethodManagerGlobalInvoker.getCurrentInputMethodInfoAsUser(
+                UserHandle.myUserId());
+    }
+
+    /**
+     * Returns the {@link InputMethodInfo} for currently selected input method for the given user.
+     *
+     * @param user user to query.
+     * @hide
+     */
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+    @Nullable
+    @SystemApi
+    @SuppressLint("UserHandle")
+    public InputMethodInfo getCurrentInputMethodInfoAsUser(@NonNull UserHandle user) {
+        Objects.requireNonNull(user);
+        return IInputMethodManagerGlobalInvoker.getCurrentInputMethodInfoAsUser(
+                user.getIdentifier());
+    }
+
+    /**
      * Returns the list of enabled input methods.
      *
      * <p>On multi user environment, this API returns a result for the calling process user.</p>
@@ -2012,10 +2045,11 @@
     private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, int flags,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         if (statsToken == null) {
-            statsToken = ImeTracker.get().onRequestShow(null /* component */,
+            statsToken = ImeTracker.forLogging().onRequestShow(null /* component */,
                     Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason);
         }
-
+        ImeTracker.forLatency().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+                reason, ActivityThread::currentApplication);
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this,
                 null /* icProto */);
         // Re-dispatch if there is a context mismatch.
@@ -2027,12 +2061,15 @@
         checkFocus();
         synchronized (mH) {
             if (!hasServedByInputMethodLocked(view)) {
-                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+                ImeTracker.forLatency().onShowFailed(
+                        statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED,
+                        ActivityThread::currentApplication);
                 Log.w(TAG, "Ignoring showSoftInput() as view=" + view + " is not served.");
                 return false;
             }
 
-            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
 
             // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
             // TODO(b/229426865): call WindowInsetsController#show instead.
@@ -2062,20 +2099,20 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
     public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
         synchronized (mH) {
-            final ImeTracker.Token statsToken = ImeTracker.get().onRequestShow(null /* component */,
-                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+            final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestShow(
+                    null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
                     SoftInputShowHideReason.SHOW_SOFT_INPUT);
 
             Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
                     + " removed soon. If you are using androidx.appcompat.widget.SearchView,"
                     + " please update to version 26.0 or newer version.");
             if (mCurRootView == null || mCurRootView.getView() == null) {
-                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                 Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
                 return;
             }
 
-            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
 
             // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
             // TODO(b/229426865): call WindowInsetsController#show instead.
@@ -2153,20 +2190,24 @@
 
     private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
-        final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
-                Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, reason);
-
+        final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
+                null /* component */, Process.myUid(),
+                ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, reason);
+        ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+                reason, ActivityThread::currentApplication);
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow",
                 this, null /* icProto */);
         checkFocus();
         synchronized (mH) {
             final View servedView = getServedViewLocked();
             if (servedView == null || servedView.getWindowToken() != windowToken) {
-                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+                ImeTracker.forLatency().onHideFailed(statsToken,
+                        ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
                 return false;
             }
 
-            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
 
             return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
                     flags, resultReceiver, reason);
@@ -2802,18 +2843,22 @@
 
     @UnsupportedAppUsage
     void closeCurrentInput() {
-        final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
-                Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+        final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
+                null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
                 SoftInputShowHideReason.HIDE_SOFT_INPUT);
+        ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+                SoftInputShowHideReason.HIDE_SOFT_INPUT, ActivityThread::currentApplication);
 
         synchronized (mH) {
             if (mCurRootView == null || mCurRootView.getView() == null) {
-                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+                ImeTracker.forLatency().onHideFailed(statsToken,
+                        ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
                 Log.w(TAG, "No current root view, ignoring closeCurrentInput()");
                 return;
             }
 
-            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
 
             IInputMethodManagerGlobalInvoker.hideSoftInput(
                     mClient,
@@ -2872,11 +2917,13 @@
         synchronized (mH) {
             final View servedView = getServedViewLocked();
             if (servedView == null || servedView.getWindowToken() != windowToken) {
-                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
+                ImeTracker.forLogging().onFailed(statsToken,
+                        ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
                 return false;
             }
 
-            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
+            ImeTracker.forLogging().onProgress(statsToken,
+                    ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
 
             showSoftInput(servedView, statsToken, 0 /* flags */, null /* resultReceiver */,
                     SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
@@ -2894,21 +2941,25 @@
      */
     public void notifyImeHidden(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
         if (statsToken == null) {
-            statsToken = ImeTracker.get().onRequestHide(null /* component */,
+            statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
                     Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
                     SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
         }
-
+        ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+                SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API,
+                ActivityThread::currentApplication);
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
                 null /* icProto */);
         synchronized (mH) {
             if (!isImeSessionAvailableLocked() || mCurRootView == null
                     || mCurRootView.getWindowToken() != windowToken) {
-                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+                ImeTracker.forLatency().onHideFailed(statsToken,
+                        ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
                 return;
             }
 
-            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
 
             IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
                     0 /* flags */, null /* resultReceiver */,
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index b33afa5..95a42aa 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -76,6 +76,7 @@
 import android.text.method.KeyListener;
 import android.text.method.MetaKeyKeyListener;
 import android.text.method.MovementMethod;
+import android.text.method.OffsetMapping;
 import android.text.method.WordIterator;
 import android.text.style.EasyEditSpan;
 import android.text.style.SuggestionRangeSpan;
@@ -571,7 +572,7 @@
                 mTextView.getContext().getResources().getDisplayMetrics());
 
         final Layout layout = mTextView.getLayout();
-        final int line = layout.getLineForOffset(mTextView.getSelectionStart());
+        final int line = layout.getLineForOffset(mTextView.getSelectionStartTransformed());
         final int sourceHeight = layout.getLineBottom(line, /* includeLineSpacing= */ false)
                 - layout.getLineTop(line);
         final int height = (int)(sourceHeight * zoom);
@@ -1279,12 +1280,16 @@
      * Get the minimum range of paragraphs that contains startOffset and endOffset.
      */
     private long getParagraphsRange(int startOffset, int endOffset) {
+        final int startOffsetTransformed = mTextView.originalToTransformed(startOffset,
+                OffsetMapping.MAP_STRATEGY_CURSOR);
+        final int endOffsetTransformed = mTextView.originalToTransformed(endOffset,
+                OffsetMapping.MAP_STRATEGY_CURSOR);
         final Layout layout = mTextView.getLayout();
         if (layout == null) {
             return TextUtils.packRangeInLong(-1, -1);
         }
-        final CharSequence text = mTextView.getText();
-        int minLine = layout.getLineForOffset(startOffset);
+        final CharSequence text = layout.getText();
+        int minLine = layout.getLineForOffset(startOffsetTransformed);
         // Search paragraph start.
         while (minLine > 0) {
             final int prevLineEndOffset = layout.getLineEnd(minLine - 1);
@@ -1293,7 +1298,7 @@
             }
             minLine--;
         }
-        int maxLine = layout.getLineForOffset(endOffset);
+        int maxLine = layout.getLineForOffset(endOffsetTransformed);
         // Search paragraph end.
         while (maxLine < layout.getLineCount() - 1) {
             final int lineEndOffset = layout.getLineEnd(maxLine);
@@ -1302,7 +1307,11 @@
             }
             maxLine++;
         }
-        return TextUtils.packRangeInLong(layout.getLineStart(minLine), layout.getLineEnd(maxLine));
+        final int paragraphStart = mTextView.transformedToOriginal(layout.getLineStart(minLine),
+                OffsetMapping.MAP_STRATEGY_CURSOR);
+        final int paragraphEnd = mTextView.transformedToOriginal(layout.getLineEnd(maxLine),
+                OffsetMapping.MAP_STRATEGY_CURSOR);
+        return TextUtils.packRangeInLong(paragraphStart, paragraphEnd);
     }
 
     void onLocaleChanged() {
@@ -1339,8 +1348,16 @@
     private int getNextCursorOffset(int offset, boolean findAfterGivenOffset) {
         final Layout layout = mTextView.getLayout();
         if (layout == null) return offset;
-        return findAfterGivenOffset == layout.isRtlCharAt(offset)
-                ? layout.getOffsetToLeftOf(offset) : layout.getOffsetToRightOf(offset);
+        final int offsetTransformed =
+                mTextView.originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
+        final int nextCursor;
+        if (findAfterGivenOffset == layout.isRtlCharAt(offsetTransformed)) {
+            nextCursor = layout.getOffsetToLeftOf(offsetTransformed);
+        } else {
+            nextCursor = layout.getOffsetToRightOf(offsetTransformed);
+        }
+
+        return mTextView.transformedToOriginal(nextCursor, OffsetMapping.MAP_STRATEGY_CURSOR);
     }
 
     private long getCharClusterRange(int offset) {
@@ -1396,9 +1413,11 @@
         Layout layout = mTextView.getLayout();
         if (layout == null) return false;
 
-        final int line = layout.getLineForOffset(offset);
+        final int offsetTransformed =
+                mTextView.originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
+        final int line = layout.getLineForOffset(offsetTransformed);
         final int lineBottom = layout.getLineBottom(line);
-        final int primaryHorizontal = (int) layout.getPrimaryHorizontal(offset);
+        final int primaryHorizontal = (int) layout.getPrimaryHorizontal(offsetTransformed);
         return mTextView.isPositionVisible(
                 primaryHorizontal + mTextView.viewportToContentHorizontalOffset(),
                 lineBottom + mTextView.viewportToContentVerticalOffset());
@@ -2300,8 +2319,12 @@
      */
     void invalidateTextDisplayList(Layout layout, int start, int end) {
         if (mTextRenderNodes != null && layout instanceof DynamicLayout) {
-            final int firstLine = layout.getLineForOffset(start);
-            final int lastLine = layout.getLineForOffset(end);
+            final int startTransformed =
+                    mTextView.originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CHARACTER);
+            final int endTransformed =
+                    mTextView.originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CHARACTER);
+            final int firstLine = layout.getLineForOffset(startTransformed);
+            final int lastLine = layout.getLineForOffset(endTransformed);
 
             DynamicLayout dynamicLayout = (DynamicLayout) layout;
             int[] blockEndLines = dynamicLayout.getBlockEndLines();
@@ -2344,12 +2367,14 @@
 
         final Layout layout = mTextView.getLayout();
         final int offset = mTextView.getSelectionStart();
-        final int line = layout.getLineForOffset(offset);
+        final int transformedOffset = mTextView.originalToTransformed(offset,
+                OffsetMapping.MAP_STRATEGY_CURSOR);
+        final int line = layout.getLineForOffset(transformedOffset);
         final int top = layout.getLineTop(line);
         final int bottom = layout.getLineBottom(line, /* includeLineSpacing= */ false);
 
         final boolean clamped = layout.shouldClampCursor(line);
-        updateCursorPosition(top, bottom, layout.getPrimaryHorizontal(offset, clamped));
+        updateCursorPosition(top, bottom, layout.getPrimaryHorizontal(transformedOffset, clamped));
     }
 
     void refreshTextActionMode() {
@@ -3628,10 +3653,14 @@
             measureContent();
             final int width = mContentView.getMeasuredWidth();
             final int offset = getTextOffset();
-            mPositionX = (int) (mTextView.getLayout().getPrimaryHorizontal(offset) - width / 2.0f);
+            final int transformedOffset = mTextView.originalToTransformed(offset,
+                    OffsetMapping.MAP_STRATEGY_CURSOR);
+            final Layout layout = mTextView.getLayout();
+
+            mPositionX = (int) (layout.getPrimaryHorizontal(transformedOffset) - width / 2.0f);
             mPositionX += mTextView.viewportToContentHorizontalOffset();
 
-            final int line = mTextView.getLayout().getLineForOffset(offset);
+            final int line = layout.getLineForOffset(transformedOffset);
             mPositionY = getVerticalLocalPosition(line);
             mPositionY += mTextView.viewportToContentVerticalOffset();
         }
@@ -4564,19 +4593,20 @@
                 super.onGetContentRect(mode, view, outRect);
                 return;
             }
-            if (mTextView.getSelectionStart() != mTextView.getSelectionEnd()) {
+            final int selectionStart = mTextView.getSelectionStartTransformed();
+            final int selectionEnd = mTextView.getSelectionEndTransformed();
+            final Layout layout = mTextView.getLayout();
+            if (selectionStart != selectionEnd) {
                 // We have a selection.
                 mSelectionPath.reset();
-                mTextView.getLayout().getSelectionPath(
-                        mTextView.getSelectionStart(), mTextView.getSelectionEnd(), mSelectionPath);
+                layout.getSelectionPath(selectionStart, selectionEnd, mSelectionPath);
                 mSelectionPath.computeBounds(mSelectionBounds, true);
                 mSelectionBounds.bottom += mHandleHeight;
             } else {
                 // We have a cursor.
-                Layout layout = mTextView.getLayout();
-                int line = layout.getLineForOffset(mTextView.getSelectionStart());
-                float primaryHorizontal = clampHorizontalPosition(null,
-                        layout.getPrimaryHorizontal(mTextView.getSelectionStart()));
+                int line = layout.getLineForOffset(selectionStart);
+                float primaryHorizontal =
+                        clampHorizontalPosition(null, layout.getPrimaryHorizontal(selectionEnd));
                 mSelectionBounds.set(
                         primaryHorizontal,
                         layout.getLineTop(line),
@@ -4679,8 +4709,9 @@
                         mTextView.viewportToContentHorizontalOffset();
                 final float viewportToContentVerticalOffset =
                         mTextView.viewportToContentVerticalOffset();
-
-                if (includeCharacterBounds) {
+                final boolean isTextTransformed = (mTextView.getTransformationMethod() != null
+                        && mTextView.getTransformed() instanceof OffsetMapping);
+                if (includeCharacterBounds && !isTextTransformed) {
                     final CharSequence text = mTextView.getText();
                     if (text instanceof Spannable) {
                         final Spannable sp = (Spannable) text;
@@ -4708,10 +4739,12 @@
                 if (includeInsertionMarker) {
                     // Treat selectionStart as the insertion point.
                     if (0 <= selectionStart) {
-                        final int offset = selectionStart;
-                        final int line = layout.getLineForOffset(offset);
-                        final float insertionMarkerX = layout.getPrimaryHorizontal(offset)
-                                + viewportToContentHorizontalOffset;
+                        final int offsetTransformed = mTextView.originalToTransformed(
+                                selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR);
+                        final int line = layout.getLineForOffset(offsetTransformed);
+                        final float insertionMarkerX =
+                                layout.getPrimaryHorizontal(offsetTransformed)
+                                        + viewportToContentHorizontalOffset;
                         final float insertionMarkerTop = layout.getLineTop(line)
                                 + viewportToContentVerticalOffset;
                         final float insertionMarkerBaseline = layout.getLineBaseline(line)
@@ -4730,7 +4763,7 @@
                         if (!isTopVisible || !isBottomVisible) {
                             insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
                         }
-                        if (layout.isRtlCharAt(offset)) {
+                        if (layout.isRtlCharAt(offsetTransformed)) {
                             insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL;
                         }
                         builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop,
@@ -5107,12 +5140,28 @@
         protected abstract int getMagnifierHandleTrigger();
 
         protected boolean isAtRtlRun(@NonNull Layout layout, int offset) {
-            return layout.isRtlCharAt(offset);
+            final int transformedOffset =
+                    mTextView.originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
+            return layout.isRtlCharAt(transformedOffset);
         }
 
         @VisibleForTesting
         public float getHorizontal(@NonNull Layout layout, int offset) {
-            return layout.getPrimaryHorizontal(offset);
+            final int transformedOffset =
+                    mTextView.originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
+            return layout.getPrimaryHorizontal(transformedOffset);
+        }
+
+        /**
+         * Return the line number for a given offset.
+         * @param layout the {@link Layout} to query.
+         * @param offset the index of the character to query.
+         * @return the index of the line the given offset belongs to.
+         */
+        public int getLineForOffset(@NonNull Layout layout, int offset) {
+            final int transformedOffset =
+                    mTextView.originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
+            return layout.getLineForOffset(transformedOffset);
         }
 
         protected int getOffsetAtCoordinate(@NonNull Layout layout, int line, float x) {
@@ -5129,13 +5178,12 @@
         protected void positionAtCursorOffset(int offset, boolean forceUpdatePosition,
                 boolean fromTouchScreen) {
             // A HandleView relies on the layout, which may be nulled by external methods
-            Layout layout = mTextView.getLayout();
+            final Layout layout = mTextView.getLayout();
             if (layout == null) {
                 // Will update controllers' state, hiding them and stopping selection mode if needed
                 prepareCursorControllers();
                 return;
             }
-            layout = mTextView.getLayout();
 
             boolean offsetChanged = offset != mPreviousOffset;
             if (offsetChanged || forceUpdatePosition) {
@@ -5146,7 +5194,7 @@
                     }
                     addPositionToTouchUpFilter(offset);
                 }
-                final int line = layout.getLineForOffset(offset);
+                final int line = getLineForOffset(layout, offset);
                 mPrevLine = line;
 
                 mPositionX = getCursorHorizontalPosition(layout, offset) - mHotspotX
@@ -5246,7 +5294,7 @@
         private boolean tooLargeTextForMagnifier() {
             if (mNewMagnifierEnabled) {
                 Layout layout = mTextView.getLayout();
-                final int line = layout.getLineForOffset(getCurrentCursorOffset());
+                final int line = getLineForOffset(layout, getCurrentCursorOffset());
                 return layout.getLineBottom(line, /* includeLineSpacing= */ false)
                         - layout.getLineTop(line) >= mMaxLineHeightForMagnifier;
             }
@@ -5337,11 +5385,11 @@
             }
 
             final Layout layout = mTextView.getLayout();
-            final int lineNumber = layout.getLineForOffset(offset);
+            final int lineNumber = getLineForOffset(layout, offset);
             // Compute whether the selection handles are currently on the same line, and,
             // in this particular case, whether the selected text is right to left.
             final boolean sameLineSelection = otherHandleOffset != -1
-                    && lineNumber == layout.getLineForOffset(otherHandleOffset);
+                    && lineNumber == getLineForOffset(layout, offset);
             final boolean rtl = sameLineSelection
                     && (offset < otherHandleOffset)
                         != (getHorizontal(mTextView.getLayout(), offset)
@@ -5468,7 +5516,7 @@
                 if (mNewMagnifierEnabled) {
                     // Calculates the line bounds as the content source bounds to the magnifier.
                     Layout layout = mTextView.getLayout();
-                    int line = layout.getLineForOffset(getCurrentCursorOffset());
+                    int line = getLineForOffset(layout, getCurrentCursorOffset());
                     int lineLeft = (int) layout.getLineLeft(line);
                     lineLeft += mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
                     int lineRight = (int) layout.getLineRight(line);
@@ -5838,7 +5886,7 @@
 
         private MotionEvent transformEventForTouchThrough(MotionEvent ev) {
             final Layout layout = mTextView.getLayout();
-            final int line = layout.getLineForOffset(getCurrentCursorOffset());
+            final int line = getLineForOffset(layout, getCurrentCursorOffset());
             final int textHeight = layout.getLineBottom(line, /* includeLineSpacing= */ false)
                     - layout.getLineTop(line);
             // Transforms the touch events to screen coordinates.
@@ -6050,7 +6098,7 @@
                     || !isStartHandle() && initialOffset <= anotherHandleOffset) {
                 // Handles have crossed, bound it to the first selected line and
                 // adjust by word / char as normal.
-                currLine = layout.getLineForOffset(anotherHandleOffset);
+                currLine = getLineForOffset(layout, anotherHandleOffset);
                 initialOffset = getOffsetAtCoordinate(layout, currLine, x);
             }
 
@@ -6065,7 +6113,8 @@
             final int currentOffset = getCurrentCursorOffset();
             final boolean rtlAtCurrentOffset = isAtRtlRun(layout, currentOffset);
             final boolean atRtl = isAtRtlRun(layout, offset);
-            final boolean isLvlBoundary = layout.isLevelBoundary(offset);
+            final boolean isLvlBoundary = layout.isLevelBoundary(
+                    mTextView.originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR));
 
             // We can't determine if the user is expanding or shrinking the selection if they're
             // on a bi-di boundary, so until they've moved past the boundary we'll just place
@@ -6077,7 +6126,9 @@
                 mTouchWordDelta = 0.0f;
                 positionAndAdjustForCrossingHandles(offset, fromTouchScreen);
                 return;
-            } else if (mLanguageDirectionChanged && !isLvlBoundary) {
+            }
+
+            if (mLanguageDirectionChanged) {
                 // We've just moved past the boundary so update the position. After this we can
                 // figure out if the user is expanding or shrinking to go by word or character.
                 positionAndAdjustForCrossingHandles(offset, fromTouchScreen);
@@ -6129,7 +6180,7 @@
                     // Sometimes words can be broken across lines (Chinese, hyphenation).
                     // We still snap to the word boundary but we only use the letters on the
                     // current line to determine if the user is far enough into the word to snap.
-                    if (layout.getLineForOffset(wordBoundary) != currLine) {
+                    if (getLineForOffset(layout, wordBoundary) != currLine) {
                         wordBoundary = isStartHandle()
                                 ? layout.getLineStart(currLine) : layout.getLineEnd(currLine);
                     }
@@ -6253,12 +6304,15 @@
                         final int currentOffset = getCurrentCursorOffset();
                         final int offsetToGetRunRange = isStartHandle()
                                 ? currentOffset : Math.max(currentOffset - 1, 0);
-                        final long range = layout.getRunRange(offsetToGetRunRange);
+                        final long range = layout.getRunRange(mTextView.originalToTransformed(
+                                offsetToGetRunRange, OffsetMapping.MAP_STRATEGY_CURSOR));
                         if (isStartHandle()) {
                             offset = TextUtils.unpackRangeStartFromLong(range);
                         } else {
                             offset = TextUtils.unpackRangeEndFromLong(range);
                         }
+                        offset = mTextView.transformedToOriginal(offset,
+                                OffsetMapping.MAP_STRATEGY_CURSOR);
                         positionAtCursorOffset(offset, false, fromTouchScreen);
                         return;
                     }
@@ -6285,7 +6339,10 @@
 
         @Override
         protected boolean isAtRtlRun(@NonNull Layout layout, int offset) {
-            final int offsetToCheck = isStartHandle() ? offset : Math.max(offset - 1, 0);
+            final int transformedOffset =
+                    mTextView.transformedToOriginal(offset, OffsetMapping.MAP_STRATEGY_CHARACTER);
+            final int offsetToCheck = isStartHandle() ? transformedOffset
+                    : Math.max(transformedOffset - 1, 0);
             return layout.isRtlCharAt(offsetToCheck);
         }
 
@@ -6295,12 +6352,17 @@
         }
 
         private float getHorizontal(@NonNull Layout layout, int offset, boolean startHandle) {
-            final int line = layout.getLineForOffset(offset);
-            final int offsetToCheck = startHandle ? offset : Math.max(offset - 1, 0);
+            final int offsetTransformed = mTextView.originalToTransformed(offset,
+                    OffsetMapping.MAP_STRATEGY_CURSOR);
+            final int line = layout.getLineForOffset(offsetTransformed);
+            final int offsetToCheck =
+                    startHandle ? offsetTransformed : Math.max(offsetTransformed - 1, 0);
             final boolean isRtlChar = layout.isRtlCharAt(offsetToCheck);
             final boolean isRtlParagraph = layout.getParagraphDirection(line) == -1;
-            return (isRtlChar == isRtlParagraph)
-                    ? layout.getPrimaryHorizontal(offset) : layout.getSecondaryHorizontal(offset);
+            if  (isRtlChar != isRtlParagraph) {
+                return layout.getSecondaryHorizontal(offsetTransformed);
+            }
+            return layout.getPrimaryHorizontal(offsetTransformed);
         }
 
         @Override
@@ -6308,23 +6370,27 @@
             final float localX = mTextView.convertToLocalHorizontalCoordinate(x);
             final int primaryOffset = layout.getOffsetForHorizontal(line, localX, true);
             if (!layout.isLevelBoundary(primaryOffset)) {
-                return primaryOffset;
+                return mTextView.transformedToOriginal(primaryOffset,
+                        OffsetMapping.MAP_STRATEGY_CURSOR);
             }
             final int secondaryOffset = layout.getOffsetForHorizontal(line, localX, false);
-            final int currentOffset = getCurrentCursorOffset();
+            final int currentOffset = mTextView.originalToTransformed(getCurrentCursorOffset(),
+                    OffsetMapping.MAP_STRATEGY_CURSOR);
             final int primaryDiff = Math.abs(primaryOffset - currentOffset);
             final int secondaryDiff = Math.abs(secondaryOffset - currentOffset);
+            final int offset;
             if (primaryDiff < secondaryDiff) {
-                return primaryOffset;
+                offset = primaryOffset;
             } else if (primaryDiff > secondaryDiff) {
-                return secondaryOffset;
+                offset = secondaryOffset;
             } else {
                 final int offsetToCheck = isStartHandle()
                         ? currentOffset : Math.max(currentOffset - 1, 0);
                 final boolean isRtlChar = layout.isRtlCharAt(offsetToCheck);
                 final boolean isRtlParagraph = layout.getParagraphDirection(line) == -1;
-                return isRtlChar == isRtlParagraph ? primaryOffset : secondaryOffset;
+                offset = (isRtlChar == isRtlParagraph) ? primaryOffset : secondaryOffset;
             }
+            return mTextView.transformedToOriginal(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
         }
 
         @MagnifierHandleTrigger
@@ -7165,7 +7231,10 @@
             int end = Math.min(length, mEnd);
 
             mPath.reset();
-            layout.getSelectionPath(start, end, mPath);
+            layout.getSelectionPath(
+                    mTextView.originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CHARACTER),
+                    mTextView.originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CHARACTER),
+                    mPath);
             return true;
         }
 
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 075aa6c..c1800cf 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -33,6 +33,7 @@
 import android.text.Selection;
 import android.text.Spannable;
 import android.text.TextUtils;
+import android.text.method.OffsetMapping;
 import android.text.util.Linkify;
 import android.util.Log;
 import android.view.ActionMode;
@@ -329,8 +330,6 @@
 
     private void startSelectionActionModeWithSmartSelectAnimation(
             @Nullable SelectionResult result) {
-        final Layout layout = mTextView.getLayout();
-
         final Runnable onAnimationEndCallback = () -> {
             final SelectionResult startSelectionResult;
             if (result != null && result.mStart >= 0 && result.mEnd <= getText(mTextView).length()
@@ -352,7 +351,7 @@
         }
 
         final List<SmartSelectSprite.RectangleWithTextSelectionLayout> selectionRectangles =
-                convertSelectionToRectangles(layout, result.mStart, result.mEnd);
+                convertSelectionToRectangles(mTextView, result.mStart, result.mEnd);
 
         final PointF touchPoint = new PointF(
                 mEditor.getLastUpPositionX(),
@@ -369,7 +368,7 @@
     }
 
     private List<SmartSelectSprite.RectangleWithTextSelectionLayout> convertSelectionToRectangles(
-            final Layout layout, final int start, final int end) {
+            final TextView textView, final int start, final int end) {
         final List<SmartSelectSprite.RectangleWithTextSelectionLayout> result = new ArrayList<>();
 
         final Layout.SelectionRectangleConsumer consumer =
@@ -381,7 +380,11 @@
                                 textSelectionLayout)
                 );
 
-        layout.getSelection(start, end, consumer);
+        final int startTransformed =
+                textView.originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
+        final int endTransformed =
+                textView.originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
+        textView.getLayout().getSelection(startTransformed, endTransformed, consumer);
 
         result.sort(Comparator.comparing(
                 SmartSelectSprite.RectangleWithTextSelectionLayout::getRectangle,
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d6c2d30..6ff808e 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -135,6 +135,7 @@
 import android.text.method.LinkMovementMethod;
 import android.text.method.MetaKeyKeyListener;
 import android.text.method.MovementMethod;
+import android.text.method.OffsetMapping;
 import android.text.method.PasswordTransformationMethod;
 import android.text.method.SingleLineTransformationMethod;
 import android.text.method.TextKeyListener;
@@ -456,6 +457,14 @@
 
     private static final int CHANGE_WATCHER_PRIORITY = 100;
 
+    /**
+     * The span priority of the {@link TransformationMethod} that is set on the text. It must be
+     * higher than the {@link DynamicLayout}'s {@link TextWatcher}, so that the transformed text is
+     * updated before {@link DynamicLayout#reflow(CharSequence, int, int, int)} being triggered
+     * by {@link TextWatcher#onTextChanged(CharSequence, int, int, int)}.
+     */
+    private static final int TRANSFORMATION_SPAN_PRIORITY = 200;
+
     // New state used to change background based on whether this TextView is multiline.
     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
 
@@ -7009,7 +7018,8 @@
 
         final int textLength = text.length();
 
-        if (text instanceof Spannable && !mAllowTransformationLengthChange) {
+        if (text instanceof Spannable && (!mAllowTransformationLengthChange
+                || text instanceof OffsetMapping)) {
             Spannable sp = (Spannable) text;
 
             // Remove any ChangeWatchers that might have come from other TextViews.
@@ -7027,7 +7037,8 @@
             if (mEditor != null) mEditor.addSpanWatchers(sp);
 
             if (mTransformation != null) {
-                sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
+                        | (TRANSFORMATION_SPAN_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
             }
 
             if (mMovement != null) {
@@ -8226,6 +8237,8 @@
         if (mLayout == null) {
             invalidate();
         } else {
+            start = originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
+            end = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
             int lineStart = mLayout.getLineForOffset(start);
             int top = mLayout.getLineTop(lineStart);
 
@@ -8715,8 +8728,8 @@
         Path highlight = null;
         Paint highlightPaint = mHighlightPaint;
 
-        final int selStart = getSelectionStart();
-        final int selEnd = getSelectionEnd();
+        final int selStart = getSelectionStartTransformed();
+        final int selEnd = getSelectionEndTransformed();
         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
             if (selStart == selEnd) {
                 if (mEditor != null && mEditor.shouldRenderCursor()) {
@@ -8937,13 +8950,13 @@
             return;
         }
 
-        int selEnd = getSelectionEnd();
+        int selEnd = getSelectionEndTransformed();
         if (selEnd < 0) {
             super.getFocusedRect(r);
             return;
         }
 
-        int selStart = getSelectionStart();
+        int selStart = getSelectionStartTransformed();
         if (selStart < 0 || selStart >= selEnd) {
             int line = mLayout.getLineForOffset(selEnd);
             r.top = mLayout.getLineTop(line);
@@ -9826,6 +9839,14 @@
         return false;
     }
 
+    /**
+     * Return whether the text is transformed and has {@link OffsetMapping}.
+     * @hide
+     */
+    public boolean isOffsetMappingAvailable() {
+        return mTransformation != null && mTransformed instanceof OffsetMapping;
+    }
+
     /** @hide */
     public boolean previewHandwritingGesture(
             @NonNull PreviewableHandwritingGesture gesture,
@@ -9855,6 +9876,9 @@
     }
 
     private int performHandwritingSelectGesture(@NonNull SelectGesture gesture, boolean isPreview) {
+        if (isOffsetMappingAvailable()) {
+            return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
+        }
         int[] range = getRangeForRect(
                 convertFromScreenToContentCoordinates(gesture.getSelectionArea()),
                 gesture.getGranularity());
@@ -9881,6 +9905,9 @@
 
     private int performHandwritingSelectRangeGesture(
             @NonNull SelectRangeGesture gesture, boolean isPreview) {
+        if (isOffsetMappingAvailable()) {
+            return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
+        }
         int[] startRange = getRangeForRect(
                 convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()),
                 gesture.getGranularity());
@@ -9905,6 +9932,9 @@
     }
 
     private int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture, boolean isPreview) {
+        if (isOffsetMappingAvailable()) {
+            return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
+        }
         int[] range = getRangeForRect(
                 convertFromScreenToContentCoordinates(gesture.getDeletionArea()),
                 gesture.getGranularity());
@@ -9935,6 +9965,9 @@
 
     private int performHandwritingDeleteRangeGesture(
             @NonNull DeleteRangeGesture gesture, boolean isPreview) {
+        if (isOffsetMappingAvailable()) {
+            return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
+        }
         int[] startRange = getRangeForRect(
                 convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()),
                 gesture.getGranularity());
@@ -10015,6 +10048,9 @@
 
     /** @hide */
     public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) {
+        if (isOffsetMappingAvailable()) {
+            return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
+        }
         PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
         int line = getLineForHandwritingGesture(point);
         if (line == -1) {
@@ -10030,6 +10066,9 @@
 
     /** @hide */
     public int performHandwritingRemoveSpaceGesture(@NonNull RemoveSpaceGesture gesture) {
+        if (isOffsetMappingAvailable()) {
+            return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
+        }
         PointF startPoint = convertFromScreenToContentCoordinates(gesture.getStartPoint());
         PointF endPoint = convertFromScreenToContentCoordinates(gesture.getEndPoint());
 
@@ -10088,6 +10127,9 @@
 
     /** @hide */
     public int performHandwritingJoinOrSplitGesture(@NonNull JoinOrSplitGesture gesture) {
+        if (isOffsetMappingAvailable()) {
+            return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
+        }
         PointF point = convertFromScreenToContentCoordinates(gesture.getJoinOrSplitPoint());
 
         int line = getLineForHandwritingGesture(point);
@@ -11179,13 +11221,15 @@
             mDeferScroll = offset;
             return false;
         }
+        final int offsetTransformed =
+                originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
         boolean changed = false;
 
         Layout layout = isShowingHint() ? mHintLayout : mLayout;
 
         if (layout == null) return changed;
 
-        int line = layout.getLineForOffset(offset);
+        int line = layout.getLineForOffset(offsetTransformed);
 
         int grav;
 
@@ -11220,7 +11264,7 @@
         // right where it is most likely to be annoying.
         final boolean clamped = grav > 0;
         // FIXME: Is it okay to truncate this, or should we round?
-        final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
+        final int x = (int) layout.getPrimaryHorizontal(offsetTransformed, clamped);
         final int top = layout.getLineTop(line);
         final int bottom = layout.getLineTop(line + 1);
 
@@ -11384,8 +11428,8 @@
         if (!(mText instanceof Spannable)) {
             return false;
         }
-        int start = getSelectionStart();
-        int end = getSelectionEnd();
+        int start = getSelectionStartTransformed();
+        int end = getSelectionEndTransformed();
         if (start != end) {
             return false;
         }
@@ -11428,7 +11472,8 @@
         }
 
         if (newStart != start) {
-            Selection.setSelection(mSpannable, newStart);
+            Selection.setSelection(mSpannable,
+                    transformedToOriginal(newStart, OffsetMapping.MAP_STRATEGY_CURSOR));
             return true;
         }
 
@@ -11537,6 +11582,35 @@
     }
 
     /**
+     * Calculates the rectangles which should be highlighted to indicate a selection between start
+     * and end and feeds them into the given {@link Layout.SelectionRectangleConsumer}.
+     *
+     * @param start    the starting index of the selection
+     * @param end      the ending index of the selection
+     * @param consumer the {@link Layout.SelectionRectangleConsumer} which will receive the
+     *                 generated rectangles. It will be called every time a rectangle is generated.
+     * @hide
+     */
+    public void getSelection(int start, int end, final Layout.SelectionRectangleConsumer consumer) {
+        final int transformedStart =
+                originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
+        final int transformedEnd = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
+        mLayout.getSelection(transformedStart, transformedEnd, consumer);
+    }
+
+    int getSelectionStartTransformed() {
+        final int start = getSelectionStart();
+        if (start < 0) return start;
+        return originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
+    }
+
+    int getSelectionEndTransformed() {
+        final int end = getSelectionEnd();
+        if (end < 0) return end;
+        return originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
+    }
+
+    /**
      * Return true iff there is a selection of nonzero length inside this text view.
      */
     public boolean hasSelection() {
@@ -13186,8 +13260,12 @@
                 }
 
                 // Convert lines into character offsets.
-                int expandedTopChar = layout.getLineStart(expandedTopLine);
-                int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
+                int expandedTopChar = transformedToOriginal(
+                        layout.getLineStart(expandedTopLine),
+                        OffsetMapping.MAP_STRATEGY_CHARACTER);
+                int expandedBottomChar = transformedToOriginal(
+                        layout.getLineEnd(expandedBottomLine),
+                        OffsetMapping.MAP_STRATEGY_CHARACTER);
 
                 // Take into account selection -- if there is a selection, we need to expand
                 // the text we are returning to include that selection.
@@ -13230,8 +13308,10 @@
                         final int[] lineBaselines = new int[bottomLine - topLine + 1];
                         final int baselineOffset = getBaselineOffset();
                         for (int i = topLine; i <= bottomLine; i++) {
-                            lineOffsets[i - topLine] = layout.getLineStart(i);
-                            lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
+                            lineOffsets[i - topLine] = transformedToOriginal(layout.getLineStart(i),
+                                    OffsetMapping.MAP_STRATEGY_CHARACTER);
+                            lineBaselines[i - topLine] =
+                                    layout.getLineBaseline(i) + baselineOffset;
                         }
                         structure.setTextLines(lineOffsets, lineBaselines);
                     }
@@ -13537,6 +13617,11 @@
     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
             float viewportToContentVerticalOffset) {
+        if (isOffsetMappingAvailable()) {
+            // The text is transformed, and has different length, we don't support
+            // character bounds in this case yet.
+            return;
+        }
         final Rect rect = new Rect();
         getLocalVisibleRect(rect);
         final RectF visibleRect = new RectF(rect);
@@ -13601,8 +13686,8 @@
             return null;
         }
         final CharSequence text = layout.getText();
-        if (text == null) {
-            // It's impossible that a layout has no text. Check here to avoid NPE.
+        if (text == null || isOffsetMappingAvailable()) {
+            // The text is Null or the text has been transformed. Can't provide TextBoundsInfo.
             return null;
         }
 
@@ -14624,10 +14709,40 @@
 
     int getOffsetAtCoordinate(int line, float x) {
         x = convertToLocalHorizontalCoordinate(x);
-        return getLayout().getOffsetForHorizontal(line, x);
+        final int offset = getLayout().getOffsetForHorizontal(line, x);
+        return transformedToOriginal(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
     }
 
     /**
+     * Convenient method to convert an offset on the transformed text to the original text.
+     * @hide
+     */
+    public int transformedToOriginal(int offset, @OffsetMapping.MapStrategy int strategy) {
+        if (getTransformationMethod() == null) {
+            return offset;
+        }
+        if (mTransformed instanceof OffsetMapping) {
+            final OffsetMapping transformedText = (OffsetMapping) mTransformed;
+            return transformedText.transformedToOriginal(offset, strategy);
+        }
+        return offset;
+    }
+
+    /**
+     * Convenient method to convert an offset on the original text to the transformed text.
+     * @hide
+     */
+    public int originalToTransformed(int offset, @OffsetMapping.MapStrategy int strategy) {
+        if (getTransformationMethod() == null) {
+            return offset;
+        }
+        if (mTransformed instanceof OffsetMapping) {
+            final OffsetMapping transformedText = (OffsetMapping) mTransformed;
+            return transformedText.originalToTransformed(offset, strategy);
+        }
+        return offset;
+    }
+    /**
      * Handles drag events sent by the system following a call to
      * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int)
      * startDragAndDrop()}.
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index b9ca557..88d6425 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -27,6 +27,7 @@
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.SharedMemory;
+import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 import android.service.voice.IVoiceInteractionService;
 import android.service.voice.IVoiceInteractionSession;
@@ -299,6 +300,10 @@
      */
     void shutdownHotwordDetectionService();
 
+    void startPerceiving(in IVisualQueryDetectionVoiceInteractionCallback callback);
+
+    void stopPerceiving();
+
     void startListeningFromMic(
         in AudioFormat audioFormat,
         in IMicrophoneHotwordDetectionVoiceInteractionCallback callback);
diff --git a/core/java/com/android/internal/config/appcloning/AppCloningDeviceConfigHelper.java b/core/java/com/android/internal/config/appcloning/AppCloningDeviceConfigHelper.java
new file mode 100644
index 0000000..ddd3d2c
--- /dev/null
+++ b/core/java/com/android/internal/config/appcloning/AppCloningDeviceConfigHelper.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 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.config.appcloning;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Helper class that holds the flags related to the app_cloning namespace in {@link DeviceConfig}.
+ *
+ * @hide
+ */
+public class AppCloningDeviceConfigHelper {
+
+    @GuardedBy("sLock")
+    private static AppCloningDeviceConfigHelper sInstance;
+
+    private static final Object sLock = new Object();
+
+    private DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangeListener;
+
+    /**
+     * This flag is defined inside {@link DeviceConfig#NAMESPACE_APP_CLONING}. Please check
+     * {@link #mEnableAppCloningBuildingBlocks} for details.
+     */
+    public static final String ENABLE_APP_CLONING_BUILDING_BLOCKS =
+            "enable_app_cloning_building_blocks";
+
+    /**
+     * Checks whether the support for app-cloning building blocks (like contacts
+     * sharing/intent redirection), which are available starting from the U release, is turned on.
+     * The default value is true to ensure the features are always enabled going forward.
+     *
+     * TODO:(b/253449368) Add information about the app-cloning config and mention that the devices
+     * that do not support app-cloning should use the app-cloning config to disable all app-cloning
+     * features.
+     */
+    private volatile boolean mEnableAppCloningBuildingBlocks = true;
+
+    private AppCloningDeviceConfigHelper() {}
+
+    /**
+     * @hide
+     */
+    public static AppCloningDeviceConfigHelper getInstance(Context context) {
+        synchronized (sLock) {
+            if (sInstance == null) {
+                sInstance = new AppCloningDeviceConfigHelper();
+                sInstance.init(context);
+            }
+            return sInstance;
+        }
+    }
+
+    private void init(Context context) {
+        initializeDeviceConfigChangeListener();
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_CLONING,
+                context.getMainExecutor(),
+                mDeviceConfigChangeListener);
+    }
+
+    private void initializeDeviceConfigChangeListener() {
+        mDeviceConfigChangeListener = properties -> {
+            if (!DeviceConfig.NAMESPACE_APP_CLONING.equals(properties.getNamespace())) {
+                return;
+            }
+            for (String name : properties.getKeyset()) {
+                if (name == null) {
+                    return;
+                }
+                if (ENABLE_APP_CLONING_BUILDING_BLOCKS.equals(name)) {
+                    updateEnableAppCloningBuildingBlocks();
+                }
+            }
+        };
+    }
+
+    private void updateEnableAppCloningBuildingBlocks() {
+        mEnableAppCloningBuildingBlocks = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_APP_CLONING, ENABLE_APP_CLONING_BUILDING_BLOCKS, true);
+    }
+
+    /**
+     * Fetch the feature flag to check whether the support for the app-cloning building blocks
+     * (like contacts sharing/intent redirection) is enabled on the device.
+     * @hide
+     */
+    public boolean getEnableAppCloningBuildingBlocks() {
+        return mEnableAppCloningBuildingBlocks;
+    }
+}
diff --git a/core/java/com/android/internal/config/appcloning/OWNERS b/core/java/com/android/internal/config/appcloning/OWNERS
new file mode 100644
index 0000000..0645a8c5
--- /dev/null
+++ b/core/java/com/android/internal/config/appcloning/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1207885
+jigarthakkar@google.com
+saumyap@google.com
\ No newline at end of file
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index d9e9a5f..95a47ea 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -16,11 +16,15 @@
 
 package com.android.internal.jank;
 
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
 import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
@@ -89,17 +93,19 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.UiThread;
 import android.annotation.WorkerThread;
+import android.app.ActivityThread;
 import android.content.Context;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.provider.DeviceConfig;
-import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
@@ -232,6 +238,7 @@
     public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
     public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67;
     public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
+    public static final int CUJ_IME_INSETS_ANIMATION = 69;
 
     private static final int NO_STATSD_LOGGING = -1;
 
@@ -309,6 +316,7 @@
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION,
     };
 
     private static class InstanceHolder {
@@ -401,7 +409,8 @@
             CUJ_RECENTS_SCROLLING,
             CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
             CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
-            CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME
+            CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
+            CUJ_IME_INSETS_ANIMATION,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -422,29 +431,37 @@
      * @param worker the worker thread for the callbacks
      */
     @VisibleForTesting
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     public InteractionJankMonitor(@NonNull HandlerThread worker) {
-        // Check permission early.
-        Settings.Config.enforceReadPermission(
-            DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR);
-
         mRunningTrackers = new SparseArray<>();
         mTimeoutActions = new SparseArray<>();
         mWorker = worker;
         mWorker.start();
-        mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
         mDisplayResolutionTracker = new DisplayResolutionTracker(worker.getThreadHandler());
-
-        // Post initialization to the background in case we're running on the main
-        // thread.
-        mWorker.getThreadHandler().post(
-                () -> mPropertiesChangedListener.onPropertiesChanged(
-                        DeviceConfig.getProperties(
-                                DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR)));
-        DeviceConfig.addOnPropertiesChangedListener(
-                DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
-                new HandlerExecutor(mWorker.getThreadHandler()),
-                mPropertiesChangedListener);
+        mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
         mEnabled = DEFAULT_ENABLED;
+
+        final Context context = ActivityThread.currentApplication();
+        if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
+            // Post initialization to the background in case we're running on the main thread.
+            mWorker.getThreadHandler().post(
+                    () -> mPropertiesChangedListener.onPropertiesChanged(
+                            DeviceConfig.getProperties(
+                                    DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR)));
+            DeviceConfig.addOnPropertiesChangedListener(
+                    DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
+                    new HandlerExecutor(mWorker.getThreadHandler()),
+                    mPropertiesChangedListener);
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "Initialized the InteractionJankMonitor."
+                        + " (No READ_DEVICE_CONFIG permission to change configs)"
+                        + " enabled=" + mEnabled + ", interval=" + mSamplingInterval
+                        + ", missedFrameThreshold=" + mTraceThresholdMissedFrames
+                        + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
+                        + ", package=" + context.getPackageName());
+            }
+        }
     }
 
     /**
@@ -923,6 +940,8 @@
                 return "LAUNCHER_CLOSE_ALL_APPS_SWIPE";
             case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME:
                 return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME";
+            case CUJ_IME_INSETS_ANIMATION:
+                return "IME_INSETS_ANIMATION";
         }
         return "UNKNOWN";
     }
@@ -1178,7 +1197,7 @@
          */
         @VisibleForTesting
         public int getDisplayId() {
-            return (mSurfaceOnly ? mContext.getDisplay() : mView.getDisplay()).getDisplayId();
+            return mSurfaceOnly ? mContext.getDisplayId() : mView.getContext().getDisplayId();
         }
     }
 
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index afb526a..2d5bb6c 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -14,7 +14,10 @@
 
 package com.android.internal.util;
 
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Trace.TRACE_TAG_APP;
+import static android.provider.DeviceConfig.NAMESPACE_LATENCY_TRACKER;
 
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL_UNLOCKED;
@@ -24,6 +27,8 @@
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_SHOWN;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR;
@@ -42,9 +47,12 @@
 import static com.android.internal.util.LatencyTracker.ActionProperties.SAMPLE_INTERVAL_SUFFIX;
 import static com.android.internal.util.LatencyTracker.ActionProperties.TRACE_THRESHOLD_SUFFIX;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.app.ActivityThread;
 import android.content.Context;
 import android.os.Build;
 import android.os.ConditionVariable;
@@ -187,6 +195,16 @@
      */
     public static final int ACTION_SHOW_VOICE_INTERACTION = 19;
 
+    /**
+     * Time it takes to request IME shown animation.
+     */
+    public static final int ACTION_REQUEST_IME_SHOWN = 20;
+
+    /**
+     * Time it takes to request IME hidden animation.
+     */
+    public static final int ACTION_REQUEST_IME_HIDDEN = 21;
+
     private static final int[] ACTIONS_ALL = {
         ACTION_EXPAND_PANEL,
         ACTION_TOGGLE_RECENTS,
@@ -208,6 +226,8 @@
         ACTION_SHOW_SELECTION_TOOLBAR,
         ACTION_FOLD_TO_AOD,
         ACTION_SHOW_VOICE_INTERACTION,
+        ACTION_REQUEST_IME_SHOWN,
+        ACTION_REQUEST_IME_HIDDEN,
     };
 
     /** @hide */
@@ -232,6 +252,8 @@
         ACTION_SHOW_SELECTION_TOOLBAR,
         ACTION_FOLD_TO_AOD,
         ACTION_SHOW_VOICE_INTERACTION,
+        ACTION_REQUEST_IME_SHOWN,
+        ACTION_REQUEST_IME_HIDDEN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Action {
@@ -259,6 +281,8 @@
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR,
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD,
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_SHOWN,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN,
     };
 
     private static LatencyTracker sLatencyTracker;
@@ -284,15 +308,30 @@
         return sLatencyTracker;
     }
 
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     @VisibleForTesting
     public LatencyTracker() {
         mEnabled = DEFAULT_ENABLED;
 
-        // Post initialization to the background in case we're running on the main thread.
-        BackgroundThread.getHandler().post(() -> this.updateProperties(
-                DeviceConfig.getProperties(DeviceConfig.NAMESPACE_LATENCY_TRACKER)));
-        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
-                BackgroundThread.getExecutor(), this::updateProperties);
+        final Context context = ActivityThread.currentApplication();
+        if (context != null
+                && context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
+            // Post initialization to the background in case we're running on the main thread.
+            BackgroundThread.getHandler().post(() -> this.updateProperties(
+                    DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER)));
+            DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER,
+                    BackgroundThread.getExecutor(), this::updateProperties);
+        } else {
+            if (DEBUG) {
+                if (context == null) {
+                    Log.d(TAG, "No application for " + ActivityThread.currentActivityThread());
+                } else {
+                    Log.d(TAG, "Initialized the LatencyTracker."
+                            + " (No READ_DEVICE_CONFIG permission to change configs)"
+                            + " enabled=" + mEnabled + ", package=" + context.getPackageName());
+                }
+            }
+        }
     }
 
     private void updateProperties(DeviceConfig.Properties properties) {
@@ -368,6 +407,10 @@
                 return "ACTION_FOLD_TO_AOD";
             case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION:
                 return "ACTION_SHOW_VOICE_INTERACTION";
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_SHOWN:
+                return "ACTION_REQUEST_IME_SHOWN";
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN:
+                return "ACTION_REQUEST_IME_HIDDEN";
             default:
                 throw new IllegalArgumentException("Invalid action");
         }
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 2106426..9116cb3 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -40,6 +40,11 @@
     // TODO: Use ParceledListSlice instead
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
+    InputMethodInfo getCurrentInputMethodInfoAsUser(int userId);
+
+    // TODO: Use ParceledListSlice instead
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
     List<InputMethodInfo> getInputMethodList(int userId, int directBootAwareness);
 
     // TODO: Use ParceledListSlice instead
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index c08f264..f55d15d 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -92,12 +92,6 @@
         private float mBoundingRight;
         private float mBoundingBottom;
 
-        // Position estimator.
-        private VelocityTracker.Estimator mEstimatorX = new VelocityTracker.Estimator();
-        private VelocityTracker.Estimator mAltEstimatorX = new VelocityTracker.Estimator();
-        private VelocityTracker.Estimator mEstimatorY = new VelocityTracker.Estimator();
-        private VelocityTracker.Estimator mAltEstimatorY = new VelocityTracker.Estimator();
-
         @UnsupportedAppUsage
         public PointerState() {
         }
@@ -669,13 +663,9 @@
                 ps.addTrace(coords.x, coords.y, true);
                 ps.mXVelocity = mVelocity.getXVelocity(id);
                 ps.mYVelocity = mVelocity.getYVelocity(id);
-                mVelocity.getEstimator(MotionEvent.AXIS_X, id, ps.mEstimatorX);
-                mVelocity.getEstimator(MotionEvent.AXIS_Y, id, ps.mEstimatorY);
                 if (mAltVelocity != null) {
                     ps.mAltXVelocity = mAltVelocity.getXVelocity(id);
                     ps.mAltYVelocity = mAltVelocity.getYVelocity(id);
-                    mAltVelocity.getEstimator(MotionEvent.AXIS_X, id, ps.mAltEstimatorX);
-                    mAltVelocity.getEstimator(MotionEvent.AXIS_Y, id, ps.mAltEstimatorY);
                 }
                 ps.mToolType = event.getToolType(i);
 
diff --git a/core/java/com/android/server/backup/OWNERS b/core/java/com/android/server/backup/OWNERS
new file mode 100644
index 0000000..d99779e
--- /dev/null
+++ b/core/java/com/android/server/backup/OWNERS
@@ -0,0 +1 @@
+include /services/backup/OWNERS
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index 17cfbfc..3b409d4 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -541,87 +541,6 @@
             indices.mData, indexCount);
 }
 
-#define I(_i, _j) ((_j)+ 4*(_i))
-
-static
-void multiplyMM(float* r, const float* lhs, const float* rhs)
-{
-    for (int i=0 ; i<4 ; i++) {
-        const float rhs_i0 = rhs[ I(i,0) ];
-        float ri0 = lhs[ I(0,0) ] * rhs_i0;
-        float ri1 = lhs[ I(0,1) ] * rhs_i0;
-        float ri2 = lhs[ I(0,2) ] * rhs_i0;
-        float ri3 = lhs[ I(0,3) ] * rhs_i0;
-        for (int j=1 ; j<4 ; j++) {
-            const float rhs_ij = rhs[ I(i,j) ];
-            ri0 += lhs[ I(j,0) ] * rhs_ij;
-            ri1 += lhs[ I(j,1) ] * rhs_ij;
-            ri2 += lhs[ I(j,2) ] * rhs_ij;
-            ri3 += lhs[ I(j,3) ] * rhs_ij;
-        }
-        r[ I(i,0) ] = ri0;
-        r[ I(i,1) ] = ri1;
-        r[ I(i,2) ] = ri2;
-        r[ I(i,3) ] = ri3;
-    }
-}
-
-static
-void util_multiplyMM(JNIEnv *env, jclass clazz,
-    jfloatArray result_ref, jint resultOffset,
-    jfloatArray lhs_ref, jint lhsOffset,
-    jfloatArray rhs_ref, jint rhsOffset) {
-
-    FloatArrayHelper resultMat(env, result_ref, resultOffset, 16);
-    FloatArrayHelper lhs(env, lhs_ref, lhsOffset, 16);
-    FloatArrayHelper rhs(env, rhs_ref, rhsOffset, 16);
-
-    bool checkOK = resultMat.check() && lhs.check() && rhs.check();
-
-    if ( !checkOK ) {
-        return;
-    }
-
-    resultMat.bind();
-    lhs.bind();
-    rhs.bind();
-
-    multiplyMM(resultMat.mData, lhs.mData, rhs.mData);
-
-    resultMat.commitChanges();
-}
-
-static
-void multiplyMV(float* r, const float* lhs, const float* rhs)
-{
-    mx4transform(rhs[0], rhs[1], rhs[2], rhs[3], lhs, r);
-}
-
-static
-void util_multiplyMV(JNIEnv *env, jclass clazz,
-    jfloatArray result_ref, jint resultOffset,
-    jfloatArray lhs_ref, jint lhsOffset,
-    jfloatArray rhs_ref, jint rhsOffset) {
-
-    FloatArrayHelper resultV(env, result_ref, resultOffset, 4);
-    FloatArrayHelper lhs(env, lhs_ref, lhsOffset, 16);
-    FloatArrayHelper rhs(env, rhs_ref, rhsOffset, 4);
-
-    bool checkOK = resultV.check() && lhs.check() && rhs.check();
-
-    if ( !checkOK ) {
-        return;
-    }
-
-    resultV.bind();
-    lhs.bind();
-    rhs.bind();
-
-    multiplyMV(resultV.mData, lhs.mData, rhs.mData);
-
-    resultV.commitChanges();
-}
-
 // ---------------------------------------------------------------------------
 
 // The internal format is no longer the same as pixel format, per Table 2 in
@@ -1014,11 +933,6 @@
  * JNI registration
  */
 
-static const JNINativeMethod gMatrixMethods[] = {
-    { "multiplyMM", "([FI[FI[FI)V", (void*)util_multiplyMM },
-    { "multiplyMV", "([FI[FI[FI)V", (void*)util_multiplyMV },
-};
-
 static const JNINativeMethod gVisibilityMethods[] = {
     { "computeBoundingSphere", "([FII[FI)V", (void*)util_computeBoundingSphere },
     { "frustumCullSpheres", "([FI[FII[III)I", (void*)util_frustumCullSpheres },
@@ -1051,7 +965,6 @@
 } ClassRegistrationInfo;
 
 static const ClassRegistrationInfo gClasses[] = {
-    {"android/opengl/Matrix", gMatrixMethods, NELEM(gMatrixMethods)},
     {"android/opengl/Visibility", gVisibilityMethods, NELEM(gVisibilityMethods)},
     {"android/opengl/GLUtils", gUtilsMethods, NELEM(gUtilsMethods)},
     {"android/opengl/ETC1", gEtc1Methods, NELEM(gEtc1Methods)},
diff --git a/core/jni/android_view_VelocityTracker.cpp b/core/jni/android_view_VelocityTracker.cpp
index 343e9d8..05c9f68 100644
--- a/core/jni/android_view_VelocityTracker.cpp
+++ b/core/jni/android_view_VelocityTracker.cpp
@@ -31,13 +31,6 @@
 // Special constant to request the velocity of the active pointer.
 static const int ACTIVE_POINTER_ID = -1;
 
-static struct {
-    jfieldID coeff;
-    jfieldID degree;
-    jfieldID confidence;
-} gEstimatorClassInfo;
-
-
 // --- VelocityTrackerState ---
 
 class VelocityTrackerState {
@@ -50,7 +43,6 @@
     // a subset of the supported axes.
     void computeCurrentVelocity(int32_t units, float maxVelocity);
     float getVelocity(int32_t axis, int32_t id);
-    bool getEstimator(int32_t axis, int32_t id, VelocityTracker::Estimator* outEstimator);
 
 private:
     VelocityTracker mVelocityTracker;
@@ -80,11 +72,6 @@
     return mComputedVelocity.getVelocity(axis, id).value_or(0);
 }
 
-bool VelocityTrackerState::getEstimator(int32_t axis, int32_t id,
-                                        VelocityTracker::Estimator* outEstimator) {
-    return mVelocityTracker.getEstimator(axis, id, outEstimator);
-}
-
 // Return a strategy enum from integer value.
 inline static VelocityTracker::Strategy getStrategyFromInt(const int32_t strategy) {
     if (strategy < static_cast<int32_t>(VelocityTracker::Strategy::MIN) ||
@@ -135,24 +122,6 @@
     return state->getVelocity(axis, id);
 }
 
-static jboolean android_view_VelocityTracker_nativeGetEstimator(JNIEnv* env, jclass clazz,
-                                                                jlong ptr, jint axis, jint id,
-                                                                jobject outEstimatorObj) {
-    VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
-    VelocityTracker::Estimator estimator;
-
-    bool result = state->getEstimator(axis, id, &estimator);
-
-    jfloatArray coeffObj =
-            jfloatArray(env->GetObjectField(outEstimatorObj, gEstimatorClassInfo.coeff));
-
-    env->SetFloatArrayRegion(coeffObj, 0, VelocityTracker::Estimator::MAX_DEGREE + 1,
-                             estimator.coeff);
-    env->SetIntField(outEstimatorObj, gEstimatorClassInfo.degree, estimator.degree);
-    env->SetFloatField(outEstimatorObj, gEstimatorClassInfo.confidence, estimator.confidence);
-    return result;
-}
-
 static jboolean android_view_VelocityTracker_nativeIsAxisSupported(JNIEnv* env, jclass clazz,
                                                                    jint axis) {
     return VelocityTracker::isAxisSupported(axis);
@@ -170,23 +139,13 @@
         {"nativeComputeCurrentVelocity", "(JIF)V",
          (void*)android_view_VelocityTracker_nativeComputeCurrentVelocity},
         {"nativeGetVelocity", "(JII)F", (void*)android_view_VelocityTracker_nativeGetVelocity},
-        {"nativeGetEstimator", "(JIILandroid/view/VelocityTracker$Estimator;)Z",
-         (void*)android_view_VelocityTracker_nativeGetEstimator},
         {"nativeIsAxisSupported", "(I)Z",
          (void*)android_view_VelocityTracker_nativeIsAxisSupported},
 };
 
 int register_android_view_VelocityTracker(JNIEnv* env) {
-    int res = RegisterMethodsOrDie(env, "android/view/VelocityTracker", gVelocityTrackerMethods,
-                                   NELEM(gVelocityTrackerMethods));
-
-    jclass clazz = FindClassOrDie(env, "android/view/VelocityTracker$Estimator");
-
-    gEstimatorClassInfo.coeff = GetFieldIDOrDie(env, clazz, "coeff", "[F");
-    gEstimatorClassInfo.degree = GetFieldIDOrDie(env, clazz, "degree", "I");
-    gEstimatorClassInfo.confidence = GetFieldIDOrDie(env, clazz, "confidence", "F");
-
-    return res;
+    return RegisterMethodsOrDie(env, "android/view/VelocityTracker", gVelocityTrackerMethods,
+                                NELEM(gVelocityTrackerMethods));
 }
 
 } // namespace android
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8ee88ad..3f18e5a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4471,13 +4471,22 @@
     <permission android:name="android.permission.BIND_HOTWORD_DETECTION_SERVICE"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows an application to manage hotword detection on the device.
+    <!-- @SystemApi Allows an application to manage hotword detection and visual query detection
+         on the device.
          <p>Protection level: internal|preinstalled
          @hide This is not a third-party API (intended for OEMs and system apps).
     -->
     <permission android:name="android.permission.MANAGE_HOTWORD_DETECTION"
                 android:protectionLevel="internal|preinstalled" />
 
+    <!-- @SystemApi Must be required by a {@link android.service.voice.VisualQueryDetectionService},
+         to ensure that only the system can bind to it.
+         <p>Protection level: signature
+         @hide This is not a third-party API (intended for OEMs and system apps).
+    -->
+    <permission android:name="android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE"
+        android:protectionLevel="signature" />
+
     <!-- Allows an application to subscribe to keyguard locked (i.e., showing) state.
          <p>Protection level: internal|role
          <p>Intended for use by ROLE_ASSISTANT only.
@@ -5326,6 +5335,14 @@
     <permission android:name="android.permission.MODIFY_AUDIO_ROUTING"
         android:protectionLevel="signature|privileged|role" />
 
+    <!--@SystemApi Allows an application to modify system audio settings that shouldn't be
+        controllable by external apps, such as volume settings or volume behaviors for audio
+        devices, regardless of their connection status.
+        <p>Not for use by third-party applications.
+        @hide -->
+    <permission android:name="android.permission.MODIFY_AUDIO_SYSTEM_SETTINGS"
+                android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows an application to access the uplink and downlink audio of an ongoing
         call.
          <p>Not for use by third-party applications.</p>
diff --git a/core/res/OWNERS b/core/res/OWNERS
index b878189..bce500c 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -23,8 +23,8 @@
 
 # WindowManager team
 # TODO(262451702): Move WindowManager configs out of config.xml in a separate file
-per-file core/res/res/values/config.xml = file:/services/core/java/com/android/server/wm/OWNERS
-per-file core/res/res/values/symbols.xml = file:/services/core/java/com/android/server/wm/OWNERS
+per-file res/values/config.xml = file:/services/core/java/com/android/server/wm/OWNERS
+per-file res/values/symbols.xml = file:/services/core/java/com/android/server/wm/OWNERS
 
 # Resources finalization
 per-file res/xml/public-staging.xml = file:/tools/aapt2/OWNERS
diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml
index c382a65..2e65800 100644
--- a/core/res/res/layout/autofill_fill_dialog.xml
+++ b/core/res/res/layout/autofill_fill_dialog.xml
@@ -93,7 +93,7 @@
             android:layout_height="36dp"
             android:layout_marginTop="6dp"
             android:layout_marginBottom="6dp"
-            style="@style/AutofillHalfSheetOutlinedButton"
+            style="?android:attr/borderlessButtonStyle"
             android:text="@string/autofill_save_no">
         </Button>
 
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index fd08241..3c0b789 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -81,7 +81,7 @@
                 android:layout_height="36dp"
                 android:layout_marginTop="6dp"
                 android:layout_marginBottom="6dp"
-                style="@style/AutofillHalfSheetOutlinedButton"
+                style="?android:attr/borderlessButtonStyle"
                 android:text="@string/autofill_save_no">
             </Button>
 
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
new file mode 100644
index 0000000..76f4171
--- /dev/null
+++ b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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.text;
+
+import static android.text.Layout.Alignment.ALIGN_NORMAL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+import android.text.method.OffsetMapping;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DynamicLayoutOffsetMappingTest {
+    private static final int WIDTH = 10000;
+    private static final TextPaint sTextPaint = new TextPaint();
+
+    @Test
+    public void textWithOffsetMapping() {
+        final String text = "abcde";
+        final SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+        final CharSequence transformedText = new TestOffsetMapping(spannable, 2, "\n");
+
+        final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+                .setAlignment(ALIGN_NORMAL)
+                .setIncludePad(false)
+                .setDisplayText(transformedText)
+                .build();
+
+        assertThat(transformedText.toString()).isEqualTo("ab\ncde");
+        assertLineRange(layout, /* lineBreaks */ 0, 3, 6);
+    }
+
+    @Test
+    public void textWithOffsetMapping_deletion() {
+        final String text = "abcdef";
+        final SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+        final CharSequence transformedText =
+                new TestOffsetMapping(spannable, 3, "\n\n");
+
+        final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+                .setAlignment(ALIGN_NORMAL)
+                .setIncludePad(false)
+                .setDisplayText(transformedText)
+                .build();
+
+        // delete character 'c', original text becomes "abdef"
+        spannable.delete(2, 3);
+        assertThat(transformedText.toString()).isEqualTo("ab\n\ndef");
+        assertLineRange(layout, /* lineBreaks */ 0, 3, 4, 7);
+
+        // delete character 'd', original text becomes "abef"
+        spannable.delete(2, 3);
+        assertThat(transformedText.toString()).isEqualTo("ab\n\nef");
+        assertLineRange(layout, /* lineBreaks */ 0, 3, 4, 6);
+
+        // delete "be", original text becomes "af"
+        spannable.delete(1, 3);
+        assertThat(transformedText.toString()).isEqualTo("a\n\nf");
+        assertLineRange(layout, /* lineBreaks */ 0, 2, 3, 4);
+    }
+
+    @Test
+    public void textWithOffsetMapping_insertion() {
+        final String text = "abcdef";
+        final SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+        final CharSequence transformedText = new TestOffsetMapping(spannable, 3, "\n\n");
+
+        final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+                .setAlignment(ALIGN_NORMAL)
+                .setIncludePad(false)
+                .setDisplayText(transformedText)
+                .build();
+
+        spannable.insert(3, "x");
+        assertThat(transformedText.toString()).isEqualTo("abcx\n\ndef");
+        assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 9);
+
+        spannable.insert(5, "x");
+        assertThat(transformedText.toString()).isEqualTo("abcx\n\ndxef");
+        assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 10);
+    }
+
+    @Test
+    public void textWithOffsetMapping_replace() {
+        final String text = "abcdef";
+        final SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+        final CharSequence transformedText = new TestOffsetMapping(spannable, 3, "\n\n");
+
+        final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+                .setAlignment(ALIGN_NORMAL)
+                .setIncludePad(false)
+                .setDisplayText(transformedText)
+                .build();
+
+        spannable.replace(2, 4, "xx");
+        assertThat(transformedText.toString()).isEqualTo("abxx\n\nef");
+        assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 8);
+    }
+
+    private void assertLineRange(Layout layout, int... lineBreaks) {
+        final int lineCount = lineBreaks.length - 1;
+        assertThat(layout.getLineCount()).isEqualTo(lineCount);
+        for (int line = 0; line < lineCount; ++line) {
+            assertThat(layout.getLineStart(line)).isEqualTo(lineBreaks[line]);
+        }
+        assertThat(layout.getLineEnd(lineCount - 1)).isEqualTo(lineBreaks[lineCount]);
+    }
+
+    /**
+     * A test TransformedText that inserts some text at the given offset.
+     */
+    private static class TestOffsetMapping implements OffsetMapping, CharSequence {
+        private final int mOriginalInsertOffset;
+        private final CharSequence mOriginal;
+        private final CharSequence mInsertText;
+        TestOffsetMapping(CharSequence original, int insertOffset,
+                CharSequence insertText) {
+            mOriginal = original;
+            if (mOriginal instanceof Spannable) {
+                ((Spannable) mOriginal).setSpan(INSERT_POINT, insertOffset, insertOffset,
+                        Spanned.SPAN_POINT_POINT);
+            }
+            mOriginalInsertOffset = insertOffset;
+            mInsertText = insertText;
+        }
+
+        private int getInsertOffset() {
+            if (mOriginal instanceof Spannable) {
+                return ((Spannable) mOriginal).getSpanStart(INSERT_POINT);
+            }
+            return mOriginalInsertOffset;
+        }
+
+        @Override
+        public int originalToTransformed(int offset, int strategy) {
+            final int insertOffset = getInsertOffset();
+            if (strategy == OffsetMapping.MAP_STRATEGY_CURSOR && offset == insertOffset) {
+                return offset;
+            }
+            if (offset < getInsertOffset()) {
+                return offset;
+            }
+            return offset + mInsertText.length();
+        }
+
+        @Override
+        public int transformedToOriginal(int offset, int strategy) {
+            final int insertOffset = getInsertOffset();
+            if (offset < insertOffset) {
+                return offset;
+            }
+            if (offset < insertOffset + mInsertText.length()) {
+                return insertOffset;
+            }
+            return offset - mInsertText.length();
+        }
+
+        @Override
+        public void originalToTransformed(TextUpdate textUpdate) {
+            final int insertOffset = getInsertOffset();
+            if (textUpdate.where <= insertOffset) {
+                if (textUpdate.where + textUpdate.before > insertOffset) {
+                    textUpdate.before += mInsertText.length();
+                    textUpdate.after += mInsertText.length();
+                }
+            } else {
+                textUpdate.where += mInsertText.length();
+            }
+        }
+
+        @Override
+        public int length() {
+            return mOriginal.length() + mInsertText.length();
+        }
+
+        @Override
+        public char charAt(int index) {
+            final int insertOffset = getInsertOffset();
+            if (index < insertOffset) {
+                return mOriginal.charAt(index);
+            }
+            if (index < insertOffset + mInsertText.length()) {
+                return mInsertText.charAt(index - insertOffset);
+            }
+            return mOriginal.charAt(index - mInsertText.length());
+        }
+
+        @Override
+        public CharSequence subSequence(int start, int end) {
+            StringBuilder stringBuilder = new StringBuilder();
+            for (int index = start; index < end; ++index) {
+                stringBuilder.append(charAt(index));
+            }
+            return stringBuilder.toString();
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder stringBuilder = new StringBuilder();
+            for (int index = 0; index < length(); ++index) {
+                stringBuilder.append(charAt(index));
+            }
+            return stringBuilder.toString();
+        }
+
+        static final NoCopySpan INSERT_POINT = new NoCopySpan() { };
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index cc4fbab..f5582d0 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -19,19 +19,30 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.Context;
 import android.graphics.Paint;
+import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.text.GetChars;
 import android.text.Layout;
 import android.text.PrecomputedText;
+import android.text.method.OffsetMapping;
+import android.text.method.TransformationMethod;
 import android.view.View;
 import android.view.inputmethod.EditorInfo;
 import android.widget.TextView.BufferType;
@@ -321,6 +332,94 @@
         assertEquals("hi", charWrapper.toString());
     }
 
+    @Test
+    @UiThreadTest
+    public void transformedToOriginal_noOffsetMapping() {
+        mTextView = new TextView(mActivity);
+        final String text = "Hello world";
+        mTextView.setText(text);
+        for (int offset = 0; offset < text.length(); ++offset) {
+            assertThat(mTextView.transformedToOriginal(offset, OffsetMapping.MAP_STRATEGY_CURSOR))
+                    .isEqualTo(offset);
+            assertThat(mTextView.transformedToOriginal(offset,
+                    OffsetMapping.MAP_STRATEGY_CHARACTER)).isEqualTo(offset);
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    public void originalToTransformed_noOffsetMapping() {
+        mTextView = new TextView(mActivity);
+        final String text = "Hello world";
+        mTextView.setText(text);
+        for (int offset = 0; offset < text.length(); ++offset) {
+            assertThat(mTextView.originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR))
+                    .isEqualTo(offset);
+            assertThat(mTextView.originalToTransformed(offset,
+                    OffsetMapping.MAP_STRATEGY_CHARACTER)).isEqualTo(offset);
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    public void originalToTransformed_hasOffsetMapping() {
+        mTextView = new TextView(mActivity);
+        final CharSequence text = "Hello world";
+        final TransformedText transformedText = mock(TransformedText.class);
+        when(transformedText.originalToTransformed(anyInt(), anyInt())).then((invocation) -> {
+            // plus 1 for character strategy and minus 1 for cursor strategy.
+            if ((int) invocation.getArgument(1) == OffsetMapping.MAP_STRATEGY_CHARACTER) {
+                return (int) invocation.getArgument(0) + 1;
+            }
+            return (int) invocation.getArgument(0) - 1;
+        });
+
+        final TransformationMethod transformationMethod =
+                new TestTransformationMethod(transformedText);
+        mTextView.setText(text);
+        mTextView.setTransformationMethod(transformationMethod);
+
+        assertThat(mTextView.originalToTransformed(1, OffsetMapping.MAP_STRATEGY_CHARACTER))
+                .isEqualTo(2);
+        verify(transformedText, times(1))
+                .originalToTransformed(1, OffsetMapping.MAP_STRATEGY_CHARACTER);
+
+        assertThat(mTextView.originalToTransformed(1, OffsetMapping.MAP_STRATEGY_CURSOR))
+                .isEqualTo(0);
+        verify(transformedText, times(1))
+                .originalToTransformed(1, OffsetMapping.MAP_STRATEGY_CURSOR);
+    }
+
+    @Test
+    @UiThreadTest
+    public void transformedToOriginal_hasOffsetMapping() {
+        mTextView = new TextView(mActivity);
+        final CharSequence text = "Hello world";
+        final TransformedText transformedText = mock(TransformedText.class);
+        when(transformedText.transformedToOriginal(anyInt(), anyInt())).then((invocation) -> {
+            // plus 1 for character strategy and minus 1 for cursor strategy.
+            if ((int) invocation.getArgument(1) == OffsetMapping.MAP_STRATEGY_CHARACTER) {
+                return (int) invocation.getArgument(0) + 1;
+            }
+            return (int) invocation.getArgument(0) - 1;
+        });
+
+        final TransformationMethod transformationMethod =
+                new TestTransformationMethod(transformedText);
+        mTextView.setText(text);
+        mTextView.setTransformationMethod(transformationMethod);
+
+        assertThat(mTextView.transformedToOriginal(1, OffsetMapping.MAP_STRATEGY_CHARACTER))
+                .isEqualTo(2);
+        verify(transformedText, times(1))
+                .transformedToOriginal(1, OffsetMapping.MAP_STRATEGY_CHARACTER);
+
+        assertThat(mTextView.transformedToOriginal(1, OffsetMapping.MAP_STRATEGY_CURSOR))
+                .isEqualTo(0);
+        verify(transformedText, times(1))
+                .transformedToOriginal(1, OffsetMapping.MAP_STRATEGY_CURSOR);
+    }
+
     private String createLongText() {
         int size = 600 * 1000;
         final StringBuilder builder = new StringBuilder(size);
@@ -351,4 +450,24 @@
             }
         }
     }
+
+    private interface TransformedText extends OffsetMapping, CharSequence { }
+
+    private static class TestTransformationMethod implements TransformationMethod {
+        private final CharSequence mTransformedText;
+
+        TestTransformationMethod(CharSequence transformedText) {
+            this.mTransformedText = transformedText;
+        }
+
+        @Override
+        public CharSequence getTransformation(CharSequence source, View view) {
+            return mTransformedText;
+        }
+
+        @Override
+        public void onFocusChanged(View view, CharSequence sourceText, boolean focused,
+                int direction, Rect previouslyFocusedRect) {
+        }
+    }
 }
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 5040a86..bf7f3bd 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -454,6 +454,7 @@
         <!-- Permission required for hotword detection service CTS tests -->
         <permission name="android.permission.MANAGE_HOTWORD_DETECTION" />
         <permission name="android.permission.BIND_HOTWORD_DETECTION_SERVICE" />
+        <permission name="android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE" />
         <permission name="android.permission.MANAGE_APP_HIBERNATION"/>
         <!-- Permission required for CTS test - ResourceObserverNativeTest -->
         <permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" />
@@ -464,6 +465,7 @@
         <!-- Permission required for CTS test - SystemMediaRouter2Test -->
         <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
         <permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
+        <permission name="android.permission.MODIFY_AUDIO_SYSTEM_SETTINGS"/>
         <!-- Permission required for CTS test - CallAudioInterceptionTest -->
         <permission name="android.permission.CALL_AUDIO_INTERCEPTION"/>
         <!-- Permission required for CTS test - CtsPermission5TestCases -->
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index 07d0158..c5d932e 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -79,6 +79,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+import androidx.window.extensions.core.util.function.Function;
 import androidx.window.extensions.layout.WindowLayoutComponentImpl;
 import androidx.window.extensions.layout.WindowLayoutInfo;
 
@@ -563,10 +564,10 @@
                                 SplitAttributes.SplitType.RatioSplitType.splitEqually()
                         )
                 ).build();
+        final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
+                params -> splitAttributes;
 
-        mController.setSplitAttributesCalculator(params -> {
-            return splitAttributes;
-        });
+        mController.setSplitAttributesCalculator(calculator);
 
         assertEquals(splitAttributes, mPresenter.computeSplitAttributes(taskProperties,
                 splitPairRule, null /* minDimensionsPair */));
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 5de5365..cddbf469 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 8121b20..e85b3c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -232,10 +232,13 @@
 
                     if (mBubble.isAppBubble()) {
                         PendingIntent pi = PendingIntent.getActivity(mContext, 0,
-                                mBubble.getAppBubbleIntent(),
-                                PendingIntent.FLAG_MUTABLE,
+                                mBubble.getAppBubbleIntent()
+                                        .addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+                                        .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
+                                PendingIntent.FLAG_IMMUTABLE,
                                 null);
-                        mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
+                        mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
+                                launchBounds);
                     } else if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
                         options.setApplyActivityFlagsForBubbles(true);
                         mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index d0aef20..9edfffc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -384,7 +384,7 @@
                 @Nullable ImeTracker.Token statsToken) {
             final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
             if (imeSource == null || mImeSourceControl == null) {
-                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
+                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
                 return;
             }
             final Rect newFrame = imeSource.getFrame();
@@ -407,7 +407,8 @@
             }
             if ((!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show))
                     || (mAnimationDirection == DIRECTION_HIDE && !show)) {
-                ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
+                ImeTracker.forLogging().onCancelled(
+                        statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
                 return;
             }
             boolean seek = false;
@@ -451,7 +452,7 @@
                 mTransactionPool.release(t);
             });
             mAnimation.setInterpolator(INTERPOLATOR);
-            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
+            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
             mAnimation.addListener(new AnimatorListenerAdapter() {
                 private boolean mCancelled = false;
                 @Nullable
@@ -474,7 +475,7 @@
                             : 1.f;
                     t.setAlpha(mImeSourceControl.getLeash(), alpha);
                     if (mAnimationDirection == DIRECTION_SHOW) {
-                        ImeTracker.get().onProgress(mStatsToken,
+                        ImeTracker.forLogging().onProgress(mStatsToken,
                                 ImeTracker.PHASE_WM_ANIMATION_RUNNING);
                         t.show(mImeSourceControl.getLeash());
                     }
@@ -511,15 +512,15 @@
                     }
                     dispatchEndPositioning(mDisplayId, mCancelled, t);
                     if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
-                        ImeTracker.get().onProgress(mStatsToken,
+                        ImeTracker.forLogging().onProgress(mStatsToken,
                                 ImeTracker.PHASE_WM_ANIMATION_RUNNING);
                         t.hide(mImeSourceControl.getLeash());
                         removeImeSurface();
-                        ImeTracker.get().onHidden(mStatsToken);
+                        ImeTracker.forLogging().onHidden(mStatsToken);
                     } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) {
-                        ImeTracker.get().onShown(mStatsToken);
+                        ImeTracker.forLogging().onShown(mStatsToken);
                     } else if (mCancelled) {
-                        ImeTracker.get().onCancelled(mStatsToken,
+                        ImeTracker.forLogging().onCancelled(mStatsToken,
                                 ImeTracker.PHASE_WM_ANIMATION_RUNNING);
                     }
                     if (DEBUG_IME_VISIBILITY) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 8759301..9bdda14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -162,10 +162,12 @@
                 @Nullable ImeTracker.Token statsToken) {
             CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
             if (listeners == null) {
-                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+                ImeTracker.forLogging().onFailed(
+                        statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
                 return;
             }
-            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+            ImeTracker.forLogging().onProgress(
+                    statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
             for (OnInsetsChangedListener listener : listeners) {
                 listener.showInsets(types, fromIme, statsToken);
             }
@@ -175,10 +177,12 @@
                 @Nullable ImeTracker.Token statsToken) {
             CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
             if (listeners == null) {
-                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+                ImeTracker.forLogging().onFailed(
+                        statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
                 return;
             }
-            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+            ImeTracker.forLogging().onProgress(
+                    statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
             for (OnInsetsChangedListener listener : listeners) {
                 listener.hideInsets(types, fromIme, statsToken);
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index fcbf9e0..fa3a6ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -78,6 +78,7 @@
     private final Rect mResizingBounds = new Rect();
     private final Rect mTempRect = new Rect();
     private ValueAnimator mFadeAnimator;
+    private ValueAnimator mScreenshotAnimator;
 
     private int mIconSize;
     private int mOffsetX;
@@ -135,8 +136,17 @@
 
     /** Releases the surfaces for split decor. */
     public void release(SurfaceControl.Transaction t) {
-        if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
-            mFadeAnimator.cancel();
+        if (mFadeAnimator != null) {
+            if (mFadeAnimator.isRunning()) {
+                mFadeAnimator.cancel();
+            }
+            mFadeAnimator = null;
+        }
+        if (mScreenshotAnimator != null) {
+            if (mScreenshotAnimator.isRunning()) {
+                mScreenshotAnimator.cancel();
+            }
+            mScreenshotAnimator = null;
         }
         if (mViewHost != null) {
             mViewHost.release();
@@ -238,16 +248,20 @@
     /** Stops showing resizing hint. */
     public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
         if (mScreenshot != null) {
+            if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+                mScreenshotAnimator.cancel();
+            }
+
             t.setPosition(mScreenshot, mOffsetX, mOffsetY);
 
             final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
-            final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
-            va.addUpdateListener(valueAnimator -> {
+            mScreenshotAnimator = ValueAnimator.ofFloat(1, 0);
+            mScreenshotAnimator.addUpdateListener(valueAnimator -> {
                 final float progress = (float) valueAnimator.getAnimatedValue();
                 animT.setAlpha(mScreenshot, progress);
                 animT.apply();
             });
-            va.addListener(new AnimatorListenerAdapter() {
+            mScreenshotAnimator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationStart(Animator animation) {
                     mRunningAnimationCount++;
@@ -266,7 +280,7 @@
                     }
                 }
             });
-            va.start();
+            mScreenshotAnimator.start();
         }
 
         if (mResizingIconView == null) {
@@ -292,9 +306,6 @@
                 });
                 return;
             }
-
-            // If fade-in animation is running, cancel it and re-run fade-out one.
-            mFadeAnimator.cancel();
         }
         if (mShown) {
             fadeOutDecor(animFinishedCallback);
@@ -332,6 +343,11 @@
      * directly. */
     public void fadeOutDecor(Runnable finishedCallback) {
         if (mShown) {
+            // If previous animation is running, just cancel it.
+            if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
+                mFadeAnimator.cancel();
+            }
+
             startFadeAnimation(false /* show */, true, finishedCallback);
             mShown = false;
         } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
index b189163..8d4a384 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
@@ -125,7 +125,9 @@
             cancelScheduledPlacement();
             applyPlacement(placement, shouldStash, animationDuration);
         } else {
-            applyPlacementBounds(mCurrentPlacementBounds, animationDuration);
+            if (mCurrentPlacementBounds != null) {
+                applyPlacementBounds(mCurrentPlacementBounds, animationDuration);
+            }
             schedulePinnedStackPlacement(placement, animationDuration);
         }
     }
@@ -188,7 +190,7 @@
         applyPlacementBounds(bounds, animationDuration);
     }
 
-    void onPipDismissed() {
+    void reset() {
         mCurrentPlacementBounds = null;
         mPipTargetBounds = null;
         cancelScheduledPlacement();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index fd4fcff..4426423 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -450,18 +450,6 @@
         mPipMediaController.registerSessionListenerForCurrentUser();
     }
 
-    private void checkIfPinnedTaskAppeared() {
-        final TaskInfo pinnedTask = getPinnedTaskInfo();
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: checkIfPinnedTaskAppeared(), task=%s", TAG, pinnedTask);
-        if (pinnedTask == null || pinnedTask.topActivity == null) return;
-        mPinnedTaskId = pinnedTask.taskId;
-
-        mPipMediaController.onActivityPinned();
-        mActionBroadcastReceiver.register();
-        mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
-    }
-
     private void checkIfPinnedTaskIsGone() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: onTaskStackChanged()", TAG);
@@ -482,7 +470,7 @@
 
         mTvPipMenuController.closeMenu();
         mTvPipBoundsState.resetTvPipState();
-        mTvPipBoundsController.onPipDismissed();
+        mTvPipBoundsController.reset();
         setState(STATE_NO_PIP);
         mPinnedTaskId = NONEXISTENT_TASK_ID;
     }
@@ -537,7 +525,16 @@
         taskStackListener.addListener(new TaskStackListenerCallback() {
             @Override
             public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
-                checkIfPinnedTaskAppeared();
+                final TaskInfo pinnedTask = getPinnedTaskInfo();
+                ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: onActivityPinned(), task=%s", TAG, pinnedTask);
+                if (pinnedTask == null || pinnedTask.topActivity == null) return;
+                mPinnedTaskId = pinnedTask.taskId;
+
+                mPipMediaController.onActivityPinned();
+                mActionBroadcastReceiver.register();
+                mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
+                mTvPipBoundsController.reset();
                 mAppOpsListener.onActivityPinned(packageName);
             }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 1488469..a6c4ac2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1072,6 +1072,7 @@
             mSideStage.removeAllTasks(wct, false /* toTop */);
             mMainStage.deactivate(wct, false /* toTop */);
             wct.reorder(mRootTaskInfo.token, false /* onTop */);
+            wct.setForceTranslucent(mRootTaskInfo.token, true);
             wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
             onTransitionAnimationComplete();
         } else {
@@ -1103,6 +1104,7 @@
                     mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
                     mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
                     finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
+                    finishedWCT.setForceTranslucent(mRootTaskInfo.token, true);
                     finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
                     mSyncQueue.queue(finishedWCT);
                     mSyncQueue.runInSync(at -> {
@@ -1485,6 +1487,12 @@
     }
 
     private void onStageVisibilityChanged(StageListenerImpl stageListener) {
+        // If split didn't active, just ignore this callback because we should already did these
+        // on #applyExitSplitScreen.
+        if (!isSplitActive()) {
+            return;
+        }
+
         final boolean sideStageVisible = mSideStageListener.mVisible;
         final boolean mainStageVisible = mMainStageListener.mVisible;
 
@@ -1493,20 +1501,23 @@
             return;
         }
 
+        // Check if it needs to dismiss split screen when both stage invisible.
+        if (!mainStageVisible && mExitSplitScreenOnHide) {
+            exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
+            return;
+        }
+
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (!mainStageVisible) {
+            // Split entering background.
             wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
                     true /* setReparentLeafTaskIfRelaunch */);
             wct.setForceTranslucent(mRootTaskInfo.token, true);
-            // Both stages are not visible, check if it needs to dismiss split screen.
-            if (mExitSplitScreenOnHide) {
-                exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
-            }
         } else {
             wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
                     false /* setReparentLeafTaskIfRelaunch */);
-            wct.setForceTranslucent(mRootTaskInfo.token, false);
         }
+
         mSyncQueue.queue(wct);
         mSyncQueue.runInSync(t -> {
             setDividerVisibility(mainStageVisible, t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java
index 5c45527..04ae2f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java
@@ -24,12 +24,12 @@
 
 /**
  * Algorithm for determining the type of a new starting window on Android TV.
- * For now we always show empty splash screens on Android TV.
+ * For now we do not want to show any splash screens on Android TV.
  */
 public class TvStartingWindowTypeAlgorithm implements StartingWindowTypeAlgorithm {
     @Override
     public int getSuggestedWindowType(StartingWindowInfo windowInfo) {
-        // For now we want to always show empty splash screens on TV.
+        // For now we do not want to show any splash screens on TV.
         return STARTING_WINDOW_TYPE_NONE;
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 1e72c56..129924a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -136,7 +136,7 @@
     }
 
     private void setupCaptionColor(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
-        int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
+        final int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
         decoration.setCaptionColor(statusBarColor);
     }
 
@@ -152,7 +152,7 @@
             SurfaceControl taskSurface,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+        final CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
         if (oldDecoration != null) {
             // close the old decoration if it exists to avoid two window decorations being added
             oldDecoration.close();
@@ -169,9 +169,9 @@
                         mSyncQueue);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
-        TaskPositioner taskPositioner =
+        final TaskPositioner taskPositioner =
                 new TaskPositioner(mTaskOrganizer, windowDecoration);
-        CaptionTouchEventListener touchEventListener =
+        final CaptionTouchEventListener touchEventListener =
                 new CaptionTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
         windowDecoration.setDragResizeCallback(taskPositioner);
@@ -221,11 +221,11 @@
             if (e.getAction() != MotionEvent.ACTION_DOWN) {
                 return false;
             }
-            RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+            final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
             if (taskInfo.isFocused) {
                 return false;
             }
-            WindowContainerTransaction wct = new WindowContainerTransaction();
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
             wct.reorder(mTaskToken, true /* onTop */);
             mSyncQueue.queue(wct);
             return true;
@@ -236,7 +236,7 @@
          * @return {@code true} if a drag is happening; or {@code false} if it is not
          */
         private void handleEventForMove(MotionEvent e) {
-            RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+            final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
             if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
                 return;
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 8609c6b..d26f1fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -48,15 +48,13 @@
     private View.OnClickListener mOnCaptionButtonClickListener;
     private View.OnTouchListener mOnCaptionTouchListener;
     private DragResizeCallback mDragResizeCallback;
-
     private DragResizeInputListener mDragResizeListener;
+    private final DragDetector mDragDetector;
 
     private RelayoutParams mRelayoutParams = new RelayoutParams();
     private final RelayoutResult<WindowDecorLinearLayout> mResult =
             new RelayoutResult<>();
 
-    private DragDetector mDragDetector;
-
     CaptionWindowDecoration(
             Context context,
             DisplayController displayController,
@@ -104,14 +102,14 @@
                 taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
         final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
 
-        WindowDecorLinearLayout oldRootView = mResult.mRootView;
+        final WindowDecorLinearLayout oldRootView = mResult.mRootView;
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
-        int outsetLeftId = R.dimen.freeform_resize_handle;
-        int outsetTopId = R.dimen.freeform_resize_handle;
-        int outsetRightId = R.dimen.freeform_resize_handle;
-        int outsetBottomId = R.dimen.freeform_resize_handle;
+        final int outsetLeftId = R.dimen.freeform_resize_handle;
+        final int outsetTopId = R.dimen.freeform_resize_handle;
+        final int outsetRightId = R.dimen.freeform_resize_handle;
+        final int outsetBottomId = R.dimen.freeform_resize_handle;
 
         mRelayoutParams.reset();
         mRelayoutParams.mRunningTaskInfo = taskInfo;
@@ -123,7 +121,7 @@
         }
 
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
-        taskInfo = null; // Clear it just in case we use it accidentally
+        // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
 
         mTaskOrganizer.applyTransaction(wct);
 
@@ -152,12 +150,13 @@
                     mDragResizeCallback);
         }
 
-        int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
+        final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
+                .getScaledTouchSlop();
         mDragDetector.setTouchSlop(touchSlop);
 
-        int resize_handle = mResult.mRootView.getResources()
+        final int resize_handle = mResult.mRootView.getResources()
                 .getDimensionPixelSize(R.dimen.freeform_resize_handle);
-        int resize_corner = mResult.mRootView.getResources()
+        final int resize_corner = mResult.mRootView.getResources()
                 .getDimensionPixelSize(R.dimen.freeform_resize_corner);
         mDragResizeListener.setGeometry(
                 mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
@@ -167,15 +166,15 @@
      * Sets up listeners when a new root view is created.
      */
     private void setupRootView() {
-        View caption = mResult.mRootView.findViewById(R.id.caption);
+        final View caption = mResult.mRootView.findViewById(R.id.caption);
         caption.setOnTouchListener(mOnCaptionTouchListener);
-        View close = caption.findViewById(R.id.close_window);
+        final View close = caption.findViewById(R.id.close_window);
         close.setOnClickListener(mOnCaptionButtonClickListener);
-        View back = caption.findViewById(R.id.back_button);
+        final View back = caption.findViewById(R.id.back_button);
         back.setOnClickListener(mOnCaptionButtonClickListener);
-        View minimize = caption.findViewById(R.id.minimize_window);
+        final View minimize = caption.findViewById(R.id.minimize_window);
         minimize.setOnClickListener(mOnCaptionButtonClickListener);
-        View maximize = caption.findViewById(R.id.maximize_window);
+        final View maximize = caption.findViewById(R.id.maximize_window);
         maximize.setOnClickListener(mOnCaptionButtonClickListener);
     }
 
@@ -184,31 +183,31 @@
             return;
         }
 
-        View caption = mResult.mRootView.findViewById(R.id.caption);
-        GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
+        final View caption = mResult.mRootView.findViewById(R.id.caption);
+        final GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
         captionDrawable.setColor(captionColor);
 
-        int buttonTintColorRes =
+        final int buttonTintColorRes =
                 Color.valueOf(captionColor).luminance() < 0.5
                         ? R.color.decor_button_light_color
                         : R.color.decor_button_dark_color;
-        ColorStateList buttonTintColor =
+        final ColorStateList buttonTintColor =
                 caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
 
-        View back = caption.findViewById(R.id.back_button);
-        VectorDrawable backBackground = (VectorDrawable) back.getBackground();
+        final View back = caption.findViewById(R.id.back_button);
+        final VectorDrawable backBackground = (VectorDrawable) back.getBackground();
         backBackground.setTintList(buttonTintColor);
 
-        View minimize = caption.findViewById(R.id.minimize_window);
-        VectorDrawable minimizeBackground = (VectorDrawable) minimize.getBackground();
+        final View minimize = caption.findViewById(R.id.minimize_window);
+        final VectorDrawable minimizeBackground = (VectorDrawable) minimize.getBackground();
         minimizeBackground.setTintList(buttonTintColor);
 
-        View maximize = caption.findViewById(R.id.maximize_window);
-        VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground();
+        final View maximize = caption.findViewById(R.id.maximize_window);
+        final VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground();
         maximizeBackground.setTintList(buttonTintColor);
 
-        View close = caption.findViewById(R.id.close_window);
-        VectorDrawable closeBackground = (VectorDrawable) close.getBackground();
+        final View close = caption.findViewById(R.id.close_window);
+        final VectorDrawable closeBackground = (VectorDrawable) close.getBackground();
         closeBackground.setTintList(buttonTintColor);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 13b4a95..2863adc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -68,9 +68,8 @@
     private final Choreographer mMainChoreographer;
     private final DisplayController mDisplayController;
     private final SyncTransactionQueue mSyncQueue;
-    private FreeformTaskTransitionStarter mTransitionStarter;
-    private Optional<DesktopModeController> mDesktopModeController;
-    private Optional<DesktopTasksController> mDesktopTasksController;
+    private final Optional<DesktopModeController> mDesktopModeController;
+    private final Optional<DesktopTasksController> mDesktopTasksController;
     private boolean mTransitionDragActive;
 
     private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -78,7 +77,7 @@
     private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId =
             new SparseArray<>();
     private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
-    private InputMonitorFactory mInputMonitorFactory;
+    private final InputMonitorFactory mInputMonitorFactory;
     private TaskOperations mTaskOperations;
 
     public DesktopModeWindowDecorViewModel(
@@ -199,7 +198,7 @@
         if (decoration == null) return;
 
         decoration.close();
-        int displayId = taskInfo.displayId;
+        final int displayId = taskInfo.displayId;
         if (mEventReceiversByDisplay.contains(displayId)) {
             removeTaskFromEventReceiver(displayId);
         }
@@ -227,7 +226,7 @@
 
         @Override
         public void onClick(View v) {
-            DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+            final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
             final int id = v.getId();
             if (id == R.id.close_window) {
                 mTaskOperations.closeTask(mTaskToken);
@@ -250,7 +249,7 @@
         @Override
         public boolean onTouch(View v, MotionEvent e) {
             boolean isDrag = false;
-            int id = v.getId();
+            final int id = v.getId();
             if (id != R.id.caption_handle && id != R.id.desktop_mode_caption) {
                 return false;
             }
@@ -261,11 +260,11 @@
             if (e.getAction() != MotionEvent.ACTION_DOWN) {
                 return isDrag;
             }
-            RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+            final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
             if (taskInfo.isFocused) {
                 return isDrag;
             }
-            WindowContainerTransaction wct = new WindowContainerTransaction();
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
             wct.reorder(mTaskToken, true /* onTop */);
             mSyncQueue.queue(wct);
             return true;
@@ -276,7 +275,7 @@
          * @return {@code true} if a drag is happening; or {@code false} if it is not
          */
         private void handleEventForMove(MotionEvent e) {
-            RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+            final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
             if (DesktopModeStatus.isProto2Enabled()
                     && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
                 return;
@@ -295,16 +294,16 @@
                     break;
                 }
                 case MotionEvent.ACTION_MOVE: {
-                    int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+                    final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     mDragResizeCallback.onDragResizeMove(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     break;
                 }
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
-                    int dragPointerIdx = e.findPointerIndex(mDragPointerId);
-                    int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
-                            .stableInsets().top;
+                    final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+                    final int statusBarHeight = mDisplayController
+                            .getDisplayLayout(taskInfo.displayId).stableInsets().top;
                     mDragResizeCallback.onDragResizeEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
@@ -378,7 +377,7 @@
      */
     private void incrementEventReceiverTasks(int displayId) {
         if (mEventReceiversByDisplay.contains(displayId)) {
-            EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+            final EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
             eventReceiver.incrementTaskNumber();
         } else {
             createInputChannel(displayId);
@@ -388,7 +387,7 @@
     // If all tasks on this display are gone, we don't need to monitor its input.
     private void removeTaskFromEventReceiver(int displayId) {
         if (!mEventReceiversByDisplay.contains(displayId)) return;
-        EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+        final EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
         if (eventReceiver == null) return;
         eventReceiver.decrementTaskNumber();
         if (eventReceiver.getTasksOnDisplay() == 0) {
@@ -403,7 +402,7 @@
      */
     private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
         if (DesktopModeStatus.isProto2Enabled()) {
-            DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
+            final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
             if (focusedDecor == null
                     || focusedDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
                 handleCaptionThroughStatusBar(ev);
@@ -428,9 +427,9 @@
 
     // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
     private void handleEventOutsideFocusedCaption(MotionEvent ev) {
-        int action = ev.getActionMasked();
+        final int action = ev.getActionMasked();
         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
-            DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
+            final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
             if (focusedDecor == null) {
                 return;
             }
@@ -450,7 +449,7 @@
         switch (ev.getActionMasked()) {
             case MotionEvent.ACTION_DOWN: {
                 // Begin drag through status bar if applicable.
-                DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
+                final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
                 if (focusedDecor != null) {
                     boolean dragFromStatusBarAllowed = false;
                     if (DesktopModeStatus.isProto2Enabled()) {
@@ -469,14 +468,14 @@
                 break;
             }
             case MotionEvent.ACTION_UP: {
-                DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
+                final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
                 if (focusedDecor == null) {
                     mTransitionDragActive = false;
                     return;
                 }
                 if (mTransitionDragActive) {
                     mTransitionDragActive = false;
-                    int statusBarHeight = mDisplayController
+                    final int statusBarHeight = mDisplayController
                             .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
                     if (ev.getY() > statusBarHeight) {
                         if (DesktopModeStatus.isProto2Enabled()) {
@@ -500,10 +499,10 @@
 
     @Nullable
     private DesktopModeWindowDecoration getFocusedDecor() {
-        int size = mWindowDecorByTaskId.size();
+        final int size = mWindowDecorByTaskId.size();
         DesktopModeWindowDecoration focusedDecor = null;
         for (int i = 0; i < size; i++) {
-            DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+            final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
             if (decor != null && decor.isFocused()) {
                 focusedDecor = decor;
                 break;
@@ -513,16 +512,16 @@
     }
 
     private void createInputChannel(int displayId) {
-        InputManager inputManager = InputManager.getInstance();
-        InputMonitor inputMonitor =
+        final InputManager inputManager = InputManager.getInstance();
+        final InputMonitor inputMonitor =
                 mInputMonitorFactory.create(inputManager, mContext);
-        EventReceiver eventReceiver = new EventReceiver(inputMonitor,
+        final EventReceiver eventReceiver = new EventReceiver(inputMonitor,
                 inputMonitor.getInputChannel(), Looper.myLooper());
         mEventReceiversByDisplay.put(displayId, eventReceiver);
     }
 
     private void disposeInputChannel(int displayId) {
-        EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId);
+        final EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId);
         if (eventReceiver != null) {
             eventReceiver.dispose();
         }
@@ -541,7 +540,7 @@
             SurfaceControl taskSurface,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        DesktopModeWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+        final DesktopModeWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
         if (oldDecoration != null) {
             // close the old decoration if it exists to avoid two window decorations being added
             oldDecoration.close();
@@ -558,9 +557,9 @@
                         mSyncQueue);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
-        TaskPositioner taskPositioner =
+        final TaskPositioner taskPositioner =
                 new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener);
-        DesktopModeTouchEventListener touchEventListener =
+        final DesktopModeTouchEventListener touchEventListener =
                 new DesktopModeTouchEventListener(
                         taskInfo, taskPositioner, windowDecoration.getDragDetector());
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 9c2beb9..1a38d24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -56,17 +56,14 @@
     private View.OnClickListener mOnCaptionButtonClickListener;
     private View.OnTouchListener mOnCaptionTouchListener;
     private DragResizeCallback mDragResizeCallback;
-
     private DragResizeInputListener mDragResizeListener;
+    private final DragDetector mDragDetector;
 
     private RelayoutParams mRelayoutParams = new RelayoutParams();
     private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
             new WindowDecoration.RelayoutResult<>();
 
     private boolean mDesktopActive;
-
-    private DragDetector mDragDetector;
-
     private AdditionalWindow mHandleMenu;
 
     DesktopModeWindowDecoration(
@@ -121,14 +118,14 @@
                 taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
         final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
 
-        WindowDecorLinearLayout oldRootView = mResult.mRootView;
+        final WindowDecorLinearLayout oldRootView = mResult.mRootView;
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
-        int outsetLeftId = R.dimen.freeform_resize_handle;
-        int outsetTopId = R.dimen.freeform_resize_handle;
-        int outsetRightId = R.dimen.freeform_resize_handle;
-        int outsetBottomId = R.dimen.freeform_resize_handle;
+        final int outsetLeftId = R.dimen.freeform_resize_handle;
+        final int outsetTopId = R.dimen.freeform_resize_handle;
+        final int outsetRightId = R.dimen.freeform_resize_handle;
+        final int outsetBottomId = R.dimen.freeform_resize_handle;
 
         mRelayoutParams.reset();
         mRelayoutParams.mRunningTaskInfo = taskInfo;
@@ -152,7 +149,7 @@
         mRelayoutParams.setCaptionPosition(captionLeft, captionTop);
 
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
-        taskInfo = null; // Clear it just in case we use it accidentally
+        // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
 
         mTaskOrganizer.applyTransaction(wct);
 
@@ -197,12 +194,13 @@
                     mDragResizeCallback);
         }
 
-        int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
+        final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
+                .getScaledTouchSlop();
         mDragDetector.setTouchSlop(touchSlop);
 
-        int resize_handle = mResult.mRootView.getResources()
+        final int resize_handle = mResult.mRootView.getResources()
                 .getDimensionPixelSize(R.dimen.freeform_resize_handle);
-        int resize_corner = mResult.mRootView.getResources()
+        final int resize_corner = mResult.mRootView.getResources()
                 .getDimensionPixelSize(R.dimen.freeform_resize_corner);
         mDragResizeListener.setGeometry(
                 mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
@@ -212,27 +210,27 @@
      * Sets up listeners when a new root view is created.
      */
     private void setupRootView() {
-        View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
+        final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
         caption.setOnTouchListener(mOnCaptionTouchListener);
-        View close = caption.findViewById(R.id.close_window);
+        final View close = caption.findViewById(R.id.close_window);
         close.setOnClickListener(mOnCaptionButtonClickListener);
-        View back = caption.findViewById(R.id.back_button);
+        final View back = caption.findViewById(R.id.back_button);
         back.setOnClickListener(mOnCaptionButtonClickListener);
-        View handle = caption.findViewById(R.id.caption_handle);
+        final View handle = caption.findViewById(R.id.caption_handle);
         handle.setOnTouchListener(mOnCaptionTouchListener);
         handle.setOnClickListener(mOnCaptionButtonClickListener);
         updateButtonVisibility();
     }
 
     private void setupHandleMenu() {
-        View menu = mHandleMenu.mWindowViewHost.getView();
-        View fullscreen = menu.findViewById(R.id.fullscreen_button);
+        final View menu = mHandleMenu.mWindowViewHost.getView();
+        final View fullscreen = menu.findViewById(R.id.fullscreen_button);
         fullscreen.setOnClickListener(mOnCaptionButtonClickListener);
-        View desktop = menu.findViewById(R.id.desktop_button);
+        final View desktop = menu.findViewById(R.id.desktop_button);
         desktop.setOnClickListener(mOnCaptionButtonClickListener);
-        View split = menu.findViewById(R.id.split_screen_button);
+        final View split = menu.findViewById(R.id.split_screen_button);
         split.setOnClickListener(mOnCaptionButtonClickListener);
-        View more = menu.findViewById(R.id.more_button);
+        final View more = menu.findViewById(R.id.more_button);
         more.setOnClickListener(mOnCaptionButtonClickListener);
     }
 
@@ -242,8 +240,8 @@
      * @param visible whether or not the caption should be visible
      */
     private void setCaptionVisibility(boolean visible) {
-        int v = visible ? View.VISIBLE : View.GONE;
-        View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
+        final int v = visible ? View.VISIBLE : View.GONE;
+        final View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
         captionView.setVisibility(v);
         if (!visible) closeHandleMenu();
     }
@@ -264,19 +262,19 @@
      * Show or hide buttons
      */
     void setButtonVisibility(boolean visible) {
-        int visibility = visible ? View.VISIBLE : View.GONE;
-        View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
-        View back = caption.findViewById(R.id.back_button);
-        View close = caption.findViewById(R.id.close_window);
+        final int visibility = visible ? View.VISIBLE : View.GONE;
+        final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
+        final View back = caption.findViewById(R.id.back_button);
+        final View close = caption.findViewById(R.id.close_window);
         back.setVisibility(visibility);
         close.setVisibility(visibility);
-        int buttonTintColorRes =
+        final int buttonTintColorRes =
                 mDesktopActive ? R.color.decor_button_dark_color
                         : R.color.decor_button_light_color;
-        ColorStateList buttonTintColor =
+        final ColorStateList buttonTintColor =
                 caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
-        View handle = caption.findViewById(R.id.caption_handle);
-        VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
+        final View handle = caption.findViewById(R.id.caption_handle);
+        final VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
         handleBackground.setTintList(buttonTintColor);
         caption.getBackground().setTint(visible ? Color.WHITE : Color.TRANSPARENT);
     }
@@ -297,12 +295,12 @@
      * Create and display handle menu window
      */
     void createHandleMenu() {
-        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         final Resources resources = mDecorWindowContext.getResources();
-        int x = mRelayoutParams.mCaptionX;
-        int y = mRelayoutParams.mCaptionY;
-        int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
-        int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+        final int x = mRelayoutParams.mCaptionX;
+        final int y = mRelayoutParams.mCaptionY;
+        final int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+        final int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
         String namePrefix = "Caption Menu";
         mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t,
                 x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY,
@@ -353,8 +351,8 @@
      * @return the point of the input in local space
      */
     private PointF offsetCaptionLocation(MotionEvent ev) {
-        PointF result = new PointF(ev.getX(), ev.getY());
-        Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
+        final PointF result = new PointF(ev.getX(), ev.getY());
+        final Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
                 .positionInParent;
         result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
         result.offset(-positionInParent.x, -positionInParent.y);
@@ -370,8 +368,8 @@
      */
     private boolean checkEventInCaptionView(MotionEvent ev, int layoutId) {
         if (mResult.mRootView == null) return false;
-        PointF inputPoint = offsetCaptionLocation(ev);
-        View view = mResult.mRootView.findViewById(layoutId);
+        final PointF inputPoint = offsetCaptionLocation(ev);
+        final View view = mResult.mRootView.findViewById(layoutId);
         return view != null && view.pointInView(inputPoint.x, inputPoint.y, 0);
     }
 
@@ -389,20 +387,20 @@
      */
     void checkClickEvent(MotionEvent ev) {
         if (mResult.mRootView == null) return;
-        View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
-        PointF inputPoint = offsetCaptionLocation(ev);
+        final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
+        final PointF inputPoint = offsetCaptionLocation(ev);
         if (!isHandleMenuActive()) {
-            View handle = caption.findViewById(R.id.caption_handle);
+            final View handle = caption.findViewById(R.id.caption_handle);
             clickIfPointInView(inputPoint, handle);
         } else {
-            View menu = mHandleMenu.mWindowViewHost.getView();
-            View fullscreen = menu.findViewById(R.id.fullscreen_button);
+            final View menu = mHandleMenu.mWindowViewHost.getView();
+            final View fullscreen = menu.findViewById(R.id.fullscreen_button);
             if (clickIfPointInView(inputPoint, fullscreen)) return;
-            View desktop = menu.findViewById(R.id.desktop_button);
+            final View desktop = menu.findViewById(R.id.desktop_button);
             if (clickIfPointInView(inputPoint, desktop)) return;
-            View split = menu.findViewById(R.id.split_screen_button);
+            final View split = menu.findViewById(R.id.split_screen_button);
             if (clickIfPointInView(inputPoint, split)) return;
-            View more = menu.findViewById(R.id.more_button);
+            final View more = menu.findViewById(R.id.more_button);
             clickIfPointInView(inputPoint, more);
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 27fc381a..65923ff 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -22,6 +22,10 @@
         <!-- Ensure output directory is empty at the start -->
         <option name="run-command" value="rm -rf /sdcard/flicker" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
+        <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
+    </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
         <option name="test-file-name" value="WMShellFlickerTests.apk"/>
diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java
index 4e2ce91..77fa9dc 100644
--- a/media/java/android/media/AudioDeviceVolumeManager.java
+++ b/media/java/android/media/AudioDeviceVolumeManager.java
@@ -16,6 +16,7 @@
 
 package android.media;
 
+import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -318,8 +319,10 @@
      * @param ada the device for which volume is to be modified
      */
     @SystemApi
-    // TODO alternatively require MODIFY_AUDIO_SYSTEM_SETTINGS when defined
-    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MODIFY_AUDIO_ROUTING,
+            Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS
+    })
     public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada) {
         try {
             getService().setDeviceVolume(vi, ada, mPackageName);
@@ -340,8 +343,10 @@
      * @param ada the device for which volume is to be retrieved
      */
     @SystemApi
-    // TODO alternatively require MODIFY_AUDIO_SYSTEM_SETTINGS when defined
-    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MODIFY_AUDIO_ROUTING,
+            Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS
+    })
     public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi,
             @NonNull AudioDeviceAttributes ada) {
         try {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 5ee32d6..c06352c 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -106,9 +106,11 @@
     void setStreamVolumeWithAttribution(int streamType, int index, int flags,
             in String callingPackage, in String attributionTag);
 
+    @EnforcePermission(anyOf = {"MODIFY_AUDIO_ROUTING", "MODIFY_AUDIO_SYSTEM_SETTINGS"})
     void setDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada,
             in String callingPackage);
 
+    @EnforcePermission(anyOf = {"MODIFY_AUDIO_ROUTING", "MODIFY_AUDIO_SYSTEM_SETTINGS"})
     VolumeInfo getDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada,
             in String callingPackage);
 
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index 98819a3..0198419 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -28,11 +28,11 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * @hide
@@ -140,7 +140,7 @@
      */
     public static int getLegacyStreamTypeForStrategyWithAudioAttributes(
             @NonNull AudioAttributes audioAttributes) {
-        Preconditions.checkNotNull(audioAttributes, "AudioAttributes must not be null");
+        Objects.requireNonNull(audioAttributes, "AudioAttributes must not be null");
         for (final AudioProductStrategy productStrategy :
                 AudioProductStrategy.getAudioProductStrategies()) {
             if (productStrategy.supportsAudioAttributes(audioAttributes)) {
@@ -160,6 +160,30 @@
         return AudioSystem.STREAM_MUSIC;
     }
 
+    /**
+     * @hide
+     * @param attributes the {@link AudioAttributes} to identify VolumeGroupId with
+     * @param fallbackOnDefault if set, allows to fallback on the default group (e.g. the group
+     *                          associated to {@link AudioManager#STREAM_MUSIC}).
+     * @return volume group id associated with the given {@link AudioAttributes} if found,
+     *     default volume group id if fallbackOnDefault is set
+     * <p>By convention, the product strategy with default attributes will be associated to the
+     * default volume group (e.g. associated to {@link AudioManager#STREAM_MUSIC})
+     * or {@link AudioVolumeGroup#DEFAULT_VOLUME_GROUP} if not found.
+     */
+    public static int getVolumeGroupIdForAudioAttributes(
+            @NonNull AudioAttributes attributes, boolean fallbackOnDefault) {
+        Objects.requireNonNull(attributes, "attributes must not be null");
+        int volumeGroupId = getVolumeGroupIdForAudioAttributesInt(attributes);
+        if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
+            return volumeGroupId;
+        }
+        if (fallbackOnDefault) {
+            return getVolumeGroupIdForAudioAttributesInt(getDefaultAttributes());
+        }
+        return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
+    }
+
     private static List<AudioProductStrategy> initializeAudioProductStrategies() {
         ArrayList<AudioProductStrategy> apsList = new ArrayList<AudioProductStrategy>();
         int status = native_list_audio_product_strategies(apsList);
@@ -190,8 +214,8 @@
      */
     private AudioProductStrategy(@NonNull String name, int id,
             @NonNull AudioAttributesGroup[] aag) {
-        Preconditions.checkNotNull(name, "name must not be null");
-        Preconditions.checkNotNull(aag, "AudioAttributesGroups must not be null");
+        Objects.requireNonNull(name, "name must not be null");
+        Objects.requireNonNull(aag, "AudioAttributesGroups must not be null");
         mName = name;
         mId = id;
         mAudioAttributesGroups = aag;
@@ -241,7 +265,7 @@
      */
     @TestApi
     public int getLegacyStreamTypeForAudioAttributes(@NonNull AudioAttributes aa) {
-        Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
+        Objects.requireNonNull(aa, "AudioAttributes must not be null");
         for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
             if (aag.supportsAttributes(aa)) {
                 return aag.getStreamType();
@@ -258,7 +282,7 @@
      */
     @SystemApi
     public boolean supportsAudioAttributes(@NonNull AudioAttributes aa) {
-        Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
+        Objects.requireNonNull(aa, "AudioAttributes must not be null");
         for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
             if (aag.supportsAttributes(aa)) {
                 return true;
@@ -291,7 +315,7 @@
      */
     @TestApi
     public int getVolumeGroupIdForAudioAttributes(@NonNull AudioAttributes aa) {
-        Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
+        Objects.requireNonNull(aa, "AudioAttributes must not be null");
         for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
             if (aag.supportsAttributes(aa)) {
                 return aag.getVolumeGroupId();
@@ -300,6 +324,17 @@
         return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
     }
 
+    private static int getVolumeGroupIdForAudioAttributesInt(@NonNull AudioAttributes attributes) {
+        Objects.requireNonNull(attributes, "attributes must not be null");
+        for (AudioProductStrategy productStrategy : getAudioProductStrategies()) {
+            int volumeGroupId = productStrategy.getVolumeGroupIdForAudioAttributes(attributes);
+            if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
+                return volumeGroupId;
+            }
+        }
+        return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -374,8 +409,8 @@
      */
     private static boolean attributesMatches(@NonNull AudioAttributes refAttr,
             @NonNull AudioAttributes attr) {
-        Preconditions.checkNotNull(refAttr, "refAttr must not be null");
-        Preconditions.checkNotNull(attr, "attr must not be null");
+        Objects.requireNonNull(refAttr, "reference AudioAttributes must not be null");
+        Objects.requireNonNull(attr, "requester's AudioAttributes must not be null");
         String refFormattedTags = TextUtils.join(";", refAttr.getTags());
         String cliFormattedTags = TextUtils.join(";", attr.getTags());
         if (refAttr.equals(DEFAULT_ATTRIBUTES)) {
diff --git a/media/java/android/media/projection/TEST_MAPPING b/media/java/android/media/projection/TEST_MAPPING
new file mode 100644
index 0000000..a792498
--- /dev/null
+++ b/media/java/android/media/projection/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+  "presubmit": [
+    {
+      "name": "MediaProjectionTests",
+      "options": [
+        {
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/media/tests/projection/TEST_MAPPING b/media/tests/projection/TEST_MAPPING
deleted file mode 100644
index ddb68af..0000000
--- a/media/tests/projection/TEST_MAPPING
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-    "presubmit": [
-        {
-            "name": "FrameworksServicesTests",
-            "options": [
-                {"include-filter": "android.media.projection.mediaprojectiontests"},
-                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-                {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-                {"exclude-annotation": "org.junit.Ignore"}
-            ]
-        }
-    ]
-}
diff --git a/opengl/java/android/opengl/Matrix.java b/opengl/java/android/opengl/Matrix.java
index ce3f57e..5ae341b 100644
--- a/opengl/java/android/opengl/Matrix.java
+++ b/opengl/java/android/opengl/Matrix.java
@@ -16,6 +16,8 @@
 
 package android.opengl;
 
+import androidx.annotation.NonNull;
+
 /**
  * Matrix math utilities. These methods operate on OpenGL ES format
  * matrices and vectors stored in float arrays.
@@ -38,7 +40,11 @@
 public class Matrix {
 
     /** Temporary memory for operations that need temporary matrix data. */
-    private final static float[] sTemp = new float[32];
+    private static final ThreadLocal<float[]> ThreadTmp = new ThreadLocal() {
+        @Override protected float[] initialValue() {
+            return new float[32];
+        }
+    };
 
     /**
      * @deprecated All methods are static, do not instantiate this class.
@@ -46,6 +52,40 @@
     @Deprecated
     public Matrix() {}
 
+    private static boolean overlap(
+            float[] a, int aStart, int aLength, float[] b, int bStart, int bLength) {
+        if (a != b) {
+            return false;
+        }
+
+        if (aStart == bStart) {
+            return true;
+        }
+
+        int aEnd = aStart + aLength;
+        int bEnd = bStart + bLength;
+
+        if (aEnd == bEnd) {
+            return true;
+        }
+
+        if (aStart < bStart && bStart < aEnd) {
+            return true;
+        }
+        if (aStart < bEnd   && bEnd   < aEnd) {
+            return true;
+        }
+
+        if (bStart < aStart && aStart < bEnd) {
+            return true;
+        }
+        if (bStart < aEnd   && aEnd   < bEnd) {
+            return true;
+        }
+
+        return false;
+    }
+
     /**
      * Multiplies two 4x4 matrices together and stores the result in a third 4x4
      * matrix. In matrix notation: result = lhs x rhs. Due to the way
@@ -53,9 +93,9 @@
      * effect as first multiplying by the rhs matrix, then multiplying by
      * the lhs matrix. This is the opposite of what you might expect.
      * <p>
-     * The same float array may be passed for result, lhs, and/or rhs. However,
-     * the result element values are undefined if the result elements overlap
-     * either the lhs or rhs elements.
+     * The same float array may be passed for result, lhs, and/or rhs. This
+     * operation is expected to do the correct thing if the result elements
+     * overlap with either of the lhs or rhs elements.
      *
      * @param result The float array that holds the result.
      * @param resultOffset The offset into the result array where the result is
@@ -65,20 +105,101 @@
      * @param rhs The float array that holds the right-hand-side matrix.
      * @param rhsOffset The offset into the rhs array where the rhs is stored.
      *
-     * @throws IllegalArgumentException if result, lhs, or rhs are null, or if
-     * resultOffset + 16 > result.length or lhsOffset + 16 > lhs.length or
-     * rhsOffset + 16 > rhs.length.
+     * @throws IllegalArgumentException under any of the following conditions:
+     * result, lhs, or rhs are null;
+     * resultOffset + 16 > result.length
+     * or lhsOffset + 16 > lhs.length
+     * or rhsOffset + 16 > rhs.length;
+     * resultOffset < 0 or lhsOffset < 0 or rhsOffset < 0
      */
-    public static native void multiplyMM(float[] result, int resultOffset,
-            float[] lhs, int lhsOffset, float[] rhs, int rhsOffset);
+    public static void multiplyMM(float[] result, int resultOffset,
+            float[] lhs, int lhsOffset, float[] rhs, int rhsOffset) {
+        // error checking
+        if (result == null) {
+            throw new IllegalArgumentException("result == null");
+        }
+        if (lhs == null) {
+            throw new IllegalArgumentException("lhs == null");
+        }
+        if (rhs == null) {
+            throw new IllegalArgumentException("rhs == null");
+        }
+        if (resultOffset < 0) {
+            throw new IllegalArgumentException("resultOffset < 0");
+        }
+        if (lhsOffset < 0) {
+            throw new IllegalArgumentException("lhsOffset < 0");
+        }
+        if (rhsOffset < 0) {
+            throw new IllegalArgumentException("rhsOffset < 0");
+        }
+        if (result.length < resultOffset + 16) {
+            throw new IllegalArgumentException("result.length < resultOffset + 16");
+        }
+        if (lhs.length < lhsOffset + 16) {
+            throw new IllegalArgumentException("lhs.length < lhsOffset + 16");
+        }
+        if (rhs.length < rhsOffset + 16) {
+            throw new IllegalArgumentException("rhs.length < rhsOffset + 16");
+        }
+
+        // Check for overlap between rhs and result or lhs and result
+        if ( overlap(result, resultOffset, 16, lhs, lhsOffset, 16)
+                || overlap(result, resultOffset, 16, rhs, rhsOffset, 16) ) {
+            float[] tmp = ThreadTmp.get();
+            for (int i=0; i<4; i++) {
+                final float rhs_i0 = rhs[ 4*i + 0 + rhsOffset ];
+                float ri0 = lhs[ 0 + lhsOffset ] * rhs_i0;
+                float ri1 = lhs[ 1 + lhsOffset ] * rhs_i0;
+                float ri2 = lhs[ 2 + lhsOffset ] * rhs_i0;
+                float ri3 = lhs[ 3 + lhsOffset ] * rhs_i0;
+                for (int j=1; j<4; j++) {
+                    final float rhs_ij = rhs[ 4*i + j + rhsOffset];
+                    ri0 += lhs[ 4*j + 0 + lhsOffset ] * rhs_ij;
+                    ri1 += lhs[ 4*j + 1 + lhsOffset ] * rhs_ij;
+                    ri2 += lhs[ 4*j + 2 + lhsOffset ] * rhs_ij;
+                    ri3 += lhs[ 4*j + 3 + lhsOffset ] * rhs_ij;
+                }
+                tmp[ 4*i + 0 ] = ri0;
+                tmp[ 4*i + 1 ] = ri1;
+                tmp[ 4*i + 2 ] = ri2;
+                tmp[ 4*i + 3 ] = ri3;
+            }
+
+            // copy from tmp to result
+            for (int i=0; i < 16; i++) {
+                result[ i + resultOffset ] = tmp[ i ];
+            }
+
+        } else {
+            for (int i=0; i<4; i++) {
+                final float rhs_i0 = rhs[ 4*i + 0 + rhsOffset ];
+                float ri0 = lhs[ 0 + lhsOffset ] * rhs_i0;
+                float ri1 = lhs[ 1 + lhsOffset ] * rhs_i0;
+                float ri2 = lhs[ 2 + lhsOffset ] * rhs_i0;
+                float ri3 = lhs[ 3 + lhsOffset ] * rhs_i0;
+                for (int j=1; j<4; j++) {
+                    final float rhs_ij = rhs[ 4*i + j + rhsOffset];
+                    ri0 += lhs[ 4*j + 0 + lhsOffset ] * rhs_ij;
+                    ri1 += lhs[ 4*j + 1 + lhsOffset ] * rhs_ij;
+                    ri2 += lhs[ 4*j + 2 + lhsOffset ] * rhs_ij;
+                    ri3 += lhs[ 4*j + 3 + lhsOffset ] * rhs_ij;
+                }
+                result[ 4*i + 0 + resultOffset ] = ri0;
+                result[ 4*i + 1 + resultOffset ] = ri1;
+                result[ 4*i + 2 + resultOffset ] = ri2;
+                result[ 4*i + 3 + resultOffset ] = ri3;
+            }
+        }
+    }
 
     /**
      * Multiplies a 4 element vector by a 4x4 matrix and stores the result in a
      * 4-element column vector. In matrix notation: result = lhs x rhs
      * <p>
      * The same float array may be passed for resultVec, lhsMat, and/or rhsVec.
-     * However, the resultVec element values are undefined if the resultVec
-     * elements overlap either the lhsMat or rhsVec elements.
+     * This operation is expected to do the correct thing if the result elements
+     * overlap with either of the lhs or rhs elements.
      *
      * @param resultVec The float array that holds the result vector.
      * @param resultVecOffset The offset into the result array where the result
@@ -89,14 +210,67 @@
      * @param rhsVecOffset The offset into the rhs vector where the rhs vector
      *        is stored.
      *
-     * @throws IllegalArgumentException if resultVec, lhsMat,
-     * or rhsVec are null, or if resultVecOffset + 4 > resultVec.length
-     * or lhsMatOffset + 16 > lhsMat.length or
-     * rhsVecOffset + 4 > rhsVec.length.
+     * @throws IllegalArgumentException under any of the following conditions:
+     * resultVec, lhsMat, or rhsVec are null;
+     * resultVecOffset + 4  > resultVec.length
+     * or lhsMatOffset + 16 > lhsMat.length
+     * or rhsVecOffset + 4  > rhsVec.length;
+     * resultVecOffset < 0 or lhsMatOffset < 0 or rhsVecOffset < 0
      */
-    public static native void multiplyMV(float[] resultVec,
+    public static void multiplyMV(float[] resultVec,
             int resultVecOffset, float[] lhsMat, int lhsMatOffset,
-            float[] rhsVec, int rhsVecOffset);
+            float[] rhsVec, int rhsVecOffset) {
+        // error checking
+        if (resultVec == null) {
+            throw new IllegalArgumentException("resultVec == null");
+        }
+        if (lhsMat == null) {
+            throw new IllegalArgumentException("lhsMat == null");
+        }
+        if (rhsVec == null) {
+            throw new IllegalArgumentException("rhsVec == null");
+        }
+        if (resultVecOffset < 0) {
+            throw new IllegalArgumentException("resultVecOffset < 0");
+        }
+        if (lhsMatOffset < 0) {
+            throw new IllegalArgumentException("lhsMatOffset < 0");
+        }
+        if (rhsVecOffset < 0) {
+            throw new IllegalArgumentException("rhsVecOffset < 0");
+        }
+        if (resultVec.length < resultVecOffset + 4) {
+            throw new IllegalArgumentException("resultVec.length < resultVecOffset + 4");
+        }
+        if (lhsMat.length < lhsMatOffset + 16) {
+            throw new IllegalArgumentException("lhsMat.length < lhsMatOffset + 16");
+        }
+        if (rhsVec.length < rhsVecOffset + 4) {
+            throw new IllegalArgumentException("rhsVec.length < rhsVecOffset + 4");
+        }
+
+        float tmp0 = lhsMat[0 + 4 * 0 + lhsMatOffset] * rhsVec[0 + rhsVecOffset] +
+                     lhsMat[0 + 4 * 1 + lhsMatOffset] * rhsVec[1 + rhsVecOffset] +
+                     lhsMat[0 + 4 * 2 + lhsMatOffset] * rhsVec[2 + rhsVecOffset] +
+                     lhsMat[0 + 4 * 3 + lhsMatOffset] * rhsVec[3 + rhsVecOffset];
+        float tmp1 = lhsMat[1 + 4 * 0 + lhsMatOffset] * rhsVec[0 + rhsVecOffset] +
+                     lhsMat[1 + 4 * 1 + lhsMatOffset] * rhsVec[1 + rhsVecOffset] +
+                     lhsMat[1 + 4 * 2 + lhsMatOffset] * rhsVec[2 + rhsVecOffset] +
+                     lhsMat[1 + 4 * 3 + lhsMatOffset] * rhsVec[3 + rhsVecOffset];
+        float tmp2 = lhsMat[2 + 4 * 0 + lhsMatOffset] * rhsVec[0 + rhsVecOffset] +
+                     lhsMat[2 + 4 * 1 + lhsMatOffset] * rhsVec[1 + rhsVecOffset] +
+                     lhsMat[2 + 4 * 2 + lhsMatOffset] * rhsVec[2 + rhsVecOffset] +
+                     lhsMat[2 + 4 * 3 + lhsMatOffset] * rhsVec[3 + rhsVecOffset];
+        float tmp3 = lhsMat[3 + 4 * 0 + lhsMatOffset] * rhsVec[0 + rhsVecOffset] +
+                     lhsMat[3 + 4 * 1 + lhsMatOffset] * rhsVec[1 + rhsVecOffset] +
+                     lhsMat[3 + 4 * 2 + lhsMatOffset] * rhsVec[2 + rhsVecOffset] +
+                     lhsMat[3 + 4 * 3 + lhsMatOffset] * rhsVec[3 + rhsVecOffset];
+
+        resultVec[ 0 + resultVecOffset ] = tmp0;
+        resultVec[ 1 + resultVecOffset ] = tmp1;
+        resultVec[ 2 + resultVecOffset ] = tmp2;
+        resultVec[ 3 + resultVecOffset ] = tmp3;
+    }
 
     /**
      * Transposes a 4 x 4 matrix.
@@ -537,10 +711,9 @@
     public static void rotateM(float[] rm, int rmOffset,
             float[] m, int mOffset,
             float a, float x, float y, float z) {
-        synchronized(sTemp) {
-            setRotateM(sTemp, 0, a, x, y, z);
-            multiplyMM(rm, rmOffset, m, mOffset, sTemp, 0);
-        }
+        float[] tmp = ThreadTmp.get();
+        setRotateM(tmp, 16, a, x, y, z);
+        multiplyMM(rm, rmOffset, m, mOffset, tmp, 16);
     }
 
     /**
@@ -556,11 +729,7 @@
      */
     public static void rotateM(float[] m, int mOffset,
             float a, float x, float y, float z) {
-        synchronized(sTemp) {
-            setRotateM(sTemp, 0, a, x, y, z);
-            multiplyMM(sTemp, 16, m, mOffset, sTemp, 0);
-            System.arraycopy(sTemp, 16, m, mOffset, 16);
-        }
+        rotateM(m, mOffset, m, mOffset, a, x, y, z);
     }
 
     /**
@@ -640,9 +809,14 @@
      * @param rm returns the result
      * @param rmOffset index into rm where the result matrix starts
      * @param x angle of rotation, in degrees
-     * @param y angle of rotation, in degrees
+     * @param y is broken, do not use
      * @param z angle of rotation, in degrees
+     *
+     * @deprecated This method is incorrect around the y axis. This method is
+     *             deprecated and replaced (below) by setRotateEulerM2 which
+     *             behaves correctly
      */
+    @Deprecated
     public static void setRotateEulerM(float[] rm, int rmOffset,
             float x, float y, float z) {
         x *= (float) (Math.PI / 180.0f);
@@ -679,6 +853,64 @@
     }
 
     /**
+     * Converts Euler angles to a rotation matrix.
+     *
+     * @param rm returns the result
+     * @param rmOffset index into rm where the result matrix starts
+     * @param x angle of rotation, in degrees
+     * @param y angle of rotation, in degrees
+     * @param z angle of rotation, in degrees
+     *
+     * @throws IllegalArgumentException if rm is null;
+     * or if rmOffset + 16 > rm.length;
+     * rmOffset < 0
+     */
+    public static void setRotateEulerM2(@NonNull float[] rm, int rmOffset,
+            float x, float y, float z) {
+        if (rm == null) {
+            throw new IllegalArgumentException("rm == null");
+        }
+        if (rmOffset < 0) {
+            throw new IllegalArgumentException("rmOffset < 0");
+        }
+        if (rm.length < rmOffset + 16) {
+            throw new IllegalArgumentException("rm.length < rmOffset + 16");
+        }
+
+        x *= (float) (Math.PI / 180.0f);
+        y *= (float) (Math.PI / 180.0f);
+        z *= (float) (Math.PI / 180.0f);
+        float cx = (float) Math.cos(x);
+        float sx = (float) Math.sin(x);
+        float cy = (float) Math.cos(y);
+        float sy = (float) Math.sin(y);
+        float cz = (float) Math.cos(z);
+        float sz = (float) Math.sin(z);
+        float cxsy = cx * sy;
+        float sxsy = sx * sy;
+
+        rm[rmOffset + 0]  =  cy * cz;
+        rm[rmOffset + 1]  = -cy * sz;
+        rm[rmOffset + 2]  =  sy;
+        rm[rmOffset + 3]  =  0.0f;
+
+        rm[rmOffset + 4]  =  sxsy * cz + cx * sz;
+        rm[rmOffset + 5]  = -sxsy * sz + cx * cz;
+        rm[rmOffset + 6]  = -sx * cy;
+        rm[rmOffset + 7]  =  0.0f;
+
+        rm[rmOffset + 8]  = -cxsy * cz + sx * sz;
+        rm[rmOffset + 9]  =  cxsy * sz + sx * cz;
+        rm[rmOffset + 10] =  cx * cy;
+        rm[rmOffset + 11] =  0.0f;
+
+        rm[rmOffset + 12] =  0.0f;
+        rm[rmOffset + 13] =  0.0f;
+        rm[rmOffset + 14] =  0.0f;
+        rm[rmOffset + 15] =  1.0f;
+    }
+
+    /**
      * Defines a viewing transformation in terms of an eye point, a center of
      * view, and an up vector.
      *
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index df4705b..6e2927d 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -17,9 +17,9 @@
     <!-- Telephony notification channel name for performance boost notifications. -->
     <string name="performance_boost_notification_channel">Performance boost</string>
     <!-- Notification title text for the performance boost notification. -->
-    <string name="performance_boost_notification_title">%s recommends a performance boost</string>
+    <string name="performance_boost_notification_title">Improve your 5G experience</string>
     <!-- Notification detail text for the performance boost notification. -->
-    <string name="performance_boost_notification_detail">Buy a performance boost for better network performance</string>
+    <string name="performance_boost_notification_detail">%1$s recommends buying a performance boost plan. Tap to buy through %2$s.</string>
     <!-- Notification button text to cancel the performance boost notification. -->
     <string name="performance_boost_notification_button_not_now">Not now</string>
     <!-- Notification button text to manage the performance boost notification. -->
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index 1b02c2b..d4ce5f5 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -178,9 +178,9 @@
             return false;
         }
 
-        String appName = intent.getStringExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME);
-        if (TextUtils.isEmpty(appName)) {
-            loge("isIntentValid: empty requesting application name: " + appName);
+        String carrier = intent.getStringExtra(SlicePurchaseController.EXTRA_CARRIER);
+        if (TextUtils.isEmpty(carrier)) {
+            loge("isIntentValid: empty carrier: " + carrier);
             return false;
         }
 
@@ -310,14 +310,14 @@
         channel.setBlockable(true);
         context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
 
+        String carrier = intent.getStringExtra(SlicePurchaseController.EXTRA_CARRIER);
+
         Notification notification =
                 new Notification.Builder(context, PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID)
-                        .setContentTitle(String.format(res.getString(
-                                R.string.performance_boost_notification_title),
-                                intent.getStringExtra(
-                                        SlicePurchaseController.EXTRA_REQUESTING_APP_NAME)))
-                        .setContentText(res.getString(
-                                R.string.performance_boost_notification_detail))
+                        .setContentTitle(res.getString(
+                                R.string.performance_boost_notification_title))
+                        .setContentText(String.format(res.getString(
+                                R.string.performance_boost_notification_detail), carrier, carrier))
                         .setSmallIcon(R.drawable.ic_performance_boost)
                         .setContentIntent(createContentIntent(context, intent, 1))
                         .setDeleteIntent(intent.getParcelableExtra(
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
index 1bf644e..daf0b42 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
@@ -48,7 +48,7 @@
 
 @RunWith(AndroidJUnit4.class)
 public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchaseActivity> {
-    private static final String TAG = "SlicePurchaseActivityTest";
+    private static final String CARRIER = "Some Carrier";
     private static final String URL = "file:///android_asset/slice_purchase_test.html";
     private static final int PHONE_ID = 0;
 
@@ -95,7 +95,7 @@
                 TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
         intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_URL,
                 SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
-        intent.putExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME, TAG);
+        intent.putExtra(SlicePurchaseController.EXTRA_CARRIER, CARRIER);
         Intent spiedIntent = spy(intent);
 
         // set up pending intents
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
index 568d63c..952789c 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
@@ -62,7 +62,7 @@
 @RunWith(AndroidJUnit4.class)
 public class SlicePurchaseBroadcastReceiverTest {
     private static final int PHONE_ID = 0;
-    private static final String TAG = "SlicePurchaseBroadcastReceiverTest";
+    private static final String CARRIER = "Some Carrier";
     private static final String EXTRA = "EXTRA";
 
     @Mock Intent mIntent;
@@ -136,8 +136,7 @@
                 eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
         doReturn(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).when(mIntent).getStringExtra(
                 eq(SlicePurchaseController.EXTRA_PURCHASE_URL));
-        doReturn(TAG).when(mIntent).getStringExtra(
-                eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
+        doReturn(CARRIER).when(mIntent).getStringExtra(eq(SlicePurchaseController.EXTRA_CARRIER));
         assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
 
         // set up pending intent
@@ -229,8 +228,7 @@
                 eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
         doReturn(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).when(mIntent).getStringExtra(
                 eq(SlicePurchaseController.EXTRA_PURCHASE_URL));
-        doReturn(TAG).when(mIntent).getStringExtra(
-                eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
+        doReturn(CARRIER).when(mIntent).getStringExtra(eq(SlicePurchaseController.EXTRA_CARRIER));
         mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
     }
 
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index 90bb2d1..2cb3468 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -40,6 +40,7 @@
     ],
 
     platform_apis: true,
+    privileged: true,
 
     kotlincflags: ["-Xjvm-default=enable"],
 
diff --git a/packages/SettingsLib/HelpUtils/Android.bp b/packages/SettingsLib/HelpUtils/Android.bp
index aea51b1..3ec4366a 100644
--- a/packages/SettingsLib/HelpUtils/Android.bp
+++ b/packages/SettingsLib/HelpUtils/Android.bp
@@ -22,5 +22,6 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.permission",
+        "com.android.healthconnect",
     ],
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index a81e2e3..4d8b89b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -49,7 +49,7 @@
 import com.android.settingslib.spa.framework.compose.localNavController
 import com.android.settingslib.spa.framework.compose.rememberAnimatedNavController
 import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.framework.util.PageEvent
+import com.android.settingslib.spa.framework.util.PageWithEvent
 import com.android.settingslib.spa.framework.util.getDestination
 import com.android.settingslib.spa.framework.util.getEntryId
 import com.android.settingslib.spa.framework.util.getSessionName
@@ -118,32 +118,25 @@
                 arguments = spp.parameter,
                 enterTransition = {
                     slideIntoContainer(
-                        AnimatedContentScope.SlideDirection.Left,
-                        animationSpec = slideEffect
+                        AnimatedContentScope.SlideDirection.Left, animationSpec = slideEffect
                     ) + fadeIn(animationSpec = fadeEffect)
                 },
                 exitTransition = {
                     slideOutOfContainer(
-                        AnimatedContentScope.SlideDirection.Left,
-                        animationSpec = slideEffect
+                        AnimatedContentScope.SlideDirection.Left, animationSpec = slideEffect
                     ) + fadeOut(animationSpec = fadeEffect)
                 },
                 popEnterTransition = {
                     slideIntoContainer(
-                        AnimatedContentScope.SlideDirection.Right,
-                        animationSpec = slideEffect
+                        AnimatedContentScope.SlideDirection.Right, animationSpec = slideEffect
                     ) + fadeIn(animationSpec = fadeEffect)
                 },
                 popExitTransition = {
                     slideOutOfContainer(
-                        AnimatedContentScope.SlideDirection.Right,
-                        animationSpec = slideEffect
+                        AnimatedContentScope.SlideDirection.Right, animationSpec = slideEffect
                     ) + fadeOut(animationSpec = fadeEffect)
                 },
-            ) { navBackStackEntry ->
-                spp.PageEvent(navBackStackEntry.arguments)
-                spp.Page(navBackStackEntry.arguments)
-            }
+            ) { navBackStackEntry -> spp.PageWithEvent(navBackStackEntry.arguments) }
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 0871304..2175e55 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -185,6 +185,8 @@
     private var sliceDataFn: SliceDataGetter = { _: Uri, _: Bundle? -> null }
 
     fun build(): SettingsEntry {
+        val page = fromPage ?: owner
+        val isEnabled = page.isEnabled()
         return SettingsEntry(
             id = id(),
             name = name,
@@ -196,10 +198,10 @@
             toPage = toPage,
 
             // attributes
-            isAllowSearch = isAllowSearch,
+            isAllowSearch = isEnabled && isAllowSearch,
             isSearchDataDynamic = isSearchDataDynamic,
             hasMutableStatus = hasMutableStatus,
-            hasSliceSupport = hasSliceSupport,
+            hasSliceSupport = isEnabled && hasSliceSupport,
 
             // functions
             statusDataImpl = statusDataFn,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 2bfa2a4..a362877 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -94,6 +94,12 @@
         return !isCreateBy(NULL_PAGE_NAME) &&
             !hasRuntimeParam()
     }
+
+    fun isEnabled(): Boolean {
+        if (!SpaEnvironmentFactory.isReady()) return false
+        val pageProviderRepository by SpaEnvironmentFactory.instance.pageProviderRepository
+        return pageProviderRepository.getProviderOrNull(sppName)?.isEnabled(arguments) ?: false
+    }
 }
 
 fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 940005d..42e5f7e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -37,6 +37,14 @@
     val parameter: List<NamedNavArgument>
         get() = emptyList()
 
+    /**
+     * The API to indicate whether the page is enabled or not.
+     * During SPA page migration, one can use it to enable certain pages in one release.
+     * When the page is disabled, all its related functionalities, such as browsing, search,
+     * slice provider, are disabled as well.
+     */
+    fun isEnabled(arguments: Bundle?): Boolean = true
+
     fun getTitle(arguments: Bundle?): String = displayName
 
     fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
index 73eae07..22a4563 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
@@ -33,13 +33,15 @@
 import com.android.settingslib.spa.framework.compose.NavControllerWrapper
 
 @Composable
-internal fun SettingsPageProvider.PageEvent(arguments: Bundle? = null) {
+internal fun SettingsPageProvider.PageWithEvent(arguments: Bundle? = null) {
+    if (!isEnabled(arguments)) return
     val page = remember(arguments) { createSettingsPage(arguments) }
     val navController = LocalNavController.current
     LifecycleEffect(
         onStart = { page.logPageEvent(LogEvent.PAGE_ENTER, navController) },
         onStop = { page.logPageEvent(LogEvent.PAGE_LEAVE, navController) },
     )
+    Page(arguments)
 }
 
 private fun SettingsPage.logPageEvent(event: LogEvent, navController: NavControllerWrapper) {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
index bd5884d..218f569 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
@@ -30,6 +30,7 @@
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
 import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
+import com.android.settingslib.spa.tests.testutils.SppDisabled
 import com.android.settingslib.spa.tests.testutils.SppHome
 import com.android.settingslib.spa.testutils.waitUntil
 import com.google.common.truth.Truth
@@ -46,12 +47,12 @@
 
     private val context: Context = ApplicationProvider.getApplicationContext()
     private val spaLogger = SpaLoggerForTest()
-    private val spaEnvironment =
-        SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()), logger = spaLogger)
 
     @Test
     fun testBrowsePage() {
         spaLogger.reset()
+        val spaEnvironment =
+            SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()), logger = spaLogger)
         SpaEnvironmentFactory.reset(spaEnvironment)
 
         val sppRepository by spaEnvironment.pageProviderRepository
@@ -75,6 +76,24 @@
         spaLogger.verifyPageEvent(pageHome.id, 1, 1)
         spaLogger.verifyPageEvent(pageLayer1.id, 1, 0)
     }
+
+    @Test
+    fun testBrowseDisabledPage() {
+        spaLogger.reset()
+        val spaEnvironment = SpaEnvironmentForTest(
+            context, listOf(SppDisabled.createSettingsPage()), logger = spaLogger
+        )
+        SpaEnvironmentFactory.reset(spaEnvironment)
+
+        val sppRepository by spaEnvironment.pageProviderRepository
+        val sppDisabled = sppRepository.getProviderOrNull("SppDisabled")!!
+        val pageDisabled = sppDisabled.createSettingsPage()
+
+        composeTestRule.setContent { BrowseContent(sppRepository) }
+
+        composeTestRule.onNodeWithText(sppDisabled.getTitle(null)).assertDoesNotExist()
+        spaLogger.verifyPageEvent(pageDisabled.id, 0, 0)
+    }
 }
 
 private fun SpaLoggerForTest.verifyPageEvent(id: String, entryCount: Int, leaveCount: Int) {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
index b600ac6..6de1ae5 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -16,13 +16,16 @@
 
 package com.android.settingslib.spa.framework.common
 
+import android.content.Context
 import android.net.Uri
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.core.os.bundleOf
+import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.slice.appendSpaParams
 import com.android.settingslib.spa.slice.getEntryId
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
 import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
 import com.android.settingslib.spa.tests.testutils.getUniquePageId
 import com.google.common.truth.Truth.assertThat
@@ -53,6 +56,9 @@
 
 @RunWith(AndroidJUnit4::class)
 class SettingsEntryTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaEnvironment = SpaEnvironmentForTest(context)
+
     @get:Rule
     val composeTestRule = createComposeRule()
 
@@ -77,15 +83,15 @@
         val owner = SettingsPage.create("mySpp")
         val fromPage = SettingsPage.create("fromSpp")
         val toPage = SettingsPage.create("toSpp")
-        val entryFrom = SettingsEntryBuilder.createLinkFrom("myEntry", owner)
-            .setLink(toPage = toPage).build()
+        val entryFrom =
+            SettingsEntryBuilder.createLinkFrom("myEntry", owner).setLink(toPage = toPage).build()
         assertThat(entryFrom.id).isEqualTo(getUniqueEntryId("myEntry", owner, owner, toPage))
         assertThat(entryFrom.displayName).isEqualTo("myEntry")
         assertThat(entryFrom.fromPage!!.sppName).isEqualTo("mySpp")
         assertThat(entryFrom.toPage!!.sppName).isEqualTo("toSpp")
 
-        val entryTo = SettingsEntryBuilder.createLinkTo("myEntry", owner)
-            .setLink(fromPage = fromPage).build()
+        val entryTo =
+            SettingsEntryBuilder.createLinkTo("myEntry", owner).setLink(fromPage = fromPage).build()
         assertThat(entryTo.id).isEqualTo(getUniqueEntryId("myEntry", owner, fromPage, owner))
         assertThat(entryTo.displayName).isEqualTo("myEntry")
         assertThat(entryTo.fromPage!!.sppName).isEqualTo("fromSpp")
@@ -98,9 +104,7 @@
         val entryInject = SettingsEntryBuilder.createInject(owner).build()
         assertThat(entryInject.id).isEqualTo(
             getUniqueEntryId(
-                INJECT_ENTRY_NAME_TEST,
-                owner,
-                toPage = owner
+                INJECT_ENTRY_NAME_TEST, owner, toPage = owner
             )
         )
         assertThat(entryInject.displayName).isEqualTo("${INJECT_ENTRY_NAME_TEST}_mySpp")
@@ -114,9 +118,7 @@
         val entryInject = SettingsEntryBuilder.createRoot(owner, "myRootEntry").build()
         assertThat(entryInject.id).isEqualTo(
             getUniqueEntryId(
-                ROOT_ENTRY_NAME_TEST,
-                owner,
-                toPage = owner
+                ROOT_ENTRY_NAME_TEST, owner, toPage = owner
             )
         )
         assertThat(entryInject.displayName).isEqualTo("myRootEntry")
@@ -126,13 +128,15 @@
 
     @Test
     fun testSetAttributes() {
-        val owner = SettingsPage.create("mySpp")
-        val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry")
-            .setDisplayName("myEntryDisplay")
-            .setIsSearchDataDynamic(false)
-            .setHasMutableStatus(true)
-            .setSearchDataFn { null }
-            .setSliceDataFn { _, _ -> null }
+        SpaEnvironmentFactory.reset(spaEnvironment)
+        val owner = SettingsPage.create("SppHome")
+        val entryBuilder =
+            SettingsEntryBuilder.create(owner, "myEntry")
+                .setDisplayName("myEntryDisplay")
+                .setIsSearchDataDynamic(false)
+                .setHasMutableStatus(true)
+                .setSearchDataFn { null }
+                .setSliceDataFn { _, _ -> null }
         val entry = entryBuilder.build()
         assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
         assertThat(entry.displayName).isEqualTo("myEntryDisplay")
@@ -143,21 +147,52 @@
         assertThat(entry.hasMutableStatus).isTrue()
         assertThat(entry.hasSliceSupport).isTrue()
 
+        // Test disabled Spp
+        val ownerDisabled = SettingsPage.create("SppDisabled")
+        val entryBuilderDisabled =
+            SettingsEntryBuilder.create(ownerDisabled, "myEntry")
+                .setDisplayName("myEntryDisplay")
+                .setIsSearchDataDynamic(false)
+                .setHasMutableStatus(true)
+                .setSearchDataFn { null }
+                .setSliceDataFn { _, _ -> null }
+        val entryDisabled = entryBuilderDisabled.build()
+        assertThat(entryDisabled.id).isEqualTo(getUniqueEntryId("myEntry", ownerDisabled))
+        assertThat(entryDisabled.displayName).isEqualTo("myEntryDisplay")
+        assertThat(entryDisabled.fromPage).isNull()
+        assertThat(entryDisabled.toPage).isNull()
+        assertThat(entryDisabled.isAllowSearch).isFalse()
+        assertThat(entryDisabled.isSearchDataDynamic).isFalse()
+        assertThat(entryDisabled.hasMutableStatus).isTrue()
+        assertThat(entryDisabled.hasSliceSupport).isFalse()
+
+        // Clear search data fn
         val entry2 = entryBuilder.clearSearchDataFn().build()
         assertThat(entry2.isAllowSearch).isFalse()
+
+        // Clear SppHome in spa environment
+        SpaEnvironmentFactory.reset()
+        val entry3 = entryBuilder.build()
+        assertThat(entry3.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+        assertThat(entry3.displayName).isEqualTo("myEntryDisplay")
+        assertThat(entry3.fromPage).isNull()
+        assertThat(entry3.toPage).isNull()
+        assertThat(entry3.isAllowSearch).isFalse()
+        assertThat(entry3.isSearchDataDynamic).isFalse()
+        assertThat(entry3.hasMutableStatus).isTrue()
+        assertThat(entry3.hasSliceSupport).isFalse()
     }
 
     @Test
     fun testSetMarco() {
-        val owner = SettingsPage.create("mySpp", arguments = bundleOf("param" to "v1"))
-        val entry = SettingsEntryBuilder.create(owner, "myEntry")
-            .setMacro {
-                assertThat(it?.getString("param")).isEqualTo("v1")
-                assertThat(it?.getString("rtParam")).isEqualTo("v2")
-                assertThat(it?.getString("unknown")).isNull()
-                MacroForTest(getUniquePageId("mySpp"), getUniqueEntryId("myEntry", owner))
-            }
-            .build()
+        SpaEnvironmentFactory.reset(spaEnvironment)
+        val owner = SettingsPage.create("SppHome", arguments = bundleOf("param" to "v1"))
+        val entry = SettingsEntryBuilder.create(owner, "myEntry").setMacro {
+            assertThat(it?.getString("param")).isEqualTo("v1")
+            assertThat(it?.getString("rtParam")).isEqualTo("v2")
+            assertThat(it?.getString("unknown")).isNull()
+            MacroForTest(getUniquePageId("SppHome"), getUniqueEntryId("myEntry", owner))
+        }.build()
 
         val rtArguments = bundleOf("rtParam" to "v2")
         composeTestRule.setContent { entry.UiLayout(rtArguments) }
@@ -175,14 +210,14 @@
 
     @Test
     fun testSetSliceDataFn() {
-        val owner = SettingsPage.create("mySpp")
+        SpaEnvironmentFactory.reset(spaEnvironment)
+        val owner = SettingsPage.create("SppHome")
         val entryId = getUniqueEntryId("myEntry", owner)
         val emptySliceData = EntrySliceData()
 
-        val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry")
-            .setSliceDataFn { uri, _ ->
-                return@setSliceDataFn if (uri.getEntryId() == entryId) emptySliceData else null
-            }
+        val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry").setSliceDataFn { uri, _ ->
+            return@setSliceDataFn if (uri.getEntryId() == entryId) emptySliceData else null
+        }
         val entry = entryBuilder.build()
         assertThat(entry.id).isEqualTo(entryId)
         assertThat(entry.hasSliceSupport).isTrue()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
index 1bdba29..530d2ed 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
@@ -23,6 +23,7 @@
 import androidx.slice.Slice
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
 import com.android.settingslib.spa.tests.testutils.SppHome
@@ -44,6 +45,8 @@
 
     @Test
     fun getOrBuildSliceDataTest() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+
         // Slice empty
         assertThat(sliceDataRepository.getOrBuildSliceData(Uri.EMPTY)).isNull()
 
@@ -67,6 +70,8 @@
 
     @Test
     fun getActiveSliceDataTest() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+
         val page = SppLayer2.createSettingsPage()
         val entryId = getUniqueEntryId("Layer2Entry1", page)
         val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
index f38bd08..2755b4e 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -92,6 +92,23 @@
     }
 }
 
+object SppDisabled : SettingsPageProvider {
+    override val name = "SppDisabled"
+
+    override fun isEnabled(arguments: Bundle?): Boolean = false
+
+    override fun getTitle(arguments: Bundle?): String {
+        return "TitleDisabled"
+    }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SppLayer1.buildInject().setLink(fromPage = owner).build(),
+        )
+    }
+}
+
 object SppLayer1 : SettingsPageProvider {
     override val name = "SppLayer1"
 
@@ -190,7 +207,7 @@
         SettingsPageProviderRepository(
             listOf(
                 SppHome, SppLayer1, SppLayer2,
-                SppForSearch,
+                SppForSearch, SppDisabled,
                 object : SettingsPageProvider {
                     override val name = "SppWithParam"
                     override val parameter = listOf(
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index cbb4fbe..ce0c551 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -26,6 +26,7 @@
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.runBlocking
 
 /**
  * The config used to load the App List.
@@ -47,8 +48,21 @@
         userIdFlow: Flow<Int>,
         showSystemFlow: Flow<Boolean>,
     ): Flow<(app: ApplicationInfo) -> Boolean>
+
+    /** Gets the system app package names. */
+    fun getSystemPackageNamesBlocking(config: AppListConfig): Set<String>
 }
 
+/**
+ * Util for app list repository.
+ */
+object AppListRepositoryUtil {
+    /** Gets the system app package names. */
+    @JvmStatic
+    fun getSystemPackageNames(context: Context, config: AppListConfig): Set<String> {
+        return AppListRepositoryImpl(context).getSystemPackageNamesBlocking(config)
+    }
+}
 
 internal class AppListRepositoryImpl(private val context: Context) : AppListRepository {
     private val packageManager = context.packageManager
@@ -83,15 +97,26 @@
     ): Flow<(app: ApplicationInfo) -> Boolean> =
         userIdFlow.combine(showSystemFlow, ::showSystemPredicate)
 
+    override fun getSystemPackageNamesBlocking(config: AppListConfig) = runBlocking {
+        getSystemPackageNames(config)
+    }
+
+    private suspend fun getSystemPackageNames(config: AppListConfig): Set<String> =
+            coroutineScope {
+                val loadAppsDeferred = async { loadApps(config) }
+                val homeOrLauncherPackages = loadHomeOrLauncherPackages(config.userId)
+                val showSystemPredicate =
+                        { app: ApplicationInfo -> isSystemApp(app, homeOrLauncherPackages) }
+                loadAppsDeferred.await().filter(showSystemPredicate).map { it.packageName }.toSet()
+            }
+
     private suspend fun showSystemPredicate(
         userId: Int,
         showSystem: Boolean,
     ): (app: ApplicationInfo) -> Boolean {
         if (showSystem) return { true }
         val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId)
-        return { app ->
-            app.isUpdatedSystemApp || !app.isSystemApp || app.packageName in homeOrLauncherPackages
-        }
+        return { app -> !isSystemApp(app, homeOrLauncherPackages) }
     }
 
     private suspend fun loadHomeOrLauncherPackages(userId: Int): Set<String> {
@@ -117,6 +142,11 @@
         }
     }
 
+    private fun isSystemApp(app: ApplicationInfo, homeOrLauncherPackages: Set<String>): Boolean {
+        return !app.isUpdatedSystemApp && app.isSystemApp &&
+            !(app.packageName in homeOrLauncherPackages)
+    }
+
     companion object {
         private fun ApplicationInfo.isInAppList(
             showInstantApps: Boolean,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
index df828f2..6cd1c0d 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
@@ -51,7 +51,7 @@
 }
 
 internal interface IAppListViewModel<T : AppRecord> {
-    val option: StateFlowBridge<Int?>
+    val optionFlow: MutableStateFlow<Int?>
     val spinnerOptionsFlow: Flow<List<SpinnerOption>>
     val appListDataFlow: Flow<AppListData<T>>
 }
@@ -69,7 +69,7 @@
     val appListConfig = StateFlowBridge<AppListConfig>()
     val listModel = StateFlowBridge<AppListModel<T>>()
     val showSystem = StateFlowBridge<Boolean>()
-    final override val option = StateFlowBridge<Int?>()
+    final override val optionFlow = MutableStateFlow<Int?>(null)
     val searchQuery = StateFlowBridge<String>()
 
     private val appListRepository = appListRepositoryFactory(application)
@@ -97,7 +97,7 @@
             listModel.getSpinnerOptions(recordList)
         }
 
-    override val appListDataFlow = option.flow.flatMapLatest(::filterAndSort)
+    override val appListDataFlow = optionFlow.filterNotNull().flatMapLatest(::filterAndSort)
         .combine(searchQuery.flow) { appListData, searchQuery ->
             appListData.filter {
                 it.label.contains(other = searchQuery, ignoreCase = true)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 34c3ee0..7199e3a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -24,11 +24,10 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.State
 import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.Dp
@@ -37,7 +36,6 @@
 import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
 import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
 import com.android.settingslib.spa.framework.compose.toState
-import com.android.settingslib.spa.framework.util.StateFlowBridge
 import com.android.settingslib.spa.widget.ui.CategoryTitle
 import com.android.settingslib.spa.widget.ui.PlaceholderTitle
 import com.android.settingslib.spa.widget.ui.Spinner
@@ -52,6 +50,7 @@
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.IAppListViewModel
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
 
 private const val TAG = "AppList"
 private const val CONTENT_TYPE_HEADER = "header"
@@ -88,7 +87,7 @@
     val viewModel = viewModelSupplier()
     Column(Modifier.fillMaxSize()) {
         val optionsState = viewModel.spinnerOptionsFlow.collectAsState(null, Dispatchers.IO)
-        SpinnerOptions(optionsState, viewModel.option)
+        SpinnerOptions(optionsState, viewModel.optionFlow)
         val appListData = viewModel.appListDataFlow.collectAsState(null, Dispatchers.IO)
         listModel.AppListWidget(appListData, header, bottomPadding, noItemMessage)
     }
@@ -97,15 +96,18 @@
 @Composable
 private fun SpinnerOptions(
     optionsState: State<List<SpinnerOption>?>,
-    optionBridge: StateFlowBridge<Int?>,
+    optionFlow: MutableStateFlow<Int?>,
 ) {
     val options = optionsState.value
-    val selectedOption = rememberSaveable(options) {
-        mutableStateOf(options?.let { it.firstOrNull()?.id ?: -1 })
+    LaunchedEffect(options) {
+        if (options != null && !options.any { it.id == optionFlow.value }) {
+            // Reset to first option if the available options changed, and the current selected one
+            // does not in the new options.
+            optionFlow.value = options.let { it.firstOrNull()?.id ?: -1 }
+        }
     }
-    optionBridge.Sync(selectedOption)
     if (options != null) {
-        Spinner(options, selectedOption.value) { selectedOption.value = it }
+        Spinner(options, optionFlow.collectAsState().value) { optionFlow.value = it }
     }
 }
 
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index 2d8f009..b0ea40a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -180,9 +180,7 @@
 
     @Test
     fun showSystemPredicate_showSystem() = runTest {
-        val app = ApplicationInfo().apply {
-            flags = ApplicationInfo.FLAG_SYSTEM
-        }
+        val app = SYSTEM_APP
 
         val showSystemPredicate = getShowSystemPredicate(showSystem = true)
 
@@ -191,9 +189,7 @@
 
     @Test
     fun showSystemPredicate_notShowSystemAndIsSystemApp() = runTest {
-        val app = ApplicationInfo().apply {
-            flags = ApplicationInfo.FLAG_SYSTEM
-        }
+        val app = SYSTEM_APP
 
         val showSystemPredicate = getShowSystemPredicate(showSystem = false)
 
@@ -202,9 +198,7 @@
 
     @Test
     fun showSystemPredicate_isUpdatedSystemApp() = runTest {
-        val app = ApplicationInfo().apply {
-            flags = ApplicationInfo.FLAG_SYSTEM or ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
-        }
+        val app = UPDATED_SYSTEM_APP
 
         val showSystemPredicate = getShowSystemPredicate(showSystem = false)
 
@@ -213,10 +207,8 @@
 
     @Test
     fun showSystemPredicate_isHome() = runTest {
-        val app = ApplicationInfo().apply {
-            flags = ApplicationInfo.FLAG_SYSTEM
-            packageName = "home.app"
-        }
+        val app = HOME_APP
+
         whenever(packageManager.getHomeActivities(any())).thenAnswer {
             @Suppress("UNCHECKED_CAST")
             val resolveInfos = it.arguments[0] as MutableList<ResolveInfo>
@@ -231,10 +223,8 @@
 
     @Test
     fun showSystemPredicate_appInLauncher() = runTest {
-        val app = ApplicationInfo().apply {
-            flags = ApplicationInfo.FLAG_SYSTEM
-            packageName = "app.in.launcher"
-        }
+        val app = IN_LAUMCHER_APP
+
         whenever(
             packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
         ).thenReturn(listOf(resolveInfoOf(packageName = app.packageName)))
@@ -244,6 +234,17 @@
         assertThat(showSystemPredicate(app)).isTrue()
     }
 
+    @Test
+    fun getSystemPackageNames_returnExpectedValues() = runTest {
+        mockInstalledApplications(listOf(
+                NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUMCHER_APP))
+        val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
+
+        val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames(context, appListConfig)
+
+        assertThat(systemPackageNames).containsExactly("system.app", "home.app", "app.in.launcher")
+    }
+
     private suspend fun getShowSystemPredicate(showSystem: Boolean) =
         repository.showSystemPredicate(
             userIdFlow = flowOf(USER_ID),
@@ -264,6 +265,26 @@
             privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
         }
 
+        val SYSTEM_APP = ApplicationInfo().apply {
+            packageName = "system.app"
+            flags = ApplicationInfo.FLAG_SYSTEM
+        }
+
+        val UPDATED_SYSTEM_APP = ApplicationInfo().apply {
+            packageName = "updated.system.app"
+            flags = ApplicationInfo.FLAG_SYSTEM or ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
+        }
+
+        val HOME_APP = ApplicationInfo().apply {
+            packageName = "home.app"
+            flags = ApplicationInfo.FLAG_SYSTEM
+        }
+
+        val IN_LAUMCHER_APP = ApplicationInfo().apply {
+            packageName = "app.in.launcher"
+            flags = ApplicationInfo.FLAG_SYSTEM
+        }
+
         fun resolveInfoOf(packageName: String) = ResolveInfo().apply {
             activityInfo = ActivityInfo().apply {
                 this.packageName = packageName
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index f514487..b1d02f5 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -56,7 +56,7 @@
         viewModel.appListConfig.setIfAbsent(CONFIG)
         viewModel.listModel.setIfAbsent(listModel)
         viewModel.showSystem.setIfAbsent(false)
-        viewModel.option.setIfAbsent(0)
+        viewModel.optionFlow.value = 0
         viewModel.searchQuery.setIfAbsent("")
         viewModel.reloadApps()
         return viewModel
@@ -90,6 +90,8 @@
             userIdFlow: Flow<Int>,
             showSystemFlow: Flow<Boolean>,
         ): Flow<(app: ApplicationInfo) -> Boolean> = flowOf { true }
+
+        override fun getSystemPackageNamesBlocking(config: AppListConfig): Set<String> = setOf()
     }
 
     private object FakeAppRepository : AppRepository {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index 2a1f7a4..d5c7c19 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -29,7 +29,6 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.framework.compose.toState
-import com.android.settingslib.spa.framework.util.StateFlowBridge
 import com.android.settingslib.spa.widget.ui.SpinnerOption
 import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.model.app.AppEntry
@@ -38,6 +37,7 @@
 import com.android.settingslib.spaprivileged.model.app.IAppListViewModel
 import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel
 import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import org.junit.Rule
 import org.junit.Test
@@ -125,7 +125,7 @@
                 bottomPadding = 0.dp,
             ).AppListImpl {
                 object : IAppListViewModel<TestAppRecord> {
-                    override val option: StateFlowBridge<Int?> = StateFlowBridge()
+                    override val optionFlow: MutableStateFlow<Int?> = MutableStateFlow(null)
                     override val spinnerOptionsFlow = flowOf(options.mapIndexed { index, option ->
                         SpinnerOption(id = index, text = option)
                     })
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
index 1f72609..a80061e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
@@ -53,8 +53,8 @@
     static final String PREFIX_BT_SYNC_INTERVAL = "SI:";
     static final String PREFIX_BT_IS_ENCRYPTED = "E:";
     static final String PREFIX_BT_BROADCAST_CODE = "C:";
-    static final String PREFIX_BT_PRESENTATION_DELAY = "D:";
-    static final String PREFIX_BT_SUBGROUPS = "G:";
+    static final String PREFIX_BT_PRESENTATION_DELAY = "PD:";
+    static final String PREFIX_BT_SUBGROUPS = "SG:";
     static final String PREFIX_BT_ANDROID_VERSION = "V:";
 
     // BluetoothLeBroadcastSubgroup
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
index aff9a6e..c61ebc0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
@@ -28,7 +28,9 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -38,6 +40,43 @@
     private static final String METADATA_START = "<";
     private static final String METADATA_END = ">";
     private static final String PATTERN_REGEX = "<(.*?)>";
+    private static final String PATTERN_BT_BROADCAST_METADATA =
+            "T:<(.*?)>;+D:<(.*?)>;+AS:<(.*?)>;+B:<(.*?)>;+SI:<(.*?)>;+E:<(.*?)>;+C:<(.*?)>;"
+                + "+PD:<(.*?)>;+SG:(.*)";
+    private static final String PATTERN_BT_SUBGROUP =
+            "CID:<(.*?)>;+CC:<(.*?);>;+AC:<(.*?);>;+CP:<(.*?)>;+BC:<(.*)>;>;";
+    private static final String PATTERN_BT_CHANNEL = "CI:<(.*?)>;+BCCM:<(.*?);>;";
+
+    /* Index for BluetoothLeBroadcastMetadata */
+    private static int MATCH_INDEX_ADDRESS_TYPE = 1;
+    private static int MATCH_INDEX_DEVICE = 2;
+    private static int MATCH_INDEX_ADVERTISING_SID = 3;
+    private static int MATCH_INDEX_BROADCAST_ID = 4;
+    private static int MATCH_INDEX_SYNC_INTERVAL = 5;
+    private static int MATCH_INDEX_IS_ENCRYPTED = 6;
+    private static int MATCH_INDEX_BROADCAST_CODE = 7;
+    private static int MATCH_INDEX_PRESENTATION_DELAY = 8;
+    private static int MATCH_INDEX_SUBGROUPS = 9;
+
+    /* Index for BluetoothLeBroadcastSubgroup */
+    private static int MATCH_INDEX_CODEC_ID = 1;
+    private static int MATCH_INDEX_CODEC_CONFIG = 2;
+    private static int MATCH_INDEX_AUDIO_CONTENT = 3;
+    private static int MATCH_INDEX_CHANNEL_PREF = 4;
+    private static int MATCH_INDEX_BROADCAST_CHANNEL = 5;
+
+    /* Index for BluetoothLeAudioCodecConfigMetadata */
+    private static int LIST_INDEX_AUDIO_LOCATION = 0;
+    private static int LIST_INDEX_CODEC_CONFIG_RAW_METADATA = 1;
+
+    /* Index for BluetoothLeAudioContentMetadata */
+    private static int LIST_INDEX_PROGRAM_INFO = 0;
+    private static int LIST_INDEX_LANGUAGE = 1;
+    private static int LIST_INDEX_AUDIO_CONTENT_RAW_METADATA = 2;
+
+    /* Index for BluetoothLeBroadcastChannel */
+    private static int MATCH_INDEX_CHANNEL_INDEX = 1;
+    private static int MATCH_INDEX_CHANNEL_CODEC_CONFIG = 2;
 
     private BluetoothLeBroadcastSubgroup mSubgroup;
     private List<BluetoothLeBroadcastSubgroup> mSubgroupList;
@@ -55,17 +94,20 @@
     private byte[] mBroadcastCode;
 
     // BluetoothLeBroadcastSubgroup
-    private long mCodecId;
+    private int mCodecId;
     private BluetoothLeAudioContentMetadata mContentMetadata;
     private BluetoothLeAudioCodecConfigMetadata mConfigMetadata;
-    private BluetoothLeBroadcastChannel mChannel;
+    private Boolean mNoChannelPreference;
+    private List<BluetoothLeBroadcastChannel> mChannel;
 
     // BluetoothLeAudioCodecConfigMetadata
     private long mAudioLocation;
+    private byte[] mCodecConfigMetadata;
 
     // BluetoothLeAudioContentMetadata
     private String mLanguage;
     private String mProgramInfo;
+    private byte[] mAudioContentMetadata;
 
     // BluetoothLeBroadcastChannel
     private boolean mIsSelected;
@@ -135,6 +177,7 @@
         for (BluetoothLeBroadcastSubgroup subgroup: subgroupList) {
             String audioCodec = convertAudioCodecConfigToString(subgroup.getCodecSpecificConfig());
             String audioContent = convertAudioContentToString(subgroup.getContentMetadata());
+            boolean hasChannelPreference = subgroup.hasChannelPreference();
             String channels = convertChannelToString(subgroup.getChannels());
             subgroupString = new StringBuilder()
                     .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_ID)
@@ -146,6 +189,9 @@
                     .append(BluetoothBroadcastUtils.PREFIX_BTSG_AUDIO_CONTENT)
                     .append(METADATA_START).append(audioContent).append(METADATA_END)
                     .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                    .append(BluetoothBroadcastUtils.PREFIX_BTSG_CHANNEL_PREF)
+                    .append(METADATA_START).append(hasChannelPreference).append(METADATA_END)
+                    .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
                     .append(BluetoothBroadcastUtils.PREFIX_BTSG_BROADCAST_CHANNEL)
                     .append(METADATA_START).append(channels).append(METADATA_END)
                     .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
@@ -211,26 +257,35 @@
         if (DEBUG) {
             Log.d(TAG, "Convert " + qrCodeString + "to BluetoothLeBroadcastMetadata");
         }
-        Pattern pattern = Pattern.compile(PATTERN_REGEX);
+
+        Pattern pattern = Pattern.compile(PATTERN_BT_BROADCAST_METADATA);
         Matcher match = pattern.matcher(qrCodeString);
         if (match.find()) {
-            ArrayList<String> resultList = new ArrayList<>();
-            resultList.add(match.group(1));
-            mSourceAddressType = Integer.parseInt(resultList.get(0));
+            mSourceAddressType = Integer.parseInt(match.group(MATCH_INDEX_ADDRESS_TYPE));
             mSourceDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
-                    resultList.get(1));
-            mSourceAdvertisingSid = Integer.parseInt(resultList.get(2));
-            mBroadcastId = Integer.parseInt(resultList.get(3));
-            mPaSyncInterval = Integer.parseInt(resultList.get(4));
-            mIsEncrypted = Boolean.valueOf(resultList.get(5));
-            mBroadcastCode = resultList.get(6).getBytes();
-            mPresentationDelayMicros = Integer.parseInt(resultList.get(7));
-            mSubgroup = convertToSubgroup(resultList.get(8));
+                    match.group(MATCH_INDEX_DEVICE));
+            mSourceAdvertisingSid = Integer.parseInt(match.group(MATCH_INDEX_ADVERTISING_SID));
+            mBroadcastId = Integer.parseInt(match.group(MATCH_INDEX_BROADCAST_ID));
+            mPaSyncInterval = Integer.parseInt(match.group(MATCH_INDEX_SYNC_INTERVAL));
+            mIsEncrypted = Boolean.valueOf(match.group(MATCH_INDEX_IS_ENCRYPTED));
+            mBroadcastCode = match.group(MATCH_INDEX_BROADCAST_CODE).getBytes();
+            mPresentationDelayMicros =
+                  Integer.parseInt(match.group(MATCH_INDEX_PRESENTATION_DELAY));
 
             if (DEBUG) {
-                Log.d(TAG, "Converted qrCodeString result: " + match.group());
+                Log.d(TAG, "Converted qrCodeString result: "
+                        + " ,Type = " + mSourceAddressType
+                        + " ,Device = " + mSourceDevice
+                        + " ,AdSid = " + mSourceAdvertisingSid
+                        + " ,BroadcastId = " + mBroadcastId
+                        + " ,paSync = " + mPaSyncInterval
+                        + " ,encrypted = " + mIsEncrypted
+                        + " ,BroadcastCode = " + Arrays.toString(mBroadcastCode)
+                        + " ,delay = " + mPresentationDelayMicros);
             }
 
+            mSubgroup = convertToSubgroup(match.group(MATCH_INDEX_SUBGROUPS));
+
             return new BluetoothLeBroadcastMetadata.Builder()
                     .setSourceDevice(mSourceDevice, mSourceAddressType)
                     .setSourceAdvertisingSid(mSourceAdvertisingSid)
@@ -254,26 +309,26 @@
         if (DEBUG) {
             Log.d(TAG, "Convert " + subgroupString + "to BluetoothLeBroadcastSubgroup");
         }
-        Pattern pattern = Pattern.compile(PATTERN_REGEX);
+        Pattern pattern = Pattern.compile(PATTERN_BT_SUBGROUP);
         Matcher match = pattern.matcher(subgroupString);
         if (match.find()) {
-            ArrayList<String> resultList = new ArrayList<>();
-            resultList.add(match.group(1));
-            mCodecId = Long.getLong(resultList.get(0));
-            mConfigMetadata = convertToConfigMetadata(resultList.get(1));
-            mContentMetadata = convertToContentMetadata(resultList.get(2));
-            mChannel = convertToChannel(resultList.get(3), mConfigMetadata);
+            mCodecId = Integer.parseInt(match.group(MATCH_INDEX_CODEC_ID));
+            mConfigMetadata = convertToConfigMetadata(match.group(MATCH_INDEX_CODEC_CONFIG));
+            mContentMetadata = convertToContentMetadata(match.group(MATCH_INDEX_AUDIO_CONTENT));
+            mNoChannelPreference = Boolean.valueOf(match.group(MATCH_INDEX_CHANNEL_PREF));
+            mChannel =
+                  convertToChannel(match.group(MATCH_INDEX_BROADCAST_CHANNEL), mConfigMetadata);
 
-            if (DEBUG) {
-                Log.d(TAG, "Converted subgroupString result: " + match.group());
+            BluetoothLeBroadcastSubgroup.Builder subgroupBuilder =
+                    new BluetoothLeBroadcastSubgroup.Builder();
+            subgroupBuilder.setCodecId(mCodecId);
+            subgroupBuilder.setCodecSpecificConfig(mConfigMetadata);
+            subgroupBuilder.setContentMetadata(mContentMetadata);
+
+            for (BluetoothLeBroadcastChannel channel : mChannel) {
+                subgroupBuilder.addChannel(channel);
             }
-
-            return new BluetoothLeBroadcastSubgroup.Builder()
-                    .setCodecId(mCodecId)
-                    .setCodecSpecificConfig(mConfigMetadata)
-                    .setContentMetadata(mContentMetadata)
-                    .addChannel(mChannel)
-                    .build();
+            return subgroupBuilder.build();
         } else {
             if (DEBUG) {
                 Log.d(TAG,
@@ -291,15 +346,17 @@
         }
         Pattern pattern = Pattern.compile(PATTERN_REGEX);
         Matcher match = pattern.matcher(configMetadataString);
-        if (match.find()) {
-            ArrayList<String> resultList = new ArrayList<>();
+        ArrayList<String> resultList = new ArrayList<>();
+        while (match.find()) {
             resultList.add(match.group(1));
-            mAudioLocation = Long.getLong(resultList.get(0));
-
-            if (DEBUG) {
-                Log.d(TAG, "Converted configMetadataString result: " + match.group());
-            }
-
+            Log.d(TAG, "Codec Config match : " + match.group(1));
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Converted configMetadataString result: " + resultList.size());
+        }
+        if (resultList.size() > 0) {
+            mAudioLocation = Long.parseLong(resultList.get(LIST_INDEX_AUDIO_LOCATION));
+            mCodecConfigMetadata = resultList.get(LIST_INDEX_CODEC_CONFIG_RAW_METADATA).getBytes();
             return new BluetoothLeAudioCodecConfigMetadata.Builder()
                     .setAudioLocation(mAudioLocation)
                     .build();
@@ -319,14 +376,25 @@
         }
         Pattern pattern = Pattern.compile(PATTERN_REGEX);
         Matcher match = pattern.matcher(contentMetadataString);
-        if (match.find()) {
-            ArrayList<String> resultList = new ArrayList<>();
+        ArrayList<String> resultList = new ArrayList<>();
+        while (match.find()) {
+            Log.d(TAG, "Audio Content match : " + match.group(1));
             resultList.add(match.group(1));
-            mProgramInfo = resultList.get(0);
-            mLanguage = resultList.get(1);
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Converted contentMetadataString result: " + resultList.size());
+        }
+        if (resultList.size() > 0) {
+            mProgramInfo = resultList.get(LIST_INDEX_PROGRAM_INFO);
+            mLanguage = resultList.get(LIST_INDEX_LANGUAGE);
+            mAudioContentMetadata =
+                  resultList.get(LIST_INDEX_AUDIO_CONTENT_RAW_METADATA).getBytes();
 
-            if (DEBUG) {
-                Log.d(TAG, "Converted contentMetadataString result: " + match.group());
+            /* TODO(b/265253566) : Need to set the default value for language when the user starts
+            *  the broadcast.
+            */
+            if (mLanguage.equals("null")) {
+                mLanguage = "eng";
             }
 
             return new BluetoothLeAudioContentMetadata.Builder()
@@ -342,28 +410,34 @@
         }
     }
 
-    private BluetoothLeBroadcastChannel convertToChannel(String channelString,
+    private List<BluetoothLeBroadcastChannel> convertToChannel(String channelString,
             BluetoothLeAudioCodecConfigMetadata configMetadata) {
         if (DEBUG) {
             Log.d(TAG, "Convert " + channelString + "to BluetoothLeBroadcastChannel");
         }
-        Pattern pattern = Pattern.compile(PATTERN_REGEX);
+        Pattern pattern = Pattern.compile(PATTERN_BT_CHANNEL);
         Matcher match = pattern.matcher(channelString);
-        if (match.find()) {
-            ArrayList<String> resultList = new ArrayList<>();
-            resultList.add(match.group(1));
-            mIsSelected = Boolean.valueOf(resultList.get(0));
-            mChannelIndex = Integer.parseInt(resultList.get(1));
+        Map<Integer, BluetoothLeAudioCodecConfigMetadata> channel =
+                new HashMap<Integer, BluetoothLeAudioCodecConfigMetadata>();
+        while (match.find()) {
+            channel.put(Integer.parseInt(match.group(MATCH_INDEX_CHANNEL_INDEX)),
+                    convertToConfigMetadata(match.group(MATCH_INDEX_CHANNEL_CODEC_CONFIG)));
+        }
 
-            if (DEBUG) {
-                Log.d(TAG, "Converted channelString result: " + match.group());
+        if (channel.size() > 0) {
+            mIsSelected = false;
+            ArrayList<BluetoothLeBroadcastChannel> broadcastChannelList = new ArrayList<>();
+            for (Map.Entry<Integer, BluetoothLeAudioCodecConfigMetadata> entry :
+                    channel.entrySet()) {
+
+                broadcastChannelList.add(
+                        new BluetoothLeBroadcastChannel.Builder()
+                            .setSelected(mIsSelected)
+                            .setChannelIndex(entry.getKey())
+                            .setCodecMetadata(entry.getValue())
+                            .build());
             }
-
-            return new BluetoothLeBroadcastChannel.Builder()
-                    .setSelected(mIsSelected)
-                    .setChannelIndex(mChannelIndex)
-                    .setCodecMetadata(configMetadata)
-                    .build();
+            return broadcastChannelList;
         } else {
             if (DEBUG) {
                 Log.d(TAG,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 5ee36f3..4365a9b 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -695,7 +695,6 @@
                  Settings.Secure.BACKUP_AUTO_RESTORE,
                  Settings.Secure.BACKUP_ENABLED,
                  Settings.Secure.BACKUP_PROVISIONED,
-                 Settings.Secure.BACKUP_SCHEDULING_ENABLED,
                  Settings.Secure.BACKUP_TRANSPORT,
                  Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT,
                  Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED, // Candidate for backup?
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index d716b32..c641a85 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -620,6 +620,8 @@
     <uses-permission android:name="android.permission.MANAGE_HOTWORD_DETECTION" />
     <uses-permission android:name="android.permission.BIND_HOTWORD_DETECTION_SERVICE" />
 
+    <uses-permission android:name="android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE" />
+
     <!-- Permission required for CTS test - KeyguardLockedStateApiTest -->
     <uses-permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
 
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 854d96e..697e181 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -272,6 +272,7 @@
         "LowLightDreamLib",
         "motion_tool_lib",
         "androidx.core_core-animation-testing-nodeps",
+        "androidx.compose.ui_ui",
     ],
 }
 
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 90d3488..115cf792 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -343,6 +343,7 @@
     <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
+    <protected-broadcast android:name="com.android.systemui.STARTED" />
 
     <application
         android:name=".SystemUIApplication"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index a450d3a..9a9236b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -791,13 +791,13 @@
         // Move the drawing of the source in the overlay of this dialog, then animate. We trigger a
         // one-off synchronization to make sure that this is done in sync between the two different
         // windows.
+        controller.startDrawingInOverlayOf(decorView)
         synchronizeNextDraw(
             then = {
                 isSourceDrawnInDialog = true
                 maybeStartLaunchAnimation()
             }
         )
-        controller.startDrawingInOverlayOf(decorView)
     }
 
     /**
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 0028d13..dfac02d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -195,14 +195,16 @@
         backgroundDrawable = WrappedDrawable(background)
         backgroundView?.background = backgroundDrawable
 
+        // Delay the calls to `ghostedView.setVisibility()` during the animation. This must be
+        // called before `GhostView.addGhost()` is called because the latter will change the
+        // *transition* visibility, which won't be blocked and will affect the normal View
+        // visibility that is saved by `setShouldBlockVisibilityChanges()` for a later restoration.
+        (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+
         // Create a ghost of the view that will be moving and fading out. This allows to fade out
         // the content before fading out the background.
         ghostView = GhostView.addGhost(ghostedView, launchContainer)
 
-        // The ghost was just created, so ghostedView is currently invisible. We need to make sure
-        // that it stays invisible as long as we are animating.
-        (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
-
         val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
         matrix.getValues(initialGhostViewMatrixValues)
 
@@ -297,14 +299,19 @@
         backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
 
         GhostView.removeGhost(ghostedView)
-        (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
         launchContainerOverlay.remove(backgroundView)
 
-        // Make sure that the view is considered VISIBLE by accessibility by first making it
-        // INVISIBLE then VISIBLE (see b/204944038#comment17 for more info).
-        ghostedView.visibility = View.INVISIBLE
-        ghostedView.visibility = View.VISIBLE
-        ghostedView.invalidate()
+        if (ghostedView is LaunchableView) {
+            // Restore the ghosted view visibility.
+            ghostedView.setShouldBlockVisibilityChanges(false)
+        } else {
+            // Make the ghosted view visible. We ensure that the view is considered VISIBLE by
+            // accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17
+            // for more info).
+            ghostedView.visibility = View.INVISIBLE
+            ghostedView.visibility = View.VISIBLE
+            ghostedView.invalidate()
+        }
     }
 
     companion object {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
index 67b59e0..774255b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
@@ -21,15 +21,19 @@
 /** A view that can expand/launch into an app or a dialog. */
 interface LaunchableView {
     /**
-     * Set whether this view should block/postpone all visibility changes. This ensures that this
-     * view:
+     * Set whether this view should block/postpone all calls to [View.setVisibility]. This ensures
+     * that this view:
      * - remains invisible during the launch animation given that it is ghosted and already drawn
      * somewhere else.
      * - remains invisible as long as a dialog expanded from it is shown.
      * - restores its expected visibility once the dialog expanded from it is dismissed.
      *
-     * Note that when this is set to true, both the [normal][android.view.View.setVisibility] and
-     * [transition][android.view.View.setTransitionVisibility] visibility changes must be blocked.
+     * When `setShouldBlockVisibilityChanges(false)` is called, then visibility of the View should
+     * be restored to its expected value, i.e. it should have the visibility of the last call to
+     * `View.setVisibility()` that was made after `setShouldBlockVisibilityChanges(true)`, if any,
+     * or the original view visibility otherwise.
+     *
+     * Note that calls to [View.setTransitionVisibility] shouldn't be blocked.
      *
      * @param block whether we should block/postpone all calls to `setVisibility` and
      * `setTransitionVisibility`.
@@ -46,27 +50,31 @@
      * super.setVisibility(visibility).
      */
     private val superSetVisibility: (Int) -> Unit,
-
-    /**
-     * The lambda that should set the actual transition visibility of [view], usually by calling
-     * super.setTransitionVisibility(visibility).
-     */
-    private val superSetTransitionVisibility: (Int) -> Unit,
-) {
+) : LaunchableView {
     private var blockVisibilityChanges = false
     private var lastVisibility = view.visibility
 
     /** Call this when [LaunchableView.setShouldBlockVisibilityChanges] is called. */
-    fun setShouldBlockVisibilityChanges(block: Boolean) {
+    override fun setShouldBlockVisibilityChanges(block: Boolean) {
         if (block == blockVisibilityChanges) {
             return
         }
 
         blockVisibilityChanges = block
         if (block) {
+            // Save the current visibility for later.
             lastVisibility = view.visibility
         } else {
-            superSetVisibility(lastVisibility)
+            // Restore the visibility. To avoid accessibility issues, we change the visibility twice
+            // which makes sure that we trigger a visibility flag change (see b/204944038#comment17
+            // for more info).
+            if (lastVisibility == View.VISIBLE) {
+                superSetVisibility(View.INVISIBLE)
+                superSetVisibility(View.VISIBLE)
+            } else {
+                superSetVisibility(View.VISIBLE)
+                superSetVisibility(lastVisibility)
+            }
         }
     }
 
@@ -79,16 +87,4 @@
 
         superSetVisibility(visibility)
     }
-
-    /** Call this when [View.setTransitionVisibility] is called. */
-    fun setTransitionVisibility(visibility: Int) {
-        if (blockVisibilityChanges) {
-            // View.setTransitionVisibility just sets the visibility flag, so we don't have to save
-            // the transition visibility separately from the normal visibility.
-            lastVisibility = visibility
-            return
-        }
-
-        superSetTransitionVisibility(visibility)
-    }
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
index 964ef8c..46d5a5c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -34,23 +34,29 @@
     override val sourceIdentity: Any = source
 
     override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
+        // Delay the calls to `source.setVisibility()` during the animation. This must be called
+        // before `GhostView.addGhost()` is called because the latter will change the *transition*
+        // visibility, which won't be blocked and will affect the normal View visibility that is
+        // saved by `setShouldBlockVisibilityChanges()` for a later restoration.
+        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+
         // Create a temporary ghost of the source (which will make it invisible) and add it
         // to the host dialog.
         GhostView.addGhost(source, viewGroup)
-
-        // The ghost of the source was just created, so the source is currently invisible.
-        // We need to make sure that it stays invisible as long as the dialog is shown or
-        // animating.
-        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
     }
 
     override fun stopDrawingInOverlay() {
         // Note: here we should remove the ghost from the overlay, but in practice this is
-        // already done by the launch controllers created below.
+        // already done by the launch controller created below.
 
-        // Make sure we allow the source to change its visibility again.
-        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
-        source.visibility = View.VISIBLE
+        if (source is LaunchableView) {
+            // Make sure we allow the source to change its visibility again and restore its previous
+            // value.
+            source.setShouldBlockVisibilityChanges(false)
+        } else {
+            // We made the source invisible earlier, so let's make it visible again.
+            source.visibility = View.VISIBLE
+        }
     }
 
     override fun createLaunchController(): LaunchAnimator.Controller {
@@ -67,10 +73,14 @@
             override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
                 delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
 
-                // We hide the source when the dialog is showing. We will make this view
-                // visible again when dismissing the dialog. This does nothing if the source
-                // implements [LaunchableView], as it's already INVISIBLE in that case.
-                source.visibility = View.INVISIBLE
+                // At this point the view visibility is restored by the delegate, so we delay the
+                // visibility changes again and make it invisible while the dialog is shown.
+                if (source is LaunchableView) {
+                    source.setShouldBlockVisibilityChanges(true)
+                    source.setTransitionVisibility(View.INVISIBLE)
+                } else {
+                    source.visibility = View.INVISIBLE
+                }
             }
         }
     }
@@ -90,13 +100,15 @@
     }
 
     override fun onExitAnimationCancelled() {
-        // Make sure we allow the source to change its visibility again.
-        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
-
-        // If the view is invisible it's probably because of us, so we make it visible
-        // again.
-        if (source.visibility == View.INVISIBLE) {
-            source.visibility = View.VISIBLE
+        if (source is LaunchableView) {
+            // Make sure we allow the source to change its visibility again.
+            source.setShouldBlockVisibilityChanges(false)
+        } else {
+            // If the view is invisible it's probably because of us, so we make it visible
+            // again.
+            if (source.visibility == View.INVISIBLE) {
+                source.visibility = View.VISIBLE
+            }
         }
     }
 
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index 259f0ed..4e96dda 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -41,6 +41,7 @@
 import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.movableContentOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCompositionContext
@@ -73,6 +74,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.ViewTreeLifecycleOwner
 import androidx.lifecycle.ViewTreeViewModelStoreOwner
+import com.android.compose.runtime.movableContentOf
 import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.LaunchAnimator
 import kotlin.math.max
@@ -170,25 +172,25 @@
     val contentColor = controller.contentColor
     val shape = controller.shape
 
-    // TODO(b/230830644): Use movableContentOf to preserve the content state instead once the
-    // Compose libraries have been updated and include aosp/2163631.
     val wrappedContent =
-        @Composable { controller: ExpandableController ->
-            CompositionLocalProvider(
-                LocalContentColor provides contentColor,
-            ) {
-                // We make sure that the content itself (wrapped by the background) is at least
-                // 40.dp, which is the same as the M3 buttons. This applies even if onClick is
-                // null, to make it easier to write expandables that are sometimes clickable and
-                // sometimes not. There shouldn't be any Expandable smaller than 40dp because if
-                // the expandable is not clickable directly, then something in its content should
-                // be (and with a size >= 40dp).
-                val minSize = 40.dp
-                Box(
-                    Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
-                    contentAlignment = Alignment.Center,
+        remember(content) {
+            movableContentOf { expandable: Expandable ->
+                CompositionLocalProvider(
+                    LocalContentColor provides contentColor,
                 ) {
-                    content(controller.expandable)
+                    // We make sure that the content itself (wrapped by the background) is at least
+                    // 40.dp, which is the same as the M3 buttons. This applies even if onClick is
+                    // null, to make it easier to write expandables that are sometimes clickable and
+                    // sometimes not. There shouldn't be any Expandable smaller than 40dp because if
+                    // the expandable is not clickable directly, then something in its content
+                    // should be (and with a size >= 40dp).
+                    val minSize = 40.dp
+                    Box(
+                        Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
+                        contentAlignment = Alignment.Center,
+                    ) {
+                        content(expandable)
+                    }
                 }
             }
         }
@@ -270,7 +272,7 @@
                     .onGloballyPositioned {
                         controller.boundsInComposeViewRoot.value = it.boundsInRoot()
                     }
-            ) { wrappedContent(controller) }
+            ) { wrappedContent(controller.expandable) }
         }
         else -> {
             val clickModifier =
@@ -301,7 +303,7 @@
                         controller.boundsInComposeViewRoot.value = it.boundsInRoot()
                     },
             ) {
-                wrappedContent(controller)
+                wrappedContent(controller.expandable)
             }
         }
     }
@@ -315,7 +317,7 @@
     animatorState: State<LaunchAnimator.State?>,
     overlay: ViewGroupOverlay,
     controller: ExpandableControllerImpl,
-    content: @Composable (ExpandableController) -> Unit,
+    content: @Composable (Expandable) -> Unit,
     composeViewRoot: View,
     onOverlayComposeViewChanged: (View?) -> Unit,
     density: Density,
@@ -370,7 +372,7 @@
                             // We center the content in the expanding container.
                             contentAlignment = Alignment.Center,
                         ) {
-                            Box(contentModifier) { content(controller) }
+                            Box(contentModifier) { content(controller.expandable) }
                         }
                     }
                 }
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index 6e728ce..e253fb9 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -17,13 +17,21 @@
 
 package com.android.systemui.compose
 
+import android.content.Context
+import android.view.View
 import androidx.activity.ComponentActivity
+import androidx.lifecycle.LifecycleOwner
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 
 /** The Compose facade, when Compose is *not* available. */
 object ComposeFacade : BaseComposeFacade {
     override fun isComposeAvailable(): Boolean = false
 
+    override fun composeInitializer(): ComposeInitializer {
+        throwComposeUnavailableError()
+    }
+
     override fun setPeopleSpaceActivityContent(
         activity: ComponentActivity,
         viewModel: PeopleViewModel,
@@ -32,7 +40,15 @@
         throwComposeUnavailableError()
     }
 
-    private fun throwComposeUnavailableError() {
+    override fun createFooterActionsView(
+        context: Context,
+        viewModel: FooterActionsViewModel,
+        qsVisibilityLifecycleOwner: LifecycleOwner
+    ): View {
+        throwComposeUnavailableError()
+    }
+
+    private fun throwComposeUnavailableError(): Nothing {
         error(
             "Compose is not available. Make sure to check isComposeAvailable() before calling any" +
                 " other function on ComposeFacade."
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 6991ff8..1ea18fe 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -16,16 +16,24 @@
 
 package com.android.systemui.compose
 
+import android.content.Context
+import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
+import androidx.compose.ui.platform.ComposeView
+import androidx.lifecycle.LifecycleOwner
 import com.android.compose.theme.PlatformTheme
 import com.android.systemui.people.ui.compose.PeopleScreen
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import com.android.systemui.qs.footer.ui.compose.FooterActions
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 
 /** The Compose facade, when Compose is available. */
 object ComposeFacade : BaseComposeFacade {
     override fun isComposeAvailable(): Boolean = true
 
+    override fun composeInitializer(): ComposeInitializer = ComposeInitializerImpl
+
     override fun setPeopleSpaceActivityContent(
         activity: ComponentActivity,
         viewModel: PeopleViewModel,
@@ -33,4 +41,14 @@
     ) {
         activity.setContent { PlatformTheme { PeopleScreen(viewModel, onResult) } }
     }
+
+    override fun createFooterActionsView(
+        context: Context,
+        viewModel: FooterActionsViewModel,
+        qsVisibilityLifecycleOwner: LifecycleOwner,
+    ): View {
+        return ComposeView(context).apply {
+            setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
new file mode 100644
index 0000000..772c891
--- /dev/null
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 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.compose
+
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewTreeLifecycleOwner
+import androidx.savedstate.SavedStateRegistry
+import androidx.savedstate.SavedStateRegistryController
+import androidx.savedstate.SavedStateRegistryOwner
+import com.android.compose.animation.ViewTreeSavedStateRegistryOwner
+import com.android.systemui.lifecycle.ViewLifecycleOwner
+
+internal object ComposeInitializerImpl : ComposeInitializer {
+    override fun onAttachedToWindow(root: View) {
+        if (ViewTreeLifecycleOwner.get(root) != null) {
+            error("root $root already has a LifecycleOwner")
+        }
+
+        val parent = root.parent
+        if (parent is View && parent.id != android.R.id.content) {
+            error(
+                "ComposeInitializer.onAttachedToWindow(View) must be called on the content child." +
+                    "Outside of activities and dialogs, this is usually the top-most View of a " +
+                    "window."
+            )
+        }
+
+        // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root] is
+        // both visible and focused.
+        val lifecycleOwner = ViewLifecycleOwner(root)
+
+        // We create a trivial implementation of [SavedStateRegistryOwner] that does not do any save
+        // or restore because SystemUI process is always running and top-level windows using this
+        // initializer are created once, when the process is started.
+        val savedStateRegistryOwner =
+            object : SavedStateRegistryOwner {
+                private val savedStateRegistry =
+                    SavedStateRegistryController.create(this).apply { performRestore(null) }
+
+                override fun getLifecycle(): Lifecycle = lifecycleOwner.lifecycle
+
+                override fun getSavedStateRegistry(): SavedStateRegistry {
+                    return savedStateRegistry.savedStateRegistry
+                }
+            }
+
+        // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner]
+        // because `onCreate` might move the lifecycle state to STARTED which will make
+        // [SavedStateRegistryController.performRestore] throw.
+        lifecycleOwner.onCreate()
+
+        // Set the owners on the root. They will be reused by any ComposeView inside the root
+        // hierarchy.
+        ViewTreeLifecycleOwner.set(root, lifecycleOwner)
+        ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner)
+    }
+
+    override fun onDetachedFromWindow(root: View) {
+        (ViewTreeLifecycleOwner.get(root) as ViewLifecycleOwner).onDestroy()
+        ViewTreeLifecycleOwner.set(root, null)
+        ViewTreeSavedStateRegistryOwner.set(root, null)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt
new file mode 100644
index 0000000..9eb78e1
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.compose.modifiers
+
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.testTagsAsResourceId
+
+/**
+ * Set a test tag on this node so that it is associated with [resId]. This node will then be
+ * accessible by integration tests using `sysuiResSelector(resId)`.
+ */
+@OptIn(ExperimentalComposeUiApi::class)
+fun Modifier.sysuiResTag(resId: String): Modifier {
+    return this.semantics { testTagsAsResourceId = true }.testTag("com.android.systemui:id/$resId")
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
index 23dacf9..3eeadae 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -51,6 +51,7 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.R
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 
@@ -110,7 +111,9 @@
     recentTiles: List<PeopleTileViewModel>,
     onTileClicked: (PeopleTileViewModel) -> Unit,
 ) {
-    Column {
+    Column(
+        Modifier.sysuiResTag("top_level_with_conversations"),
+    ) {
         Column(
             Modifier.fillMaxWidth().padding(PeopleSpacePadding),
             horizontalAlignment = Alignment.CenterHorizontally,
@@ -132,7 +135,7 @@
         }
 
         LazyColumn(
-            Modifier.fillMaxWidth(),
+            Modifier.fillMaxWidth().sysuiResTag("scroll_view"),
             contentPadding =
                 PaddingValues(
                     top = 16.dp,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 5c5ceef..349f5c3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -73,6 +73,7 @@
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
@@ -180,9 +181,9 @@
 
             security?.let { SecurityButton(it, Modifier.weight(1f)) }
             foregroundServices?.let { ForegroundServicesButton(it) }
-            userSwitcher?.let { IconButton(it) }
-            IconButton(viewModel.settings)
-            viewModel.power?.let { IconButton(it) }
+            userSwitcher?.let { IconButton(it, Modifier.sysuiResTag("multi_user_switch")) }
+            IconButton(viewModel.settings, Modifier.sysuiResTag("settings_button_container"))
+            viewModel.power?.let { IconButton(it, Modifier.sysuiResTag("pm_lite")) }
         }
     }
 }
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index a4ee62c..10bb00c 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -24,12 +24,44 @@
 # TODO(b/264686688): Handle these cases with more targeted annotations.
 -keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
   private com.android.keyguard.KeyguardUpdateMonitorCallback *;
+  private com.android.systemui.privacy.PrivacyConfig$Callback *;
   private com.android.systemui.privacy.PrivacyItemController$Callback *;
   private com.android.systemui.settings.UserTracker$Callback *;
   private com.android.systemui.statusbar.phone.StatusBarWindowCallback *;
   private com.android.systemui.util.service.Observer$Callback *;
   private com.android.systemui.util.service.ObservableServiceConnection$Callback *;
 }
+# Note that these rules are temporary companions to the above rules, required
+# for cases like Kotlin where fields with anonymous types use the anonymous type
+# rather than the supertype.
+-if class * extends com.android.keyguard.KeyguardUpdateMonitorCallback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+  <1> *;
+}
+-if class * extends com.android.systemui.privacy.PrivacyConfig$Callback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+  <1> *;
+}
+-if class * extends com.android.systemui.privacy.PrivacyItemController$Callback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+  <1> *;
+}
+-if class * extends com.android.systemui.settings.UserTracker$Callback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+  <1> *;
+}
+-if class * extends com.android.systemui.statusbar.phone.StatusBarWindowCallback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+  <1> *;
+}
+-if class * extends com.android.systemui.util.service.Observer$Callback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+  <1> *;
+}
+-if class * extends com.android.systemui.util.service.ObservableServiceConnection$Callback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+  <1> *;
+}
 
 -keepclasseswithmembers class * {
     public <init>(android.content.Context, android.util.AttributeSet);
diff --git a/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml
index 4da47af..9fea32c 100644
--- a/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml
+++ b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml
@@ -1,27 +1,22 @@
-<?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.

--->

-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

-    <item>

-        <shape android:shape="rectangle">

-            <solid android:color="@color/accessibility_magnifier_bg" />

-            <corners android:radius="24dp" />

-            <stroke

-                android:color="@color/accessibility_magnifier_bg_stroke"

-                android:width="1dp" />

-        </shape>

-    </item>

-</layer-list>
\ No newline at end of file
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/magnification_setting_background_corner_radius" />
+    <solid android:color="?androidprv:attr/colorSurface" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_button_done_bg.xml
similarity index 65%
rename from packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml
rename to packages/SystemUI/res/drawable/accessibility_window_magnification_button_done_bg.xml
index eefe364..5c2bf3e 100644
--- a/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_button_done_bg.xml
@@ -14,13 +14,13 @@
     limitations under the License.

 -->

 <shape xmlns:android="http://schemas.android.com/apk/res/android"

-    android:shape="oval">

-    <solid android:color="@color/accessibility_window_magnifier_button_bg" />

+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"

+    android:shape="rectangle">

     <size

-        android:width="40dp"

-        android:height="40dp"/>

-    <corners android:radius="2dp"/>

+        android:width="@dimen/magnification_setting_button_done_width"

+        android:height="@dimen/magnification_setting_button_done_height"/>

+    <corners android:radius="@dimen/magnification_setting_button_done_corner_radius"/>

     <stroke

-        android:color="@color/accessibility_window_magnifier_button_bg_stroke"

+        android:color="?androidprv:attr/colorAccent"

         android:width="1dp" />

  </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index a3c0554..714d551 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -16,12 +16,13 @@
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/magnifier_panel_view"
-    android:layout_width="@dimen/magnification_max_size"
-    android:layout_height="match_parent"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
     android:background="@drawable/accessibility_magnification_setting_view_bg"
-    android:orientation="vertical">
+    android:orientation="vertical"
+    android:padding="@dimen/magnification_setting_background_padding">
     <LinearLayout
-        android:layout_width="@dimen/magnification_max_size"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal">
         <TextView
@@ -29,27 +30,23 @@
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:text="@string/accessibility_magnifier_size"
-            android:textAppearance="?android:attr/textAppearanceListItem"
-            android:textColor="?android:attr/textColorAlertDialogListItem"
+            android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
             android:focusable="true"
-            android:layout_gravity="center_vertical|left"
-            android:layout_marginStart="20dp"/>
+            android:layout_gravity="center_vertical|left" />
 
         <Button
             android:id="@+id/magnifier_edit_button"
-            android:background="@drawable/accessibility_magnification_setting_view_btn_bg"
+            android:background="@null"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@string/accessibility_magnifier_edit"
-            android:textAppearance="?android:attr/textAppearanceListItem"
-            android:textColor="?android:attr/textColorAlertDialogListItem"
+            android:textAppearance="@style/TextAppearance.MagnificationSetting.EditButton"
             android:focusable="true"
-            android:layout_gravity="right"
-            android:layout_marginEnd="20dp"/>
+            android:layout_gravity="right" />
     </LinearLayout>
 
     <LinearLayout
-        android:layout_width="@dimen/magnification_max_size"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="horizontal">
         <ImageButton
@@ -58,7 +55,6 @@
             android:layout_height="56dp"
             android:scaleType="center"
             android:layout_weight="1"
-            android:layout_marginStart="12dp"
             android:background="@drawable/accessibility_magnification_setting_view_btn_bg"
             android:padding="@dimen/magnification_switch_button_padding"
             android:src="@drawable/ic_magnification_menu_small"
@@ -95,7 +91,6 @@
             android:layout_height="56dp"
             android:scaleType="center"
             android:layout_weight="1"
-            android:layout_marginEnd="12dp"
             android:background="@drawable/accessibility_magnification_setting_view_btn_bg"
             android:padding="@dimen/magnification_switch_button_padding"
             android:src="@drawable/ic_open_in_new_fullscreen"
@@ -107,68 +102,53 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
-        android:paddingTop="8dp"
-        android:paddingEnd="20dp"
-        android:paddingStart="20dp"
+        android:layout_marginTop="@dimen/magnification_setting_view_margin"
+        android:layout_marginBottom="@dimen/magnification_setting_view_margin"
         android:focusable="true">
 
-        <LinearLayout
+        <TextView
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="1"
-            android:background="?android:attr/selectableItemBackground"
-            android:ellipsize="marquee"
-            android:gravity="center_vertical"
-            android:minHeight="?android:attr/listPreferredItemHeightSmall"
-            android:orientation="vertical">
-
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:singleLine="true"
-                android:text="@string/accessibility_allow_diagonal_scrolling"
-                android:textAppearance="?android:attr/textAppearanceListItem"
-                android:textColor="?android:attr/textColorAlertDialogListItem" />
-        </LinearLayout>
+            android:singleLine="true"
+            android:text="@string/accessibility_allow_diagonal_scrolling"
+            android:textAppearance="@style/TextAppearance.MagnificationSetting.Title" />
 
         <Switch
             android:id="@+id/magnifier_horizontal_lock_switch"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="right|center"
-            android:theme="@android:style/Theme.DeviceDefault.DayNight"/>
+            android:layout_gravity="right" />
     </LinearLayout>
 
     <TextView
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:text="@string/accessibility_magnification_zoom"
-        android:textAppearance="?android:attr/textAppearanceListItem"
-        android:textColor="?android:attr/textColorAlertDialogListItem"
-        android:focusable="true"
-        android:layout_marginStart="20dp"
-        android:paddingTop="2dp"
-        android:paddingBottom="10dp"/>
+        android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
+        android:focusable="true" />
 
     <SeekBar
         android:id="@+id/magnifier_zoom_seekbar"
         android:layout_height="wrap_content"
         android:layout_width="match_parent"
+        android:layout_marginTop="@dimen/magnification_setting_seekbar_margin"
         android:progress="0"
-        android:max="6"
-        android:layout_marginEnd="20dp"
-        android:theme="@android:style/Theme.DeviceDefault.DayNight"/>
-
+        android:max="6" />
 
     <Button
-        android:id="@+id/magnifier_close_button"
-        android:background="@drawable/accessibility_magnification_setting_view_btn_bg"
-        android:layout_width="wrap_content"
+        android:id="@+id/magnifier_done_button"
+        android:background="@drawable/accessibility_window_magnification_button_done_bg"
+        android:minHeight="@dimen/magnification_setting_button_done_height"
+        android:layout_width="@dimen/magnification_setting_button_done_width"
         android:layout_height="wrap_content"
-        android:text="@string/accessibility_magnification_close"
-        android:textAppearance="?android:attr/textAppearanceListItem"
-        android:textColor="?android:attr/textColorAlertDialogListItem"
+        android:text="@string/accessibility_magnification_done"
+        android:textAppearance="@style/TextAppearance.MagnificationSetting.DoneButton"
         android:focusable="true"
         android:layout_gravity="center_horizontal"
-        android:paddingBottom="24dp"/>
+        android:layout_marginTop="@dimen/magnification_setting_view_margin"
+        android:paddingLeft="@dimen/magnification_setting_button_done_padding_horizontal"
+        android:paddingRight="@dimen/magnification_setting_button_done_padding_horizontal"
+        android:paddingTop="@dimen/magnification_setting_button_done_padding_vertical"
+        android:paddingBottom="@dimen/magnification_setting_button_done_padding_vertical" />
 </LinearLayout>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index ba6977a..5bb96c4 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -175,9 +175,7 @@
     <color name="accessibility_magnifier_bg">#FCFCFC</color>
     <color name="accessibility_magnifier_bg_stroke">#E0E0E0</color>
     <color name="accessibility_magnifier_icon_color">#252525</color>
-    <color name="accessibility_window_magnifier_button_bg">#0680FD</color>
     <color name="accessibility_window_magnifier_icon_color">#FAFAFA</color>
-    <color name="accessibility_window_magnifier_button_bg_stroke">#252525</color>
     <color name="accessibility_window_magnifier_corner_view_color">#0680FD</color>
 
     <!-- Volume dialog colors -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6d5eb6a..fc67015 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1110,13 +1110,24 @@
     <!-- The extra padding to show the whole outer border -->
     <dimen name="magnifier_drag_handle_padding">3dp</dimen>
     <dimen name="magnification_max_frame_size">300dp</dimen>
+    <!-- Magnification settings panel -->
+    <dimen name="magnification_setting_view_margin">24dp</dimen>
+    <dimen name="magnification_setting_text_size">18sp</dimen>
+    <dimen name="magnification_setting_background_padding">24dp</dimen>
+    <dimen name="magnification_setting_background_corner_radius">28dp</dimen>
+    <dimen name="magnification_setting_seekbar_margin">16dp</dimen>
+    <dimen name="magnification_setting_button_line_height">20sp</dimen>
+    <dimen name="magnification_setting_button_done_width">312dp</dimen>
+    <dimen name="magnification_setting_button_done_height">48dp</dimen>
+    <dimen name="magnification_setting_button_done_corner_radius">100dp</dimen>
+    <dimen name="magnification_setting_button_done_padding_vertical">10dp</dimen>
+    <dimen name="magnification_setting_button_done_padding_horizontal">24dp</dimen>
 
     <!-- How far from the right edge of the screen you need to drag the window before the button
          repositions to the other side. -->
     <dimen name="magnification_button_reposition_threshold_from_edge">32dp</dimen>
 
     <dimen name="magnification_drag_size">15dp</dimen>
-    <dimen name="magnification_max_size">360dp</dimen>
     <dimen name="magnifier_panel_size">265dp</dimen>
 
     <!-- Home Controls -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2745202..4a89bb4 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2289,9 +2289,9 @@
     <string name="accessibility_magnification_small">Small</string>
     <!-- Click action label for magnification panel large size [CHAR LIMIT=NONE]-->
     <string name="accessibility_magnification_large">Large</string>
-    <!-- Click action label for magnification panel Close [CHAR LIMIT=NONE]-->
-    <string name="accessibility_magnification_close">Close</string>
-    <!-- Click action label for edit magnification size [CHAR LIMIT=NONE]-->
+    <!-- Click action label for magnification panel Done [CHAR LIMIT=20]-->
+    <string name="accessibility_magnification_done">Done</string>
+    <!-- Click action label for edit magnification size [CHAR LIMIT=20]-->
     <string name="accessibility_magnifier_edit">Edit</string>
     <!-- Click action label for magnification panel settings [CHAR LIMIT=NONE]-->
     <string name="accessibility_magnification_magnifier_window_settings">Magnifier window settings</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index aafa47f..f8f5e83 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1334,4 +1334,29 @@
         <item name="biometricsEnrollProgressHelp">@color/udfps_enroll_progress_help</item>
         <item name="biometricsEnrollProgressHelpWithTalkback">@color/udfps_enroll_progress_help_with_talkback</item>
     </style>
+
+    <!-- Magnification styles -->
+    <style name="TextAppearance.MagnificationSetting" />
+
+    <style name="TextAppearance.MagnificationSetting.Title">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:textColor">?androidprv:attr/textColorPrimary</item>
+        <item name="android:textSize">@dimen/magnification_setting_text_size</item>
+    </style>
+
+    <style name="TextAppearance.MagnificationSetting.EditButton">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:textColor">?androidprv:attr/colorAccent</item>
+        <item name="android:textSize">@dimen/magnification_setting_text_size</item>
+        <item name="android:lineHeight">@dimen/magnification_setting_button_line_height</item>
+        <item name="android:textAlignment">center</item>
+    </style>
+
+    <style name="TextAppearance.MagnificationSetting.DoneButton">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:textColor">?androidprv:attr/textColorPrimary</item>
+        <item name="android:textSize">@dimen/magnification_setting_text_size</item>
+        <item name="android:lineHeight">@dimen/magnification_setting_button_line_height</item>
+        <item name="android:textAlignment">center</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
index 7f2933e..c9e57b4 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
@@ -15,21 +15,51 @@
 package com.android.systemui.unfold.system
 
 import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
 import android.app.WindowConfiguration
+import android.os.Trace
+import com.android.systemui.shared.system.TaskStackChangeListener
+import com.android.systemui.shared.system.TaskStackChangeListeners
 import com.android.systemui.unfold.util.CurrentActivityTypeProvider
 import javax.inject.Inject
 import javax.inject.Singleton
 
 @Singleton
-class ActivityManagerActivityTypeProvider @Inject constructor(
-    private val activityManager: ActivityManager
-) : CurrentActivityTypeProvider {
+class ActivityManagerActivityTypeProvider
+@Inject
+constructor(private val activityManager: ActivityManager) : CurrentActivityTypeProvider {
 
     override val isHomeActivity: Boolean?
-        get() {
-            val activityType = activityManager.getRunningTasks(/* maxNum= */ 1)
-                    ?.getOrNull(0)?.topActivityType ?: return null
+        get() = _isHomeActivity
 
-            return activityType == WindowConfiguration.ACTIVITY_TYPE_HOME
+    private var _isHomeActivity: Boolean? = null
+
+
+    override fun init() {
+        _isHomeActivity = activityManager.isOnHomeActivity()
+        TaskStackChangeListeners.getInstance().registerTaskStackListener(taskStackChangeListener)
+    }
+
+    override fun uninit() {
+        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(taskStackChangeListener)
+    }
+
+    private val taskStackChangeListener =
+        object : TaskStackChangeListener {
+            override fun onTaskMovedToFront(taskInfo: RunningTaskInfo) {
+                _isHomeActivity = taskInfo.isHomeActivity()
+            }
         }
+
+    private fun RunningTaskInfo.isHomeActivity(): Boolean =
+        topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME
+
+    private fun ActivityManager.isOnHomeActivity(): Boolean? {
+        try {
+            Trace.beginSection("isOnHomeActivity")
+            return getRunningTasks(/* maxNum= */ 1)?.firstOrNull()?.isHomeActivity()
+        } finally {
+            Trace.endSection()
+        }
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
index 24ae42a..fe607e1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
-import com.android.systemui.unfold.dagger.UnfoldBackground
+import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
 import com.android.systemui.unfold.dagger.UnfoldMain
 import com.android.systemui.unfold.updates.FoldProvider
 import com.android.systemui.unfold.util.CurrentActivityTypeProvider
@@ -56,6 +56,6 @@
     abstract fun mainHandler(@Main handler: Handler): Handler
 
     @Binds
-    @UnfoldBackground
+    @UnfoldSingleThreadBg
     abstract fun backgroundExecutor(@UiBackground executor: Executor): Executor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 4f03b63..9537ce0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -89,6 +89,7 @@
         protected WindowMagnificationController createInstance(Display display) {
             final Context windowContext = mContext.createWindowContext(display,
                     TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null);
+            windowContext.setTheme(com.android.systemui.R.style.Theme_SystemUI);
             return new WindowMagnificationController(
                     windowContext,
                     mHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 9f857a8..56602ad 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -85,7 +85,7 @@
     private ImageButton mSmallButton;
     private ImageButton mMediumButton;
     private ImageButton mLargeButton;
-    private Button mCloseButton;
+    private Button mDoneButton;
     private Button mEditButton;
     private ImageButton mChangeModeButton;
     private boolean mAllowDiagonalScrolling = false;
@@ -160,9 +160,9 @@
         } else if (viewId == R.id.magnifier_large_button) {
             return mContext.getResources().getString(
                     R.string.accessibility_magnification_large);
-        } else if (viewId == R.id.magnifier_close_button) {
+        } else if (viewId == R.id.magnifier_done_button) {
             return mContext.getResources().getString(
-                    R.string.accessibility_magnification_close);
+                    R.string.accessibility_magnification_done);
         } else if (viewId == R.id.magnifier_edit_button) {
             return mContext.getResources().getString(
                     R.string.accessibility_resize);
@@ -247,7 +247,7 @@
                 setMagnifierSize(MagnificationSize.LARGE);
             } else if (id == R.id.magnifier_edit_button) {
                 editMagnifierSizeMode(true);
-            } else if (id == R.id.magnifier_close_button) {
+            } else if (id == R.id.magnifier_done_button) {
                 hideSettingPanel();
             } else if (id == R.id.magnifier_full_button) {
                 hideSettingPanel();
@@ -381,7 +381,7 @@
         mSmallButton = mSettingView.findViewById(R.id.magnifier_small_button);
         mMediumButton = mSettingView.findViewById(R.id.magnifier_medium_button);
         mLargeButton = mSettingView.findViewById(R.id.magnifier_large_button);
-        mCloseButton = mSettingView.findViewById(R.id.magnifier_close_button);
+        mDoneButton = mSettingView.findViewById(R.id.magnifier_done_button);
         mEditButton = mSettingView.findViewById(R.id.magnifier_edit_button);
         mChangeModeButton = mSettingView.findViewById(R.id.magnifier_full_button);
 
@@ -408,8 +408,8 @@
         mLargeButton.setAccessibilityDelegate(mButtonDelegate);
         mLargeButton.setOnClickListener(mButtonClickListener);
 
-        mCloseButton.setAccessibilityDelegate(mButtonDelegate);
-        mCloseButton.setOnClickListener(mButtonClickListener);
+        mDoneButton.setAccessibilityDelegate(mButtonDelegate);
+        mDoneButton.setOnClickListener(mButtonClickListener);
 
         mChangeModeButton.setAccessibilityDelegate(mButtonDelegate);
         mChangeModeButton.setOnClickListener(mButtonClickListener);
@@ -428,7 +428,8 @@
     }
 
     void onConfigurationChanged(int configDiff) {
-        if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
+        if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0
+                || (configDiff & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) {
             boolean showSettingPanelAfterThemeChange = mIsVisible;
             hideSettingPanel(/* resetPosition= */ false);
             inflateView();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 199e630..79c09fd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -239,6 +239,10 @@
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("mSensorProps=(" + mSensorProps + ")");
+        pw.println("Using new touch detection framework: " + mFeatureFlags.isEnabled(
+                Flags.UDFPS_NEW_TOUCH_DETECTION));
+        pw.println("Using ellipse touch detection: " + mFeatureFlags.isEnabled(
+                Flags.UDFPS_ELLIPSE_DETECTION));
     }
 
     public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt
index aa60522..28bc2b7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt
@@ -26,28 +26,28 @@
      * Value obtained from [MotionEvent.getPointerId], or [MotionEvent.INVALID_POINTER_ID] if the ID
      * is not available.
      */
-    val pointerId: Int,
+    val pointerId: Int = MotionEvent.INVALID_POINTER_ID,
 
     /** [MotionEvent.getRawX] mapped to natural orientation and native resolution. */
-    val x: Float,
+    val x: Float = 0f,
 
     /** [MotionEvent.getRawY] mapped to natural orientation and native resolution. */
-    val y: Float,
+    val y: Float = 0f,
 
     /** [MotionEvent.getTouchMinor] mapped to natural orientation and native resolution. */
-    val minor: Float,
+    val minor: Float = 0f,
 
     /** [MotionEvent.getTouchMajor] mapped to natural orientation and native resolution. */
-    val major: Float,
+    val major: Float = 0f,
 
     /** [MotionEvent.getOrientation] mapped to natural orientation. */
-    val orientation: Float,
+    val orientation: Float = 0f,
 
     /** [MotionEvent.getEventTime]. */
-    val time: Long,
+    val time: Long = 0,
 
     /** [MotionEvent.getDownTime]. */
-    val gestureStart: Long,
+    val gestureStart: Long = 0,
 ) {
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
index 693f64a..3a01cd5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
@@ -43,74 +43,72 @@
     ): TouchProcessorResult {
 
         fun preprocess(): PreprocessedTouch {
-            // TODO(b/253085297): Add multitouch support. pointerIndex can be > 0 for ACTION_MOVE.
-            val pointerIndex = 0
-            val touchData = event.normalize(pointerIndex, overlayParams)
-            val isGoodOverlap =
-                overlapDetector.isGoodOverlap(touchData, overlayParams.nativeSensorBounds)
-            return PreprocessedTouch(touchData, previousPointerOnSensorId, isGoodOverlap)
+            val touchData = List(event.pointerCount) { event.normalize(it, overlayParams) }
+            val pointersOnSensor =
+                touchData
+                    .filter { overlapDetector.isGoodOverlap(it, overlayParams.nativeSensorBounds) }
+                    .map { it.pointerId }
+            return PreprocessedTouch(touchData, previousPointerOnSensorId, pointersOnSensor)
         }
 
         return when (event.actionMasked) {
-            MotionEvent.ACTION_DOWN -> processActionDown(preprocess())
+            MotionEvent.ACTION_DOWN,
+            MotionEvent.ACTION_POINTER_DOWN,
             MotionEvent.ACTION_MOVE -> processActionMove(preprocess())
-            MotionEvent.ACTION_UP -> processActionUp(preprocess())
-            MotionEvent.ACTION_CANCEL ->
-                processActionCancel(event.normalize(pointerIndex = 0, overlayParams))
+            MotionEvent.ACTION_UP,
+            MotionEvent.ACTION_POINTER_UP ->
+                processActionUp(preprocess(), event.getPointerId(event.actionIndex))
+            MotionEvent.ACTION_CANCEL -> processActionCancel(NormalizedTouchData())
             else ->
                 Failure("Unsupported MotionEvent." + MotionEvent.actionToString(event.actionMasked))
         }
     }
 }
 
+/**
+ * [data] contains a list of NormalizedTouchData for pointers in the motionEvent ordered by
+ * pointerIndex
+ *
+ * [previousPointerOnSensorId] the pointerId of the previous pointer on the sensor,
+ * [MotionEvent.INVALID_POINTER_ID] if none
+ *
+ * [pointersOnSensor] contains a list of ids of pointers on the sensor
+ */
 private data class PreprocessedTouch(
-    val data: NormalizedTouchData,
+    val data: List<NormalizedTouchData>,
     val previousPointerOnSensorId: Int,
-    val isGoodOverlap: Boolean,
+    val pointersOnSensor: List<Int>,
 )
 
-private fun processActionDown(touch: PreprocessedTouch): TouchProcessorResult {
-    return if (touch.isGoodOverlap) {
-        ProcessedTouch(InteractionEvent.DOWN, pointerOnSensorId = touch.data.pointerId, touch.data)
-    } else {
-        val event =
-            if (touch.data.pointerId == touch.previousPointerOnSensorId) {
-                InteractionEvent.UP
-            } else {
-                InteractionEvent.UNCHANGED
-            }
-        ProcessedTouch(event, pointerOnSensorId = INVALID_POINTER_ID, touch.data)
-    }
-}
-
 private fun processActionMove(touch: PreprocessedTouch): TouchProcessorResult {
     val hadPointerOnSensor = touch.previousPointerOnSensorId != INVALID_POINTER_ID
-    val interactionEvent =
-        when {
-            touch.isGoodOverlap && !hadPointerOnSensor -> InteractionEvent.DOWN
-            !touch.isGoodOverlap && hadPointerOnSensor -> InteractionEvent.UP
-            else -> InteractionEvent.UNCHANGED
-        }
-    val pointerOnSensorId =
-        when (interactionEvent) {
-            InteractionEvent.UNCHANGED -> touch.previousPointerOnSensorId
-            InteractionEvent.DOWN -> touch.data.pointerId
-            else -> INVALID_POINTER_ID
-        }
-    return ProcessedTouch(interactionEvent, pointerOnSensorId, touch.data)
+    val hasPointerOnSensor = touch.pointersOnSensor.isNotEmpty()
+    val pointerOnSensorId = touch.pointersOnSensor.firstOrNull() ?: INVALID_POINTER_ID
+
+    return if (!hadPointerOnSensor && hasPointerOnSensor) {
+        val data = touch.data.find { it.pointerId == pointerOnSensorId } ?: NormalizedTouchData()
+        ProcessedTouch(InteractionEvent.DOWN, data.pointerId, data)
+    } else if (hadPointerOnSensor && !hasPointerOnSensor) {
+        ProcessedTouch(InteractionEvent.UP, INVALID_POINTER_ID, NormalizedTouchData())
+    } else {
+        val data = touch.data.find { it.pointerId == pointerOnSensorId } ?: NormalizedTouchData()
+        ProcessedTouch(InteractionEvent.UNCHANGED, pointerOnSensorId, data)
+    }
 }
 
-private fun processActionUp(touch: PreprocessedTouch): TouchProcessorResult {
-    return if (touch.isGoodOverlap) {
-        ProcessedTouch(InteractionEvent.UP, pointerOnSensorId = INVALID_POINTER_ID, touch.data)
+private fun processActionUp(touch: PreprocessedTouch, actionId: Int): TouchProcessorResult {
+    // Finger lifted and it was the only finger on the sensor
+    return if (touch.pointersOnSensor.size == 1 && touch.pointersOnSensor.contains(actionId)) {
+        ProcessedTouch(
+            InteractionEvent.UP,
+            pointerOnSensorId = INVALID_POINTER_ID,
+            NormalizedTouchData()
+        )
     } else {
-        val event =
-            if (touch.previousPointerOnSensorId != INVALID_POINTER_ID) {
-                InteractionEvent.UP
-            } else {
-                InteractionEvent.UNCHANGED
-            }
-        ProcessedTouch(event, pointerOnSensorId = INVALID_POINTER_ID, touch.data)
+        // Pick new pointerOnSensor that's not the finger that was lifted
+        val pointerOnSensorId = touch.pointersOnSensor.find { it != actionId } ?: INVALID_POINTER_ID
+        val data = touch.data.find { it.pointerId == pointerOnSensorId } ?: NormalizedTouchData()
+        ProcessedTouch(InteractionEvent.UNCHANGED, data.pointerId, data)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
index f95a8ee..7bbfec7 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
@@ -28,7 +28,6 @@
         LaunchableViewDelegate(
             this,
             superSetVisibility = { super.setVisibility(it) },
-            superSetTransitionVisibility = { super.setTransitionVisibility(it) },
         )
 
     constructor(context: Context?) : super(context)
@@ -53,8 +52,4 @@
     override fun setVisibility(visibility: Int) {
         delegate.setVisibility(visibility)
     }
-
-    override fun setTransitionVisibility(visibility: Int) {
-        delegate.setTransitionVisibility(visibility)
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
index c27b82a..ddde628 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
@@ -28,7 +28,6 @@
         LaunchableViewDelegate(
             this,
             superSetVisibility = { super.setVisibility(it) },
-            superSetTransitionVisibility = { super.setTransitionVisibility(it) },
         )
 
     constructor(context: Context?) : super(context)
@@ -53,8 +52,4 @@
     override fun setVisibility(visibility: Int) {
         delegate.setVisibility(visibility)
     }
-
-    override fun setTransitionVisibility(visibility: Int) {
-        delegate.setTransitionVisibility(visibility)
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index e5ec727..c0f8549 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -17,8 +17,12 @@
 
 package com.android.systemui.compose
 
+import android.content.Context
+import android.view.View
 import androidx.activity.ComponentActivity
+import androidx.lifecycle.LifecycleOwner
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 
 /**
  * A facade to interact with Compose, when it is available.
@@ -35,10 +39,22 @@
      */
     fun isComposeAvailable(): Boolean
 
+    /**
+     * Return the [ComposeInitializer] to make Compose usable in windows outside normal activities.
+     */
+    fun composeInitializer(): ComposeInitializer
+
     /** Bind the content of [activity] to [viewModel]. */
     fun setPeopleSpaceActivityContent(
         activity: ComponentActivity,
         viewModel: PeopleViewModel,
         onResult: (PeopleViewModel.Result) -> Unit,
     )
+
+    /** Create a [View] to represent [viewModel] on screen. */
+    fun createFooterActionsView(
+        context: Context,
+        viewModel: FooterActionsViewModel,
+        qsVisibilityLifecycleOwner: LifecycleOwner,
+    ): View
 }
diff --git a/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt b/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt
new file mode 100644
index 0000000..90dc3a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.compose
+
+import android.view.View
+
+/**
+ * An initializer to use Compose outside of an Activity, e.g. inside a window added directly using
+ * [android.view.WindowManager.addView] (like the shade or status bar) or inside a dialog.
+ *
+ * Example:
+ * ```
+ *    windowManager.addView(MyWindowRootView(context), /* layoutParams */)
+ *
+ *    class MyWindowRootView(context: Context) : FrameLayout(context) {
+ *        override fun onAttachedToWindow() {
+ *            super.onAttachedToWindow()
+ *            ComposeInitializer.onAttachedToWindow(this)
+ *        }
+ *
+ *        override fun onDetachedFromWindow() {
+ *            super.onDetachedFromWindow()
+ *            ComposeInitializer.onDetachedFromWindow(this)
+ *        }
+ *    }
+ * ```
+ */
+interface ComposeInitializer {
+    /** Function to be called on your window root view's [View.onAttachedToWindow] function. */
+    fun onAttachedToWindow(root: View)
+
+    /** Function to be called on your window root view's [View.onDetachedFromWindow] function. */
+    fun onDetachedFromWindow(root: View)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c880c59..a5d9fee 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -75,9 +75,7 @@
         unreleasedFlag(119, "notification_memory_logging_enabled", teamfood = true)
 
     // TODO(b/254512731): Tracking Bug
-    @JvmField
-    val NOTIFICATION_DISMISSAL_FADE =
-        unreleasedFlag(113, "notification_dismissal_fade", teamfood = true)
+    @JvmField val NOTIFICATION_DISMISSAL_FADE = releasedFlag(113, "notification_dismissal_fade")
 
     // TODO(b/259558771): Tracking Bug
     val STABILITY_INDEX_FIX = releasedFlag(114, "stability_index_fix")
@@ -210,6 +208,12 @@
     // TODO(b/262859270): Tracking Bug
     @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded")
 
+    /** Enables code to show contextual loyalty cards in wallet entrypoints */
+    // TODO(b/247587924): Tracking Bug
+    @JvmField
+    val ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS =
+        unreleasedFlag(226, "enable_wallet_contextual_loyalty_cards", teamfood = false)
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -291,7 +295,7 @@
 
     // 801 - region sampling
     // TODO(b/254512848): Tracking Bug
-    val REGION_SAMPLING = unreleasedFlag(801, "region_sampling", teamfood = true)
+    val REGION_SAMPLING = unreleasedFlag(801, "region_sampling")
 
     // 803 - screen contents translation
     // TODO(b/254513187): Tracking Bug
@@ -407,6 +411,17 @@
     val WM_DESKTOP_WINDOWING_2 =
         sysPropBooleanFlag(1112, "persist.wm.debug.desktop_mode_2", default = false)
 
+    // TODO(b/254513207): Tracking Bug to delete
+    @Keep
+    @JvmField
+    val WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES =
+        unreleasedFlag(
+            1113,
+            name = "screen_record_enterprise_policies",
+            namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+            teamfood = false
+        )
+
     // 1200 - predictive back
     @Keep
     @JvmField
@@ -498,6 +513,7 @@
     @JvmField val ENABLE_STYLUS_CHARGING_UI = unreleasedFlag(2301, "enable_stylus_charging_ui")
     @JvmField
     val ENABLE_USI_BATTERY_NOTIFICATIONS = unreleasedFlag(2302, "enable_usi_battery_notifications")
+    @JvmField val ENABLE_STYLUS_EDUCATION = unreleasedFlag(2303, "enable_stylus_education")
 
     // 2400 - performance tools and debugging info
     // TODO(b/238923086): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index e364918..d69ac7f 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -145,7 +145,7 @@
  * └───────────────┴───────────────────┴──────────────┴─────────────────┘
  * ```
  */
-private class ViewLifecycleOwner(
+class ViewLifecycleOwner(
     private val view: View,
 ) : LifecycleOwner {
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index ceb4845..a692ad7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -18,6 +18,7 @@
 import android.app.ActivityOptions
 import android.content.Intent
 import android.content.res.Configuration
+import android.content.res.Resources
 import android.media.projection.IMediaProjection
 import android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION
 import android.os.Binder
@@ -27,6 +28,7 @@
 import android.os.UserHandle
 import android.view.ViewGroup
 import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider
 import com.android.internal.app.ChooserActivity
 import com.android.internal.app.ResolverListController
 import com.android.internal.app.chooser.NotSelectableTargetInfo
@@ -59,16 +61,12 @@
     private lateinit var configurationController: ConfigurationController
     private lateinit var controller: MediaProjectionAppSelectorController
     private lateinit var recentsViewController: MediaProjectionRecentsViewController
+    private lateinit var component: MediaProjectionAppSelectorComponent
 
     override fun getLayoutResource() = R.layout.media_projection_app_selector
 
     public override fun onCreate(bundle: Bundle?) {
-        val component =
-            componentFactory.create(
-                activity = this,
-                view = this,
-                resultHandler = this
-            )
+        component = componentFactory.create(activity = this, view = this, resultHandler = this)
 
         // Create a separate configuration controller for this activity as the configuration
         // might be different from the global one
@@ -76,11 +74,12 @@
         controller = component.controller
         recentsViewController = component.recentsViewController
 
-        val queryIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
-        intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
+        intent.configureChooserIntent(
+            resources,
+            component.hostUserHandle,
+            component.personalProfileUserHandle
+        )
 
-        val title = getString(R.string.media_projection_permission_app_selector_title)
-        intent.putExtra(Intent.EXTRA_TITLE, title)
         super.onCreate(bundle)
         controller.init()
     }
@@ -183,6 +182,13 @@
 
     override fun shouldShowContentPreview() = true
 
+    override fun shouldShowContentPreviewWhenEmpty(): Boolean = true
+
+    override fun createMyUserIdProvider(): MyUserIdProvider =
+        object : MyUserIdProvider() {
+            override fun getMyUserId(): Int = component.hostUserHandle.identifier
+        }
+
     override fun createContentPreviewView(parent: ViewGroup): ViewGroup =
         recentsViewController.createView(parent)
 
@@ -193,6 +199,34 @@
          * instance through activity result.
          */
         const val EXTRA_CAPTURE_REGION_RESULT_RECEIVER = "capture_region_result_receiver"
+
+        /** UID of the app that originally launched the media projection flow (host app user) */
+        const val EXTRA_HOST_APP_USER_HANDLE = "launched_from_user_handle"
         const val KEY_CAPTURE_TARGET = "capture_region"
+
+        /** Set up intent for the [ChooserActivity] */
+        private fun Intent.configureChooserIntent(
+            resources: Resources,
+            hostUserHandle: UserHandle,
+            personalProfileUserHandle: UserHandle
+        ) {
+            // Specify the query intent to show icons for all apps on the chooser screen
+            val queryIntent =
+                Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
+            putExtra(Intent.EXTRA_INTENT, queryIntent)
+
+            // Update the title of the chooser
+            val title = resources.getString(R.string.media_projection_permission_app_selector_title)
+            putExtra(Intent.EXTRA_TITLE, title)
+
+            // Select host app's profile tab by default
+            val selectedProfile =
+                if (hostUserHandle == personalProfileUserHandle) {
+                    PROFILE_PERSONAL
+                } else {
+                    PROFILE_WORK
+                }
+            putExtra(EXTRA_SELECTED_PROFILE, selectedProfile)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index bfa67a8..d830fc4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -22,6 +22,7 @@
 import static com.android.systemui.screenrecord.ScreenShareOptionKt.SINGLE_APP;
 
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -35,6 +36,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.text.BidiFormatter;
 import android.text.SpannableString;
 import android.text.TextPaint;
@@ -208,8 +210,14 @@
                 final Intent intent = new Intent(this, MediaProjectionAppSelectorActivity.class);
                 intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
                         projection.asBinder());
+                intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
+                        UserHandle.getUserHandleForUid(getLaunchedFromUid()));
                 intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
-                startActivity(intent);
+
+                // Start activity from the current foreground user to avoid creating a separate
+                // SystemUI process without access to recent tasks because it won't have
+                // WM Shell running inside.
+                startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser()));
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error granting projection permission", e);
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 9f28d46..6a5e725 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -864,7 +864,7 @@
                     notificationKey = key,
                     hasCheckedForResume = hasCheckedForResume,
                     isPlaying = isPlaying,
-                    isClearable = sbn.isClearable(),
+                    isClearable = !sbn.isOngoing,
                     lastActive = lastActive,
                     instanceId = instanceId,
                     appUid = appUid,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 6c41caa..1d86343 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -19,9 +19,11 @@
 import android.app.Activity
 import android.content.ComponentName
 import android.content.Context
+import android.os.UserHandle
 import com.android.launcher3.icons.IconFactory
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.MediaProjectionAppSelectorActivity
+import com.android.systemui.media.MediaProjectionAppSelectorActivity.Companion.EXTRA_HOST_APP_USER_HANDLE
 import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
 import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
 import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
@@ -30,6 +32,8 @@
 import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
 import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.system.ActivityManagerWrapper
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
 import com.android.systemui.statusbar.policy.ConfigurationController
 import dagger.Binds
@@ -39,6 +43,7 @@
 import dagger.Subcomponent
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
+import java.lang.IllegalArgumentException
 import javax.inject.Qualifier
 import javax.inject.Scope
 import kotlinx.coroutines.CoroutineScope
@@ -46,6 +51,12 @@
 
 @Qualifier @Retention(AnnotationRetention.BINARY) annotation class MediaProjectionAppSelector
 
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUserHandle
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile
+
 @Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope
 
 @Module(subcomponents = [MediaProjectionAppSelectorComponent::class])
@@ -83,7 +94,7 @@
         @MediaProjectionAppSelector
         @MediaProjectionAppSelectorScope
         fun provideAppSelectorComponentName(context: Context): ComponentName =
-                ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
+            ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
 
         @Provides
         @MediaProjectionAppSelector
@@ -93,9 +104,32 @@
         ): ConfigurationController = ConfigurationControllerImpl(activity)
 
         @Provides
-        fun bindIconFactory(
-            context: Context
-        ): IconFactory = IconFactory.obtain(context)
+        @PersonalProfile
+        @MediaProjectionAppSelectorScope
+        fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle {
+            // Current foreground user is the 'personal' profile
+            return UserHandle.of(activityManagerWrapper.currentUserId)
+        }
+
+        @Provides
+        @WorkProfile
+        @MediaProjectionAppSelectorScope
+        fun workProfileUserHandle(userTracker: UserTracker): UserHandle? =
+            userTracker.userProfiles.find { it.isManagedProfile }?.userHandle
+
+        @Provides
+        @HostUserHandle
+        @MediaProjectionAppSelectorScope
+        fun hostUserHandle(activity: MediaProjectionAppSelectorActivity): UserHandle {
+            val extras =
+                activity.intent.extras
+                    ?: error("MediaProjectionAppSelectorActivity should be launched with extras")
+            return extras.getParcelable(EXTRA_HOST_APP_USER_HANDLE)
+                ?: error("MediaProjectionAppSelectorActivity should be provided with " +
+                        "$EXTRA_HOST_APP_USER_HANDLE extra")
+        }
+
+        @Provides fun bindIconFactory(context: Context): IconFactory = IconFactory.obtain(context)
 
         @Provides
         @MediaProjectionAppSelector
@@ -124,6 +158,8 @@
 
     val controller: MediaProjectionAppSelectorController
     val recentsViewController: MediaProjectionRecentsViewController
+    @get:HostUserHandle val hostUserHandle: UserHandle
+    @get:PersonalProfile val personalProfileUserHandle: UserHandle
 
     @MediaProjectionAppSelector val configurationController: ConfigurationController
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index d744a40b..52c7ca3 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -17,24 +17,36 @@
 package com.android.systemui.mediaprojection.appselector
 
 import android.content.ComponentName
+import android.os.UserHandle
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
 @MediaProjectionAppSelectorScope
-class MediaProjectionAppSelectorController @Inject constructor(
+class MediaProjectionAppSelectorController
+@Inject
+constructor(
     private val recentTaskListProvider: RecentTaskListProvider,
     private val view: MediaProjectionAppSelectorView,
+    private val flags: FeatureFlags,
+    @HostUserHandle private val hostUserHandle: UserHandle,
     @MediaProjectionAppSelector private val scope: CoroutineScope,
     @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName
 ) {
 
     fun init() {
         scope.launch {
-            val tasks = recentTaskListProvider.loadRecentTasks().sortTasks()
+            val recentTasks = recentTaskListProvider.loadRecentTasks()
+
+            val tasks = recentTasks
+                .filterDevicePolicyRestrictedTasks()
+                .sortedTasks()
+
             view.bind(tasks)
         }
     }
@@ -43,9 +55,20 @@
         scope.cancel()
     }
 
-    private fun List<RecentTask>.sortTasks(): List<RecentTask> =
-        sortedBy {
-            // Show normal tasks first and only then tasks with opened app selector
-            it.topActivityComponent == appSelectorComponentName
+    /**
+     * Removes all recent tasks that are different from the profile of the host app to avoid any
+     * cross-profile sharing
+     */
+    private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> =
+        if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
+            // TODO(b/263950746): filter tasks based on the enterprise policies
+            this
+        } else {
+            filter { UserHandle.of(it.userId) == hostUserHandle }
         }
+
+    private fun List<RecentTask>.sortedTasks(): List<RecentTask> = sortedBy {
+        // Show normal tasks first and only then tasks with opened app selector
+        it.topActivityComponent == appSelectorComponentName
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index cd994b8..41e2286 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -17,11 +17,12 @@
 package com.android.systemui.mediaprojection.appselector.data
 
 import android.annotation.ColorInt
+import android.annotation.UserIdInt
 import android.content.ComponentName
 
 data class RecentTask(
     val taskId: Int,
-    val userId: Int,
+    @UserIdInt val userId: Int,
     val topActivityComponent: ComponentName?,
     val baseIntentComponent: ComponentName?,
     @ColorInt val colorBackground: Int?
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index e3d9f6d..e0aa6a8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -1031,6 +1031,9 @@
         pw.println("  mOrientedHandleSamplingRegion: " + mOrientedHandleSamplingRegion);
         mView.dump(pw);
         mRegionSamplingHelper.dump(pw);
+        if (mAutoHideController != null) {
+            mAutoHideController.dump(pw);
+        }
     }
 
     // ----- CommandQueue Callbacks -----
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index fba5f63..7f0f894 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -68,8 +68,10 @@
         };
 
         if (ComposeFacade.INSTANCE.isComposeAvailable()) {
+            Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity");
             ComposeFacade.INSTANCE.setPeopleSpaceActivityContent(this, viewModel, onResult);
         } else {
+            Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity");
             ViewGroup view = PeopleViewBinder.create(this);
             PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this, onResult);
             setContentView(view);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 5ef7126..b673f0e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -48,6 +48,7 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.controls.ui.MediaHost;
@@ -227,9 +228,7 @@
 
         mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */
                 this);
-        LinearLayout footerActionsView = view.findViewById(R.id.qs_footer_actions);
-        FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
-                mListeningAndVisibilityLifecycleOwner);
+        bindFooterActionsView(view);
         mFooterActionsController.init();
 
         mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
@@ -290,6 +289,33 @@
                 });
     }
 
+    private void bindFooterActionsView(View root) {
+        LinearLayout footerActionsView = root.findViewById(R.id.qs_footer_actions);
+
+        if (!ComposeFacade.INSTANCE.isComposeAvailable()) {
+            Log.d(TAG, "Binding the View implementation of the QS footer actions");
+            FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
+                    mListeningAndVisibilityLifecycleOwner);
+            return;
+        }
+
+        // Compose is available, so let's use the Compose implementation of the footer actions.
+        Log.d(TAG, "Binding the Compose implementation of the QS footer actions");
+        View composeView = ComposeFacade.INSTANCE.createFooterActionsView(root.getContext(),
+                mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner);
+
+        // The id R.id.qs_footer_actions is used by QSContainerImpl to set the horizontal margin
+        // to all views except for qs_footer_actions, so we set it to the Compose view.
+        composeView.setId(R.id.qs_footer_actions);
+
+        // Replace the View by the Compose provided one.
+        ViewGroup parent = (ViewGroup) footerActionsView.getParent();
+        ViewGroup.LayoutParams layoutParams = footerActionsView.getLayoutParams();
+        int index = parent.indexOfChild(footerActionsView);
+        parent.removeViewAt(index);
+        parent.addView(composeView, index, layoutParams);
+    }
+
     @Override
     public void setScrollListener(ScrollListener listener) {
         mScrollListener = listener;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 3d48fd1..84a18d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -132,7 +132,7 @@
             final String slot = tile.getComponent().getClassName();
             // TileServices doesn't know how to add more than 1 icon per slot, so remove all
             mMainHandler.post(() -> mHost.getIconController()
-                    .removeAllIconsForSlot(slot));
+                    .removeAllIconsForExternalSlot(slot));
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index b355d4b..29d7fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -145,7 +145,6 @@
     private val launchableViewDelegate = LaunchableViewDelegate(
         this,
         superSetVisibility = { super.setVisibility(it) },
-        superSetTransitionVisibility = { super.setTransitionVisibility(it) },
     )
     private var lastDisabledByPolicy = false
 
@@ -362,10 +361,6 @@
         launchableViewDelegate.setVisibility(visibility)
     }
 
-    override fun setTransitionVisibility(visibility: Int) {
-        launchableViewDelegate.setTransitionVisibility(visibility)
-    }
-
     // Accessibility
 
     override fun onInitializeAccessibilityEvent(event: AccessibilityEvent) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index 44b18ec..68e3dcd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -23,6 +23,7 @@
 import android.os.Handler
 import android.os.Looper
 import android.os.ResultReceiver
+import android.os.UserHandle
 import android.view.View
 import android.view.View.GONE
 import android.view.View.VISIBLE
@@ -77,6 +78,14 @@
                     MediaProjectionAppSelectorActivity.EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
                     CaptureTargetResultReceiver()
                 )
+
+                // Send SystemUI's user handle as the host app user handle because SystemUI
+                // is the 'host app' (the app that receives screen capture data)
+                intent.putExtra(
+                    MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
+                    UserHandle.of(UserHandle.myUserId())
+                )
+
                 val animationController = dialogLaunchAnimator.createActivityLaunchController(v!!)
                 if (animationController == null) {
                     dismiss()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 964d0b2..fd31e49 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -715,7 +715,7 @@
         updatePanelExpansionAndVisibility();
     };
     private final Runnable mMaybeHideExpandedRunnable = () -> {
-        if (getExpansionFraction() == 0.0f) {
+        if (getExpandedFraction() == 0.0f) {
             postToView(mHideExpandedRunnable);
         }
     };
@@ -5451,10 +5451,6 @@
                 InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
     }
 
-    private float getExpansionFraction() {
-        return mExpandedFraction;
-    }
-
     private ShadeExpansionStateManager getShadeExpansionStateManager() {
         return mShadeExpansionStateManager;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index ca03127..f712629 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -58,6 +58,7 @@
 import com.android.internal.view.FloatingActionMode;
 import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
 import com.android.systemui.R;
+import com.android.systemui.compose.ComposeFacade;
 
 /**
  * Combined keyguard and notification panel view. Also holding backdrop and scrims.
@@ -149,6 +150,18 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         setWillNotDraw(!DEBUG);
+
+        if (ComposeFacade.INSTANCE.isComposeAvailable()) {
+            ComposeFacade.INSTANCE.composeInitializer().onAttachedToWindow(this);
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (ComposeFacade.INSTANCE.isComposeAvailable()) {
+            ComposeFacade.INSTANCE.composeInitializer().onDetachedFromWindow(this);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
index 662f70e..438b0f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
@@ -36,10 +36,6 @@
             visibility -> {
                 super.setVisibility(visibility);
                 return Unit.INSTANCE;
-            },
-            visibility -> {
-                super.setTransitionVisibility(visibility);
-                return Unit.INSTANCE;
             });
 
     public AlphaOptimizedFrameLayout(Context context) {
@@ -73,9 +69,4 @@
     public void setVisibility(int visibility) {
         mLaunchableViewDelegate.setVisibility(visibility);
     }
-
-    @Override
-    public void setTransitionVisibility(int visibility) {
-        mLaunchableViewDelegate.setTransitionVisibility(visibility);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index 3ccef9d..eb81c46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -16,25 +16,35 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
+
 import android.content.Context;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.IWindowManager;
 import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.annotation.NonNull;
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.AutoHideUiElement;
 
+import java.io.PrintWriter;
+
 import javax.inject.Inject;
 
 /** A controller to control all auto-hide things. Also see {@link AutoHideUiElement}. */
 @SysUISingleton
 public class AutoHideController {
     private static final String TAG = "AutoHideController";
-    private static final long AUTO_HIDE_TIMEOUT_MS = 2250;
+    private static final int AUTO_HIDE_TIMEOUT_MS = 2250;
+    private static final int USER_AUTO_HIDE_TIMEOUT_MS = 350;
 
+    private final AccessibilityManager mAccessibilityManager;
     private final IWindowManager mWindowManagerService;
     private final Handler mHandler;
 
@@ -52,11 +62,12 @@
     };
 
     @Inject
-    public AutoHideController(Context context, @Main Handler handler,
+    public AutoHideController(Context context,
+            @Main Handler handler,
             IWindowManager iWindowManager) {
+        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mHandler = handler;
         mWindowManagerService = iWindowManager;
-
         mDisplayId = context.getDisplayId();
     }
 
@@ -138,7 +149,12 @@
 
     private void scheduleAutoHide() {
         cancelAutoHide();
-        mHandler.postDelayed(mAutoHide, AUTO_HIDE_TIMEOUT_MS);
+        mHandler.postDelayed(mAutoHide, getAutoHideTimeout());
+    }
+
+    private int getAutoHideTimeout() {
+        return mAccessibilityManager.getRecommendedTimeoutMillis(AUTO_HIDE_TIMEOUT_MS,
+                FLAG_CONTENT_CONTROLS);
     }
 
     public void checkUserAutoHide(MotionEvent event) {
@@ -160,7 +176,13 @@
 
     private void userAutoHide() {
         cancelAutoHide();
-        mHandler.postDelayed(mAutoHide, 350); // longer than app gesture -> flag clear
+        // longer than app gesture -> flag clear
+        mHandler.postDelayed(mAutoHide, getUserAutoHideTimeout());
+    }
+
+    private int getUserAutoHideTimeout() {
+        return mAccessibilityManager.getRecommendedTimeoutMillis(USER_AUTO_HIDE_TIMEOUT_MS,
+                FLAG_CONTENT_CONTROLS);
     }
 
     private boolean isAnyTransientBarShown() {
@@ -175,6 +197,15 @@
         return false;
     }
 
+    public void dump(@NonNull PrintWriter pw) {
+        pw.println("AutoHideController:");
+        pw.println("\tmAutoHideSuspended=" + mAutoHideSuspended);
+        pw.println("\tisAnyTransientBarShown=" + isAnyTransientBarShown());
+        pw.println("\thasPendingAutoHide=" + mHandler.hasCallbacks(mAutoHide));
+        pw.println("\tgetAutoHideTimeout=" + getAutoHideTimeout());
+        pw.println("\tgetUserAutoHideTimeout=" + getUserAutoHideTimeout());
+    }
+
     /**
      * Injectable factory for creating a {@link AutoHideController}.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index b965ac9..ff1b31d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -30,6 +30,9 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN
+import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.tuner.TunerService
 import java.io.PrintWriter
@@ -40,11 +43,19 @@
 
     private val mKeyguardStateController: KeyguardStateController
     private val statusBarStateController: StatusBarStateController
+    private val devicePostureController: DevicePostureController
     @BypassOverride private val bypassOverride: Int
     private var hasFaceFeature: Boolean
+    @DevicePostureInt private val configFaceAuthSupportedPosture: Int
+    @DevicePostureInt private var postureState: Int = DEVICE_POSTURE_UNKNOWN
     private var pendingUnlock: PendingUnlock? = null
     private val listeners = mutableListOf<OnBypassStateChangedListener>()
-
+    private val postureCallback = DevicePostureController.Callback { posture ->
+        if (postureState != posture) {
+            postureState = posture
+            notifyListeners()
+        }
+    }
     private val faceAuthEnabledChangedCallback = object : KeyguardStateController.Callback {
         override fun onFaceAuthEnabledChanged() = notifyListeners()
     }
@@ -86,7 +97,8 @@
                 FACE_UNLOCK_BYPASS_NEVER -> false
                 else -> field
             }
-            return enabled && mKeyguardStateController.isFaceAuthEnabled
+            return enabled && mKeyguardStateController.isFaceAuthEnabled &&
+                    isPostureAllowedForFaceAuth()
         }
         private set(value) {
             field = value
@@ -106,18 +118,31 @@
         lockscreenUserManager: NotificationLockscreenUserManager,
         keyguardStateController: KeyguardStateController,
         shadeExpansionStateManager: ShadeExpansionStateManager,
+        devicePostureController: DevicePostureController,
         dumpManager: DumpManager
     ) {
         this.mKeyguardStateController = keyguardStateController
         this.statusBarStateController = statusBarStateController
+        this.devicePostureController = devicePostureController
 
         bypassOverride = context.resources.getInteger(R.integer.config_face_unlock_bypass_override)
+        configFaceAuthSupportedPosture =
+            context.resources.getInteger(R.integer.config_face_auth_supported_posture)
 
-        hasFaceFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)
+        hasFaceFeature = context.packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)
         if (!hasFaceFeature) {
             return
         }
 
+        if (configFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
+            devicePostureController.addCallback { posture ->
+                if (postureState != posture) {
+                    postureState = posture
+                    notifyListeners()
+                }
+            }
+        }
+
         dumpManager.registerDumpable("KeyguardBypassController", this)
         statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
             override fun onStateChanged(newState: Int) {
@@ -203,6 +228,13 @@
         pendingUnlock = null
     }
 
+    fun isPostureAllowedForFaceAuth(): Boolean {
+        return when (configFaceAuthSupportedPosture) {
+            DEVICE_POSTURE_UNKNOWN -> true
+            else -> (postureState == configFaceAuthSupportedPosture)
+        }
+    }
+
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.println("KeyguardBypassController:")
         if (pendingUnlock != null) {
@@ -219,6 +251,7 @@
         pw.println("  launchingAffordance: $launchingAffordance")
         pw.println("  qSExpanded: $qsExpanded")
         pw.println("  hasFaceFeature: $hasFaceFeature")
+        pw.println("  postureState: $postureState")
     }
 
     /** Registers a listener for bypass state changes. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 1a14a036..24ad55d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -79,12 +79,30 @@
 
     /** Refresh the state of an IconManager by recreating the views */
     void refreshIconGroup(IconManager iconManager);
-    /** */
+
+    /**
+     * Adds or updates an icon for a given slot for a **tile service icon**.
+     *
+     * TODO(b/265307726): Merge with {@link #setIcon(String, StatusBarIcon)} or make this method
+     *   much more clearly distinct from that method.
+     */
     void setExternalIcon(String slot);
-    /** */
+
+    /**
+     * Adds or updates an icon for the given slot for **internal system icons**.
+     *
+     * TODO(b/265307726): Rename to `setInternalIcon`, or merge this appropriately with the
+     * {@link #setIcon(String, StatusBarIcon)} method.
+     */
     void setIcon(String slot, int resourceId, CharSequence contentDescription);
-    /** */
+
+    /**
+     * Adds or updates an icon for the given slot for an **externally-provided icon**.
+     *
+     * TODO(b/265307726): Rename to `setExternalIcon` or something similar.
+     */
     void setIcon(String slot, StatusBarIcon icon);
+
     /** */
     void setWifiIcon(String slot, WifiIconState state);
 
@@ -133,9 +151,17 @@
      * TAG_PRIMARY to refer to the first icon at a given slot.
      */
     void removeIcon(String slot, int tag);
+
     /** */
     void removeAllIconsForSlot(String slot);
 
+    /**
+     * Removes all the icons for the given slot.
+     *
+     * Only use this for icons that have come from **an external process**.
+     */
+    void removeAllIconsForExternalSlot(String slot);
+
     // TODO: See if we can rename this tunable name.
     String ICON_HIDE_LIST = "icon_blacklist";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 9fbe6cb..416bc71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -28,6 +28,8 @@
 import android.util.Log;
 import android.view.ViewGroup;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
@@ -63,6 +65,10 @@
         ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController, DemoMode {
 
     private static final String TAG = "StatusBarIconController";
+    // Use this suffix to prevent external icon slot names from unintentionally overriding our
+    // internal, system-level slot names. See b/255428281.
+    @VisibleForTesting
+    protected static final String EXTERNAL_SLOT_SUFFIX = "__external";
 
     private final StatusBarIconList mStatusBarIconList;
     private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
@@ -346,21 +352,26 @@
 
     @Override
     public void setExternalIcon(String slot) {
-        int viewIndex = mStatusBarIconList.getViewIndex(slot, 0);
+        String slotName = createExternalSlotName(slot);
+        int viewIndex = mStatusBarIconList.getViewIndex(slotName, 0);
         int height = mContext.getResources().getDimensionPixelSize(
                 R.dimen.status_bar_icon_drawing_size);
         mIconGroups.forEach(l -> l.onIconExternal(viewIndex, height));
     }
 
-    //TODO: remove this (used in command queue and for 3rd party tiles?)
+    // Override for *both* CommandQueue.Callbacks AND StatusBarIconController.
+    // TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to
+    //  differentiate between those callback methods and StatusBarIconController methods.
+    @Override
     public void setIcon(String slot, StatusBarIcon icon) {
+        String slotName = createExternalSlotName(slot);
         if (icon == null) {
-            removeAllIconsForSlot(slot);
+            removeAllIconsForSlot(slotName);
             return;
         }
 
         StatusBarIconHolder holder = StatusBarIconHolder.fromIcon(icon);
-        setIcon(slot, holder);
+        setIcon(slotName, holder);
     }
 
     private void setIcon(String slot, @NonNull StatusBarIconHolder holder) {
@@ -406,10 +417,12 @@
         }
     }
 
-    /** */
+    // CommandQueue.Callbacks override
+    // TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to
+    //  differentiate between those callback methods and StatusBarIconController methods.
     @Override
     public void removeIcon(String slot) {
-        removeAllIconsForSlot(slot);
+        removeAllIconsForExternalSlot(slot);
     }
 
     /** */
@@ -423,6 +436,11 @@
         mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
     }
 
+    @Override
+    public void removeAllIconsForExternalSlot(String slotName) {
+        removeAllIconsForSlot(createExternalSlotName(slotName));
+    }
+
     /** */
     @Override
     public void removeAllIconsForSlot(String slotName) {
@@ -506,4 +524,12 @@
     public void onDensityOrFontScaleChanged() {
         refreshIconGroups();
     }
+
+    private String createExternalSlotName(String slot) {
+        if (slot.endsWith(EXTERNAL_SLOT_SUFFIX)) {
+            return slot;
+        } else {
+            return slot + EXTERNAL_SLOT_SUFFIX;
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index 7c1e384..cac4a0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -12,11 +12,13 @@
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.WindowManager
+import android.widget.FrameLayout
 import android.widget.LinearLayout
 import androidx.test.filters.SmallTest
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.policy.DecorView
 import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertNotNull
@@ -205,25 +207,74 @@
         verify(interactionJankMonitor).end(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN)
     }
 
+    @Test
+    fun testAnimationDoesNotChangeLaunchableViewVisibility_viewVisible() {
+        val touchSurface = createTouchSurface()
+
+        // View is VISIBLE when starting the animation.
+        runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.VISIBLE }
+
+        // View is invisible while the dialog is shown.
+        val dialog = showDialogFromView(touchSurface)
+        assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE)
+
+        // View is visible again when the dialog is dismissed.
+        runOnMainThreadAndWaitForIdleSync { dialog.dismiss() }
+        assertThat(touchSurface.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    fun testAnimationDoesNotChangeLaunchableViewVisibility_viewInvisible() {
+        val touchSurface = createTouchSurface()
+
+        // View is INVISIBLE when starting the animation.
+        runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.INVISIBLE }
+
+        // View is INVISIBLE while the dialog is shown.
+        val dialog = showDialogFromView(touchSurface)
+        assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE)
+
+        // View is invisible like it was before showing the dialog.
+        runOnMainThreadAndWaitForIdleSync { dialog.dismiss() }
+        assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    fun testAnimationDoesNotChangeLaunchableViewVisibility_viewVisibleThenGone() {
+        val touchSurface = createTouchSurface()
+
+        // View is VISIBLE when starting the animation.
+        runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.VISIBLE }
+
+        // View is INVISIBLE while the dialog is shown.
+        val dialog = showDialogFromView(touchSurface)
+        assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE)
+
+        // Some external call makes the View GONE. It remains INVISIBLE while the dialog is shown,
+        // as all visibility changes should be blocked.
+        runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.GONE }
+        assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE)
+
+        // View is restored to GONE once the dialog is dismissed.
+        runOnMainThreadAndWaitForIdleSync { dialog.dismiss() }
+        assertThat(touchSurface.visibility).isEqualTo(View.GONE)
+    }
+
     private fun createAndShowDialog(
         animator: DialogLaunchAnimator = dialogLaunchAnimator,
     ): TestDialog {
         val touchSurface = createTouchSurface()
-        return runOnMainThreadAndWaitForIdleSync {
-            val dialog = TestDialog(context)
-            animator.showFromView(dialog, touchSurface)
-            dialog
-        }
+        return showDialogFromView(touchSurface, animator)
     }
 
     private fun createTouchSurface(): View {
         return runOnMainThreadAndWaitForIdleSync {
             val touchSurfaceRoot = LinearLayout(context)
-            val touchSurface = View(context)
+            val touchSurface = TouchSurfaceView(context)
             touchSurfaceRoot.addView(touchSurface)
 
             // We need to attach the root to the window manager otherwise the exit animation will
-            // be skipped
+            // be skipped.
             ViewUtils.attachView(touchSurfaceRoot)
             attachedViews.add(touchSurfaceRoot)
 
@@ -231,6 +282,17 @@
         }
     }
 
+    private fun showDialogFromView(
+        touchSurface: View,
+        animator: DialogLaunchAnimator = dialogLaunchAnimator,
+    ): TestDialog {
+        return runOnMainThreadAndWaitForIdleSync {
+            val dialog = TestDialog(context)
+            animator.showFromView(dialog, touchSurface)
+            dialog
+        }
+    }
+
     private fun createDialogAndShowFromDialog(animateFrom: Dialog): TestDialog {
         return runOnMainThreadAndWaitForIdleSync {
             val dialog = TestDialog(context)
@@ -248,6 +310,22 @@
         return result
     }
 
+    private class TouchSurfaceView(context: Context) : FrameLayout(context), LaunchableView {
+        private val delegate =
+            LaunchableViewDelegate(
+                this,
+                superSetVisibility = { super.setVisibility(it) },
+            )
+
+        override fun setShouldBlockVisibilityChanges(block: Boolean) {
+            delegate.setShouldBlockVisibilityChanges(block)
+        }
+
+        override fun setVisibility(visibility: Int) {
+            delegate.setVisibility(visibility)
+        }
+    }
+
     private class TestDialog(context: Context) : Dialog(context) {
         companion object {
             const val DIALOG_WIDTH = 100
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
index 56043e30..34ddf79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
@@ -39,7 +39,8 @@
 
     @Test
     fun processTouch() {
-        overlapDetector.shouldReturn = testCase.isGoodOverlap
+        overlapDetector.shouldReturn =
+            testCase.currentPointers.associate { pointer -> pointer.id to pointer.onSensor }
 
         val actual =
             underTest.processTouch(
@@ -56,7 +57,7 @@
 
     data class TestCase(
         val event: MotionEvent,
-        val isGoodOverlap: Boolean,
+        val currentPointers: List<TestPointer>,
         val previousPointerOnSensorId: Int,
         val overlayParams: UdfpsOverlayParams,
         val expected: TouchProcessorResult,
@@ -91,28 +92,21 @@
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_DOWN,
                         previousPointerOnSensorId = INVALID_POINTER_ID,
-                        isGoodOverlap = true,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
                         expectedInteractionEvent = InteractionEvent.DOWN,
-                        expectedPointerOnSensorId = POINTER_ID,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_DOWN,
-                        previousPointerOnSensorId = POINTER_ID,
-                        isGoodOverlap = true,
-                        expectedInteractionEvent = InteractionEvent.DOWN,
-                        expectedPointerOnSensorId = POINTER_ID,
+                        expectedPointerOnSensorId = POINTER_ID_1,
                     ),
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_DOWN,
                         previousPointerOnSensorId = INVALID_POINTER_ID,
-                        isGoodOverlap = false,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
                         expectedPointerOnSensorId = INVALID_POINTER_ID,
                     ),
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_DOWN,
-                        previousPointerOnSensorId = POINTER_ID,
-                        isGoodOverlap = false,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
                         expectedInteractionEvent = InteractionEvent.UP,
                         expectedPointerOnSensorId = INVALID_POINTER_ID,
                     ),
@@ -120,109 +114,226 @@
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_MOVE,
                         previousPointerOnSensorId = INVALID_POINTER_ID,
-                        isGoodOverlap = true,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
                         expectedInteractionEvent = InteractionEvent.DOWN,
-                        expectedPointerOnSensorId = POINTER_ID,
+                        expectedPointerOnSensorId = POINTER_ID_1,
                     ),
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_MOVE,
-                        previousPointerOnSensorId = POINTER_ID,
-                        isGoodOverlap = true,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
-                        expectedPointerOnSensorId = POINTER_ID,
+                        expectedPointerOnSensorId = POINTER_ID_1,
                     ),
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_MOVE,
                         previousPointerOnSensorId = INVALID_POINTER_ID,
-                        isGoodOverlap = false,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
                         expectedPointerOnSensorId = INVALID_POINTER_ID,
                     ),
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_MOVE,
-                        previousPointerOnSensorId = POINTER_ID,
-                        isGoodOverlap = false,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
                         expectedInteractionEvent = InteractionEvent.UP,
                         expectedPointerOnSensorId = INVALID_POINTER_ID,
                     ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_MOVE,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = false),
+                                TestPointer(id = POINTER_ID_2, onSensor = true)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.DOWN,
+                        expectedPointerOnSensorId = POINTER_ID_2,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_MOVE,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = false),
+                                TestPointer(id = POINTER_ID_2, onSensor = true)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = POINTER_ID_2,
+                    ),
                     // MotionEvent.ACTION_UP
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_UP,
                         previousPointerOnSensorId = INVALID_POINTER_ID,
-                        isGoodOverlap = true,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
                         expectedInteractionEvent = InteractionEvent.UP,
                         expectedPointerOnSensorId = INVALID_POINTER_ID,
                     ),
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_UP,
-                        previousPointerOnSensorId = POINTER_ID,
-                        isGoodOverlap = true,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
                         expectedInteractionEvent = InteractionEvent.UP,
                         expectedPointerOnSensorId = INVALID_POINTER_ID,
                     ),
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_UP,
                         previousPointerOnSensorId = INVALID_POINTER_ID,
-                        isGoodOverlap = false,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
                         expectedPointerOnSensorId = INVALID_POINTER_ID,
                     ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_UP,
-                        previousPointerOnSensorId = POINTER_ID,
-                        isGoodOverlap = false,
-                        expectedInteractionEvent = InteractionEvent.UP,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
                     // MotionEvent.ACTION_CANCEL
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_CANCEL,
                         previousPointerOnSensorId = INVALID_POINTER_ID,
-                        isGoodOverlap = true,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
                         expectedInteractionEvent = InteractionEvent.CANCEL,
                         expectedPointerOnSensorId = INVALID_POINTER_ID,
                     ),
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_CANCEL,
-                        previousPointerOnSensorId = POINTER_ID,
-                        isGoodOverlap = true,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
                         expectedInteractionEvent = InteractionEvent.CANCEL,
                         expectedPointerOnSensorId = INVALID_POINTER_ID,
                     ),
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_CANCEL,
                         previousPointerOnSensorId = INVALID_POINTER_ID,
-                        isGoodOverlap = false,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
                         expectedInteractionEvent = InteractionEvent.CANCEL,
                         expectedPointerOnSensorId = INVALID_POINTER_ID,
                     ),
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_CANCEL,
-                        previousPointerOnSensorId = POINTER_ID,
-                        isGoodOverlap = false,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
                         expectedInteractionEvent = InteractionEvent.CANCEL,
                         expectedPointerOnSensorId = INVALID_POINTER_ID,
                     ),
+                    // MotionEvent.ACTION_POINTER_DOWN
+                    genPositiveTestCases(
+                        motionEventAction =
+                            MotionEvent.ACTION_POINTER_DOWN +
+                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = true),
+                                TestPointer(id = POINTER_ID_2, onSensor = false)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.DOWN,
+                        expectedPointerOnSensorId = POINTER_ID_1,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction =
+                            MotionEvent.ACTION_POINTER_DOWN +
+                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = false),
+                                TestPointer(id = POINTER_ID_2, onSensor = true)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.DOWN,
+                        expectedPointerOnSensorId = POINTER_ID_2
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction =
+                            MotionEvent.ACTION_POINTER_DOWN +
+                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = true),
+                                TestPointer(id = POINTER_ID_2, onSensor = false)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = POINTER_ID_1,
+                    ),
+                    // MotionEvent.ACTION_POINTER_UP
+                    genPositiveTestCases(
+                        motionEventAction =
+                            MotionEvent.ACTION_POINTER_UP +
+                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = false),
+                                TestPointer(id = POINTER_ID_2, onSensor = false)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction =
+                            MotionEvent.ACTION_POINTER_UP +
+                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+                        previousPointerOnSensorId = POINTER_ID_2,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = false),
+                                TestPointer(id = POINTER_ID_2, onSensor = true)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_POINTER_UP,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = true),
+                                TestPointer(id = POINTER_ID_2, onSensor = false)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction =
+                            MotionEvent.ACTION_POINTER_UP +
+                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = true),
+                                TestPointer(id = POINTER_ID_2, onSensor = false)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = POINTER_ID_1
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_POINTER_UP,
+                        previousPointerOnSensorId = POINTER_ID_2,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = false),
+                                TestPointer(id = POINTER_ID_2, onSensor = true)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = POINTER_ID_2
+                    )
                 )
                 .flatten() +
                 listOf(
-                        // Unsupported MotionEvent actions.
-                        genTestCasesForUnsupportedAction(MotionEvent.ACTION_POINTER_DOWN),
-                        genTestCasesForUnsupportedAction(MotionEvent.ACTION_POINTER_UP),
                         genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_ENTER),
                         genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_MOVE),
-                        genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_EXIT),
+                        genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_EXIT)
                     )
                     .flatten()
     }
 }
 
+data class TestPointer(val id: Int, val onSensor: Boolean)
+
 /* Display dimensions in native resolution and natural orientation. */
 private const val ROTATION_0_NATIVE_DISPLAY_WIDTH = 400
 private const val ROTATION_0_NATIVE_DISPLAY_HEIGHT = 600
 
 /* Placeholder touch parameters. */
-private const val POINTER_ID = 42
+private const val POINTER_ID_1 = 42
+private const val POINTER_ID_2 = 43
 private const val NATIVE_MINOR = 2.71828f
 private const val NATIVE_MAJOR = 3.14f
 private const val ORIENTATION = 1.2345f
@@ -325,7 +436,7 @@
 private val MOTION_EVENT =
     obtainMotionEvent(
         action = 0,
-        pointerId = POINTER_ID,
+        pointerId = POINTER_ID_1,
         x = 0f,
         y = 0f,
         minor = 0f,
@@ -338,7 +449,7 @@
 /* Template [NormalizedTouchData]. */
 private val NORMALIZED_TOUCH_DATA =
     NormalizedTouchData(
-        POINTER_ID,
+        POINTER_ID_1,
         x = 0f,
         y = 0f,
         NATIVE_MINOR,
@@ -384,7 +495,7 @@
 private fun genPositiveTestCases(
     motionEventAction: Int,
     previousPointerOnSensorId: Int,
-    isGoodOverlap: Boolean,
+    currentPointers: List<TestPointer>,
     expectedInteractionEvent: InteractionEvent,
     expectedPointerOnSensorId: Int
 ): List<SinglePointerTouchProcessorTest.TestCase> {
@@ -399,22 +510,47 @@
     return scaleFactors.flatMap { scaleFactor ->
         orientations.map { orientation ->
             val overlayParams = orientation.toOverlayParams(scaleFactor)
-            val nativeX = orientation.getNativeX(isGoodOverlap)
-            val nativeY = orientation.getNativeY(isGoodOverlap)
+
+            val pointerProperties =
+                currentPointers
+                    .map { pointer ->
+                        val pp = MotionEvent.PointerProperties()
+                        pp.id = pointer.id
+                        pp
+                    }
+                    .toList()
+
+            val pointerCoords =
+                currentPointers
+                    .map { pointer ->
+                        val pc = MotionEvent.PointerCoords()
+                        pc.x = orientation.getNativeX(pointer.onSensor) * scaleFactor
+                        pc.y = orientation.getNativeY(pointer.onSensor) * scaleFactor
+                        pc.touchMinor = NATIVE_MINOR * scaleFactor
+                        pc.touchMajor = NATIVE_MAJOR * scaleFactor
+                        pc.orientation = orientation.nativeOrientation
+                        pc
+                    }
+                    .toList()
+
             val event =
                 MOTION_EVENT.copy(
                     action = motionEventAction,
-                    x = nativeX * scaleFactor,
-                    y = nativeY * scaleFactor,
-                    minor = NATIVE_MINOR * scaleFactor,
-                    major = NATIVE_MAJOR * scaleFactor,
-                    orientation = orientation.nativeOrientation
+                    pointerProperties = pointerProperties,
+                    pointerCoords = pointerCoords
                 )
+
             val expectedTouchData =
-                NORMALIZED_TOUCH_DATA.copy(
-                    x = ROTATION_0_INPUTS.getNativeX(isGoodOverlap),
-                    y = ROTATION_0_INPUTS.getNativeY(isGoodOverlap),
-                )
+                if (expectedPointerOnSensorId != INVALID_POINTER_ID) {
+                    NORMALIZED_TOUCH_DATA.copy(
+                        pointerId = expectedPointerOnSensorId,
+                        x = ROTATION_0_INPUTS.getNativeX(isWithinSensor = true),
+                        y = ROTATION_0_INPUTS.getNativeY(isWithinSensor = true)
+                    )
+                } else {
+                    NormalizedTouchData()
+                }
+
             val expected =
                 TouchProcessorResult.ProcessedTouch(
                     event = expectedInteractionEvent,
@@ -423,7 +559,7 @@
                 )
             SinglePointerTouchProcessorTest.TestCase(
                 event = event,
-                isGoodOverlap = isGoodOverlap,
+                currentPointers = currentPointers,
                 previousPointerOnSensorId = previousPointerOnSensorId,
                 overlayParams = overlayParams,
                 expected = expected,
@@ -436,7 +572,7 @@
     motionEventAction: Int
 ): List<SinglePointerTouchProcessorTest.TestCase> {
     val isGoodOverlap = true
-    val previousPointerOnSensorIds = listOf(INVALID_POINTER_ID, POINTER_ID)
+    val previousPointerOnSensorIds = listOf(INVALID_POINTER_ID, POINTER_ID_1)
     return previousPointerOnSensorIds.map { previousPointerOnSensorId ->
         val overlayParams = ROTATION_0_INPUTS.toOverlayParams(scaleFactor = 1f)
         val nativeX = ROTATION_0_INPUTS.getNativeX(isGoodOverlap)
@@ -451,7 +587,7 @@
             )
         SinglePointerTouchProcessorTest.TestCase(
             event = event,
-            isGoodOverlap = isGoodOverlap,
+            currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = isGoodOverlap)),
             previousPointerOnSensorId = previousPointerOnSensorId,
             overlayParams = overlayParams,
             expected = TouchProcessorResult.Failure(),
@@ -478,13 +614,23 @@
     pc.touchMinor = minor
     pc.touchMajor = major
     pc.orientation = orientation
+    return obtainMotionEvent(action, arrayOf(pp), arrayOf(pc), time, gestureStart)
+}
+
+private fun obtainMotionEvent(
+    action: Int,
+    pointerProperties: Array<MotionEvent.PointerProperties>,
+    pointerCoords: Array<MotionEvent.PointerCoords>,
+    time: Long,
+    gestureStart: Long,
+): MotionEvent {
     return MotionEvent.obtain(
         gestureStart /* downTime */,
         time /* eventTime */,
         action /* action */,
-        1 /* pointerCount */,
-        arrayOf(pp) /* pointerProperties */,
-        arrayOf(pc) /* pointerCoords */,
+        pointerCoords.size /* pointerCount */,
+        pointerProperties /* pointerProperties */,
+        pointerCoords /* pointerCoords */,
         0 /* metaState */,
         0 /* buttonState */,
         1f /* xPrecision */,
@@ -508,4 +654,19 @@
     gestureStart: Long = this.downTime,
 ) = obtainMotionEvent(action, pointerId, x, y, minor, major, orientation, time, gestureStart)
 
+private fun MotionEvent.copy(
+    action: Int = this.action,
+    pointerProperties: List<MotionEvent.PointerProperties>,
+    pointerCoords: List<MotionEvent.PointerCoords>,
+    time: Long = this.eventTime,
+    gestureStart: Long = this.downTime
+) =
+    obtainMotionEvent(
+        action,
+        pointerProperties.toTypedArray(),
+        pointerCoords.toTypedArray(),
+        time,
+        gestureStart
+    )
+
 private fun Rect.scaled(scaleFactor: Float) = Rect(this).apply { scale(scaleFactor) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt
new file mode 100644
index 0000000..3e6cc3b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.compose
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.testing.ViewUtils
+import android.widget.FrameLayout
+import androidx.compose.ui.platform.ComposeView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ComposeInitializerTest : SysuiTestCase() {
+    @Test
+    fun testCanAddComposeViewInInitializedWindow() {
+        if (!ComposeFacade.isComposeAvailable()) {
+            return
+        }
+
+        val root = TestWindowRoot(context)
+        try {
+            runOnMainThreadAndWaitForIdleSync { ViewUtils.attachView(root) }
+            assertThat(root.isAttachedToWindow).isTrue()
+
+            runOnMainThreadAndWaitForIdleSync { root.addView(ComposeView(context)) }
+        } finally {
+            runOnMainThreadAndWaitForIdleSync { ViewUtils.detachView(root) }
+        }
+    }
+
+    private fun runOnMainThreadAndWaitForIdleSync(f: () -> Unit) {
+        mContext.mainExecutor.execute(f)
+        waitForIdleSync()
+    }
+
+    class TestWindowRoot(context: Context) : FrameLayout(context) {
+        override fun onAttachedToWindow() {
+            super.onAttachedToWindow()
+            ComposeFacade.composeInitializer().onAttachedToWindow(this)
+        }
+
+        override fun onDetachedFromWindow() {
+            super.onDetachedFromWindow()
+            ComposeFacade.composeInitializer().onDetachedFromWindow(this)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index c24c8c7..1687fdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media.controls.pipeline
 
 import android.app.Notification
+import android.app.Notification.FLAG_NO_CLEAR
 import android.app.Notification.MediaStyle
 import android.app.PendingIntent
 import android.app.smartspace.SmartspaceAction
@@ -1451,6 +1452,39 @@
         assertThat(mediaDataCaptor.value.semanticActions).isNull()
     }
 
+    @Test
+    fun testNoClearNotOngoing_canDismiss() {
+        mediaNotification =
+            SbnBuilder().run {
+                setPkg(PACKAGE_NAME)
+                modifyNotification(context).also {
+                    it.setSmallIcon(android.R.drawable.ic_media_pause)
+                    it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                    it.setOngoing(false)
+                    it.setFlag(FLAG_NO_CLEAR, true)
+                }
+                build()
+            }
+        addNotificationAndLoad()
+        assertThat(mediaDataCaptor.value.isClearable).isTrue()
+    }
+
+    @Test
+    fun testOngoing_cannotDismiss() {
+        mediaNotification =
+            SbnBuilder().run {
+                setPkg(PACKAGE_NAME)
+                modifyNotification(context).also {
+                    it.setSmallIcon(android.R.drawable.ic_media_pause)
+                    it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                    it.setOngoing(true)
+                }
+                build()
+            }
+        addNotificationAndLoad()
+        assertThat(mediaDataCaptor.value.isClearable).isFalse()
+    }
+
     /** Helper function to add a media notification and capture the resulting MediaData */
     private fun addNotificationAndLoad() {
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 19d2d33..1042ea7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -1,12 +1,16 @@
 package com.android.systemui.mediaprojection.appselector
 
 import android.content.ComponentName
+import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import org.junit.Test
@@ -21,11 +25,17 @@
     private val scope = CoroutineScope(Dispatchers.Unconfined)
     private val appSelectorComponentName = ComponentName("com.test", "AppSelector")
 
+    private val hostUserHandle = UserHandle.of(123)
+    private val otherUserHandle = UserHandle.of(456)
+
     private val view: MediaProjectionAppSelectorView = mock()
+    private val featureFlags: FeatureFlags = mock()
 
     private val controller = MediaProjectionAppSelectorController(
         taskListProvider,
         view,
+        featureFlags,
+        hostUserHandle,
         scope,
         appSelectorComponentName
     )
@@ -98,15 +108,72 @@
         )
     }
 
+    @Test
+    fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesDisabled_bindsOnlyTasksWithHostProfile() {
+        givenEnterprisePoliciesFeatureFlag(enabled = false)
+
+        val tasks = listOf(
+            createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+            createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
+            createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+            createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
+            createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+        )
+        taskListProvider.tasks = tasks
+
+        controller.init()
+
+        verify(view).bind(
+            listOf(
+                createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+                createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+                createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+            )
+        )
+    }
+
+    @Test
+    fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesEnabled_bindsAllTasks() {
+        givenEnterprisePoliciesFeatureFlag(enabled = true)
+
+        val tasks = listOf(
+            createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+            createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
+            createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+            createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
+            createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+        )
+        taskListProvider.tasks = tasks
+
+        controller.init()
+
+        // TODO(b/233348916) should filter depending on the policies
+        verify(view).bind(
+            listOf(
+                createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+                createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
+                createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+                createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
+                createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+            )
+        )
+    }
+
+    private fun givenEnterprisePoliciesFeatureFlag(enabled: Boolean) {
+        whenever(featureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
+            .thenReturn(enabled)
+    }
+
     private fun createRecentTask(
         taskId: Int,
-        topActivityComponent: ComponentName? = null
+        topActivityComponent: ComponentName? = null,
+        userId: Int = hostUserHandle.identifier
     ): RecentTask {
         return RecentTask(
             taskId = taskId,
             topActivityComponent = topActivityComponent,
             baseIntentComponent = ComponentName("com", "Test"),
-            userId = 0,
+            userId = userId,
             colorBackground = 0
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
index 4d89495..3b85dba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
@@ -39,11 +39,10 @@
 
     @Test
     public void needReinflate_differentLength() {
-        // TODO(b/174258598) Please replace FLAG_MUTABLE_UNAUDITED below
-        // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
         PendingIntent pendingIntent =
-                PendingIntent.getActivity(mContext, 0, new Intent(),
-                        PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                PendingIntent.getActivity(mContext, 0,
+                        new Intent().setPackage(mContext.getPackageName()),
+                        PendingIntent.FLAG_MUTABLE);
         Notification.Action action =
                 createActionBuilder("first", R.drawable.ic_corp_icon, pendingIntent).build();
         assertThat(NotificationUiAdjustment.needReinflate(
@@ -54,11 +53,10 @@
 
     @Test
     public void needReinflate_differentLabels() {
-        // TODO(b/174258598) Please replace FLAG_MUTABLE_UNAUDITED below
-        // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
         PendingIntent pendingIntent =
-                PendingIntent.getActivity(mContext, 0, new Intent(),
-                        PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                PendingIntent.getActivity(mContext, 0,
+                        new Intent().setPackage(mContext.getPackageName()),
+                        PendingIntent.FLAG_MUTABLE);
         Notification.Action firstAction =
                 createActionBuilder("first", R.drawable.ic_corp_icon, pendingIntent).build();
         Notification.Action secondAction =
@@ -72,11 +70,10 @@
 
     @Test
     public void needReinflate_differentIcons() {
-        // TODO(b/174258598) Please replace FLAG_MUTABLE_UNAUDITED below
-        // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
         PendingIntent pendingIntent =
-                PendingIntent.getActivity(mContext, 0, new Intent(),
-                        PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                PendingIntent.getActivity(mContext, 0,
+                        new Intent().setPackage(mContext.getPackageName()),
+                        PendingIntent.FLAG_MUTABLE);
         Notification.Action firstAction =
                 createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent).build();
         Notification.Action secondAction =
@@ -91,14 +88,15 @@
 
     @Test
     public void needReinflate_differentPendingIntent() {
-        // TODO(b/174258598) Please replace FLAG_MUTABLE_UNAUDITED below
-        // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
         PendingIntent firstPendingIntent =
-                PendingIntent.getActivity(mContext, 0, new Intent(Intent.ACTION_VIEW),
-                        PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                PendingIntent.getActivity(mContext, 0,
+                        new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()),
+                        PendingIntent.FLAG_MUTABLE);
         PendingIntent secondPendingIntent =
-                PendingIntent.getActivity(mContext, 0, new Intent(Intent.ACTION_PROCESS_TEXT),
-                        PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                PendingIntent.getActivity(mContext, 0,
+                        new Intent(Intent.ACTION_PROCESS_TEXT)
+                                .setPackage(mContext.getPackageName()),
+                        PendingIntent.FLAG_MUTABLE);
         Notification.Action firstAction =
                 createActionBuilder("same", R.drawable.ic_corp_icon, firstPendingIntent)
                         .build();
@@ -114,11 +112,10 @@
 
     @Test
     public void needReinflate_differentChoices() {
-        // TODO(b/174258598) Please replace FLAG_MUTABLE_UNAUDITED below
-        // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
         PendingIntent pendingIntent =
-                PendingIntent.getActivity(mContext, 0, new Intent(),
-                        PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                PendingIntent.getActivity(mContext, 0,
+                        new Intent().setPackage(mContext.getPackageName()),
+                        PendingIntent.FLAG_MUTABLE);
 
         RemoteInput firstRemoteInput =
                 createRemoteInput("same", "same", new CharSequence[] {"first"});
@@ -142,11 +139,10 @@
 
     @Test
     public void needReinflate_differentRemoteInputLabel() {
-        // TODO(b/174258598) Please replace FLAG_MUTABLE_UNAUDITED below
-        // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
         PendingIntent pendingIntent =
-                PendingIntent.getActivity(mContext, 0, new Intent(),
-                        PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                PendingIntent.getActivity(mContext, 0,
+                        new Intent().setPackage(mContext.getPackageName()),
+                        PendingIntent.FLAG_MUTABLE);
 
         RemoteInput firstRemoteInput =
                 createRemoteInput("same", "first", new CharSequence[] {"same"});
@@ -170,11 +166,10 @@
 
     @Test
     public void needReinflate_negative() {
-        // TODO(b/174258598) Please replace FLAG_MUTABLE_UNAUDITED below
-        // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
         PendingIntent pendingIntent =
-                PendingIntent.getActivity(mContext, 0, new Intent(),
-                        PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                PendingIntent.getActivity(mContext, 0,
+                        new Intent().setPackage(mContext.getPackageName()),
+                        PendingIntent.FLAG_MUTABLE);
         RemoteInput firstRemoteInput =
                 createRemoteInput("same", "same", new CharSequence[] {"same"});
         RemoteInput secondRemoteInput =
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 601771d..58fe2a0 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
@@ -885,7 +885,8 @@
 
     private NotificationEntry createBubble(String groupKey, Integer groupAlert) {
         Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder(
-                PendingIntent.getActivity(mContext, 0, new Intent(),
+                PendingIntent.getActivity(mContext, 0,
+                        new Intent().setPackage(mContext.getPackageName()),
                         PendingIntent.FLAG_MUTABLE),
                 Icon.createWithResource(mContext.getResources(), R.drawable.android))
                 .build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
new file mode 100644
index 0000000..3e90ed9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2023 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.phone
+
+import android.content.pm.PackageManager
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_FLIPPED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.tuner.TunerService
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class KeyguardBypassControllerTest : SysuiTestCase() {
+
+    private lateinit var keyguardBypassController: KeyguardBypassController
+    private lateinit var postureControllerCallback: DevicePostureController.Callback
+    @Mock private lateinit var tunerService: TunerService
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+    @Mock private lateinit var devicePostureController: DevicePostureController
+    @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var packageManager: PackageManager
+    @Captor
+    private val postureCallbackCaptor =
+        ArgumentCaptor.forClass(DevicePostureController.Callback::class.java)
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
+
+    @Before
+    fun setUp() {
+        context.setMockPackageManager(packageManager)
+        whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true)
+        whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
+    }
+
+    @After
+    fun tearDown() {
+        reset(devicePostureController)
+        reset(keyguardStateController)
+    }
+
+    private fun defaultConfigPostureClosed() {
+        context.orCreateTestableResources.addOverride(
+            R.integer.config_face_auth_supported_posture,
+            DEVICE_POSTURE_CLOSED
+        )
+        initKeyguardBypassController()
+        verify(devicePostureController).addCallback(postureCallbackCaptor.capture())
+        postureControllerCallback = postureCallbackCaptor.value
+    }
+
+    private fun defaultConfigPostureOpened() {
+        context.orCreateTestableResources.addOverride(
+            R.integer.config_face_auth_supported_posture,
+            DEVICE_POSTURE_OPENED
+        )
+        initKeyguardBypassController()
+        verify(devicePostureController).addCallback(postureCallbackCaptor.capture())
+        postureControllerCallback = postureCallbackCaptor.value
+    }
+
+    private fun defaultConfigPostureFlipped() {
+        context.orCreateTestableResources.addOverride(
+            R.integer.config_face_auth_supported_posture,
+            DEVICE_POSTURE_FLIPPED
+        )
+        initKeyguardBypassController()
+        verify(devicePostureController).addCallback(postureCallbackCaptor.capture())
+        postureControllerCallback = postureCallbackCaptor.value
+    }
+
+    private fun defaultConfigPostureUnknown() {
+        context.orCreateTestableResources.addOverride(
+            R.integer.config_face_auth_supported_posture,
+            DEVICE_POSTURE_UNKNOWN
+        )
+        initKeyguardBypassController()
+        verify(devicePostureController, never()).addCallback(postureCallbackCaptor.capture())
+    }
+
+    private fun initKeyguardBypassController() {
+        keyguardBypassController =
+            KeyguardBypassController(
+                context,
+                tunerService,
+                statusBarStateController,
+                lockscreenUserManager,
+                keyguardStateController,
+                shadeExpansionStateManager,
+                devicePostureController,
+                dumpManager
+            )
+    }
+
+    @Test
+    fun configDevicePostureClosed_matchState_isPostureAllowedForFaceAuth_returnTrue() {
+        defaultConfigPostureClosed()
+
+        postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED)
+
+        assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isTrue()
+    }
+
+    @Test
+    fun configDevicePostureOpen_matchState_isPostureAllowedForFaceAuth_returnTrue() {
+        defaultConfigPostureOpened()
+
+        postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isTrue()
+    }
+
+    @Test
+    fun configDevicePostureFlipped_matchState_isPostureAllowedForFaceAuth_returnTrue() {
+        defaultConfigPostureFlipped()
+
+        postureControllerCallback.onPostureChanged(DEVICE_POSTURE_FLIPPED)
+
+        assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isTrue()
+    }
+
+    @Test
+    fun configDevicePostureClosed_changeOpened_isPostureAllowedForFaceAuth_returnFalse() {
+        defaultConfigPostureClosed()
+
+        postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse()
+    }
+
+    @Test
+    fun configDevicePostureClosed_changeFlipped_isPostureAllowedForFaceAuth_returnFalse() {
+        defaultConfigPostureClosed()
+
+        postureControllerCallback.onPostureChanged(DEVICE_POSTURE_FLIPPED)
+
+        assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse()
+    }
+
+    @Test
+    fun configDevicePostureOpened_changeClosed_isPostureAllowedForFaceAuth_returnFalse() {
+        defaultConfigPostureOpened()
+
+        postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED)
+
+        assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse()
+    }
+
+    @Test
+    fun configDevicePostureOpened_changeFlipped_isPostureAllowedForFaceAuth_returnFalse() {
+        defaultConfigPostureOpened()
+
+        postureControllerCallback.onPostureChanged(DEVICE_POSTURE_FLIPPED)
+
+        assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse()
+    }
+
+    @Test
+    fun configDevicePostureFlipped_changeClosed_isPostureAllowedForFaceAuth_returnFalse() {
+        defaultConfigPostureFlipped()
+
+        postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED)
+
+        assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse()
+    }
+
+    @Test
+    fun configDevicePostureFlipped_changeOpened_isPostureAllowedForFaceAuth_returnFalse() {
+        defaultConfigPostureFlipped()
+
+        postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse()
+    }
+
+    @Test
+    fun defaultConfigPostureClosed_canOverrideByPassAlways_shouldReturnFalse() {
+        context.orCreateTestableResources.addOverride(
+            R.integer.config_face_unlock_bypass_override,
+            1 /* FACE_UNLOCK_BYPASS_ALWAYS */
+        )
+
+        defaultConfigPostureClosed()
+
+        postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        assertThat(keyguardBypassController.bypassEnabled).isFalse()
+    }
+
+    @Test
+    fun defaultConfigPostureUnknown_canNotOverrideByPassAlways_shouldReturnTrue() {
+        context.orCreateTestableResources.addOverride(
+            R.integer.config_face_unlock_bypass_override,
+            1 /* FACE_UNLOCK_BYPASS_ALWAYS */
+        )
+
+        defaultConfigPostureUnknown()
+
+        assertThat(keyguardBypassController.bypassEnabled).isTrue()
+    }
+
+    @Test
+    fun defaultConfigPostureUnknown_canNotOverrideByPassNever_shouldReturnFalse() {
+        context.orCreateTestableResources.addOverride(
+            R.integer.config_face_unlock_bypass_override,
+            2 /* FACE_UNLOCK_BYPASS_NEVER */
+        )
+
+        defaultConfigPostureUnknown()
+
+        assertThat(keyguardBypassController.bypassEnabled).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
new file mode 100644
index 0000000..3bc288a2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2023 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.phone
+
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY
+import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl.EXTERNAL_SLOT_SUFFIX
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+@SmallTest
+class StatusBarIconControllerImplTest : SysuiTestCase() {
+
+    private lateinit var underTest: StatusBarIconControllerImpl
+
+    private lateinit var iconList: StatusBarIconList
+    private val iconGroup: StatusBarIconController.IconManager = mock()
+
+    @Before
+    fun setUp() {
+        iconList = StatusBarIconList(arrayOf())
+        underTest =
+            StatusBarIconControllerImpl(
+                context,
+                mock(),
+                mock(),
+                mock(),
+                mock(),
+                mock(),
+                iconList,
+                mock(),
+            )
+        underTest.addIconGroup(iconGroup)
+    }
+
+    /** Regression test for b/255428281. */
+    @Test
+    fun internalAndExternalIconWithSameName_bothDisplayed() {
+        val slotName = "mute"
+
+        // Internal
+        underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+        // External
+        val externalIcon =
+            StatusBarIcon(
+                "external.package",
+                UserHandle.ALL,
+                /* iconId= */ 2,
+                /* iconLevel= */ 0,
+                /* number= */ 0,
+                "contentDescription",
+            )
+        underTest.setIcon(slotName, externalIcon)
+
+        assertThat(iconList.slots).hasSize(2)
+        // Whichever was added last comes first
+        assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+        assertThat(iconList.slots[1].name).isEqualTo(slotName)
+        assertThat(iconList.slots[0].hasIconsInSlot()).isTrue()
+        assertThat(iconList.slots[1].hasIconsInSlot()).isTrue()
+    }
+
+    /** Regression test for b/255428281. */
+    @Test
+    fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveIcon_internalStays() {
+        val slotName = "mute"
+
+        // Internal
+        underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+        // External
+        underTest.setIcon(slotName, createExternalIcon())
+
+        // WHEN the external icon is removed via #removeIcon
+        underTest.removeIcon(slotName)
+
+        // THEN the external icon is removed but the internal icon remains
+        // Note: [StatusBarIconList] never removes slots from its list, it just sets the holder for
+        // the slot to null when an icon is removed.
+        assertThat(iconList.slots).hasSize(2)
+        assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+        assertThat(iconList.slots[1].name).isEqualTo(slotName)
+        assertThat(iconList.slots[0].hasIconsInSlot()).isFalse() // Indicates removal
+        assertThat(iconList.slots[1].hasIconsInSlot()).isTrue()
+
+        verify(iconGroup).onRemoveIcon(0)
+    }
+
+    /** Regression test for b/255428281. */
+    @Test
+    fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveAll_internalStays() {
+        val slotName = "mute"
+
+        // Internal
+        underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+        // External
+        underTest.setIcon(slotName, createExternalIcon())
+
+        // WHEN the external icon is removed via #removeAllIconsForExternalSlot
+        underTest.removeAllIconsForExternalSlot(slotName)
+
+        // THEN the external icon is removed but the internal icon remains
+        assertThat(iconList.slots).hasSize(2)
+        assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+        assertThat(iconList.slots[1].name).isEqualTo(slotName)
+        assertThat(iconList.slots[0].hasIconsInSlot()).isFalse() // Indicates removal
+        assertThat(iconList.slots[1].hasIconsInSlot()).isTrue()
+
+        verify(iconGroup).onRemoveIcon(0)
+    }
+
+    /** Regression test for b/255428281. */
+    @Test
+    fun internalAndExternalIconWithSameName_externalRemoved_viaSetNull_internalStays() {
+        val slotName = "mute"
+
+        // Internal
+        underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+        // External
+        underTest.setIcon(slotName, createExternalIcon())
+
+        // WHEN the external icon is removed via a #setIcon(null)
+        underTest.setIcon(slotName, /* icon= */ null)
+
+        // THEN the external icon is removed but the internal icon remains
+        assertThat(iconList.slots).hasSize(2)
+        assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+        assertThat(iconList.slots[1].name).isEqualTo(slotName)
+        assertThat(iconList.slots[0].hasIconsInSlot()).isFalse() // Indicates removal
+        assertThat(iconList.slots[1].hasIconsInSlot()).isTrue()
+
+        verify(iconGroup).onRemoveIcon(0)
+    }
+
+    /** Regression test for b/255428281. */
+    @Test
+    fun internalAndExternalIconWithSameName_internalRemoved_viaRemove_externalStays() {
+        val slotName = "mute"
+
+        // Internal
+        underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+        // External
+        underTest.setIcon(slotName, createExternalIcon())
+
+        // WHEN the internal icon is removed via #removeIcon
+        underTest.removeIcon(slotName, /* tag= */ 0)
+
+        // THEN the external icon is removed but the internal icon remains
+        assertThat(iconList.slots).hasSize(2)
+        assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+        assertThat(iconList.slots[1].name).isEqualTo(slotName)
+        assertThat(iconList.slots[0].hasIconsInSlot()).isTrue()
+        assertThat(iconList.slots[1].hasIconsInSlot()).isFalse() // Indicates removal
+
+        verify(iconGroup).onRemoveIcon(1)
+    }
+
+    /** Regression test for b/255428281. */
+    @Test
+    fun internalAndExternalIconWithSameName_internalRemoved_viaRemoveAll_externalStays() {
+        val slotName = "mute"
+
+        // Internal
+        underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+        // External
+        underTest.setIcon(slotName, createExternalIcon())
+
+        // WHEN the internal icon is removed via #removeAllIconsForSlot
+        underTest.removeAllIconsForSlot(slotName)
+
+        // THEN the external icon is removed but the internal icon remains
+        assertThat(iconList.slots).hasSize(2)
+        assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+        assertThat(iconList.slots[1].name).isEqualTo(slotName)
+        assertThat(iconList.slots[0].hasIconsInSlot()).isTrue()
+        assertThat(iconList.slots[1].hasIconsInSlot()).isFalse() // Indicates removal
+
+        verify(iconGroup).onRemoveIcon(1)
+    }
+
+    /** Regression test for b/255428281. */
+    @Test
+    fun internalAndExternalIconWithSameName_internalUpdatedIndependently() {
+        val slotName = "mute"
+
+        // Internal
+        underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+        // External
+        val startingExternalIcon =
+            StatusBarIcon(
+                "external.package",
+                UserHandle.ALL,
+                /* iconId= */ 20,
+                /* iconLevel= */ 0,
+                /* number= */ 0,
+                "externalDescription",
+            )
+        underTest.setIcon(slotName, startingExternalIcon)
+
+        // WHEN the internal icon is updated
+        underTest.setIcon(slotName, /* resourceId= */ 11, "newContentDescription")
+
+        // THEN only the internal slot gets the updates
+        val internalSlot = iconList.slots[1]
+        val internalHolder = internalSlot.getHolderForTag(TAG_PRIMARY)!!
+        assertThat(internalSlot.name).isEqualTo(slotName)
+        assertThat(internalHolder.icon!!.contentDescription).isEqualTo("newContentDescription")
+        assertThat(internalHolder.icon!!.icon.resId).isEqualTo(11)
+
+        // And the external slot has its own values
+        val externalSlot = iconList.slots[0]
+        val externalHolder = externalSlot.getHolderForTag(TAG_PRIMARY)!!
+        assertThat(externalSlot.name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+        assertThat(externalHolder.icon!!.contentDescription).isEqualTo("externalDescription")
+        assertThat(externalHolder.icon!!.icon.resId).isEqualTo(20)
+    }
+
+    /** Regression test for b/255428281. */
+    @Test
+    fun internalAndExternalIconWithSameName_externalUpdatedIndependently() {
+        val slotName = "mute"
+
+        // Internal
+        underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+        // External
+        val startingExternalIcon =
+            StatusBarIcon(
+                "external.package",
+                UserHandle.ALL,
+                /* iconId= */ 20,
+                /* iconLevel= */ 0,
+                /* number= */ 0,
+                "externalDescription",
+            )
+        underTest.setIcon(slotName, startingExternalIcon)
+
+        // WHEN the external icon is updated
+        val newExternalIcon =
+            StatusBarIcon(
+                "external.package",
+                UserHandle.ALL,
+                /* iconId= */ 21,
+                /* iconLevel= */ 0,
+                /* number= */ 0,
+                "newExternalDescription",
+            )
+        underTest.setIcon(slotName, newExternalIcon)
+
+        // THEN only the external slot gets the updates
+        val externalSlot = iconList.slots[0]
+        val externalHolder = externalSlot.getHolderForTag(TAG_PRIMARY)!!
+        assertThat(externalSlot.name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+        assertThat(externalHolder.icon!!.contentDescription).isEqualTo("newExternalDescription")
+        assertThat(externalHolder.icon!!.icon.resId).isEqualTo(21)
+
+        // And the internal slot has its own values
+        val internalSlot = iconList.slots[1]
+        val internalHolder = internalSlot.getHolderForTag(TAG_PRIMARY)!!
+        assertThat(internalSlot.name).isEqualTo(slotName)
+        assertThat(internalHolder.icon!!.contentDescription).isEqualTo("contentDescription")
+        assertThat(internalHolder.icon!!.icon.resId).isEqualTo(10)
+    }
+
+    @Test
+    fun externalSlot_alreadyEndsWithSuffix_suffixNotAddedTwice() {
+        underTest.setIcon("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon())
+
+        assertThat(iconList.slots).hasSize(1)
+        assertThat(iconList.slots[0].name).isEqualTo("myslot$EXTERNAL_SLOT_SUFFIX")
+    }
+
+    private fun createExternalIcon(): StatusBarIcon {
+        return StatusBarIcon(
+            "external.package",
+            UserHandle.ALL,
+            /* iconId= */ 2,
+            /* iconLevel= */ 0,
+            /* number= */ 0,
+            "contentDescription",
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index 9764b8c..4aa86c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -197,7 +197,7 @@
     public void testPinEntry_logsPeek() {
         // Needs full screen intent in order to be pinned
         final PendingIntent fullScreenIntent = PendingIntent.getActivity(mContext, 0,
-                new Intent(), PendingIntent.FLAG_MUTABLE);
+                new Intent().setPackage(mContext.getPackageName()), PendingIntent.FLAG_MUTABLE);
 
         HeadsUpManager.HeadsUpEntry entryToPin = mHeadsUpManager.new HeadsUpEntry();
         entryToPin.setEntry(new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
index 5e2fa98..85052e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
@@ -574,7 +574,8 @@
     private void setupAppGeneratedReplies(
             CharSequence[] smartReplies, boolean allowSystemGeneratedReplies) {
         PendingIntent pendingIntent =
-                PendingIntent.getBroadcast(mContext, 0, TEST_INTENT,
+                PendingIntent.getBroadcast(mContext, 0,
+                        TEST_INTENT.setPackage(mContext.getPackageName()),
                         PendingIntent.FLAG_MUTABLE);
         Notification.Action action =
                 new Notification.Action.Builder(null, "Test Action", pendingIntent).build();
@@ -606,7 +607,8 @@
     }
 
     private Notification.Action.Builder createActionBuilder(String actionTitle, Intent intent) {
-        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent,
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
+                intent.setPackage(mContext.getPackageName()),
                 PendingIntent.FLAG_MUTABLE);
         return new Notification.Action.Builder(mActionIcon, actionTitle, pendingIntent);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 0cca7b2..391c8ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -140,7 +140,8 @@
 
     private void setTestPendingIntent(RemoteInputViewController controller) {
         PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
-                new Intent(TEST_ACTION), PendingIntent.FLAG_MUTABLE);
+                new Intent(TEST_ACTION).setPackage(mContext.getPackageName()),
+                PendingIntent.FLAG_MUTABLE);
         RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).build();
         RemoteInput[] inputs = {input};
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index a7ff59c..d9d8b63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -488,7 +488,8 @@
     private SmartReplyView.SmartReplies createSmartReplies(CharSequence[] choices,
             boolean fromAssistant) {
         PendingIntent pendingIntent =
-                PendingIntent.getBroadcast(mContext, 0, new Intent(TEST_ACTION),
+                PendingIntent.getBroadcast(mContext, 0,
+                        new Intent(TEST_ACTION).setPackage(mContext.getPackageName()),
                         PendingIntent.FLAG_MUTABLE);
         RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(choices).build();
         return new SmartReplyView.SmartReplies(
@@ -505,7 +506,8 @@
 
     private Notification.Action createAction(String actionTitle) {
         PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
-                new Intent(TEST_ACTION), PendingIntent.FLAG_MUTABLE);
+                new Intent(TEST_ACTION).setPackage(mContext.getPackageName()),
+                PendingIntent.FLAG_MUTABLE);
         return new Notification.Action.Builder(mActionIcon, actionTitle, pendingIntent).build();
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt
index 8176dd0..1bdee36 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt
@@ -19,9 +19,10 @@
 import android.graphics.Rect
 
 class FakeOverlapDetector : OverlapDetector {
-    var shouldReturn: Boolean = false
+    var shouldReturn: Map<Int, Boolean> = mapOf()
 
     override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean {
-        return shouldReturn
+        return shouldReturn[touchData.pointerId]
+            ?: error("Unexpected PointerId not declared in TestCase currentPointers")
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 2d6d29a..926c6c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -98,6 +98,10 @@
     }
 
     @Override
+    public void removeAllIconsForExternalSlot(String slot) {
+    }
+
+    @Override
     public void setIconAccessibilityLiveRegion(String slot, int mode) {
     }
 
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
index 5a868a4..cfb959e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
@@ -22,8 +22,8 @@
 import android.os.Handler
 import android.view.IWindowManager
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
-import com.android.systemui.unfold.dagger.UnfoldBackground
 import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
 import com.android.systemui.unfold.updates.FoldProvider
 import com.android.systemui.unfold.updates.RotationChangeProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
@@ -58,7 +58,7 @@
             @BindsInstance sensorManager: SensorManager,
             @BindsInstance @UnfoldMain handler: Handler,
             @BindsInstance @UnfoldMain executor: Executor,
-            @BindsInstance @UnfoldBackground backgroundExecutor: Executor,
+            @BindsInstance @UnfoldSingleThreadBg singleThreadBgExecutor: Executor,
             @BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
             @BindsInstance windowManager: IWindowManager,
             @BindsInstance contentResolver: ContentResolver = context.contentResolver
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index 3fa5469..31616fa 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -16,9 +16,7 @@
 
 package com.android.systemui.unfold
 
-import android.hardware.SensorManager
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
-import com.android.systemui.unfold.dagger.UnfoldBackground
 import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
 import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
 import com.android.systemui.unfold.updates.DeviceFoldStateProvider
@@ -34,55 +32,18 @@
 import dagger.Module
 import dagger.Provides
 import java.util.Optional
-import java.util.concurrent.Executor
+import javax.inject.Provider
 import javax.inject.Singleton
 
-@Module
+@Module(includes = [UnfoldSharedInternalModule::class])
 class UnfoldSharedModule {
     @Provides
     @Singleton
-    fun unfoldTransitionProgressProvider(
-        config: UnfoldTransitionConfig,
-        scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory,
-        tracingListener: ATraceLoggerTransitionProgressListener,
-        foldStateProvider: FoldStateProvider
-    ): Optional<UnfoldTransitionProgressProvider> =
-        if (!config.isEnabled) {
-            Optional.empty()
-        } else {
-            val baseProgressProvider =
-                if (config.isHingeAngleEnabled) {
-                    PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
-                } else {
-                    FixedTimingTransitionProgressProvider(foldStateProvider)
-                }
-            Optional.of(
-                scaleAwareProviderFactory.wrap(baseProgressProvider).apply {
-                    // Always present callback that logs animation beginning and end.
-                    addCallback(tracingListener)
-                }
-            )
-        }
-
-    @Provides
-    @Singleton
     fun provideFoldStateProvider(
         deviceFoldStateProvider: DeviceFoldStateProvider
     ): FoldStateProvider = deviceFoldStateProvider
 
     @Provides
-    fun hingeAngleProvider(
-        config: UnfoldTransitionConfig,
-        sensorManager: SensorManager,
-        @UnfoldBackground executor: Executor
-    ): HingeAngleProvider =
-        if (config.isHingeAngleEnabled) {
-            HingeSensorAngleProvider(sensorManager, executor)
-        } else {
-            EmptyHingeAngleProvider
-        }
-
-    @Provides
     @Singleton
     fun unfoldKeyguardVisibilityProvider(
         impl: UnfoldKeyguardVisibilityManagerImpl
@@ -94,3 +55,51 @@
         impl: UnfoldKeyguardVisibilityManagerImpl
     ): UnfoldKeyguardVisibilityManager = impl
 }
+
+/**
+ * Needed as methods inside must be public, but their parameters can be internal (and, a public
+ * method can't have internal parameters). Making the module internal and included in a public one
+ * fixes the issue.
+ */
+@Module
+internal class UnfoldSharedInternalModule {
+    @Provides
+    @Singleton
+    fun unfoldTransitionProgressProvider(
+        config: UnfoldTransitionConfig,
+        scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory,
+        tracingListener: ATraceLoggerTransitionProgressListener,
+        physicsBasedUnfoldTransitionProgressProvider:
+            Provider<PhysicsBasedUnfoldTransitionProgressProvider>,
+        fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>,
+    ): Optional<UnfoldTransitionProgressProvider> {
+        if (!config.isEnabled) {
+            return Optional.empty()
+        }
+        val baseProgressProvider =
+            if (config.isHingeAngleEnabled) {
+                physicsBasedUnfoldTransitionProgressProvider.get()
+            } else {
+                fixedTimingTransitionProgressProvider.get()
+            }
+
+        return Optional.of(
+            scaleAwareProviderFactory.wrap(baseProgressProvider).apply {
+                // Always present callback that logs animation beginning and end.
+                addCallback(tracingListener)
+            }
+        )
+    }
+
+    @Provides
+    fun hingeAngleProvider(
+        config: UnfoldTransitionConfig,
+        hingeAngleSensorProvider: Provider<HingeSensorAngleProvider>
+    ): HingeAngleProvider {
+        return if (config.isHingeAngleEnabled) {
+            hingeAngleSensorProvider.get()
+        } else {
+            EmptyHingeAngleProvider
+        }
+    }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index a1ed178..aa93c629 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -37,29 +37,29 @@
  * This should **never** be called from sysui, as the object is already provided in that process.
  */
 fun createUnfoldSharedComponent(
-    context: Context,
-    config: UnfoldTransitionConfig,
-    screenStatusProvider: ScreenStatusProvider,
-    foldProvider: FoldProvider,
-    activityTypeProvider: CurrentActivityTypeProvider,
-    sensorManager: SensorManager,
-    mainHandler: Handler,
-    mainExecutor: Executor,
-    backgroundExecutor: Executor,
-    tracingTagPrefix: String,
-    windowManager: IWindowManager,
+        context: Context,
+        config: UnfoldTransitionConfig,
+        screenStatusProvider: ScreenStatusProvider,
+        foldProvider: FoldProvider,
+        activityTypeProvider: CurrentActivityTypeProvider,
+        sensorManager: SensorManager,
+        mainHandler: Handler,
+        mainExecutor: Executor,
+        singleThreadBgExecutor: Executor,
+        tracingTagPrefix: String,
+        windowManager: IWindowManager,
 ): UnfoldSharedComponent =
-    DaggerUnfoldSharedComponent.factory()
-        .create(
-            context,
-            config,
-            screenStatusProvider,
-            foldProvider,
-            activityTypeProvider,
-            sensorManager,
-            mainHandler,
-            mainExecutor,
-            backgroundExecutor,
-            tracingTagPrefix,
-            windowManager,
-        )
+        DaggerUnfoldSharedComponent.factory()
+                .create(
+                        context,
+                        config,
+                        screenStatusProvider,
+                        foldProvider,
+                        activityTypeProvider,
+                        sensorManager,
+                        mainHandler,
+                        mainExecutor,
+                        singleThreadBgExecutor,
+                        tracingTagPrefix,
+                        windowManager,
+                )
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldSingleThreadBg.kt
similarity index 89%
rename from packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldSingleThreadBg.kt
index 6074795..dcac531 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldSingleThreadBg.kt
@@ -18,8 +18,7 @@
 
 /**
  * Alternative to [UiBackground] qualifier annotation in unfold module.
+ *
  * It is needed as we can't depend on SystemUI code in this module.
  */
-@Qualifier
-@Retention(AnnotationRetention.RUNTIME)
-annotation class UnfoldBackground
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class UnfoldSingleThreadBg
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
index fa59cb4..4622464 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
@@ -24,11 +24,13 @@
 import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
 import com.android.systemui.unfold.updates.FoldStateProvider
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
+import javax.inject.Inject
 
 /** Emits animation progress with fixed timing after unfolding */
-internal class FixedTimingTransitionProgressProvider(
-    private val foldStateProvider: FoldStateProvider
-) : UnfoldTransitionProgressProvider, FoldStateProvider.FoldUpdatesListener {
+internal class FixedTimingTransitionProgressProvider
+@Inject
+constructor(private val foldStateProvider: FoldStateProvider) :
+    UnfoldTransitionProgressProvider, FoldStateProvider.FoldUpdatesListener {
 
     private val animatorListener = AnimatorListener()
     private val animator =
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 074b1e1..6ffbe5a 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -33,9 +33,10 @@
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
 import com.android.systemui.unfold.updates.name
+import javax.inject.Inject
 
 /** Maps fold updates to unfold transition progress using DynamicAnimation. */
-class PhysicsBasedUnfoldTransitionProgressProvider(
+class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
     private val foldStateProvider: FoldStateProvider
 ) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener {
 
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 5b45897..97c9ba9 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -79,6 +79,7 @@
         screenStatusProvider.addCallback(screenListener)
         hingeAngleProvider.addCallback(hingeAngleListener)
         rotationChangeProvider.addCallback(rotationListener)
+        activityTypeProvider.init()
     }
 
     override fun stop() {
@@ -87,6 +88,7 @@
         hingeAngleProvider.removeCallback(hingeAngleListener)
         hingeAngleProvider.stop()
         rotationChangeProvider.removeCallback(rotationListener)
+        activityTypeProvider.uninit()
     }
 
     override fun addCallback(listener: FoldUpdatesListener) {
@@ -115,19 +117,17 @@
         }
 
         val isClosing = angle < lastHingeAngle
-        val closingThreshold = getClosingThreshold()
-        val closingThresholdMet = closingThreshold == null || angle < closingThreshold
         val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
         val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
         val screenAvailableEventSent = isUnfoldHandled
 
         if (isClosing // hinge angle should be decreasing since last update
-                && closingThresholdMet // hinge angle is below certain threshold
                 && !closingEventDispatched  // we haven't sent closing event already
                 && !isFullyOpened // do not send closing event if we are in fully opened hinge
                                   // angle range as closing threshold could overlap this range
                 && screenAvailableEventSent // do not send closing event if we are still in
                                             // the process of turning on the inner display
+                && isClosingThresholdMet(angle) // hinge angle is below certain threshold.
         ) {
             notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
         }
@@ -146,6 +146,11 @@
         outputListeners.forEach { it.onHingeAngleUpdate(angle) }
     }
 
+    private fun isClosingThresholdMet(currentAngle: Float) : Boolean {
+        val closingThreshold = getClosingThreshold()
+        return closingThreshold == null || currentAngle < closingThreshold
+    }
+
     /**
      * Fold animation should be started only after the threshold returned here.
      *
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
index 577137c..89fb12e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
@@ -20,35 +20,43 @@
 import android.hardware.SensorManager
 import android.os.Trace
 import androidx.core.util.Consumer
+import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
 import java.util.concurrent.Executor
+import javax.inject.Inject
 
-internal class HingeSensorAngleProvider(
+internal class HingeSensorAngleProvider
+@Inject
+constructor(
     private val sensorManager: SensorManager,
-    private val executor: Executor
-) :
-    HingeAngleProvider {
+    @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor
+) : HingeAngleProvider {
 
     private val sensorListener = HingeAngleSensorListener()
     private val listeners: MutableList<Consumer<Float>> = arrayListOf()
     var started = false
 
-    override fun start() = executor.execute {
-        if (started) return@execute
-        Trace.beginSection("HingeSensorAngleProvider#start")
-        val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE)
-        sensorManager.registerListener(
-            sensorListener,
-            sensor,
-            SensorManager.SENSOR_DELAY_FASTEST
-        )
-        Trace.endSection()
-        started = true
+    override fun start() {
+        singleThreadBgExecutor.execute {
+            if (started) return@execute
+            Trace.beginSection("HingeSensorAngleProvider#start")
+            val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE)
+            sensorManager.registerListener(
+                sensorListener,
+                sensor,
+                SensorManager.SENSOR_DELAY_FASTEST
+            )
+            Trace.endSection()
+
+            started = true
+        }
     }
 
-    override fun stop() = executor.execute {
-        if (!started) return@execute
-        sensorManager.unregisterListener(sensorListener)
-        started = false
+    override fun stop() {
+        singleThreadBgExecutor.execute {
+            if (!started) return@execute
+            sensorManager.unregisterListener(sensorListener)
+            started = false
+        }
     }
 
     override fun removeCallback(listener: Consumer<Float>) {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt
index d0e6cdc..34e7c38 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt
@@ -16,6 +16,11 @@
 
 interface CurrentActivityTypeProvider {
     val isHomeActivity: Boolean?
+
+    /** Starts listening for task updates. */
+    fun init() {}
+    /** Stop listening for task updates. */
+    fun uninit() {}
 }
 
 class EmptyCurrentActivityTypeProvider(override val isHomeActivity: Boolean? = null) :
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 81fbd78..939047f 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2651,8 +2651,8 @@
                 final CharSequence serviceLabel;
                 final Drawable serviceIcon;
                 synchronized (mLock) {
-                    serviceLabel = mService.getServiceLabelLocked();
-                    serviceIcon = mService.getServiceIconLocked();
+                    serviceIcon = getServiceIcon(response);
+                    serviceLabel = getServiceLabel(response);
                 }
                 if (serviceLabel == null || serviceIcon == null) {
                     wtf(null, "showSaveLocked(): no service label or icon");
@@ -2662,7 +2662,8 @@
 
                 getUiForShowing().showSaveUi(serviceLabel, serviceIcon,
                         mService.getServicePackageName(), saveInfo, this,
-                        mComponentName, this, mPendingSaveUi, isUpdate, mCompatMode);
+                        mComponentName, this, mPendingSaveUi, isUpdate, mCompatMode,
+                        response.getShowSaveDialogIcon());
                 if (client != null) {
                     try {
                         client.setSaveUiState(id, true);
@@ -3600,9 +3601,13 @@
                 if (sDebug) Log.w(TAG, "Last fill dialog triggered ids are changed.");
                 return false;
             }
+
         }
 
-        final Drawable serviceIcon = getServiceIcon();
+        Drawable serviceIcon = null;
+        synchronized (mLock) {
+            serviceIcon = getServiceIcon(response);
+        }
 
         getUiForShowing().showFillDialog(filledId, response, filterText,
                 mService.getServicePackageName(), mComponentName, serviceIcon, this,
@@ -3610,13 +3615,64 @@
         return true;
     }
 
+    /**
+     * Get the custom icon that was passed through FillResponse. If the custom icon wasn't able
+     * to be fetched, use the default provider icon instead
+     *
+     * @return Drawable of the provider icon, if it was able to be fetched. Null otherwise
+     */
     @SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's
                                    // actually the same object as mLock.
                                    // TODO: Expose mService.mLock or redesign instead.
-    private Drawable getServiceIcon() {
-        synchronized (mLock) {
-            return mService.getServiceIconLocked();
+    @GuardedBy("mLock")
+    private Drawable getServiceIcon(FillResponse response) {
+        Drawable serviceIcon = null;
+        // Try to get the custom Icon, if one was passed through FillResponse
+        int iconResourceId = response.getIconResourceId();
+        if (iconResourceId != 0) {
+            serviceIcon = mService.getMaster().getContext().getPackageManager()
+                .getDrawable(
+                    mService.getServicePackageName(),
+                    iconResourceId,
+                    null);
         }
+
+        // Custom icon wasn't fetched, use the default package icon instead
+        if (serviceIcon == null) {
+            serviceIcon = mService.getServiceIconLocked();
+        }
+
+        return serviceIcon;
+    }
+
+    /**
+     * Get the custom label that was passed through FillResponse. If the custom label
+     * wasn't able to be fetched, use the default provider icon instead
+     *
+     * @return Drawable of the provider icon, if it was able to be fetched. Null otherwise
+     */
+    @SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's
+                                   // actually the same object as mLock.
+                                   // TODO: Expose mService.mLock or redesign instead.
+    @GuardedBy("mLock")
+    private CharSequence getServiceLabel(FillResponse response) {
+        CharSequence serviceLabel = null;
+        // Try to get the custom Service name, if one was passed through FillResponse
+        int customServiceNameId = response.getServiceDisplayNameResourceId();
+        if (customServiceNameId != 0) {
+            serviceLabel = mService.getMaster().getContext().getPackageManager()
+                .getText(
+                    mService.getServicePackageName(),
+                    customServiceNameId,
+                    null);
+        }
+
+        // Custom label wasn't fetched, use the default package name instead
+        if (serviceLabel == null) {
+            serviceLabel = mService.getServiceLabelLocked();
+        }
+
+        return serviceLabel;
     }
 
     /**
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 5f0f9a3..7db6e6f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -315,7 +315,7 @@
             @Nullable String servicePackageName, @NonNull SaveInfo info,
             @NonNull ValueFinder valueFinder, @NonNull ComponentName componentName,
             @NonNull AutoFillUiCallback callback, @NonNull PendingUi pendingSaveUi,
-            boolean isUpdate, boolean compatMode) {
+            boolean isUpdate, boolean compatMode, boolean showServiceIcon) {
         if (sVerbose) {
             Slog.v(TAG, "showSaveUi(update=" + isUpdate + ") for " + componentName.toShortString()
                     + ": " + info);
@@ -379,7 +379,7 @@
                 public void startIntentSender(IntentSender intentSender, Intent intent) {
                     callback.startIntentSender(intentSender, intent);
                 }
-            }, mUiModeMgr.isNightMode(), isUpdate, compatMode);
+            }, mUiModeMgr.isNightMode(), isUpdate, compatMode, showServiceIcon);
         });
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index c2c630e..72a38eb 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -117,7 +117,9 @@
         final LayoutInflater inflater = LayoutInflater.from(mContext);
         final View decor = inflater.inflate(R.layout.autofill_fill_dialog, null);
 
-        setServiceIcon(decor, serviceIcon);
+        if (response.getShowFillDialogIcon()) {
+            setServiceIcon(decor, serviceIcon);
+        }
         setHeader(decor, response);
 
         mVisibleDatasetsMaxCount = getVisibleDatasetsMaxCount();
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 8c2c964..7db27ac 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -177,7 +177,7 @@
            @Nullable String servicePackageName, @NonNull ComponentName componentName,
            @NonNull SaveInfo info, @NonNull ValueFinder valueFinder,
            @NonNull OverlayControl overlayControl, @NonNull OnSaveListener listener,
-           boolean nightMode, boolean isUpdate, boolean compatMode) {
+           boolean nightMode, boolean isUpdate, boolean compatMode, boolean showServiceIcon) {
         if (sVerbose) Slog.v(TAG, "nightMode: " + nightMode);
         mThemeId = nightMode ? THEME_ID_DARK : THEME_ID_LIGHT;
         mPendingUi = pendingUi;
@@ -288,7 +288,9 @@
         }
         titleView.setText(mTitle);
 
-        setServiceIcon(context, view, serviceIcon);
+        if (showServiceIcon) {
+            setServiceIcon(context, view, serviceIcon);
+        }
 
         final boolean hasCustomDescription =
                 applyCustomDescription(context, view, valueFinder, info);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index dc475f6..8c09dcd 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -25,6 +25,7 @@
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.backup.BackupManager;
+import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.app.backup.IBackupManager;
 import android.app.backup.IBackupManagerMonitor;
@@ -722,17 +723,6 @@
     }
 
     @Override
-    public void setFrameworkSchedulingEnabledForUser(int userId, boolean isEnabled) {
-        UserBackupManagerService userBackupManagerService =
-                getServiceForUserIfCallerHasPermission(userId,
-                        "setFrameworkSchedulingEnabledForUser()");
-
-        if (userBackupManagerService != null) {
-            userBackupManagerService.setFrameworkSchedulingEnabled(isEnabled);
-        }
-    }
-
-    @Override
     public void setBackupEnabledForUser(@UserIdInt int userId, boolean isEnabled)
             throws RemoteException {
         if (isUserReadyForBackup(userId)) {
diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java
index fe0e1c6..0bb25e3 100644
--- a/services/backup/java/com/android/server/backup/FullBackupJob.java
+++ b/services/backup/java/com/android/server/backup/FullBackupJob.java
@@ -45,12 +45,9 @@
     private final SparseArray<JobParameters> mParamsForUser = new SparseArray<>();
 
     public static void schedule(int userId, Context ctx, long minDelay,
-            UserBackupManagerService userBackupManagerService) {
-        if (!userBackupManagerService.isFrameworkSchedulingEnabled()) return;
-
+            BackupManagerConstants constants) {
         JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
         JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId), sIdleService);
-        final BackupManagerConstants constants = userBackupManagerService.getConstants();
         synchronized (constants) {
             builder.setRequiresDeviceIdle(true)
                     .setRequiredNetworkType(constants.getFullBackupRequiredNetworkType())
diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
index 164bbea..058dcae 100644
--- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
+++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
@@ -64,16 +64,14 @@
     @VisibleForTesting
     public static final int MAX_JOB_ID = 52418896;
 
-    public static void schedule(int userId, Context ctx,
-            UserBackupManagerService userBackupManagerService) {
-        schedule(userId, ctx, 0, userBackupManagerService);
+    public static void schedule(int userId, Context ctx, BackupManagerConstants constants) {
+        schedule(userId, ctx, 0, constants);
     }
 
     public static void schedule(int userId, Context ctx, long delay,
-            UserBackupManagerService userBackupManagerService) {
+            BackupManagerConstants constants) {
         synchronized (KeyValueBackupJob.class) {
-            if (sScheduledForUserId.get(userId)
-                    || !userBackupManagerService.isFrameworkSchedulingEnabled()) {
+            if (sScheduledForUserId.get(userId)) {
                 return;
             }
 
@@ -82,7 +80,6 @@
             final int networkType;
             final boolean needsCharging;
 
-            final BackupManagerConstants constants = userBackupManagerService.getConstants();
             synchronized (constants) {
                 interval = constants.getKeyValueBackupIntervalMilliseconds();
                 fuzz = constants.getKeyValueBackupFuzzMilliseconds();
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 40a4af9..6ba01d7 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -1958,8 +1958,8 @@
             }
             // We don't want the backup jobs to kick in any time soon.
             // Reschedules them to run in the distant future.
-            KeyValueBackupJob.schedule(mUserId, mContext, BUSY_BACKOFF_MIN_MILLIS, this);
-            FullBackupJob.schedule(mUserId, mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, this);
+            KeyValueBackupJob.schedule(mUserId, mContext, BUSY_BACKOFF_MIN_MILLIS, mConstants);
+            FullBackupJob.schedule(mUserId, mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, mConstants);
         } finally {
             Binder.restoreCallingIdentity(oldToken);
         }
@@ -2088,7 +2088,7 @@
                 final long interval = mConstants.getFullBackupIntervalMilliseconds();
                 final long appLatency = (timeSinceLast < interval) ? (interval - timeSinceLast) : 0;
                 final long latency = Math.max(transportMinLatency, appLatency);
-                FullBackupJob.schedule(mUserId, mContext, latency, this);
+                FullBackupJob.schedule(mUserId, mContext, latency, mConstants);
             } else {
                 if (DEBUG_SCHEDULING) {
                     Slog.i(
@@ -2226,7 +2226,7 @@
                         addUserIdToLogMessage(
                                 mUserId, "Deferring scheduled full backups in battery saver mode"));
             }
-            FullBackupJob.schedule(mUserId, mContext, keyValueBackupInterval, this);
+            FullBackupJob.schedule(mUserId, mContext, keyValueBackupInterval, mConstants);
             return false;
         }
 
@@ -2392,7 +2392,7 @@
                                             + "operation; rescheduling +" + latency));
                 }
                 final long deferTime = latency;     // pin for the closure
-                FullBackupJob.schedule(mUserId, mContext, deferTime, this);
+                FullBackupJob.schedule(mUserId, mContext, deferTime, mConstants);
                 return false;
             }
 
@@ -2495,7 +2495,7 @@
         }
 
         // ...and schedule a backup pass if necessary
-        KeyValueBackupJob.schedule(mUserId, mContext, this);
+        KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
     }
 
     // Note: packageName is currently unused, but may be in the future
@@ -2730,7 +2730,7 @@
                                     mUserId, "Not running backup while in battery save mode"));
                 }
                 // Try again in several hours.
-                KeyValueBackupJob.schedule(mUserId, mContext, this);
+                KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
             } else {
                 if (DEBUG) {
                     Slog.v(TAG, addUserIdToLogMessage(mUserId, "Scheduling immediate backup pass"));
@@ -3208,49 +3208,12 @@
         }
     }
 
-    synchronized void setFrameworkSchedulingEnabled(boolean isEnabled) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
-                "setFrameworkSchedulingEnabled");
-
-        boolean wasEnabled = isFrameworkSchedulingEnabled();
-        if (wasEnabled == isEnabled) return;
-
-        Slog.i(TAG, addUserIdToLogMessage(mUserId,
-                (isEnabled ? "Enabling" : "Disabling") + " backup scheduling"));
-
-        final long oldId = Binder.clearCallingIdentity();
-        try {
-            // TODO(b/264889098): Consider at a later point if we should us a sentinel file as
-            // setBackupEnabled.
-            Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                    Settings.Secure.BACKUP_SCHEDULING_ENABLED, isEnabled ? 1 : 0, mUserId);
-
-            if (!isEnabled) {
-                KeyValueBackupJob.cancel(mUserId, mContext);
-                FullBackupJob.cancel(mUserId, mContext);
-            } else {
-                KeyValueBackupJob.schedule(mUserId, mContext, this);
-                scheduleNextFullBackupJob(/* transportMinLatency */ 0);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(oldId);
-        }
-    }
-
-    synchronized boolean isFrameworkSchedulingEnabled() {
-        // By default scheduling is enabled
-        final int defaultSetting = 1;
-        int isEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                Settings.Secure.BACKUP_SCHEDULING_ENABLED, defaultSetting, mUserId);
-        return isEnabled == 1;
-    }
-
     @VisibleForTesting
     void updateStateOnBackupEnabled(boolean wasEnabled, boolean enable) {
         synchronized (mQueueLock) {
             if (enable && !wasEnabled && mSetupComplete) {
                 // if we've just been enabled, start scheduling backup passes
-                KeyValueBackupJob.schedule(mUserId, mContext, this);
+                KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
                 scheduleNextFullBackupJob(0);
             } else if (!enable) {
                 // No longer enabled, so stop running backups
@@ -4164,8 +4127,6 @@
             pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled"));
             if (mBackupRunning) pw.println("Backup currently running");
             pw.println(isBackupOperationInProgress() ? "Backup in progress" : "No backups running");
-            pw.println("Framework scheduling is "
-                    + (isFrameworkSchedulingEnabled() ? "enabled" : "disabled"));
             pw.println("Last backup pass started: " + mLastBackupPass
                     + " (now = " + System.currentTimeMillis() + ')');
             pw.println("  next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId));
diff --git a/services/backup/java/com/android/server/backup/internal/SetupObserver.java b/services/backup/java/com/android/server/backup/internal/SetupObserver.java
index f399fe9..c5e912e 100644
--- a/services/backup/java/com/android/server/backup/internal/SetupObserver.java
+++ b/services/backup/java/com/android/server/backup/internal/SetupObserver.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.os.Handler;
+import android.provider.Settings;
 import android.util.Slog;
 
 import com.android.server.backup.KeyValueBackupJob;
@@ -77,7 +78,7 @@
                     Slog.d(TAG, "Setup complete so starting backups");
                 }
                 KeyValueBackupJob.schedule(mUserBackupManagerService.getUserId(), mContext,
-                        mUserBackupManagerService);
+                        mUserBackupManagerService.getConstants());
                 mUserBackupManagerService.scheduleNextFullBackupJob(0);
             }
         }
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index 41e8092..ca92b69 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -1246,7 +1246,7 @@
             delay = 0;
         }
         KeyValueBackupJob.schedule(mBackupManagerService.getUserId(),
-                mBackupManagerService.getContext(), delay, mBackupManagerService);
+                mBackupManagerService.getContext(), delay, mBackupManagerService.getConstants());
 
         for (String packageName : mOriginalQueue) {
             mBackupManagerService.dataChangedImpl(packageName);
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 68722d2..661319f3 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -64,6 +64,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IBinaryTransparencyService;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.pm.ApexManager;
 
 import libcore.util.HexEncoding;
 
@@ -1266,10 +1267,15 @@
     private String getOriginalApexPreinstalledLocation(String packageName,
             String currentInstalledLocation) {
         try {
+            // It appears that only apexd knows the preinstalled location, and it uses module name
+            // as the identifier instead of package name. Given the input is a package name, we
+            // need to covert to module name.
+            final String moduleName = ApexManager.getInstance().getApexModuleNameForPackageName(
+                    packageName);
             IApexService apexService = IApexService.Stub.asInterface(
                     Binder.allowBlocking(ServiceManager.waitForService("apexservice")));
             for (ApexInfo info : apexService.getAllPackages()) {
-                if (packageName.equals(info.moduleName)) {
+                if (moduleName.equals(info.moduleName)) {
                     return info.preinstalledModulePath;
                 }
             }
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 274370e..b67e627 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -227,8 +227,8 @@
 # ---------------------------
 # NetworkStatsService.java
 # ---------------------------
-51100 netstats_mobile_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
-51101 netstats_wifi_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
+51100 netstats_mobile_sample (xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
+51101 netstats_wifi_sample (xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
 
 
 # ---------------------------
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 88492ed..35b5f1b 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2010,7 +2010,7 @@
 
     @Override
     public void hasFeatures(IAccountManagerResponse response,
-            Account account, String[] features, String opPackageName) {
+            Account account, String[] features, int userId, String opPackageName) {
         int callingUid = Binder.getCallingUid();
         mAppOpsManager.checkPackage(callingUid, opPackageName);
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -2018,12 +2018,22 @@
                     + ", response " + response
                     + ", features " + Arrays.toString(features)
                     + ", caller's uid " + callingUid
+                    + ", userId " + userId
                     + ", pid " + Binder.getCallingPid());
         }
         Preconditions.checkArgument(account != null, "account cannot be null");
         Preconditions.checkArgument(response != null, "response cannot be null");
         Preconditions.checkArgument(features != null, "features cannot be null");
-        int userId = UserHandle.getCallingUserId();
+
+        if (userId != UserHandle.getCallingUserId()
+                && callingUid != Process.SYSTEM_UID
+                && mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("User " + UserHandle.getCallingUserId()
+                    + " trying to check account features for " + userId);
+        }
+
         checkReadAccountsPermitted(callingUid, account.type, userId,
                 opPackageName);
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index eb10759..9752e8b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1443,6 +1443,9 @@
     private boolean mWaitForDebugger = false;
 
     @GuardedBy("this")
+    private boolean mSuspendUponWait = false;
+
+    @GuardedBy("this")
     private boolean mDebugTransient = false;
 
     @GuardedBy("this")
@@ -5011,9 +5014,15 @@
         try {
             int testMode = ApplicationThreadConstants.DEBUG_OFF;
             if (mDebugApp != null && mDebugApp.equals(processName)) {
-                testMode = mWaitForDebugger
-                    ? ApplicationThreadConstants.DEBUG_WAIT
-                    : ApplicationThreadConstants.DEBUG_ON;
+                if (mWaitForDebugger) {
+                    if (mSuspendUponWait) {
+                        testMode = ApplicationThreadConstants.DEBUG_SUSPEND;
+                    } else {
+                        testMode = ApplicationThreadConstants.DEBUG_WAIT;
+                    }
+                } else {
+                    testMode = ApplicationThreadConstants.DEBUG_ON;
+                }
                 app.setDebugging(true);
                 if (mDebugTransient) {
                     mDebugApp = mOrigDebugApp;
@@ -7160,6 +7169,11 @@
 
     public void setDebugApp(String packageName, boolean waitForDebugger,
             boolean persistent) {
+        setDebugApp(packageName, waitForDebugger, persistent, false);
+    }
+
+    private void setDebugApp(String packageName, boolean waitForDebugger,
+            boolean persistent, boolean suspendUponWait) {
         enforceCallingPermission(android.Manifest.permission.SET_DEBUG_APP,
                 "setDebugApp()");
 
@@ -7185,6 +7199,7 @@
                 }
                 mDebugApp = packageName;
                 mWaitForDebugger = waitForDebugger;
+                mSuspendUponWait = suspendUponWait;
                 mDebugTransient = !persistent;
                 if (packageName != null) {
                     forceStopPackageLocked(packageName, -1, false, false, true, true,
@@ -17868,6 +17883,10 @@
                         setDebugApp(aInfo.processName, true, false);
                     }
 
+                    if ((startFlags & ActivityManager.START_FLAG_DEBUG_SUSPEND) != 0) {
+                        setDebugApp(aInfo.processName, true, false, true);
+                    }
+
                     if ((startFlags & ActivityManager.START_FLAG_NATIVE_DEBUGGING) != 0) {
                         setNativeDebuggingAppLocked(aInfo.applicationInfo, aInfo.processName);
                     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 4b7857f..0a73eaa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -412,6 +412,8 @@
             public boolean handleOption(String opt, ShellCommand cmd) {
                 if (opt.equals("-D")) {
                     mStartFlags |= ActivityManager.START_FLAG_DEBUG;
+                } else if (opt.equals("--suspend")) {
+                    mStartFlags |= ActivityManager.START_FLAG_DEBUG_SUSPEND;
                 } else if (opt.equals("-N")) {
                     mStartFlags |= ActivityManager.START_FLAG_NATIVE_DEBUGGING;
                 } else if (opt.equals("-W")) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 9d7f53d..b1a01cc 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -397,11 +397,12 @@
                     + ": " + r);
             mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
                                       PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
+            final boolean assumeDelivered = false;
             thread.scheduleReceiverList(mReceiverBatch.manifestReceiver(
                     prepareReceiverIntent(r.intent, r.curFilteredExtras),
                     r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
-                    r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
-                    app.mState.getReportedProcState()));
+                    r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
+                    r.userId, app.mState.getReportedProcState()));
             if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
                     "Process cur broadcast " + r + " DELIVERED for app " + app);
             started = true;
@@ -735,9 +736,10 @@
                 // If we have an app thread, do the call through that so it is
                 // correctly ordered with other one-way calls.
                 try {
+                    final boolean assumeDelivered = !ordered;
                     thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
                             receiver, intent, resultCode,
-                            data, extras, ordered, sticky, sendingUser,
+                            data, extras, ordered, sticky, assumeDelivered, sendingUser,
                             app.mState.getReportedProcState()));
                 } catch (RemoteException ex) {
                     // Failed to call into the process. It's either dying or wedged. Kill it gently.
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 87d0bc7..99e2ac7 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -508,16 +508,7 @@
             mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
         }
 
-        if (waitingFor) {
-            mWaitingFor.removeIf((pair) -> {
-                if (pair.first.getAsBoolean()) {
-                    pair.second.countDown();
-                    return true;
-                } else {
-                    return false;
-                }
-            });
-        }
+        checkAndRemoveWaitingFor();
 
         traceEnd(cookie);
     }
@@ -897,21 +888,26 @@
             return true;
         }
 
+        final boolean assumeDelivered = isAssumedDelivered(r, index);
         if (receiver instanceof BroadcastFilter) {
             batch.schedule(((BroadcastFilter) receiver).receiverList.receiver,
                     receiverIntent, r.resultCode, r.resultData, r.resultExtras,
-                    r.ordered, r.initialSticky, r.userId,
+                    r.ordered, r.initialSticky, assumeDelivered, r.userId,
                     app.mState.getReportedProcState(), r, index);
             // TODO: consider making registered receivers of unordered
             // broadcasts report results to detect ANRs
-            if (!r.ordered) {
+            if (assumeDelivered) {
                 batch.success(r, index, BroadcastRecord.DELIVERY_DELIVERED, "assuming delivered");
                 return true;
             }
         } else {
             batch.schedule(receiverIntent, ((ResolveInfo) receiver).activityInfo,
-                    null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
-                    app.mState.getReportedProcState(), r, index);
+                    null, r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
+                    r.userId, app.mState.getReportedProcState(), r, index);
+            if (assumeDelivered) {
+                batch.success(r, index, BroadcastRecord.DELIVERY_DELIVERED, "assuming delivered");
+                return true;
+            }
         }
 
         return false;
@@ -1000,7 +996,8 @@
      * Return true if this receiver should be assumed to have been delivered.
      */
     private boolean isAssumedDelivered(BroadcastRecord r, int index) {
-        return (r.receivers.get(index) instanceof BroadcastFilter) && !r.ordered;
+        return (r.receivers.get(index) instanceof BroadcastFilter) && !r.ordered
+                && (r.resultTo == null);
     }
 
     /**
@@ -1059,10 +1056,11 @@
             mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
                     app, OOM_ADJ_REASON_FINISH_RECEIVER);
             try {
+                final boolean assumeDelivered = true;
                 thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
                         r.resultTo, r.intent,
                         r.resultCode, r.resultData, r.resultExtras, false, r.initialSticky,
-                        r.userId, app.mState.getReportedProcState()));
+                        assumeDelivered, r.userId, app.mState.getReportedProcState()));
             } catch (RemoteException e) {
                 final String msg = "Failed to schedule result of " + r + " via " + app + ": " + e;
                 logw(msg);
@@ -1181,6 +1179,9 @@
             mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
         }
 
+        // Given that a receiver just finished, check if the "waitingFor" conditions are met.
+        checkAndRemoveWaitingFor();
+
         if (early) {
             // This is an early receiver that was transmitted as part of a group.  The delivery
             // state has been updated but don't make any further decisions.
@@ -1509,7 +1510,7 @@
         waitFor(() -> isBeyondBarrierLocked(now, pw));
     }
 
-    public void waitFor(@NonNull BooleanSupplier condition) {
+    private void waitFor(@NonNull BooleanSupplier condition) {
         final CountDownLatch latch = new CountDownLatch(1);
         synchronized (mService) {
             mWaitingFor.add(Pair.create(condition, latch));
@@ -1531,6 +1532,19 @@
         }
     }
 
+    private void checkAndRemoveWaitingFor() {
+        if (!mWaitingFor.isEmpty()) {
+            mWaitingFor.removeIf((pair) -> {
+                if (pair.first.getAsBoolean()) {
+                    pair.second.countDown();
+                    return true;
+                } else {
+                    return false;
+                }
+            });
+        }
+    }
+
     @Override
     public void forceDelayBroadcastDelivery(@NonNull String targetPackage,
             long delayedDurationMs) {
diff --git a/services/core/java/com/android/server/am/BroadcastReceiverBatch.java b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
index a826458..226647c 100644
--- a/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
+++ b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
@@ -19,8 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ReceiverInfo;
-import android.content.Intent;
 import android.content.IIntentReceiver;
+import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.res.CompatibilityInfo;
 import android.os.Bundle;
@@ -171,12 +171,13 @@
     // Add a ReceiverInfo for a registered receiver.
     void schedule(@Nullable IIntentReceiver receiver, Intent intent,
             int resultCode, @Nullable String data, @Nullable Bundle extras, boolean ordered,
-            boolean sticky, int sendingUser, int processState,
+            boolean sticky, boolean assumeDelivered, int sendingUser, int processState,
             @Nullable BroadcastRecord r, int index) {
         ReceiverInfo ri = new ReceiverInfo();
         ri.intent = intent;
         ri.data = data;
         ri.extras = extras;
+        ri.assumeDelivered = assumeDelivered;
         ri.sendingUser = sendingUser;
         ri.processState = processState;
         ri.resultCode = resultCode;
@@ -190,12 +191,13 @@
     // Add a ReceiverInfo for a manifest receiver.
     void schedule(@Nullable Intent intent, @Nullable ActivityInfo activityInfo,
             @Nullable CompatibilityInfo compatInfo, int resultCode, @Nullable String data,
-            @Nullable Bundle extras, boolean sync, int sendingUser, int processState,
-            @Nullable BroadcastRecord r, int index) {
+            @Nullable Bundle extras, boolean sync, boolean assumeDelivered, int sendingUser,
+            int processState, @Nullable BroadcastRecord r, int index) {
         ReceiverInfo ri = new ReceiverInfo();
         ri.intent = intent;
         ri.data = data;
         ri.extras = extras;
+        ri.assumeDelivered = assumeDelivered;
         ri.sendingUser = sendingUser;
         ri.processState = processState;
         ri.resultCode = resultCode;
@@ -214,10 +216,10 @@
      */
     ArrayList<ReceiverInfo> registeredReceiver(@Nullable IIntentReceiver receiver,
             @Nullable Intent intent, int resultCode, @Nullable String data,
-            @Nullable Bundle extras, boolean ordered, boolean sticky,
+            @Nullable Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
             int sendingUser, int processState) {
         reset();
-        schedule(receiver, intent, resultCode, data, extras, ordered, sticky,
+        schedule(receiver, intent, resultCode, data, extras, ordered, sticky, assumeDelivered,
                 sendingUser, processState, null, 0);
         return receivers();
     }
@@ -225,9 +227,9 @@
     ArrayList<ReceiverInfo> manifestReceiver(@Nullable Intent intent,
             @Nullable ActivityInfo activityInfo, @Nullable CompatibilityInfo compatInfo,
             int resultCode, @Nullable String data, @Nullable Bundle extras, boolean sync,
-            int sendingUser, int processState) {
+            boolean assumeDelivered, int sendingUser, int processState) {
         reset();
-        schedule(intent, activityInfo, compatInfo, resultCode, data, extras, sync,
+        schedule(intent, activityInfo, compatInfo, resultCode, data, extras, sync, assumeDelivered,
                 sendingUser, processState, null, 0);
         return receivers();
     }
@@ -255,6 +257,7 @@
             n.intent = r.intent;
             n.data = r.data;
             n.extras = r.extras;
+            n.assumeDelivered = r.assumeDelivered;
             n.sendingUser = r.sendingUser;
             n.processState = r.processState;
             n.resultCode = r.resultCode;
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index dbd58eb..81655cf 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2620,7 +2620,7 @@
         }
 
         state.setCurRawAdj(adj);
-
+        adj = psr.modifyRawOomAdj(adj);
         if (adj > state.getMaxAdj()) {
             adj = state.getMaxAdj();
             if (adj <= PERCEPTIBLE_LOW_APP_ADJ) {
@@ -2650,7 +2650,7 @@
         // it when computing the final cached adj later.  Note that we don't need to
         // worry about this for max adj above, since max adj will always be used to
         // keep it out of the cached vaues.
-        state.setCurAdj(psr.modifyRawOomAdj(adj));
+        state.setCurAdj(adj);
         state.setCurCapability(capability);
         state.setCurrentSchedulingGroup(schedGroup);
         state.setCurProcState(procState);
diff --git a/services/core/java/com/android/server/am/SameProcessApplicationThread.java b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
index 62fd6e9..082e8e0 100644
--- a/services/core/java/com/android/server/am/SameProcessApplicationThread.java
+++ b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
@@ -47,12 +47,12 @@
 
     @Override
     public void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo,
-            int resultCode, String data, Bundle extras, boolean sync, int sendingUser,
-            int processState) {
+            int resultCode, String data, Bundle extras, boolean ordered, boolean assumeDelivered,
+            int sendingUser, int processState) {
         mHandler.post(() -> {
             try {
-                mWrapped.scheduleReceiver(intent, info, compatInfo, resultCode, data, extras, sync,
-                        sendingUser, processState);
+                mWrapped.scheduleReceiver(intent, info, compatInfo, resultCode, data, extras,
+                        ordered, assumeDelivered, sendingUser, processState);
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
@@ -61,12 +61,12 @@
 
     @Override
     public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, int resultCode,
-            String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser,
-            int processState) {
+            String data, Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
+            int sendingUser, int processState) {
         mHandler.post(() -> {
             try {
                 mWrapped.scheduleRegisteredReceiver(receiver, intent, resultCode, data, extras,
-                        ordered, sticky, sendingUser, processState);
+                        ordered, sticky, assumeDelivered, sendingUser, processState);
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
@@ -79,11 +79,11 @@
             ReceiverInfo r = info.get(i);
             if (r.registered) {
                 scheduleRegisteredReceiver(r.receiver, r.intent,
-                        r.resultCode, r.data, r.extras, r.ordered, r.sticky,
+                        r.resultCode, r.data, r.extras, r.ordered, r.sticky, r.assumeDelivered,
                         r.sendingUser, r.processState);
             } else {
                 scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
-                        r.resultCode, r.data, r.extras, r.sync,
+                        r.resultCode, r.data, r.extras, r.sync, r.assumeDelivered,
                         r.sendingUser, r.processState);
             }
         }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 58ddd9c..c794b04 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3733,18 +3733,6 @@
         }
     }
 
-    // TODO enforce MODIFY_AUDIO_SYSTEM_SETTINGS when defined
-    private void enforceModifyAudioRoutingOrSystemSettingsPermission() {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-                != PackageManager.PERMISSION_GRANTED
-                /*&& mContext.checkCallingOrSelfPermission(
-                        android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
-                            != PackageManager.PERMISSION_DENIED*/) {
-            throw new SecurityException(
-                    "Missing MODIFY_AUDIO_ROUTING or MODIFY_AUDIO_SYSTEM_SETTINGS permission");
-        }
-    }
-
     private void enforceAccessUltrasoundPermission() {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_ULTRASOUND)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -3785,12 +3773,13 @@
         super.setVolumeIndexForAttributes_enforcePermission();
 
         Objects.requireNonNull(attr, "attr must not be null");
-        final int volumeGroup = getVolumeGroupIdForAttributes(attr);
+        int volumeGroup = AudioProductStrategy.getVolumeGroupIdForAudioAttributes(
+                attr, /* fallbackOnDefault= */false);
         if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
             Log.e(TAG, ": no volume group found for attributes " + attr.toString());
             return;
         }
-        final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+        VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
 
         sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
                 index/*val1*/, flags/*val2*/, callingPackage));
@@ -3798,7 +3787,7 @@
         vgs.setVolumeIndex(index, flags);
 
         // For legacy reason, propagate to all streams associated to this volume group
-        for (final int groupedStream : vgs.getLegacyStreamTypes()) {
+        for (int groupedStream : vgs.getLegacyStreamTypes()) {
             try {
                 ensureValidStreamType(groupedStream);
             } catch (IllegalArgumentException e) {
@@ -3830,7 +3819,9 @@
         super.getVolumeIndexForAttributes_enforcePermission();
 
         Objects.requireNonNull(attr, "attr must not be null");
-        final int volumeGroup = getVolumeGroupIdForAttributes(attr);
+        final int volumeGroup =
+                AudioProductStrategy.getVolumeGroupIdForAudioAttributes(
+                        attr, /* fallbackOnDefault= */false);
         if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
             throw new IllegalArgumentException("No volume group for attributes " + attr);
         }
@@ -3856,13 +3847,16 @@
         return AudioSystem.getMinVolumeIndexForAttributes(attr);
     }
 
+    @Override
+    @android.annotation.EnforcePermission(anyOf =
+            {"MODIFY_AUDIO_ROUTING", "MODIFY_AUDIO_SYSTEM_SETTINGS"})
     /** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes)
      * Part of service interface, check permissions and parameters here
      * Note calling package is for logging purposes only, not to be trusted
      */
     public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada,
             @NonNull String callingPackage) {
-        enforceModifyAudioRoutingOrSystemSettingsPermission();
+        super.setDeviceVolume_enforcePermission();
         Objects.requireNonNull(vi);
         Objects.requireNonNull(ada);
         Objects.requireNonNull(callingPackage);
@@ -4380,31 +4374,6 @@
         sendVolumeUpdate(streamType, oldIndex, index, flags, device);
     }
 
-
-
-    private int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) {
-        Objects.requireNonNull(attributes, "attributes must not be null");
-        int volumeGroupId = getVolumeGroupIdForAttributesInt(attributes);
-        if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
-            return volumeGroupId;
-        }
-        // The default volume group is the one hosted by default product strategy, i.e.
-        // supporting Default Attributes
-        return getVolumeGroupIdForAttributesInt(AudioProductStrategy.getDefaultAttributes());
-    }
-
-    private int getVolumeGroupIdForAttributesInt(@NonNull AudioAttributes attributes) {
-        Objects.requireNonNull(attributes, "attributes must not be null");
-        for (final AudioProductStrategy productStrategy :
-                AudioProductStrategy.getAudioProductStrategies()) {
-            int volumeGroupId = productStrategy.getVolumeGroupIdForAudioAttributes(attributes);
-            if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
-                return volumeGroupId;
-            }
-        }
-        return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
-    }
-
     private void dispatchAbsoluteVolumeChanged(int streamType, AbsoluteVolumeDeviceInfo deviceInfo,
             int index) {
         VolumeInfo volumeInfo = deviceInfo.getMatchingVolumeInfoForStream(streamType);
@@ -4437,7 +4406,6 @@
         }
     }
 
-
     // No ringer or zen muted stream volumes can be changed unless it'll exit dnd
     private boolean volumeAdjustmentAllowedByDnd(int streamTypeAlias, int flags) {
         switch (mNm.getZenMode()) {
@@ -4828,12 +4796,15 @@
         }
     }
 
+    @Override
+    @android.annotation.EnforcePermission(anyOf =
+            {"MODIFY_AUDIO_ROUTING", "MODIFY_AUDIO_SYSTEM_SETTINGS"})
     /**
      * @see AudioDeviceVolumeManager#getDeviceVolume(VolumeInfo, AudioDeviceAttributes)
      */
     public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi,
             @NonNull AudioDeviceAttributes ada, @NonNull String callingPackage) {
-        enforceModifyAudioRoutingOrSystemSettingsPermission();
+        super.getDeviceVolume_enforcePermission();
         Objects.requireNonNull(vi);
         Objects.requireNonNull(ada);
         Objects.requireNonNull(callingPackage);
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index dcc98e1..5b696c2 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -104,6 +104,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.config.appcloning.AppCloningDeviceConfigHelper;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.BackgroundThread;
@@ -264,6 +265,8 @@
 
     private final SyncLogger mLogger;
 
+    private final AppCloningDeviceConfigHelper mAppCloningDeviceConfigHelper;
+
     private boolean isJobIdInUseLockedH(int jobId, List<JobInfo> pendingJobs) {
         for (int i = 0, size = pendingJobs.size(); i < size; i++) {
             JobInfo job = pendingJobs.get(i);
@@ -627,6 +630,7 @@
         }, mSyncHandler);
 
         mConstants = new SyncManagerConstants(context);
+        mAppCloningDeviceConfigHelper = AppCloningDeviceConfigHelper.getInstance(context);
 
         IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
         context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
@@ -828,6 +832,18 @@
     }
 
     /**
+     * Check whether the feature flag controlling contacts sharing for clone profile is set. If
+     * true, the contact syncs for clone profile should be disabled.
+     *
+     * @return true/false if contact sharing is enabled/disabled
+     */
+    protected boolean isContactSharingAllowedForCloneProfile() {
+        // TODO(b/253449368): This method should also check for the config controlling
+        // all app-cloning features.
+        return mAppCloningDeviceConfigHelper.getEnableAppCloningBuildingBlocks();
+    }
+
+    /**
      * Check if account sync should be disabled for the given user and provider.
      * @param userInfo
      * @param providerName
@@ -836,7 +852,9 @@
      */
     @VisibleForTesting
     protected boolean shouldDisableSyncForUser(UserInfo userInfo, String providerName) {
-        if (userInfo == null || providerName == null) return false;
+        if (userInfo == null || providerName == null || !isContactSharingAllowedForCloneProfile()) {
+            return false;
+        }
         return providerName.equals(ContactsContract.AUTHORITY)
                 && !areContactWritesEnabledForUser(userInfo);
     }
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index e21895a..35434b7 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -136,14 +136,14 @@
             @ImeVisibilityStateComputer.VisibilityState int state, int reason) {
         switch (state) {
             case STATE_SHOW_IME:
-                ImeTracker.get().onProgress(statsToken,
+                ImeTracker.forLogging().onProgress(statsToken,
                         ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                 // Send to window manager to show IME after IME layout finishes.
                 mWindowManagerInternal.showImePostLayout(windowToken, statsToken);
                 break;
             case STATE_HIDE_IME:
                 if (mService.mCurFocusedWindowClient != null) {
-                    ImeTracker.get().onProgress(statsToken,
+                    ImeTracker.forLogging().onProgress(statsToken,
                             ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                     // IMMS only knows of focused window, not the actual IME target.
                     // e.g. it isn't aware of any window that has both
@@ -154,7 +154,7 @@
                     mWindowManagerInternal.hideIme(windowToken,
                             mService.mCurFocusedWindowClient.mSelfReportedDisplayId, statsToken);
                 } else {
-                    ImeTracker.get().onFailed(statsToken,
+                    ImeTracker.forLogging().onFailed(statsToken,
                             ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                 }
                 break;
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 795e4bf..10c16b6 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -182,10 +182,10 @@
      */
     boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken, int showFlags) {
         if (mPolicy.mA11yRequestingNoSoftKeyboard || mPolicy.mImeHiddenByDisplayPolicy) {
-            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
+            ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
             return false;
         }
-        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
+        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
         if ((showFlags & InputMethodManager.SHOW_FORCED) != 0) {
             mRequestedShowExplicitly = true;
             mShowForced = true;
@@ -206,15 +206,15 @@
         if ((hideFlags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
                 && (mRequestedShowExplicitly || mShowForced)) {
             if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
-            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
+            ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
             return false;
         }
         if (mShowForced && (hideFlags & InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
             if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
-            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
+            ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
             return false;
         }
-        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
+        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
         return true;
     }
 
@@ -380,7 +380,12 @@
                 }
                 break;
             case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
-                // Do nothing.
+                // Do nothing but preserving the last IME requested visibility state.
+                final ImeTargetWindowState lastState =
+                        getWindowStateOrNull(mService.mLastImeTargetWindow);
+                if (lastState != null) {
+                    state.setRequestedImeVisible(lastState.mRequestedImeVisible);
+                }
                 break;
             case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
                 if (isForwardNavigation) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 187de930..ba9e280 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -469,11 +469,11 @@
 
     @GuardedBy("ImfLock.class")
     private boolean bindCurrentInputMethodService(ServiceConnection conn, int flags) {
-        if (getCurIntent() == null || conn == null) {
+        if (mCurIntent == null || conn == null) {
             Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
             return false;
         }
-        return mContext.bindServiceAsUser(getCurIntent(), conn, flags,
+        return mContext.bindServiceAsUser(mCurIntent, conn, flags,
                 new UserHandle(mSettings.getCurrentUserId()));
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index a94c90c..ce3abfd 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1958,6 +1958,19 @@
     }
 
     @BinderThread
+    @Nullable
+    @Override
+    public InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) {
+        if (UserHandle.getCallingUserId() != userId) {
+            mContext.enforceCallingPermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+        }
+        synchronized (ImfLock.class) {
+            return queryDefaultInputMethodForUserIdLocked(userId);
+        }
+    }
+
+    @BinderThread
     @NonNull
     @Override
     public List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
@@ -2277,7 +2290,7 @@
             mCurClient.mSessionRequestedForAccessibility = false;
             mCurClient = null;
             mCurVirtualDisplayToScreenMatrix = null;
-            ImeTracker.get().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+            ImeTracker.forLogging().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
             mCurStatsToken = null;
 
             mMenuController.hideInputMethodMenuLocked();
@@ -3263,7 +3276,8 @@
                 "InputMethodManagerService#showSoftInput");
         synchronized (ImfLock.class) {
             if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken)) {
-                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+                ImeTracker.forLogging().onFailed(
+                        statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
                 return false;
             }
             final long ident = Binder.clearCallingIdentity();
@@ -3336,15 +3350,16 @@
     @BinderThread
     @Override
     public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
-        Objects.requireNonNull(windowToken, "windowToken must not be null");
-        synchronized (ImfLock.class) {
-            if (mCurFocusedWindow != windowToken || mCurPerceptible == perceptible) {
-                return;
+        Binder.withCleanCallingIdentity(() -> {
+            Objects.requireNonNull(windowToken, "windowToken must not be null");
+            synchronized (ImfLock.class) {
+                if (mCurFocusedWindow != windowToken || mCurPerceptible == perceptible) {
+                    return;
+                }
+                mCurPerceptible = perceptible;
+                updateSystemUiLocked();
             }
-            mCurPerceptible = perceptible;
-            Binder.withCleanCallingIdentity(() ->
-                    updateSystemUiLocked(mImeWindowVis, mBackDisposition));
-        }
+        });
     }
 
     @GuardedBy("ImfLock.class")
@@ -3363,7 +3378,7 @@
             // TODO(b/261565259): to avoid using null, add package name in ClientState
             final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
             final int uid = mCurClient != null ? mCurClient.mUid : -1;
-            statsToken = ImeTracker.get().onRequestShow(packageName, uid,
+            statsToken = ImeTracker.forLogging().onRequestShow(packageName, uid,
                     ImeTracker.ORIGIN_SERVER_START_INPUT, reason);
         }
 
@@ -3372,19 +3387,19 @@
         }
 
         if (!mSystemReady) {
-            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
+            ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
             return false;
         }
-        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
+        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
 
         mVisibilityStateComputer.requestImeVisibility(windowToken, true);
 
         // Ensure binding the connection when IME is going to show.
         mBindingController.setCurrentMethodVisible();
         final IInputMethodInvoker curMethod = getCurMethodLocked();
-        ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+        ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
         if (curMethod != null) {
-            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
+            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
             mCurStatsToken = null;
 
             if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
@@ -3395,7 +3410,7 @@
             mVisibilityStateComputer.setInputShown(true);
             return true;
         } else {
-            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
             mCurStatsToken = statsToken;
         }
         return false;
@@ -3411,9 +3426,10 @@
         synchronized (ImfLock.class) {
             if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) {
                 if (isInputShown()) {
-                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+                    ImeTracker.forLogging().onFailed(
+                            statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
                 } else {
-                    ImeTracker.get().onCancelled(statsToken,
+                    ImeTracker.forLogging().onCancelled(statsToken,
                             ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
                 }
                 return false;
@@ -3446,7 +3462,7 @@
             } else {
                 uid = -1;
             }
-            statsToken = ImeTracker.get().onRequestHide(packageName, uid,
+            statsToken = ImeTracker.forLogging().onRequestHide(packageName, uid,
                     ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
         }
 
@@ -3472,15 +3488,15 @@
             // delivered to the IME process as an IPC.  Hence the inconsistency between
             // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
             // the final state.
-            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
+            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
             mVisibilityApplier.performHideIme(windowToken, statsToken, resultReceiver, reason);
         } else {
-            ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
+            ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
         }
         mBindingController.setCurrentMethodNotVisible();
         mVisibilityStateComputer.clearImeShowFlags();
         // Cancel existing statsToken for show IME as we got a hide request.
-        ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+        ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
         mCurStatsToken = null;
         return shouldHideSoftInput;
     }
@@ -3752,16 +3768,16 @@
             // be made before input is started in it.
             final ClientState cs = mClients.get(client.asBinder());
             if (cs == null) {
-                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
+                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
                 throw new IllegalArgumentException("unknown client " + client.asBinder());
             }
-            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
+            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
             if (!isImeClientFocused(mCurFocusedWindow, cs)) {
                 Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client));
                 return false;
             }
         }
-        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
         return true;
     }
 
@@ -4538,7 +4554,8 @@
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
         synchronized (ImfLock.class) {
             if (!calledWithValidTokenLocked(token)) {
-                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
+                ImeTracker.forLogging().onFailed(statsToken,
+                        ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                 return;
             }
             final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(windowToken);
@@ -5288,6 +5305,53 @@
         return mCurrentSubtype;
     }
 
+    /**
+     * Returns the default {@link InputMethodInfo} for the specific userId.
+     * @param userId user ID to query.
+     */
+    @GuardedBy("ImfLock.class")
+    private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
+        final String imeId = mSettings.getSelectedInputMethodForUser(userId);
+        if (TextUtils.isEmpty(imeId)) {
+            Slog.e(TAG, "No default input method found for userId " + userId);
+            return null;
+        }
+
+        InputMethodInfo curInputMethodInfo;
+        if (userId == mSettings.getCurrentUserId()
+                && (curInputMethodInfo = mMethodMap.get(imeId)) != null) {
+            // clone the InputMethodInfo before returning.
+            return new InputMethodInfo(curInputMethodInfo);
+        }
+
+        final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = new ArrayMap<>();
+        AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+        Context userAwareContext =
+                mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+
+        final int flags = PackageManager.GET_META_DATA
+                | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                | PackageManager.MATCH_DIRECT_BOOT_AUTO;
+        final List<ResolveInfo> services =
+                userAwareContext.getPackageManager().queryIntentServicesAsUser(
+                        new Intent(InputMethod.SERVICE_INTERFACE),
+                        PackageManager.ResolveInfoFlags.of(flags),
+                        userId);
+        for (ResolveInfo ri : services) {
+            final String imeIdResolved = InputMethodInfo.computeId(ri);
+            if (imeId.equals(imeIdResolved)) {
+                try {
+                    return new InputMethodInfo(
+                            userAwareContext, ri, additionalSubtypeMap.get(imeId));
+                } catch (Exception e) {
+                    Slog.wtf(TAG, "Unable to load input method " + imeId, e);
+                }
+            }
+        }
+        // we didn't find the InputMethodInfo for imeId. This shouldn't happen.
+        Slog.e(TAG, "Error while locating input method info for imeId: " + imeId);
+        return null;
+    }
     private ArrayMap<String, InputMethodInfo> queryMethodMapForUser(@UserIdInt int userId) {
         final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
         final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index be99bfb..559eb53 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -330,11 +330,17 @@
 
         @Nullable
         private String getString(@NonNull String key, @Nullable String defaultValue) {
+            return getStringForUser(key, defaultValue, mCurrentUserId);
+        }
+
+        @Nullable
+        private String getStringForUser(
+                @NonNull String key, @Nullable String defaultValue, @UserIdInt int userId) {
             final String result;
             if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
                 result = mCopyOnWriteDataStore.get(key);
             } else {
-                result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId);
+                result = Settings.Secure.getStringForUser(mResolver, key, userId);
             }
             return result != null ? result : defaultValue;
         }
@@ -741,6 +747,16 @@
             return imi;
         }
 
+        @Nullable
+        String getSelectedInputMethodForUser(@UserIdInt int userId) {
+            final String imi =
+                    getStringForUser(Settings.Secure.DEFAULT_INPUT_METHOD, null, userId);
+            if (DEBUG) {
+                Slog.d(TAG, "getSelectedInputMethodForUserStr: " + imi);
+            }
+            return imi;
+        }
+
         void putDefaultVoiceInputMethod(String imeId) {
             if (DEBUG) {
                 Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index b4c9596..1cc958b 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -202,6 +202,9 @@
         @Override
         public void onUserStarting(TargetUser user) {
             mUserInfoHelper.onUserStarted(user.getUserIdentifier());
+
+            // log location enabled state on start to minimize coverage loss
+            mService.logLocationEnabledState();
         }
 
         @Override
@@ -553,6 +556,7 @@
         }
 
         EVENT_LOG.logLocationEnabled(userId, enabled);
+        logLocationEnabledState();
 
         Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION)
                 .putExtra(LocationManager.EXTRA_LOCATION_ENABLED, enabled)
@@ -563,6 +567,20 @@
         refreshAppOpsRestrictions(userId);
     }
 
+    private void logLocationEnabledState() {
+        boolean locationEnabled = false;
+        // Location setting is considered on if it is enabled for any one user
+        int[] runningUserIds = mInjector.getUserInfoHelper().getRunningUserIds();
+        for (int userId : runningUserIds) {
+            locationEnabled = mInjector.getSettingsHelper().isLocationEnabled(userId);
+            if (locationEnabled) {
+                break;
+            }
+        }
+        mInjector.getLocationUsageLogger()
+            .logLocationEnabledStateChanged(locationEnabled);
+    }
+
     @Override
     public int getGnssYearOfHardware() {
         return mGnssManagerService == null ? 0 : mGnssManagerService.getGnssYearOfHardware();
diff --git a/services/core/java/com/android/server/location/injector/LocationUsageLogger.java b/services/core/java/com/android/server/location/injector/LocationUsageLogger.java
index af21bcb..a9701b3 100644
--- a/services/core/java/com/android/server/location/injector/LocationUsageLogger.java
+++ b/services/core/java/com/android/server/location/injector/LocationUsageLogger.java
@@ -122,6 +122,13 @@
         }
     }
 
+    /**
+     * Log a location enabled state change event.
+     */
+    public synchronized void logLocationEnabledStateChanged(boolean enabled) {
+        FrameworkStatsLog.write(FrameworkStatsLog.LOCATION_ENABLED_STATE_CHANGED, enabled);
+    }
+
     private static int bucketizeProvider(String provider) {
         if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
             return LocationStatsEnums.PROVIDER_NETWORK;
diff --git a/services/core/java/com/android/server/media/projection/TEST_MAPPING b/services/core/java/com/android/server/media/projection/TEST_MAPPING
new file mode 100644
index 0000000..a792498
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+  "presubmit": [
+    {
+      "name": "MediaProjectionTests",
+      "options": [
+        {
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 5ab9f38..69ea559 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -108,7 +108,7 @@
     @VisibleForTesting
     static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000;
     @VisibleForTesting
-    static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 50000;
+    static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 6000;
 
     private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000;
     private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000;
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index f85d6af..5c4c7c9 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -189,13 +189,14 @@
         void onPackagesUpdated(ArraySet<String> updatedPackages);
     }
 
-    public BackgroundDexOptService(
-            Context context, DexManager dexManager, PackageManagerService pm) {
+    public BackgroundDexOptService(Context context, DexManager dexManager, PackageManagerService pm)
+            throws LegacyDexoptDisabledException {
         this(new Injector(context, dexManager, pm));
     }
 
     @VisibleForTesting
-    public BackgroundDexOptService(Injector injector) {
+    public BackgroundDexOptService(Injector injector) throws LegacyDexoptDisabledException {
+        Installer.checkLegacyDexoptDisabled();
         mInjector = injector;
         mDexOptHelper = mInjector.getDexOptHelper();
         LocalServices.addService(BackgroundDexOptService.class, this);
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 9da319d..53e23e0 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -996,6 +996,8 @@
         artManager.addDexoptDoneCallback(false /* onlyIncludeUpdates */,
                 Runnable::run, new DexoptDoneHandler(Objects.requireNonNull(pm)));
         LocalManagerRegistry.addManager(ArtManagerLocal.class, artManager);
+
+        artManager.scheduleBackgroundDexoptJob();
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 93a119c..7f7a234 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -291,8 +291,8 @@
                     rollbackTimeoutIntent.putExtra(
                             PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_SESSION_ID,
                             sessionId);
-                    rollbackTimeoutIntent.addFlags(
-                            Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                    rollbackTimeoutIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                            | Intent.FLAG_RECEIVER_FOREGROUND);
                     mPm.mContext.sendBroadcastAsUser(rollbackTimeoutIntent, UserHandle.SYSTEM,
                             android.Manifest.permission.PACKAGE_ROLLBACK_AGENT);
                 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9cc0334..dfd305a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -789,8 +789,10 @@
 
     final ArtManagerService mArtManagerService;
 
+    // TODO(b/260124949): Remove these.
     final PackageDexOptimizer mPackageDexOptimizer;
-    final BackgroundDexOptService mBackgroundDexOptService;
+    @Nullable
+    final BackgroundDexOptService mBackgroundDexOptService; // null when ART Service is in use.
     // DexManager handles the usage of dex files (e.g. secondary files, whether or not a package
     // is used by other apps).
     private final DexManager mDexManager;
@@ -1567,7 +1569,16 @@
                 new DefaultSystemWrapper(),
                 LocalServices::getService,
                 context::getSystemService,
-                (i, pm) -> new BackgroundDexOptService(i.getContext(), i.getDexManager(), pm),
+                (i, pm) -> {
+                    if (useArtService()) {
+                        return null;
+                    }
+                    try {
+                        return new BackgroundDexOptService(i.getContext(), i.getDexManager(), pm);
+                    } catch (LegacyDexoptDisabledException e) {
+                        throw new RuntimeException(e);
+                    }
+                },
                 (i, pm) -> IBackupManager.Stub.asInterface(ServiceManager.getService(
                         Context.BACKUP_SERVICE)),
                 (i, pm) -> new SharedLibrariesImpl(pm, i),
@@ -4287,11 +4298,14 @@
                     }
                 });
 
-        // TODO(b/251903639): Call into ART Service.
-        try {
-            mBackgroundDexOptService.systemReady();
-        } catch (LegacyDexoptDisabledException e) {
-            throw new RuntimeException(e);
+        if (!useArtService()) {
+            // The background dexopt job is scheduled in DexOptHelper.initializeArtManagerLocal when
+            // ART Service is in use.
+            try {
+                mBackgroundDexOptService.systemReady();
+            } catch (LegacyDexoptDisabledException e) {
+                throw new RuntimeException(e);
+            }
         }
 
         // Prune unused static shared libraries which have been cached a period of time
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index eb033cb..13549f5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
 import android.app.backup.IBackupManager;
 import android.content.ComponentName;
@@ -138,7 +139,8 @@
     private final Singleton<DomainVerificationManagerInternal>
             mDomainVerificationManagerInternalProducer;
     private final Singleton<Handler> mHandlerProducer;
-    private final Singleton<BackgroundDexOptService> mBackgroundDexOptService;
+    private final Singleton<BackgroundDexOptService>
+            mBackgroundDexOptService; // TODO(b/260124949): Remove this.
     private final Singleton<IBackupManager> mIBackupManager;
     private final Singleton<SharedLibrariesImpl> mSharedLibrariesProducer;
     private final Singleton<CrossProfileIntentFilterHelper> mCrossProfileIntentFilterHelperProducer;
@@ -408,6 +410,7 @@
         return getLocalService(ActivityManagerInternal.class);
     }
 
+    @Nullable
     public BackgroundDexOptService getBackgroundDexOptService() {
         return mBackgroundDexOptService.get(this, mPackageManager);
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 0c617ae..08ff51d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -106,7 +106,7 @@
     public boolean isEngBuild;
     public boolean isUserDebugBuild;
     public int sdkInt = Build.VERSION.SDK_INT;
-    public BackgroundDexOptService backgroundDexOptService;
+    public @Nullable BackgroundDexOptService backgroundDexOptService;
     public final String incrementalVersion = Build.VERSION.INCREMENTAL;
     public BroadcastHelper broadcastHelper;
     public AppDataHelper appDataHelper;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 12841a4..0685435 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2875,6 +2875,8 @@
                 newUserType = UserManager.USER_TYPE_FULL_DEMO;
             } else if ("--ephemeral".equals(opt)) {
                 flags |= UserInfo.FLAG_EPHEMERAL;
+            } else if ("--for-testing".equals(opt)) {
+                flags |= UserInfo.FLAG_FOR_TESTING;
             } else if ("--pre-create-only".equals(opt)) {
                 preCreateOnly = true;
             } else if ("--user-type".equals(opt)) {
@@ -4269,8 +4271,8 @@
         pw.println("  list users");
         pw.println("    Lists the current users.");
         pw.println("");
-        pw.println("  create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral]");
-        pw.println("      [--guest] [--pre-create-only] [--user-type USER_TYPE] USER_NAME");
+        pw.println("  create-user [--profileOf USER_ID] [--managed] [--restricted] [--guest]");
+        pw.println("       [--user-type USER_TYPE] [--ephemeral] [--for-testing] [--pre-create-only]   USER_NAME");
         pw.println("    Create a new user with the given USER_NAME, printing the new user identifier");
         pw.println("    of the user.");
         // TODO(b/142482943): Consider fetching the list of user types from UMS.
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 7b15e76..1787116 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -513,4 +513,20 @@
      * @see UserManager#isMainUser()
      */
     public abstract @UserIdInt int getMainUserId();
+
+    /**
+     * Returns the id of the user which should be in the foreground after boot completes.
+     *
+     * <p>If a boot user has been provided by calling {@link UserManager#setBootUser}, the
+     * returned value will be whatever was specified, as long as that user exists and can be
+     * switched to.
+     *
+     * <p>Otherwise, in {@link UserManager#isHeadlessSystemUserMode() headless system user mode},
+     * this will be the user who was last in the foreground on this device. If there is no
+     * switchable user on the device, a new user will be created and its id will be returned.
+     *
+     * <p>In non-headless system user mode, the return value will be {@link UserHandle#USER_SYSTEM}.
+     */
+    public abstract @UserIdInt int getBootUser()
+            throws UserManager.CheckedUserOperationException;
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a8cf8cb..175c11d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -21,6 +21,7 @@
 import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
 import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
+import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
 
 import android.Manifest;
 import android.accounts.Account;
@@ -31,7 +32,6 @@
 import android.annotation.Nullable;
 import android.annotation.StringRes;
 import android.annotation.UserIdInt;
-import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerNative;
@@ -44,6 +44,7 @@
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
@@ -127,11 +128,8 @@
 import com.android.server.LockGuard;
 import com.android.server.SystemService;
 import com.android.server.am.UserState;
-import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
 import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
 import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
-import com.android.server.pm.UserManagerInternal.UserStartMode;
-import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
 import com.android.server.storage.DeviceStorageMonitorInternal;
 import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
@@ -254,7 +252,8 @@
             | UserInfo.FLAG_RESTRICTED
             | UserInfo.FLAG_GUEST
             | UserInfo.FLAG_DEMO
-            | UserInfo.FLAG_FULL;
+            | UserInfo.FLAG_FULL
+            | UserInfo.FLAG_FOR_TESTING;
 
     @VisibleForTesting
     static final int MIN_USER_ID = UserHandle.MIN_SECONDARY_USER_ID;
@@ -637,6 +636,9 @@
 
     private final UserVisibilityMediator mUserVisibilityMediator;
 
+    @GuardedBy("mUsersLock")
+    private @UserIdInt int mBootUser = UserHandle.USER_NULL;
+
     private static UserManagerService sInstance;
 
     public static UserManagerService getInstance() {
@@ -936,6 +938,26 @@
     }
 
     @Override
+    public void setBootUser(@UserIdInt int userId) {
+        checkCreateUsersPermission("Set boot user");
+        synchronized (mUsersLock) {
+            // TODO(b/263381643): Change to EventLog.
+            Slogf.i(LOG_TAG, "setBootUser %d", userId);
+            mBootUser = userId;
+        }
+    }
+
+    @Override
+    public @UserIdInt int getBootUser() {
+        checkCreateUsersPermission("Get boot user");
+        try {
+            return mLocalService.getBootUser();
+        } catch (UserManager.CheckedUserOperationException e) {
+            throw e.toServiceSpecificException();
+        }
+    }
+
+    @Override
     public int getPreviousFullUserToEnterForeground() {
         checkQueryOrCreateUsersPermission("get previous user");
         int previousUser = UserHandle.USER_NULL;
@@ -1569,6 +1591,8 @@
                     Slog.w(LOG_TAG, "System user instantiated at least " + number + " times");
                 }
                 name = getOwnerName();
+            } else if (orig.isMain()) {
+                name = getOwnerName();
             } else if (orig.isGuest()) {
                 name = getGuestName();
             }
@@ -4535,7 +4559,7 @@
                     UserHandle.USER_NULL, null);
 
             if (userInfo == null) {
-                throw new ServiceSpecificException(UserManager.USER_OPERATION_ERROR_UNKNOWN);
+                throw new ServiceSpecificException(USER_OPERATION_ERROR_UNKNOWN);
             }
         } catch (UserManager.CheckedUserOperationException e) {
             throw e.toServiceSpecificException();
@@ -4664,7 +4688,7 @@
                     if (parent == null) {
                         throwCheckedUserOperationException(
                                 "Cannot find user data for parent user " + parentId,
-                                UserManager.USER_OPERATION_ERROR_UNKNOWN);
+                                USER_OPERATION_ERROR_UNKNOWN);
                     }
                 }
                 if (!preCreate && !canAddMoreUsersOfType(userTypeDetails)) {
@@ -4692,7 +4716,7 @@
                         && !isCreationOverrideEnabled()) {
                     throwCheckedUserOperationException(
                             "Cannot add restricted profile - parent user must be system",
-                            UserManager.USER_OPERATION_ERROR_UNKNOWN);
+                            USER_OPERATION_ERROR_UNKNOWN);
                 }
 
                 userId = getNextAvailableId();
@@ -5598,29 +5622,24 @@
             // Also, add the UserHandle for mainline modules which can't use the @hide
             // EXTRA_USER_HANDLE.
             removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
-            mContext.sendOrderedBroadcastAsUser(removedIntent, UserHandle.ALL,
-                    android.Manifest.permission.MANAGE_USERS,
-
-                    new BroadcastReceiver() {
+            getActivityManagerInternal().broadcastIntentWithCallback(removedIntent,
+                    new IIntentReceiver.Stub() {
                         @Override
-                        public void onReceive(Context context, Intent intent) {
+                        public void performReceive(Intent intent, int resultCode, String data,
+                                Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
                             if (DBG) {
                                 Slog.i(LOG_TAG,
                                         "USER_REMOVED broadcast sent, cleaning up user data "
-                                        + userId);
+                                                + userId);
                             }
-                            new Thread() {
-                                @Override
-                                public void run() {
-                                    LocalServices.getService(ActivityManagerInternal.class)
-                                            .onUserRemoved(userId);
-                                    removeUserState(userId);
-                                }
-                            }.start();
+                            new Thread(() -> {
+                                getActivityManagerInternal().onUserRemoved(userId);
+                                removeUserState(userId);
+                            }).start();
                         }
                     },
-
-                    null, Activity.RESULT_OK, null, null);
+                    new String[] {android.Manifest.permission.MANAGE_USERS},
+                    UserHandle.USER_ALL, null, null, null);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -6459,6 +6478,9 @@
         if (DBG_ALLOCATION) {
             pw.println("  System user allocations: " + mUser0Allocations.get());
         }
+        synchronized (mUsersLock) {
+            pw.println("  Boot user: " + mBootUser);
+        }
 
         pw.println();
         pw.println("Number of listeners for");
@@ -6651,6 +6673,18 @@
         return mLocalService.isUserInitialized(userId);
     }
 
+    /**
+     * Creates a new user, intended to be the initial user on a device in headless system user mode.
+     */
+    private UserInfo createInitialUserForHsum() throws UserManager.CheckedUserOperationException {
+        final int flags = UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN;
+
+        // Null name will be replaced with "Owner" on-demand to allow for localisation.
+        return createUserInternalUnchecked(/* name= */ null, UserManager.USER_TYPE_FULL_SECONDARY,
+                flags, UserHandle.USER_NULL, /* preCreate= */ false,
+                /* disallowedPackages= */ null, /* token= */ null);
+    }
+
     private class LocalService extends UserManagerInternal {
         @Override
         public void setDevicePolicyUserRestrictions(@UserIdInt int originatingUserId,
@@ -7094,6 +7128,56 @@
             return getMainUserIdUnchecked();
         }
 
+        @Override
+        public @UserIdInt int getBootUser() throws UserManager.CheckedUserOperationException {
+            synchronized (mUsersLock) {
+                // TODO(b/242195409): On Automotive, block if boot user not provided.
+                if (mBootUser != UserHandle.USER_NULL) {
+                    final UserData userData = mUsers.get(mBootUser);
+                    if (userData != null && userData.info.supportsSwitchToByUser()) {
+                        Slogf.i(LOG_TAG, "Using provided boot user: %d", mBootUser);
+                        return mBootUser;
+                    } else {
+                        Slogf.w(LOG_TAG,
+                                "Provided boot user cannot be switched to: %d", mBootUser);
+                    }
+                }
+            }
+
+            if (isHeadlessSystemUserMode()) {
+                // Return the previous foreground user, if there is one.
+                final int previousUser = getPreviousFullUserToEnterForeground();
+                if (previousUser != UserHandle.USER_NULL) {
+                    Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser);
+                    return previousUser;
+                }
+                // No previous user. Return the first switchable user if there is one.
+                synchronized (mUsersLock) {
+                    final int userSize = mUsers.size();
+                    for (int i = 0; i < userSize; i++) {
+                        final UserData userData = mUsers.valueAt(i);
+                        if (userData.info.supportsSwitchToByUser()) {
+                            int firstSwitchable = userData.info.id;
+                            Slogf.i(LOG_TAG,
+                                    "Boot user is first switchable user %d", firstSwitchable);
+                            return firstSwitchable;
+                        }
+                    }
+                }
+                // No switchable users. Create the initial user.
+                final UserInfo newInitialUser = createInitialUserForHsum();
+                if (newInitialUser == null) {
+                    throw new UserManager.CheckedUserOperationException(
+                            "Initial user creation failed", USER_OPERATION_ERROR_UNKNOWN);
+                }
+                Slogf.i(LOG_TAG,
+                        "No switchable users. Boot user is new user %d", newInitialUser.id);
+                return newInitialUser.id;
+            }
+            // Not HSUM, return system user.
+            return UserHandle.USER_SYSTEM;
+        }
+
     } // class LocalService
 
 
@@ -7113,7 +7197,7 @@
                     + restriction + " is enabled.";
             Slog.w(LOG_TAG, errorMessage);
             throw new UserManager.CheckedUserOperationException(errorMessage,
-                    UserManager.USER_OPERATION_ERROR_UNKNOWN);
+                    USER_OPERATION_ERROR_UNKNOWN);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index a54f526..8ec6241 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -233,7 +233,8 @@
                 PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_SESSION_ID,
                 mSessionId);
         enableRollbackIntent.setType(PACKAGE_MIME_TYPE);
-        enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_RECEIVER_FOREGROUND);
 
         // Allow the broadcast to be sent before boot complete.
         // This is needed when committing the apk part of a staged
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index d622fd7..f9482dd 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -311,6 +311,24 @@
     private final RailStats mTmpRailStats = new RailStats();
 
     /**
+     * Estimate UID modem power usage based on their estimated mobile radio active time.
+     */
+    public static final int PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME = 1;
+    /**
+     * Estimate UID modem power consumption by proportionally attributing estimated Rx and Tx
+     * power consumption individually.
+     * ModemActivityInfo must be available.
+     */
+    public static final int PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX = 2;
+    @IntDef(flag = true, prefix = "PER_UID_MODEM_MODEL_", value = {
+            PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME,
+            PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PerUidModemPowerModel {
+    }
+
+    /**
      * Use a queue to delay removing UIDs from {@link KernelCpuUidUserSysTimeReader},
      * {@link KernelCpuUidActiveTimeReader}, {@link KernelCpuUidClusterTimeReader},
      * {@link KernelCpuUidFreqTimeReader} and from the Kernel.
@@ -12038,8 +12056,7 @@
             }
 
             final SparseDoubleArray uidEstimatedConsumptionMah;
-            if (consumedChargeUC > 0 && mMobileRadioPowerCalculator != null
-                    && mGlobalEnergyConsumerStats != null) {
+            if (consumedChargeUC > 0 && isMobileRadioEnergyConsumerSupportedLocked()) {
                 mGlobalEnergyConsumerStats.updateStandardBucket(
                         EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO, consumedChargeUC);
                 uidEstimatedConsumptionMah = new SparseDoubleArray();
@@ -12047,6 +12064,8 @@
                 uidEstimatedConsumptionMah = null;
             }
 
+            RxTxConsumption rxTxConsumption = null;
+            boolean attributeWithModemActivityInfo = false;
             if (deltaInfo != null) {
                 mHasModemReporting = true;
                 mModemActivity.getOrCreateIdleTimeCounter()
@@ -12092,7 +12111,11 @@
                     mTmpRailStats.resetCellularTotalEnergyUsed();
                 }
 
-                incrementPerRatDataLocked(deltaInfo, elapsedRealtimeMs);
+                rxTxConsumption = incrementPerRatDataLocked(deltaInfo, elapsedRealtimeMs);
+
+                attributeWithModemActivityInfo = mConstants.PER_UID_MODEM_MODEL
+                        == PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX
+                        && rxTxConsumption != null;
             }
             long totalAppRadioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked(
                     elapsedRealtimeMs * 1000);
@@ -12156,12 +12179,23 @@
                                 (totalAppRadioTimeUs * appPackets) / totalPackets;
                         u.noteMobileRadioActiveTimeLocked(appRadioTimeUs, elapsedRealtimeMs);
 
-                        // Distribute mobile radio charge consumption based on app radio
-                        // active time
                         if (uidEstimatedConsumptionMah != null) {
-                            uidEstimatedConsumptionMah.incrementValue(u.getUid(),
+                            final double uidConsumptionMah;
+                            if (attributeWithModemActivityInfo) {
+                                // Distribute measured mobile radio charge consumption based on
+                                // rx/tx packets and estimated rx/tx charge consumption.
+                                uidConsumptionMah = smearModemActivityInfoRxTxConsumptionMah(
+                                        rxTxConsumption, entry.getRxPackets(), entry.getTxPackets(),
+                                        totalRxPackets, totalTxPackets);
+                            } else {
+                                // Distribute mobile radio charge consumption based on app radio
+                                // active time
+                                uidConsumptionMah =
                                     mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah(
-                                            appRadioTimeUs / 1000));
+                                                appRadioTimeUs / 1000);
+                            }
+                            uidEstimatedConsumptionMah.incrementValue(u.getUid(),
+                                    uidConsumptionMah);
                         }
 
                         // Remove this app from the totals, so that we don't lose any time
@@ -12199,54 +12233,95 @@
                     mMobileRadioActiveUnknownCount.addCountLocked(1);
                 }
 
-
                 // Update the EnergyConsumerStats information.
                 if (uidEstimatedConsumptionMah != null) {
                     double totalEstimatedConsumptionMah = 0.0;
-
-                    // Estimate total active radio power consumption since last mark.
-                    final long totalRadioTimeMs = mMobileRadioActiveTimer.getTimeSinceMarkLocked(
-                            elapsedRealtimeMs * 1000) / 1000;
-                    mMobileRadioActiveTimer.setMark(elapsedRealtimeMs);
-                    totalEstimatedConsumptionMah +=
-                            mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah(
-                                    totalRadioTimeMs);
-
-                    // Estimate idle power consumption at each signal strength level
-                    final int numSignalStrengthLevels = mPhoneSignalStrengthsTimer.length;
-                    for (int strengthLevel = 0; strengthLevel < numSignalStrengthLevels;
-                            strengthLevel++) {
-                        final long strengthLevelDurationMs =
-                                mPhoneSignalStrengthsTimer[strengthLevel].getTimeSinceMarkLocked(
+                    if (attributeWithModemActivityInfo) {
+                        // Estimate inactive modem power consumption and combine with previously
+                        // estimated active power consumption for an estimate of total modem
+                        // power consumption.
+                        final long sleepTimeMs = deltaInfo.getSleepTimeMillis();
+                        final long idleTimeMs = deltaInfo.getIdleTimeMillis();
+                        final double inactiveConsumptionMah =
+                                mMobileRadioPowerCalculator.calcInactiveStatePowerMah(sleepTimeMs,
+                                        idleTimeMs);
+                        totalEstimatedConsumptionMah += inactiveConsumptionMah;
+                        totalEstimatedConsumptionMah += rxTxConsumption.rxConsumptionMah;
+                        totalEstimatedConsumptionMah += rxTxConsumption.txConsumptionMah;
+                    } else {
+                        // Estimate total active radio power consumption since last mark.
+                        final long totalRadioTimeMs =
+                                mMobileRadioActiveTimer.getTimeSinceMarkLocked(
                                         elapsedRealtimeMs * 1000) / 1000;
-                        mPhoneSignalStrengthsTimer[strengthLevel].setMark(elapsedRealtimeMs);
-
+                        mMobileRadioActiveTimer.setMark(elapsedRealtimeMs);
                         totalEstimatedConsumptionMah +=
-                                mMobileRadioPowerCalculator.calcIdlePowerAtSignalStrengthMah(
-                                        strengthLevelDurationMs, strengthLevel);
+                                mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah(
+                                        totalRadioTimeMs);
+
+                        // Estimate idle power consumption at each signal strength level
+                        final int numSignalStrengthLevels = mPhoneSignalStrengthsTimer.length;
+                        for (int lvl = 0; lvl < numSignalStrengthLevels; lvl++) {
+                            final long strengthLevelDurationMs =
+                                    mPhoneSignalStrengthsTimer[lvl].getTimeSinceMarkLocked(
+                                            elapsedRealtimeMs * 1000) / 1000;
+                            mPhoneSignalStrengthsTimer[lvl].setMark(elapsedRealtimeMs);
+
+                            totalEstimatedConsumptionMah +=
+                                    mMobileRadioPowerCalculator.calcIdlePowerAtSignalStrengthMah(
+                                            strengthLevelDurationMs, lvl);
+                        }
+
+                        // Estimate total active radio power consumption since last mark.
+                        final long scanTimeMs = mPhoneSignalScanningTimer.getTimeSinceMarkLocked(
+                                elapsedRealtimeMs * 1000) / 1000;
+                        mPhoneSignalScanningTimer.setMark(elapsedRealtimeMs);
+                        totalEstimatedConsumptionMah +=
+                                mMobileRadioPowerCalculator.calcScanTimePowerMah(scanTimeMs);
                     }
-
-                    // Estimate total active radio power consumption since last mark.
-                    final long scanTimeMs = mPhoneSignalScanningTimer.getTimeSinceMarkLocked(
-                            elapsedRealtimeMs * 1000) / 1000;
-                    mPhoneSignalScanningTimer.setMark(elapsedRealtimeMs);
-                    totalEstimatedConsumptionMah +=
-                            mMobileRadioPowerCalculator.calcScanTimePowerMah(scanTimeMs);
-
                     distributeEnergyToUidsLocked(EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO,
                             consumedChargeUC, uidEstimatedConsumptionMah,
                             totalEstimatedConsumptionMah, elapsedRealtimeMs);
                 }
+            }
+        }
+    }
 
-                delta = null;
+    private static class RxTxConsumption {
+        public final double rxConsumptionMah;
+        public final long rxDurationMs;
+        public final double txConsumptionMah;
+        public final long txDurationMs;
+
+        /**
+         * Represents the ratio between time spent transmitting and the total active time.
+         */
+        public final double txToTotalRatio;
+
+        RxTxConsumption(double rxMah, long rxMs, double txMah, long txMs) {
+            rxConsumptionMah = rxMah;
+            rxDurationMs = rxMs;
+            txConsumptionMah = txMah;
+            txDurationMs = txMs;
+
+            final long activeDurationMs = txDurationMs + rxDurationMs;
+            if (activeDurationMs == 0) {
+                txToTotalRatio = 0.0;
+            } else {
+                txToTotalRatio = ((double) txDurationMs) / activeDurationMs;
             }
         }
     }
 
     @GuardedBy("this")
-    private void incrementPerRatDataLocked(ModemActivityInfo deltaInfo, long elapsedRealtimeMs) {
-        final int infoSize = deltaInfo.getSpecificInfoLength();
+    @Nullable
+    private RxTxConsumption incrementPerRatDataLocked(ModemActivityInfo deltaInfo,
+            long elapsedRealtimeMs) {
+        double rxConsumptionMah = 0.0;
+        long rxDurationMs = 0;
+        double txConsumptionMah = 0.0;
+        long txDurationMs = 0;
 
+        final int infoSize = deltaInfo.getSpecificInfoLength();
         if (infoSize == 1 && deltaInfo.getSpecificInfoRat(0)
                 == AccessNetworkConstants.AccessNetworkType.UNKNOWN
                 && deltaInfo.getSpecificInfoFrequencyRange(0)
@@ -12296,6 +12371,16 @@
                                             + (totalLvlDurationMs / 2)) / totalLvlDurationMs;
                             ratStats.incrementTxDuration(freq, level, proportionalTxDurationMs);
                             frequencyDurationMs += durationMs;
+
+                            if (isMobileRadioEnergyConsumerSupportedLocked()) {
+                                // Accumulate the power cost of time spent transmitting in a
+                                // particular state.
+                                final double txStatePowerConsumptionMah =
+                                        mMobileRadioPowerCalculator.calcTxStatePowerMah(rat, freq,
+                                                level, proportionalTxDurationMs);
+                                txConsumptionMah += txStatePowerConsumptionMah;
+                                txDurationMs += proportionalTxDurationMs;
+                            }
                         }
                         final long totalRxDuration = deltaInfo.getReceiveTimeMillis();
                         // Smear HAL provided Rx power duration based on active modem
@@ -12305,6 +12390,16 @@
                                 (frequencyDurationMs * totalRxDuration + (totalActiveTimeMs
                                         / 2)) / totalActiveTimeMs;
                         ratStats.incrementRxDuration(freq, proportionalRxDurationMs);
+
+                        if (isMobileRadioEnergyConsumerSupportedLocked()) {
+                            // Accumulate the power cost of time spent receiving in a particular
+                            // state.
+                            final double rxStatePowerConsumptionMah =
+                                    mMobileRadioPowerCalculator.calcRxStatePowerMah(rat, freq,
+                                            proportionalRxDurationMs);
+                            rxConsumptionMah += rxStatePowerConsumptionMah;
+                            rxDurationMs += proportionalRxDurationMs;
+                        }
                     }
 
                 }
@@ -12324,9 +12419,28 @@
                 final int[] txTimesMs = deltaInfo.getTransmitTimeMillis(rat, freq);
 
                 ratStats.incrementRxDuration(freq, rxTimeMs);
+                if (isMobileRadioEnergyConsumerSupportedLocked()) {
+                    // Accumulate the power cost of time spent receiving in a particular state.
+                    final double rxStatePowerConsumptionMah =
+                            mMobileRadioPowerCalculator.calcRxStatePowerMah(ratBucket, freq,
+                                    rxTimeMs);
+                    rxConsumptionMah += rxStatePowerConsumptionMah;
+                    rxDurationMs += rxTimeMs;
+                }
+
                 final int numTxLvl = txTimesMs.length;
                 for (int lvl = 0; lvl < numTxLvl; lvl++) {
-                    ratStats.incrementTxDuration(freq, lvl, txTimesMs[lvl]);
+                    final long txTimeMs = txTimesMs[lvl];
+                    ratStats.incrementTxDuration(freq, lvl, txTimeMs);
+                    if (isMobileRadioEnergyConsumerSupportedLocked()) {
+                        // Accumulate the power cost of time spent transmitting in a particular
+                        // state.
+                        final double txStatePowerConsumptionMah =
+                                mMobileRadioPowerCalculator.calcTxStatePowerMah(ratBucket, freq,
+                                        lvl, txTimeMs);
+                        txConsumptionMah += txStatePowerConsumptionMah;
+                        txDurationMs += txTimeMs;
+                    }
                 }
             }
         }
@@ -12336,6 +12450,45 @@
             if (ratStats == null) continue;
             ratStats.setMark(elapsedRealtimeMs);
         }
+
+        if (isMobileRadioEnergyConsumerSupportedLocked()) {
+            return new RxTxConsumption(rxConsumptionMah, rxDurationMs, txConsumptionMah,
+                    txDurationMs);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Smear modem Rx/Tx power consumption calculated from {@link ModemActivityInfo} using Rx/Tx
+     * packets.
+     *
+     * @return the combine Rx/Tx smeared power consumption in milliamp-hours.
+     */
+    private double smearModemActivityInfoRxTxConsumptionMah(RxTxConsumption rxTxConsumption,
+            long rxPackets, long txPackets, long totalRxPackets, long totalTxPackets) {
+        // Distribute measured mobile radio charge consumption based on
+        // rx/tx packets and estimated rx/tx charge consumption.
+        double consumptionMah = 0.0;
+        if (totalRxPackets != 0) {
+            // Proportionally distribute receive battery consumption.
+            consumptionMah += rxTxConsumption.rxConsumptionMah * rxPackets
+                    / totalRxPackets;
+        }
+        if (totalTxPackets != 0 || (totalRxPackets != 0 && rxTxConsumption.txToTotalRatio != 0.0)) {
+            // ModemActivityInfo Tx time represents time spent both transmitting and receiving.
+            // There is currently no way to distinguish how many Rx packets were received during
+            // Rx time vs Tx time.
+            // Assumption: The number of packets received while transmitting is proportional
+            // to the time spent transmitting over total active time.
+            final double totalPacketsDuringTxTime =
+                    totalTxPackets + rxTxConsumption.txToTotalRatio * totalRxPackets;
+            final double packetsDuringTxTime =
+                    txPackets + rxTxConsumption.txToTotalRatio * rxPackets;
+            consumptionMah += rxTxConsumption.txConsumptionMah * packetsDuringTxTime
+                    / totalPacketsDuringTxTime;
+        }
+        return consumptionMah;
     }
 
     /**
@@ -14844,6 +14997,13 @@
         }
     }
 
+    @GuardedBy("this")
+    private boolean isMobileRadioEnergyConsumerSupportedLocked() {
+        if (mGlobalEnergyConsumerStats == null) return false;
+        return mGlobalEnergyConsumerStats.isStandardBucketSupported(
+                EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO);
+    }
+
     @NonNull
     private static String[] getBatteryConsumerProcessStateNames() {
         String[] procStateNames = new String[BatteryConsumer.PROCESS_STATE_COUNT];
@@ -14879,6 +15039,40 @@
                 "battery_charged_delay_ms";
         public static final String KEY_RECORD_USAGE_HISTORY =
                 "record_usage_history";
+        public static final String KEY_PER_UID_MODEM_POWER_MODEL =
+                "per_uid_modem_power_model";
+
+        public static final String PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME_NAME =
+                "mobile_radio_active_time";
+        public static final String PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX_NAME =
+                "modem_activity_info_rx_tx";
+
+        /** Convert {@link PerUidModemPowerModel} to string */
+        public String getPerUidModemModelName(@PerUidModemPowerModel int model) {
+            switch(model) {
+                case PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME:
+                    return PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME_NAME;
+                case PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX:
+                    return PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX_NAME;
+                default:
+                    Slog.w(TAG, "Unexpected per uid modem model (" + model + ")");
+                    return "unknown_" + model;
+            }
+        }
+
+        /** Convert string to {@link PerUidModemPowerModel} */
+        @PerUidModemPowerModel
+        public int getPerUidModemModel(String name) {
+            switch(name) {
+                case PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME_NAME:
+                    return PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME;
+                case PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX_NAME:
+                    return PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX;
+                default:
+                    Slog.w(TAG, "Unexpected per uid modem model name (" + name + ")");
+                    return DEFAULT_PER_UID_MODEM_MODEL;
+            }
+        }
 
         private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true;
         private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 1_000;
@@ -14892,6 +15086,9 @@
         private static final int DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB = 64; /*Kilo Bytes*/
         private static final int DEFAULT_BATTERY_CHARGED_DELAY_MS = 900000; /* 15 min */
         private static final boolean DEFAULT_RECORD_USAGE_HISTORY = false;
+        @PerUidModemPowerModel
+        private static final int DEFAULT_PER_UID_MODEM_MODEL =
+                PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX;
 
         public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME;
         /* Do not set default value for KERNEL_UID_READERS_THROTTLE_TIME. Need to trigger an
@@ -14908,6 +15105,7 @@
         public int MAX_HISTORY_BUFFER; /*Bytes*/
         public int BATTERY_CHARGED_DELAY_MS = DEFAULT_BATTERY_CHARGED_DELAY_MS;
         public boolean RECORD_USAGE_HISTORY = DEFAULT_RECORD_USAGE_HISTORY;
+        public int PER_UID_MODEM_MODEL = DEFAULT_PER_UID_MODEM_MODEL;
 
         private ContentResolver mResolver;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -14985,6 +15183,9 @@
                         * 1024;
                 RECORD_USAGE_HISTORY = mParser.getBoolean(
                         KEY_RECORD_USAGE_HISTORY, DEFAULT_RECORD_USAGE_HISTORY);
+                final String perUidModemModel = mParser.getString(KEY_PER_UID_MODEM_POWER_MODEL,
+                        "");
+                PER_UID_MODEM_MODEL = getPerUidModemModel(perUidModemModel);
 
                 updateBatteryChargedDelayMsLocked();
 
@@ -15053,6 +15254,8 @@
             pw.println(BATTERY_CHARGED_DELAY_MS);
             pw.print(KEY_RECORD_USAGE_HISTORY); pw.print("=");
             pw.println(RECORD_USAGE_HISTORY);
+            pw.print(KEY_PER_UID_MODEM_POWER_MODEL); pw.print("=");
+            pw.println(getPerUidModemModelName(PER_UID_MODEM_MODEL));
         }
     }
 
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
index 4608e9a..3226260 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
@@ -270,25 +270,28 @@
             // Calculate the inactive modem power consumption.
             final BatteryStats.ControllerActivityCounter modemActivity =
                     batteryStats.getModemControllerActivity();
-            if (modemActivity != null && (mSleepPowerEstimator != null
-                    || mIdlePowerEstimator != null)) {
+            double inactivePowerMah = Double.NaN;
+            if (modemActivity != null) {
                 final long sleepDurationMs = modemActivity.getSleepTimeCounter().getCountLocked(
                         BatteryStats.STATS_SINCE_CHARGED);
-                total.remainingPowerMah += mSleepPowerEstimator.calculatePower(sleepDurationMs);
                 final long idleDurationMs = modemActivity.getIdleTimeCounter().getCountLocked(
                         BatteryStats.STATS_SINCE_CHARGED);
-                total.remainingPowerMah += mIdlePowerEstimator.calculatePower(idleDurationMs);
-            } else {
+                inactivePowerMah = calcInactiveStatePowerMah(sleepDurationMs, idleDurationMs);
+            }
+            if (Double.isNaN(inactivePowerMah)) {
                 // Modem activity counters unavailable. Use legacy calculations for inactive usage.
                 final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
                         BatteryStats.STATS_SINCE_CHARGED) / 1000;
-                total.remainingPowerMah += calcScanTimePowerMah(scanningTimeMs);
+                inactivePowerMah = calcScanTimePowerMah(scanningTimeMs);
                 for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
                     long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
                             BatteryStats.STATS_SINCE_CHARGED) / 1000;
-                    total.remainingPowerMah += calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
+                    inactivePowerMah += calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
                 }
             }
+            if (!Double.isNaN(inactivePowerMah)) {
+                total.remainingPowerMah += inactivePowerMah;
+            }
 
         }
 
@@ -509,6 +512,22 @@
     }
 
     /**
+     * Calculates active transmit radio power consumption (in milliamp-hours) from the given state's
+     * duration.
+     */
+    public double calcInactiveStatePowerMah(long sleepDurationMs, long idleDurationMs) {
+        if (mSleepPowerEstimator == null || mIdlePowerEstimator == null) return Double.NaN;
+        final double sleepConsumptionMah = mSleepPowerEstimator.calculatePower(sleepDurationMs);
+        final double idleConsumptionMah = mIdlePowerEstimator.calculatePower(idleDurationMs);
+        if (DEBUG) {
+            Log.d(TAG, "Calculated sleep consumption " + sleepConsumptionMah
+                    + " mAH from a duration of " + sleepDurationMs + " ms and idle consumption "
+                    + idleConsumptionMah + " mAH from a duration of " + idleDurationMs);
+        }
+        return sleepConsumptionMah + idleConsumptionMah;
+    }
+
+    /**
      * Calculates active radio power consumption (in milliamp-hours) from active radio duration.
      */
     public double calcPowerFromRadioActiveDurationMah(long radioActiveDurationMs) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
new file mode 100644
index 0000000..a380dea
--- /dev/null
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023 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.wallpaper;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
+import android.os.Debug;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.WindowManagerInternal;
+
+import java.util.function.Consumer;
+/**
+ * Internal class used to store all the display data relevant to the wallpapers
+ */
+class WallpaperDisplayHelper {
+
+    @VisibleForTesting
+    static final class DisplayData {
+        int mWidth = -1;
+        int mHeight = -1;
+        final Rect mPadding = new Rect(0, 0, 0, 0);
+        final int mDisplayId;
+        DisplayData(int displayId) {
+            mDisplayId = displayId;
+        }
+    }
+
+    private static final String TAG = WallpaperDisplayHelper.class.getSimpleName();
+    private final SparseArray<DisplayData> mDisplayDatas = new SparseArray<>();
+    private final DisplayManager mDisplayManager;
+    private final WindowManagerInternal mWindowManagerInternal;
+
+    WallpaperDisplayHelper(
+            DisplayManager displayManager,
+            WindowManagerInternal windowManagerInternal) {
+        mDisplayManager = displayManager;
+        mWindowManagerInternal = windowManagerInternal;
+    }
+
+    DisplayData getDisplayDataOrCreate(int displayId) {
+        DisplayData wpdData = mDisplayDatas.get(displayId);
+        if (wpdData == null) {
+            wpdData = new DisplayData(displayId);
+            ensureSaneWallpaperDisplaySize(wpdData, displayId);
+            mDisplayDatas.append(displayId, wpdData);
+        }
+        return wpdData;
+    }
+
+    void removeDisplayData(int displayId) {
+        mDisplayDatas.remove(displayId);
+    }
+
+    void ensureSaneWallpaperDisplaySize(DisplayData wpdData, int displayId) {
+        // We always want to have some reasonable width hint.
+        final int baseSize = getMaximumSizeDimension(displayId);
+        if (wpdData.mWidth < baseSize) {
+            wpdData.mWidth = baseSize;
+        }
+        if (wpdData.mHeight < baseSize) {
+            wpdData.mHeight = baseSize;
+        }
+    }
+
+    int getMaximumSizeDimension(int displayId) {
+        Display display = mDisplayManager.getDisplay(displayId);
+        if (display == null) {
+            Slog.w(TAG, "Invalid displayId=" + displayId + " " + Debug.getCallers(4));
+            display = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
+        }
+        return display.getMaximumSizeDimension();
+    }
+
+    void forEachDisplayData(Consumer<DisplayData> action) {
+        for (int i = mDisplayDatas.size() - 1; i >= 0; i--) {
+            final DisplayData wpdData = mDisplayDatas.valueAt(i);
+            action.accept(wpdData);
+        }
+    }
+
+    Display[] getDisplays() {
+        return mDisplayManager.getDisplays();
+    }
+
+    DisplayInfo getDisplayInfo(int displayId) {
+        final DisplayInfo displayInfo = new DisplayInfo();
+        mDisplayManager.getDisplay(displayId).getDisplayInfo(displayInfo);
+        return displayInfo;
+    }
+
+    boolean isUsableDisplay(int displayId, int clientUid) {
+        return isUsableDisplay(mDisplayManager.getDisplay(displayId), clientUid);
+    }
+
+    boolean isUsableDisplay(Display display, int clientUid) {
+        if (display == null || !display.hasAccess(clientUid)) {
+            return false;
+        }
+        final int displayId = display.getDisplayId();
+        if (displayId == DEFAULT_DISPLAY) {
+            return true;
+        }
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mWindowManagerInternal.shouldShowSystemDecorOnDisplay(displayId);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    boolean isValidDisplay(int displayId) {
+        return mDisplayManager.getDisplay(displayId) != null;
+    }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 6edfebf..075bac1 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -32,6 +32,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
+import static com.android.server.wallpaper.WallpaperDisplayHelper.DisplayData;
 import static com.android.server.wallpaper.WallpaperUtils.RECORD_FILE;
 import static com.android.server.wallpaper.WallpaperUtils.RECORD_LOCK_FILE;
 import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
@@ -79,7 +80,6 @@
 import android.hardware.display.DisplayManager;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.Debug;
 import android.os.FileObserver;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -609,10 +609,9 @@
         boolean success = false;
 
         // Only generate crop for default display.
-        final DisplayData wpData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
+        final DisplayData wpData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
         final Rect cropHint = new Rect(wallpaper.cropHint);
-        final DisplayInfo displayInfo = new DisplayInfo();
-        mDisplayManager.getDisplay(DEFAULT_DISPLAY).getDisplayInfo(displayInfo);
+        final DisplayInfo displayInfo = mWallpaperDisplayHelper.getDisplayInfo(DEFAULT_DISPLAY);
 
         if (DEBUG) {
             Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
@@ -829,7 +828,8 @@
     private final MyPackageMonitor mMonitor;
     private final AppOpsManager mAppOpsManager;
 
-    private final DisplayManager mDisplayManager;
+    // TODO("b/264637309") probably move this in WallpaperDisplayUtils,
+    //  after logic is changed for the lockscreen lwp project
     private final DisplayManager.DisplayListener mDisplayListener =
             new DisplayManager.DisplayListener() {
 
@@ -848,12 +848,12 @@
                         targetWallpaper = mFallbackWallpaper;
                     }
                     if (targetWallpaper == null) return;
-                    WallpaperConnection.DisplayConnector connector =
+                    DisplayConnector connector =
                             targetWallpaper.connection.getDisplayConnectorOrCreate(displayId);
                     if (connector == null) return;
                     connector.disconnectLocked();
                     targetWallpaper.connection.removeDisplayConnector(displayId);
-                    removeDisplayData(displayId);
+                    mWallpaperDisplayHelper.removeDisplayData(displayId);
                 }
                 for (int i = mColorsChangedListeners.size() - 1; i >= 0; i--) {
                     final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>> callbacks =
@@ -904,8 +904,6 @@
     private final SparseArray<WallpaperData> mWallpaperMap = new SparseArray<WallpaperData>();
     private final SparseArray<WallpaperData> mLockWallpaperMap = new SparseArray<WallpaperData>();
 
-    private SparseArray<DisplayData> mDisplayDatas = new SparseArray<>();
-
     protected WallpaperData mFallbackWallpaper;
 
     private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray();
@@ -914,57 +912,7 @@
     private LocalColorRepository mLocalColorRepo = new LocalColorRepository();
 
     @VisibleForTesting
-    static final class DisplayData {
-        int mWidth = -1;
-        int mHeight = -1;
-        final Rect mPadding = new Rect(0, 0, 0, 0);
-        final int mDisplayId;
-
-        DisplayData(int displayId) {
-            mDisplayId = displayId;
-        }
-    }
-
-    private void removeDisplayData(int displayId) {
-        mDisplayDatas.remove(displayId);
-    }
-
-    private DisplayData getDisplayDataOrCreate(int displayId) {
-        DisplayData wpdData = mDisplayDatas.get(displayId);
-        if (wpdData == null) {
-            wpdData = new DisplayData(displayId);
-            ensureSaneWallpaperDisplaySize(wpdData, displayId);
-            mDisplayDatas.append(displayId, wpdData);
-        }
-        return wpdData;
-    }
-
-    private void ensureSaneWallpaperDisplaySize(DisplayData wpdData, int displayId) {
-        // We always want to have some reasonable width hint.
-        final int baseSize = getMaximumSizeDimension(displayId);
-        if (wpdData.mWidth < baseSize) {
-            wpdData.mWidth = baseSize;
-        }
-        if (wpdData.mHeight < baseSize) {
-            wpdData.mHeight = baseSize;
-        }
-    }
-
-    private int getMaximumSizeDimension(int displayId) {
-        Display display = mDisplayManager.getDisplay(displayId);
-        if (display == null) {
-            Slog.w(TAG, "Invalid displayId=" + displayId + " " + Debug.getCallers(4));
-            display = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
-        }
-        return display.getMaximumSizeDimension();
-    }
-
-    void forEachDisplayData(Consumer<DisplayData> action) {
-        for (int i = mDisplayDatas.size() - 1; i >= 0; i--) {
-            final DisplayData wpdData = mDisplayDatas.valueAt(i);
-            action.accept(wpdData);
-        }
-    }
+    final WallpaperDisplayHelper mWallpaperDisplayHelper;
 
     private boolean supportsMultiDisplay(WallpaperConnection connection) {
         if (connection != null) {
@@ -993,7 +941,7 @@
             }
         } else {
             fallbackConnection.appendConnectorWithCondition(display ->
-                    fallbackConnection.isUsableDisplay(display)
+                    mWallpaperDisplayHelper.isUsableDisplay(display, fallbackConnection.mClientUid)
                             && display.getDisplayId() != DEFAULT_DISPLAY
                             && !fallbackConnection.containsDisplay(display.getDisplayId()));
             fallbackConnection.forEachDisplayConnector(connector -> {
@@ -1004,84 +952,87 @@
         }
     }
 
-    class WallpaperConnection extends IWallpaperConnection.Stub
-            implements ServiceConnection {
+    /**
+     * Collect needed info for a display.
+     */
+    @VisibleForTesting
+    final class DisplayConnector {
+        final int mDisplayId;
+        final Binder mToken = new Binder();
+        IWallpaperEngine mEngine;
+        boolean mDimensionsChanged;
+        boolean mPaddingChanged;
 
-        /**
-         * Collect needed info for a display.
-         */
-        @VisibleForTesting
-        final class DisplayConnector {
-            final int mDisplayId;
-            final Binder mToken = new Binder();
-            IWallpaperEngine mEngine;
-            boolean mDimensionsChanged;
-            boolean mPaddingChanged;
+        DisplayConnector(int displayId) {
+            mDisplayId = displayId;
+        }
 
-            DisplayConnector(int displayId) {
-                mDisplayId = displayId;
-            }
-
-            void ensureStatusHandled() {
-                final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);
-                if (mDimensionsChanged) {
-                    try {
-                        mEngine.setDesiredSize(wpdData.mWidth, wpdData.mHeight);
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Failed to set wallpaper dimensions", e);
-                    }
-                    mDimensionsChanged = false;
-                }
-                if (mPaddingChanged) {
-                    try {
-                        mEngine.setDisplayPadding(wpdData.mPadding);
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Failed to set wallpaper padding", e);
-                    }
-                    mPaddingChanged = false;
-                }
-            }
-
-            void connectLocked(WallpaperConnection connection, WallpaperData wallpaper) {
-                if (connection.mService == null) {
-                    Slog.w(TAG, "WallpaperService is not connected yet");
-                    return;
-                }
-                TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
-                t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent);
-                if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
-                mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId,
-                        null /* options */);
-                final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);
+        void ensureStatusHandled() {
+            final DisplayData wpdData =
+                    mWallpaperDisplayHelper.getDisplayDataOrCreate(mDisplayId);
+            if (mDimensionsChanged) {
                 try {
-                    connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
-                            wpdData.mWidth, wpdData.mHeight,
-                            wpdData.mPadding, mDisplayId, mWallpaper.mWhich);
+                    mEngine.setDesiredSize(wpdData.mWidth, wpdData.mHeight);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed attaching wallpaper on display", e);
-                    if (wallpaper != null && !wallpaper.wallpaperUpdating
-                            && connection.getConnectedEngineSize() == 0) {
-                        bindWallpaperComponentLocked(null /* componentName */, false /* force */,
-                                false /* fromUser */, wallpaper, null /* reply */);
-                    }
+                    Slog.w(TAG, "Failed to set wallpaper dimensions", e);
                 }
-                t.traceEnd();
+                mDimensionsChanged = false;
             }
-
-            void disconnectLocked() {
-                if (DEBUG) Slog.v(TAG, "Removing window token: " + mToken);
-                mWindowManagerInternal.removeWindowToken(mToken, false/* removeWindows */,
-                        mDisplayId);
+            if (mPaddingChanged) {
                 try {
-                    if (mEngine != null) {
-                        mEngine.destroy();
-                    }
+                    mEngine.setDisplayPadding(wpdData.mPadding);
                 } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to set wallpaper padding", e);
                 }
-                mEngine = null;
+                mPaddingChanged = false;
             }
         }
 
+        void connectLocked(WallpaperConnection connection, WallpaperData wallpaper) {
+            if (connection.mService == null) {
+                Slog.w(TAG, "WallpaperService is not connected yet");
+                return;
+            }
+            TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+            t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent);
+            if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
+            mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId,
+                    null /* options */);
+            final DisplayData wpdData =
+                    mWallpaperDisplayHelper.getDisplayDataOrCreate(mDisplayId);
+            try {
+                connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
+                        wpdData.mWidth, wpdData.mHeight,
+                        wpdData.mPadding, mDisplayId, wallpaper.mWhich);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed attaching wallpaper on display", e);
+                if (wallpaper != null && !wallpaper.wallpaperUpdating
+                        && connection.getConnectedEngineSize() == 0) {
+                    bindWallpaperComponentLocked(null /* componentName */, false /* force */,
+                            false /* fromUser */, wallpaper, null /* reply */);
+                }
+            }
+            t.traceEnd();
+        }
+
+        void disconnectLocked() {
+            if (DEBUG) Slog.v(TAG, "Removing window token: " + mToken);
+            mWindowManagerInternal.removeWindowToken(mToken, false/* removeWindows */,
+                    mDisplayId);
+            try {
+                if (mEngine != null) {
+                    mEngine.destroy();
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Engine.destroy() threw a RemoteException");
+            }
+            mEngine = null;
+        }
+    }
+
+    class WallpaperConnection extends IWallpaperConnection.Stub
+            implements ServiceConnection {
+
         /**
          * A map for each display.
          * Use {@link #getDisplayConnectorOrCreate(int displayId)} to ensure the display is usable.
@@ -1132,7 +1083,8 @@
             if (!mWallpaper.equals(mFallbackWallpaper)) {
                 if (supportsMultiDisplay(this)) {
                     // The system wallpaper is image wallpaper or it can supports multiple displays.
-                    appendConnectorWithCondition(this::isUsableDisplay);
+                    appendConnectorWithCondition(display ->
+                            mWallpaperDisplayHelper.isUsableDisplay(display, mClientUid));
                 } else {
                     // The system wallpaper does not support multiple displays, so just attach it on
                     // default display.
@@ -1143,37 +1095,18 @@
         }
 
         private void appendConnectorWithCondition(Predicate<Display> tester) {
-            final Display[] displays = mDisplayManager.getDisplays();
+            final Display[] displays = mWallpaperDisplayHelper.getDisplays();
             for (Display display : displays) {
                 if (tester.test(display)) {
                     final int displayId = display.getDisplayId();
                     final DisplayConnector connector = mDisplayConnector.get(displayId);
                     if (connector == null) {
-                        mDisplayConnector.append(displayId,
-                                new DisplayConnector(displayId));
+                        mDisplayConnector.append(displayId, new DisplayConnector(displayId));
                     }
                 }
             }
         }
 
-        @VisibleForTesting
-        boolean isUsableDisplay(Display display) {
-            if (display == null || !display.hasAccess(mClientUid)) {
-                return false;
-            }
-            final int displayId = display.getDisplayId();
-            if (displayId == DEFAULT_DISPLAY) {
-                return true;
-            }
-
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                return mWindowManagerInternal.shouldShowSystemDecorOnDisplay(displayId);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
         void forEachDisplayConnector(Consumer<DisplayConnector> action) {
             for (int i = mDisplayConnector.size() - 1; i >= 0; i--) {
                 final DisplayConnector connector = mDisplayConnector.valueAt(i);
@@ -1193,8 +1126,7 @@
         DisplayConnector getDisplayConnectorOrCreate(int displayId) {
             DisplayConnector connector = mDisplayConnector.get(displayId);
             if (connector == null) {
-                final Display display = mDisplayManager.getDisplay(displayId);
-                if (isUsableDisplay(display)) {
+                if (mWallpaperDisplayHelper.isUsableDisplay(displayId, mClientUid)) {
                     connector = new DisplayConnector(displayId);
                     mDisplayConnector.append(displayId, connector);
                 }
@@ -1633,8 +1565,9 @@
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
         mIPackageManager = AppGlobals.getPackageManager();
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-        mDisplayManager = mContext.getSystemService(DisplayManager.class);
-        mDisplayManager.registerDisplayListener(mDisplayListener, null /* handler */);
+        DisplayManager dm = mContext.getSystemService(DisplayManager.class);
+        dm.registerDisplayListener(mDisplayListener, null /* handler */);
+        mWallpaperDisplayHelper = new WallpaperDisplayHelper(dm, mWindowManagerInternal);
         mActivityManager = mContext.getSystemService(ActivityManager.class);
         mMonitor = new MyPackageMonitor();
         mColorsChangedListeners = new SparseArray<>();
@@ -2084,10 +2017,6 @@
         return false;
     }
 
-    private boolean isValidDisplay(int displayId) {
-        return mDisplayManager.getDisplay(displayId) != null;
-    }
-
     /**
      * Sets the dimension hint for the wallpaper. These hints indicate the desired
      * minimum width and height for the wallpaper in a particular display.
@@ -2110,18 +2039,18 @@
                 throw new IllegalArgumentException("width and height must be > 0");
             }
 
-            if (!isValidDisplay(displayId)) {
+            if (!mWallpaperDisplayHelper.isValidDisplay(displayId)) {
                 throw new IllegalArgumentException("Cannot find display with id=" + displayId);
             }
 
-            final DisplayData wpdData = getDisplayDataOrCreate(displayId);
+            final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(displayId);
             if (width != wpdData.mWidth || height != wpdData.mHeight) {
                 wpdData.mWidth = width;
                 wpdData.mHeight = height;
                 if (displayId == DEFAULT_DISPLAY) saveSettingsLocked(userId);
                 if (mCurrentUserId != userId) return; // Don't change the properties now
                 if (wallpaper.connection != null) {
-                    final WallpaperConnection.DisplayConnector connector = wallpaper.connection
+                    final DisplayConnector connector = wallpaper.connection
                             .getDisplayConnectorOrCreate(displayId);
                     final IWallpaperEngine engine = connector != null ? connector.mEngine : null;
                     if (engine != null) {
@@ -2146,12 +2075,13 @@
      */
     public int getWidthHint(int displayId) throws RemoteException {
         synchronized (mLock) {
-            if (!isValidDisplay(displayId)) {
+            if (!mWallpaperDisplayHelper.isValidDisplay(displayId)) {
                 throw new IllegalArgumentException("Cannot find display with id=" + displayId);
             }
             WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
             if (wallpaper != null) {
-                final DisplayData wpdData = getDisplayDataOrCreate(displayId);
+                final DisplayData wpdData =
+                        mWallpaperDisplayHelper.getDisplayDataOrCreate(displayId);
                 return wpdData.mWidth;
             } else {
                 return 0;
@@ -2164,12 +2094,13 @@
      */
     public int getHeightHint(int displayId) throws RemoteException {
         synchronized (mLock) {
-            if (!isValidDisplay(displayId)) {
+            if (!mWallpaperDisplayHelper.isValidDisplay(displayId)) {
                 throw new IllegalArgumentException("Cannot find display with id=" + displayId);
             }
             WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
             if (wallpaper != null) {
-                final DisplayData wpdData = getDisplayDataOrCreate(displayId);
+                final DisplayData wpdData =
+                        mWallpaperDisplayHelper.getDisplayDataOrCreate(displayId);
                 return wpdData.mHeight;
             } else {
                 return 0;
@@ -2186,7 +2117,7 @@
             return;
         }
         synchronized (mLock) {
-            if (!isValidDisplay(displayId)) {
+            if (!mWallpaperDisplayHelper.isValidDisplay(displayId)) {
                 throw new IllegalArgumentException("Cannot find display with id=" + displayId);
             }
             int userId = UserHandle.getCallingUserId();
@@ -2195,7 +2126,7 @@
                 throw new IllegalArgumentException("padding must be positive: " + padding);
             }
 
-            int maxSize = getMaximumSizeDimension(displayId);
+            int maxSize = mWallpaperDisplayHelper.getMaximumSizeDimension(displayId);
 
             final int paddingWidth = padding.left + padding.right;
             final int paddingHeight = padding.top + padding.bottom;
@@ -2208,13 +2139,13 @@
                         + " exceeds max height " + maxSize);
             }
 
-            final DisplayData wpdData = getDisplayDataOrCreate(displayId);
+            final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(displayId);
             if (!padding.equals(wpdData.mPadding)) {
                 wpdData.mPadding.set(padding);
                 if (displayId == DEFAULT_DISPLAY) saveSettingsLocked(userId);
                 if (mCurrentUserId != userId) return; // Don't change the properties now
                 if (wallpaper.connection != null) {
-                    final WallpaperConnection.DisplayConnector connector = wallpaper.connection
+                    final DisplayConnector connector = wallpaper.connection
                             .getDisplayConnectorOrCreate(displayId);
                     final IWallpaperEngine engine = connector != null ? connector.mEngine : null;
                     if (engine != null) {
@@ -2268,7 +2199,8 @@
                 return null;
             }
             // Only for default display.
-            final DisplayData wpdData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
+            final DisplayData wpdData =
+                    mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
             try {
                 if (outParams != null) {
                     outParams.putInt("width", wpdData.mWidth);
@@ -3155,8 +3087,7 @@
                 Slog.w(TAG, "Failed detaching wallpaper service ", e);
             }
             mContext.unbindService(wallpaper.connection);
-            wallpaper.connection.forEachDisplayConnector(
-                    WallpaperConnection.DisplayConnector::disconnectLocked);
+            wallpaper.connection.forEachDisplayConnector(DisplayConnector::disconnectLocked);
             wallpaper.connection.mService = null;
             wallpaper.connection.mDisplayConnector.clear();
 
@@ -3286,7 +3217,7 @@
                 return;
             }
             if (supportsMultiDisplay(mLastWallpaper.connection)) {
-                final WallpaperConnection.DisplayConnector connector =
+                final DisplayConnector connector =
                         mLastWallpaper.connection.getDisplayConnectorOrCreate(displayId);
                 if (connector == null) return;
                 connector.connectLocked(mLastWallpaper.connection, mLastWallpaper);
@@ -3295,7 +3226,7 @@
             // System wallpaper does not support multiple displays, attach this display to
             // the fallback wallpaper.
             if (mFallbackWallpaper != null) {
-                final WallpaperConnection.DisplayConnector connector = mFallbackWallpaper
+                final DisplayConnector connector = mFallbackWallpaper
                         .connection.getDisplayConnectorOrCreate(displayId);
                 if (connector == null) return;
                 connector.connectLocked(mFallbackWallpaper.connection, mFallbackWallpaper);
@@ -3352,7 +3283,7 @@
         if (DEBUG) {
             Slog.v(TAG, "writeWallpaperAttributes id=" + wallpaper.wallpaperId);
         }
-        final DisplayData wpdData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
+        final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
         out.startTag(null, tag);
         out.attributeInt(null, "id", wallpaper.wallpaperId);
         out.attributeInt(null, "width", wpdData.mWidth);
@@ -3536,7 +3467,7 @@
             initializeFallbackWallpaper();
         }
         boolean success = false;
-        final DisplayData wpdData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
+        final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
         try {
             stream = new FileInputStream(file);
             TypedXmlPullParser parser = Xml.resolvePullParser(stream);
@@ -3613,7 +3544,7 @@
             }
         }
 
-        ensureSaneWallpaperDisplaySize(wpdData, DEFAULT_DISPLAY);
+        mWallpaperDisplayHelper.ensureSaneWallpaperDisplaySize(wpdData, DEFAULT_DISPLAY);
         ensureSaneWallpaperData(wallpaper);
         WallpaperData lockWallpaper = mLockWallpaperMap.get(userId);
         if (lockWallpaper != null) {
@@ -3653,7 +3584,7 @@
             wallpaper.wallpaperId = makeWallpaperIdLocked();
         }
 
-        final DisplayData wpData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
+        final DisplayData wpData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
 
         if (!keepDimensionHints) {
             wpData.mWidth = parser.getAttributeInt(null, "width");
@@ -3866,7 +3797,7 @@
                 pw.print(" User "); pw.print(wallpaper.userId);
                 pw.print(": id="); pw.println(wallpaper.wallpaperId);
                 pw.println(" Display state:");
-                forEachDisplayData(wpSize -> {
+                mWallpaperDisplayHelper.forEachDisplayData(wpSize -> {
                     pw.print("  displayId=");
                     pw.println(wpSize.mDisplayId);
                     pw.print("  mWidth=");
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index af135b7..9e258cb 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -20,6 +20,7 @@
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
 import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
 import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
+import static android.view.ViewProtoEnums.DISPLAY_STATE_OFF;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONTENT_RECORDING;
 
@@ -317,6 +318,11 @@
         if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
             mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
                     mRecordedWindowContainer.asTask().isVisibleRequested());
+        } else {
+            int currentDisplayState =
+                    mRecordedWindowContainer.asDisplayContent().getDisplay().getState();
+            mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
+                    currentDisplayState != DISPLAY_STATE_OFF);
         }
 
         // No need to clean up. In SurfaceFlinger, parents hold references to their children. The
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index eadb11e..0d6c8db 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6841,12 +6841,12 @@
         public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme,
                 @Nullable ImeTracker.Token statsToken) {
             try {
-                ImeTracker.get().onProgress(statsToken,
+                ImeTracker.forLogging().onProgress(statsToken,
                         ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS);
                 mRemoteInsetsController.showInsets(types, fromIme, statsToken);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to deliver showInsets", e);
-                ImeTracker.get().onFailed(statsToken,
+                ImeTracker.forLogging().onFailed(statsToken,
                         ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS);
             }
         }
@@ -6855,12 +6855,12 @@
         public void hideInsets(@InsetsType int types, boolean fromIme,
                 @Nullable ImeTracker.Token statsToken) {
             try {
-                ImeTracker.get().onProgress(statsToken,
+                ImeTracker.forLogging().onProgress(statsToken,
                         ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS);
                 mRemoteInsetsController.hideInsets(types, fromIme, statsToken);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to deliver hideInsets", e);
-                ImeTracker.get().onFailed(statsToken,
+                ImeTracker.forLogging().onFailed(statsToken,
                         ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS);
             }
         }
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 85938e3..60e2e95 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -182,7 +182,8 @@
         boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
         mImeRequester = imeTarget;
         // There was still a stats token, so that request presumably failed.
-        ImeTracker.get().onFailed(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+        ImeTracker.forLogging().onFailed(
+                mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
         mImeRequesterStatsToken = statsToken;
         if (targetChanged) {
             // target changed, check if new target can show IME.
@@ -197,12 +198,12 @@
         ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null
                 ? mImeRequester : mImeRequester.getWindow().getName());
         mShowImeRunner = () -> {
-            ImeTracker.get().onProgress(mImeRequesterStatsToken,
+            ImeTracker.forLogging().onProgress(mImeRequesterStatsToken,
                     ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
             ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
             // Target should still be the same.
             if (isReadyToShowIme()) {
-                ImeTracker.get().onProgress(mImeRequesterStatsToken,
+                ImeTracker.forLogging().onProgress(mImeRequesterStatsToken,
                         ImeTracker.PHASE_WM_SHOW_IME_READY);
                 final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
 
@@ -219,7 +220,7 @@
                                     ? mImeRequester.getWindow().getName() : ""));
                 }
             } else {
-                ImeTracker.get().onFailed(mImeRequesterStatsToken,
+                ImeTracker.forLogging().onFailed(mImeRequesterStatsToken,
                         ImeTracker.PHASE_WM_SHOW_IME_READY);
             }
             // Clear token here so we don't report an error in abortShowImePostLayout().
@@ -258,7 +259,8 @@
         mImeRequester = null;
         mIsImeLayoutDrawn = false;
         mShowImeRunner = null;
-        ImeTracker.get().onCancelled(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+        ImeTracker.forLogging().onCancelled(
+                mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
         mImeRequesterStatsToken = null;
     }
 
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 1df534f..874f942 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -707,7 +707,8 @@
 
         InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
             super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
-                    false /* disable */, 0 /* floatingImeBottomInsets */, null);
+                    false /* disable */, 0 /* floatingImeBottomInsets */,
+                    null /* loggingListener */, null /* jankContext */);
             mFinishCallback = finishCallback;
             mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
         }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 75ba214..9c43c1d 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1146,10 +1146,9 @@
         final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity(
                 ActivityRecord::fillsParent, mActivityRecord, false /* includeBoundary */,
                 true /* traverseTopToBottom */);
-        if (firstOpaqueActivityBeneath == null
-                || mActivityRecord.launchedFromUid != firstOpaqueActivityBeneath.getUid()) {
+        if (firstOpaqueActivityBeneath == null) {
             // We skip letterboxing if the translucent activity doesn't have any opaque
-            // activities beneath of if it's launched from a different user (e.g. notification)
+            // activities beneath
             return;
         }
         inheritConfiguration(firstOpaqueActivityBeneath);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index e253ce0..b7021c8 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -610,7 +610,7 @@
      */
     ActivityRecord mChildPipActivity;
 
-    boolean mLastSurfaceShowing = true;
+    boolean mLastSurfaceShowing;
 
     boolean mAlignActivityLocaleWithTask = false;
 
@@ -4131,13 +4131,7 @@
 
     @Override
     boolean showSurfaceOnCreation() {
-        if (mCreatedByOrganizer) {
-            // Tasks created by the organizer are default visible because they can synchronously
-            // update the leash before new children are added to the task.
-            return true;
-        }
-        // Organized tasks handle their own surface visibility
-        return !canBeOrganized();
+        return false;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index a3827c0..db17ae1 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1958,7 +1958,8 @@
     }
 
     boolean shouldSleepActivities() {
-        return false;
+        final Task task = getRootTask();
+        return task != null && task.shouldSleepActivities();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 5f186a1..a27cc3a 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -541,9 +541,12 @@
         synchronized (mGlobalLock) {
             final TaskFragmentOrganizerState organizerState =
                     mTaskFragmentOrganizerState.get(organizer.asBinder());
-            return organizerState != null
-                    ? organizerState.mRemoteAnimationDefinition
-                    : null;
+            if (organizerState == null) {
+                Slog.e(TAG, "TaskFragmentOrganizer has been unregistered or died when trying"
+                        + " to play animation on its organized windows.");
+                return null;
+            }
+            return organizerState.mRemoteAnimationDefinition;
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0243a98..5adae41 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8071,14 +8071,14 @@
                     dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout();
                 }
                 if (dc != null && dc.getImeTarget(IME_TARGET_CONTROL) != null) {
-                    ImeTracker.get().onProgress(statsToken,
+                    ImeTracker.forLogging().onProgress(statsToken,
                             ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
                     ProtoLog.d(WM_DEBUG_IME, "hideIme Control target: %s ",
                             dc.getImeTarget(IME_TARGET_CONTROL));
                     dc.getImeTarget(IME_TARGET_CONTROL).hideInsets(WindowInsets.Type.ime(),
                             true /* fromIme */, statsToken);
                 } else {
-                    ImeTracker.get().onFailed(statsToken,
+                    ImeTracker.forLogging().onFailed(statsToken,
                             ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
                 }
                 if (dc != null) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0a5e0b7..6a1adb4 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1932,6 +1932,8 @@
         ownerTask.addChild(taskFragment, position);
         taskFragment.setWindowingMode(creationParams.getWindowingMode());
         taskFragment.setBounds(creationParams.getInitialBounds());
+        // Record the initial relative embedded bounds.
+        taskFragment.updateRelativeEmbeddedBounds();
         mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment);
 
         if (transition != null) transition.collectExistenceChange(taskFragment);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index e08bacc..7a0070b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -388,14 +388,6 @@
     int mPrepareSyncSeqId = 0;
 
     /**
-     * {@code true} when the client was still drawing for sync when the sync-set was finished or
-     * cancelled. This can happen if the window goes away during a sync. In this situation we need
-     * to make sure to still apply the postDrawTransaction when it finishes to prevent the client
-     * from getting stuck in a bad state.
-     */
-    boolean mClientWasDrawingForSync = false;
-
-    /**
      * Special mode that is intended only for the rounded corner overlay: during rotation
      * transition, we un-rotate the window token such that the window appears as it did before the
      * rotation.
@@ -4001,12 +3993,12 @@
     public void showInsets(@InsetsType int types, boolean fromIme,
             @Nullable ImeTracker.Token statsToken) {
         try {
-            ImeTracker.get().onProgress(statsToken,
+            ImeTracker.forLogging().onProgress(statsToken,
                     ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS);
             mClient.showInsets(types, fromIme, statsToken);
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to deliver showInsets", e);
-            ImeTracker.get().onFailed(statsToken,
+            ImeTracker.forLogging().onFailed(statsToken,
                     ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS);
         }
     }
@@ -4015,12 +4007,12 @@
     public void hideInsets(@InsetsType int types, boolean fromIme,
             @Nullable ImeTracker.Token statsToken) {
         try {
-            ImeTracker.get().onProgress(statsToken,
+            ImeTracker.forLogging().onProgress(statsToken,
                     ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS);
             mClient.hideInsets(types, fromIme, statsToken);
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to deliver hideInsets", e);
-            ImeTracker.get().onFailed(statsToken,
+            ImeTracker.forLogging().onFailed(statsToken,
                     ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS);
         }
     }
@@ -5977,9 +5969,6 @@
 
     @Override
     void finishSync(Transaction outMergedTransaction, boolean cancel) {
-        if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mRedrawForSyncReported) {
-            mClientWasDrawingForSync = true;
-        }
         mPrepareSyncSeqId = 0;
         if (cancel) {
             // This is leaving sync so any buffers left in the sync have a chance of
@@ -6047,9 +6036,7 @@
             layoutNeeded = onSyncFinishedDrawing();
         }
 
-        layoutNeeded |=
-                mWinAnimator.finishDrawingLocked(postDrawTransaction, mClientWasDrawingForSync);
-        mClientWasDrawingForSync = false;
+        layoutNeeded |= mWinAnimator.finishDrawingLocked(postDrawTransaction);
         // We always want to force a traversal after a finish draw for blast sync.
         return !skipLayout && (hasSyncHandlers || layoutNeeded);
     }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index a1f4096..a102986 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -151,17 +151,6 @@
 
     int mAttrType;
 
-    /**
-     * Handles surface changes synchronized to after the client has drawn the surface. This
-     * transaction is currently used to reparent the old surface children to the new surface once
-     * the client has completed drawing to the new surface.
-     * This transaction is also used to merge transactions parceled in by the client. The client
-     * uses the transaction to update the relative z of its children from the old parent surface
-     * to the new parent surface once window manager reparents its children.
-     */
-    private final SurfaceControl.Transaction mPostDrawTransaction =
-            new SurfaceControl.Transaction();
-
     WindowStateAnimator(final WindowState win) {
         final WindowManagerService service = win.mWmService;
 
@@ -217,8 +206,7 @@
         }
     }
 
-    boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction,
-            boolean forceApplyNow) {
+    boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) {
         final boolean startingWindow =
                 mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
         if (startingWindow) {
@@ -240,14 +228,7 @@
         }
 
         if (postDrawTransaction != null) {
-            // If there is no surface, the last draw was for the previous surface. We don't want to
-            // wait until the new surface is shown and instead just apply the transaction right
-            // away.
-            if (mLastHidden && mDrawState != NO_SURFACE && !forceApplyNow) {
-                mPostDrawTransaction.merge(postDrawTransaction);
-            } else {
-                mWin.getSyncTransaction().merge(postDrawTransaction);
-            }
+            mWin.getSyncTransaction().merge(postDrawTransaction);
             layoutNeeded = true;
         }
 
@@ -543,7 +524,6 @@
         if (!shown)
             return false;
 
-        t.merge(mPostDrawTransaction);
         return true;
     }
 
@@ -710,10 +690,6 @@
     }
 
     void destroySurface(SurfaceControl.Transaction t) {
-        // Since the SurfaceControl is getting torn down, it's safe to just clean up any
-        // pending transactions that were in mPostDrawTransaction, as well.
-        t.merge(mPostDrawTransaction);
-
         try {
             if (mSurfaceController != null) {
                 mSurfaceController.destroy(t);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8be3df4..fa6fa53 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -146,6 +146,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
@@ -1091,13 +1092,13 @@
                 // (ACTION_DATE_CHANGED), or when manual clock adjustment is made
                 // (ACTION_TIME_CHANGED)
                 updateSystemUpdateFreezePeriodsRecord(/* saveIfChanged */ true);
-                final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
+                final int userId = getManagedUserId(mUserManager.getMainUser().getIdentifier());
                 if (userId >= 0) {
                     updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked(userId));
                 }
             } else if (ACTION_PROFILE_OFF_DEADLINE.equals(action)) {
                 Slogf.i(LOG_TAG, "Profile off deadline alarm was triggered");
-                final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
+                final int userId = getManagedUserId(mUserManager.getMainUser().getIdentifier());
                 if (userId >= 0) {
                     updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked(userId));
                 } else {
@@ -8597,9 +8598,20 @@
         Preconditions.checkArgument(admin != null);
 
         final CallerIdentity caller = getCallerIdentity();
-        // Cannot be called while holding the lock:
-        final boolean hasIncompatibleAccountsOrNonAdb =
-                hasIncompatibleAccountsOrNonAdbNoLock(caller, userId, admin);
+
+        boolean hasIncompatibleAccountsOrNonAdb =
+                !isAdb(caller) || hasIncompatibleAccountsOnAnyUser();
+
+        if (!hasIncompatibleAccountsOrNonAdb) {
+            synchronized (getLockObject()) {
+                if (!isAdminTestOnlyLocked(admin, userId) && hasAccountsOnAnyUser()) {
+                    Slogf.w(LOG_TAG,
+                            "Non test-only owner can't be installed with existing accounts.");
+                    return false;
+                }
+            }
+        }
+
         synchronized (getLockObject()) {
             enforceCanSetDeviceOwnerLocked(caller, admin, userId, hasIncompatibleAccountsOrNonAdb);
             Preconditions.checkArgument(isPackageInstalledForUser(admin.getPackageName(), userId),
@@ -14745,14 +14757,10 @@
         if (isAdb) {
             // If shell command runs after user setup completed check device status. Otherwise, OK.
             if (mIsWatch || hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
-                // In non-headless system user mode, DO can be setup only if
-                // there's no non-system user.
-                // In headless system user mode, DO can be setup only if there are
-                // two users: the headless system user and the foreground user.
-                // If there could be multiple foreground users, this constraint should be modified.
+                // DO can be setup only if there are no users which are neither created by default
+                // nor marked as FOR_TESTING
 
-                int maxNumberOfExistingUsers = isHeadlessSystemUserMode ? 2 : 1;
-                if (mUserManager.getUserCount() > maxNumberOfExistingUsers) {
+                if (nonTestNonPrecreatedUsersExist()) {
                     return STATUS_NONSYSTEM_USER_EXISTS;
                 }
 
@@ -14782,6 +14790,17 @@
         }
     }
 
+    /**
+     * True if there are any users on the device which were not setup by default (1 usually, 2 for
+     * devices with a headless system user) and also are not marked as FOR_TESTING.
+     */
+    private boolean nonTestNonPrecreatedUsersExist() {
+        int allowedUsers = UserManager.isHeadlessSystemUserMode() ? 2 : 1;
+        return mUserManagerInternal.getUsers(/* excludeDying= */ true).stream()
+                .filter(u -> !u.isForTesting())
+                .count() > allowedUsers;
+    }
+
     private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int callingUserId) {
         synchronized (getLockObject()) {
             final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
@@ -16053,8 +16072,10 @@
         wtfIfInLock();
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
-            final AccountManager am = AccountManager.get(mContext);
-            final Account accounts[] = am.getAccountsAsUser(userId);
+            AccountManager am =
+                    mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0)
+                            .getSystemService(AccountManager.class);
+            Account[] accounts = am.getAccounts();
             if (accounts.length == 0) {
                 return false;
             }
@@ -18168,8 +18189,10 @@
                         .addAction(turnProfileOnButton)
                         .addExtras(extras)
                         .build();
-        mInjector.getNotificationManager().notify(
-                SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED, notification);
+
+        mInjector.getNotificationManager().notifyAsUser(
+                null, SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED, notification,
+                UserHandle.of(getProfileParentId(profileUserId)));
     }
 
     private String getPersonalAppSuspensionButtonText() {
@@ -19125,6 +19148,9 @@
             if (preferentialNetworkServiceConfig.isEnabled()) {
                 if (preferentialNetworkServiceConfig.isFallbackToDefaultConnectionAllowed()) {
                     preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+                } else if (preferentialNetworkServiceConfig.shouldBlockNonMatchingNetworks()) {
+                    preferenceBuilder.setPreference(
+                            PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING);
                 } else {
                     preferenceBuilder.setPreference(
                             PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
@@ -19533,6 +19559,14 @@
     }
 
     @Override
+    public void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState() {
+        Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
+                android.Manifest.permission.MANAGE_ROLE_HOLDERS));
+        setBypassDevicePolicyManagementRoleQualificationStateInternal(
+                /* currentRoleHolder= */ null, /* allowBypass= */ false);
+    }
+
+    @Override
     public boolean shouldAllowBypassingDevicePolicyManagementRoleQualification() {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
                 android.Manifest.permission.MANAGE_ROLE_HOLDERS));
@@ -19546,15 +19580,51 @@
     }
 
     private boolean shouldAllowBypassingDevicePolicyManagementRoleQualificationInternal() {
-        if (mUserManager.getUserCount() > 1) {
+        if (nonTestNonPrecreatedUsersExist()) {
             return false;
         }
-        AccountManager am = AccountManager.get(mContext);
-        Account[] accounts = am.getAccounts();
-        if (accounts.length == 0) {
-            return true;
+
+
+        return !hasIncompatibleAccountsOnAnyUser();
+    }
+
+    private boolean hasAccountsOnAnyUser() {
+        long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            for (UserInfo user : mUserManagerInternal.getUsers(/* excludeDying= */ true)) {
+                AccountManager am = mContext.createContextAsUser(
+                                UserHandle.of(user.id), /* flags= */ 0)
+                        .getSystemService(AccountManager.class);
+                Account[] accounts = am.getAccounts();
+                if (accounts.length != 0) {
+                    return true;
+                }
+            }
+
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
         }
-        return !hasIncompatibleAccounts(am, accounts);
+    }
+
+    private boolean hasIncompatibleAccountsOnAnyUser() {
+        long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            for (UserInfo user : mUserManagerInternal.getUsers(/* excludeDying= */ true)) {
+                AccountManager am = mContext.createContextAsUser(
+                        UserHandle.of(user.id), /* flags= */ 0)
+                        .getSystemService(AccountManager.class);
+                Account[] accounts = am.getAccounts();
+
+                if (hasIncompatibleAccounts(am, accounts)) {
+                    return true;
+                }
+            }
+
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
     }
 
     private void setBypassDevicePolicyManagementRoleQualificationStateInternal(
diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java
index deebfc7..3d71739 100644
--- a/services/java/com/android/server/BootUserInitializer.java
+++ b/services/java/com/android/server/BootUserInitializer.java
@@ -17,7 +17,6 @@
 
 import android.annotation.UserIdInt;
 import android.content.ContentResolver;
-import android.content.pm.UserInfo;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -27,8 +26,6 @@
 import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
 
-import java.util.List;
-
 /**
  * Class responsible for booting the device in the proper user on headless system user mode.
  *
@@ -56,50 +53,18 @@
         // this class or the setup wizard app
         provisionHeadlessSystemUser();
 
-        UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
-        t.traceBegin("get-existing-users");
-        List<UserInfo> existingUsers = um.getUsers(/* excludeDying= */ true);
-        t.traceEnd();
-
-        Slogf.d(TAG, "%d existing users", existingUsers.size());
-
-        int initialUserId = UserHandle.USER_NULL;
-
-        for (int i = 0; i < existingUsers.size(); i++) {
-            UserInfo user = existingUsers.get(i);
-            if (DEBUG) {
-                Slogf.d(TAG, "User at position %d: %s", i, user.toFullString());
-            }
-            if (user.id != UserHandle.USER_SYSTEM && user.isFull()) {
-                if (DEBUG) {
-                    Slogf.d(TAG, "Found initial user: %d", user.id);
-                }
-                initialUserId = user.id;
-                break;
-            }
-        }
-
-        if (initialUserId == UserHandle.USER_NULL) {
-            Slogf.d(TAG, "Creating initial user");
-            t.traceBegin("create-initial-user");
-            try {
-                int flags = UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN;
-                // TODO(b/204091126): proper name for user
-                UserInfo newUser = um.createUserEvenWhenDisallowed("Real User",
-                        UserManager.USER_TYPE_FULL_SECONDARY, flags,
-                        /* disallowedPackages= */ null, /* token= */ null);
-                Slogf.i(TAG, "Created initial user: %s", newUser.toFullString());
-                initialUserId = newUser.id;
-            } catch (Exception e) {
-                Slogf.wtf(TAG, "failed to created initial user", e);
-                return;
-            } finally {
-                t.traceEnd(); // create-initial-user
-            }
-        }
-
         unlockSystemUser(t);
-        switchToInitialUser(initialUserId);
+
+        try {
+            t.traceBegin("getBootUser");
+            int bootUser = LocalServices.getService(UserManagerInternal.class).getBootUser();
+            t.traceEnd();
+            t.traceBegin("switchToBootUser-" + bootUser);
+            switchToBootUser(bootUser);
+            t.traceEnd();
+        } catch (UserManager.CheckedUserOperationException e) {
+            Slogf.wtf(TAG, "Failed to created boot user", e);
+        }
     }
 
     /* TODO(b/261791491): STOPSHIP - SUW should be responsible for this. */
@@ -152,12 +117,12 @@
         }
     }
 
-    private void switchToInitialUser(@UserIdInt int initialUserId) {
-        Slogf.i(TAG, "Switching to initial user %d", initialUserId);
-        boolean started = mAms.startUserInForegroundWithListener(initialUserId,
+    private void switchToBootUser(@UserIdInt int bootUserId) {
+        Slogf.i(TAG, "Switching to boot user %d", bootUserId);
+        boolean started = mAms.startUserInForegroundWithListener(bootUserId,
                 /* unlockListener= */ null);
         if (!started) {
-            Slogf.wtf(TAG, "Failed to start user %d in foreground", initialUserId);
+            Slogf.wtf(TAG, "Failed to start user %d in foreground", bootUserId);
         }
     }
 }
diff --git a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
index 8a3a44ae..0993295 100644
--- a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
@@ -21,6 +21,7 @@
 import android.annotation.WorkerThread;
 import android.content.Context;
 import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
 import android.net.Uri;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.Contacts;
@@ -149,6 +150,8 @@
 
                 found = true;
             }
+        } catch (SQLiteException exception) {
+            Slog.w("SQLite exception when querying contacts.", exception);
         }
         if (found && lookupKey != null && hasPhoneNumber) {
             return queryPhoneNumber(lookupKey);
diff --git a/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
index c8797e2..dbc0da7 100644
--- a/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
@@ -18,8 +18,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.when;
-
 import android.annotation.UserIdInt;
 import android.app.job.JobScheduler;
 import android.content.Context;
@@ -33,8 +31,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.Shadows;
@@ -49,20 +45,14 @@
     private BackupManagerConstants mConstants;
     private ShadowJobScheduler mShadowJobScheduler;
 
-    @Mock
-    private UserBackupManagerService mUserBackupManagerService;
-
     @UserIdInt private int mUserOneId;
     @UserIdInt private int mUserTwoId;
 
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
         mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver());
         mConstants.start();
-        when(mUserBackupManagerService.getConstants()).thenReturn(mConstants);
-        when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
 
         mShadowJobScheduler = Shadows.shadowOf(mContext.getSystemService(JobScheduler.class));
 
@@ -79,8 +69,8 @@
 
     @Test
     public void testSchedule_afterScheduling_jobExists() {
-        FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
-        FullBackupJob.schedule(mUserTwoId, mContext, 0, mUserBackupManagerService);
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+        FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
 
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNotNull();
@@ -88,34 +78,18 @@
 
     @Test
     public void testCancel_afterCancelling_jobDoesntExist() {
-        FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
-        FullBackupJob.schedule(mUserTwoId, mContext, 0, mUserBackupManagerService);
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+        FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
         FullBackupJob.cancel(mUserOneId, mContext);
         FullBackupJob.cancel(mUserTwoId, mContext);
 
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull();
     }
-
-    @Test
-    public void testSchedule_isNoopIfDisabled() {
-        when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(false);
-        FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
-
-        assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
-    }
-
-    @Test
-    public void testSchedule_schedulesJobIfEnabled() {
-        when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
-        FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
-
-        assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
-    }
 //
     @Test
     public void testSchedule_onlySchedulesForRequestedUser() {
-        FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
 
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull();
@@ -123,8 +97,8 @@
 //
     @Test
     public void testCancel_onlyCancelsForRequestedUser() {
-        FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
-        FullBackupJob.schedule(mUserTwoId, mContext, 0, mUserBackupManagerService);
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+        FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
         FullBackupJob.cancel(mUserOneId, mContext);
 
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
diff --git a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
index 712ac55..1c5fac2 100644
--- a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
@@ -18,8 +18,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.when;
-
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.os.Handler;
@@ -32,8 +30,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
@@ -45,20 +41,14 @@
     private Context mContext;
     private BackupManagerConstants mConstants;
 
-    @Mock
-    private UserBackupManagerService mUserBackupManagerService;
-
     @UserIdInt private int mUserOneId;
     @UserIdInt private int mUserTwoId;
 
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
         mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver());
         mConstants.start();
-        when(mUserBackupManagerService.getConstants()).thenReturn(mConstants);
-        when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
 
         mUserOneId = UserHandle.USER_SYSTEM;
         mUserTwoId = mUserOneId + 1;
@@ -72,22 +62,6 @@
     }
 
     @Test
-    public void testSchedule_isNoopIfDisabled() {
-        when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(false);
-        KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
-
-        assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
-    }
-
-    @Test
-    public void testSchedule_schedulesJobIfEnabled() {
-        when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
-        KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
-
-        assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
-    }
-
-    @Test
     public void testIsScheduled_beforeScheduling_returnsFalse() {
         assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
         assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse();
@@ -95,8 +69,8 @@
 
     @Test
     public void testIsScheduled_afterScheduling_returnsTrue() {
-        KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
-        KeyValueBackupJob.schedule(mUserTwoId, mContext, mUserBackupManagerService);
+        KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+        KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
 
         assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
         assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isTrue();
@@ -104,8 +78,8 @@
 
     @Test
     public void testIsScheduled_afterCancelling_returnsFalse() {
-        KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
-        KeyValueBackupJob.schedule(mUserTwoId, mContext, mUserBackupManagerService);
+        KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+        KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
         KeyValueBackupJob.cancel(mUserOneId, mContext);
         KeyValueBackupJob.cancel(mUserTwoId, mContext);
 
@@ -115,7 +89,7 @@
 
     @Test
     public void testIsScheduled_afterScheduling_returnsTrueOnlyForScheduledUser() {
-        KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
+        KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
 
         assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
         assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse();
@@ -123,8 +97,8 @@
 
     @Test
     public void testIsScheduled_afterCancelling_returnsFalseOnlyForCancelledUser() {
-        KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
-        KeyValueBackupJob.schedule(mUserTwoId, mContext, mUserBackupManagerService);
+        KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+        KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
         KeyValueBackupJob.cancel(mUserOneId, mContext);
 
         assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index d969cd2..5f4ff1a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -93,6 +93,7 @@
 import com.android.server.wm.ActivityTaskManagerService;
 
 import org.junit.After;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -448,9 +449,9 @@
             UnaryOperator<Bundle> extrasOperator, ReceiverInfo info) {
         final Intent intent = info.intent;
         final Bundle extras = info.extras;
-        final boolean ordered = info.ordered;
+        final boolean assumeDelivered = info.assumeDelivered;
         mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent));
-        if (!wedge && ordered) {
+        if (!wedge && !assumeDelivered) {
             assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
             assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED,
                     mQueue.getPreferredSchedulingGroupLocked(r));
@@ -701,6 +702,7 @@
             ArgumentMatcher<String> data,
             ArgumentMatcher<Bundle> extras,
             Boolean sync,
+            Boolean assumeDelivered,
             Integer sendingUser,
             Integer processState) {
         return (test) -> {
@@ -712,6 +714,7 @@
                     && matchObject(data, test.data)
                     && matchObject(extras, test.extras)
                     && matchElement(sync, test.sync)
+                    && matchElement(assumeDelivered, test.assumeDelivered)
                     && matchElement(sendingUser, test.sendingUser)
                     && matchElement(processState, test.processState);
         };
@@ -730,10 +733,12 @@
             ArgumentMatcher<String> data,
             ArgumentMatcher<Bundle> extras,
             Boolean sync,
+            Boolean assumeDelivered,
             Integer sendingUser,
             Integer processState) {
         return argThat(receiverList(manifestReceiverMatcher(intent, activityInfo, compatInfo,
-                                resultCode, data, extras, sync, sendingUser, processState)));
+                resultCode, data, extras, sync, assumeDelivered,
+                sendingUser, processState)));
     }
 
     /**
@@ -749,6 +754,7 @@
             ArgumentMatcher<Bundle> extras,
             Boolean ordered,
             Boolean sticky,
+            Boolean assumeDelivered,
             Integer sendingUser,
             Integer processState) {
         return (test) -> {
@@ -760,6 +766,7 @@
                     && matchObject(extras, test.extras)
                     && matchElement(ordered, test.ordered)
                     && matchElement(sticky, test.sticky)
+                    && matchElement(assumeDelivered, test.assumeDelivered)
                     && matchElement(sendingUser, test.sendingUser)
                     && matchElement(processState, test.processState);
         };
@@ -777,10 +784,12 @@
             ArgumentMatcher<Bundle> extras,
             Boolean ordered,
             Boolean sticky,
+            Boolean assumeDelivered,
             Integer sendingUser,
             Integer processState) {
         return argThat(receiverList(registeredReceiverMatcher(receiver, intent, resultCode,
-                                data, extras, ordered, sticky, sendingUser, processState)));
+                data, extras, ordered, sticky, assumeDelivered,
+                sendingUser, processState)));
     }
 
     /**
@@ -833,36 +842,36 @@
         final Intent targetedIntent = new Intent(intent);
         targetedIntent.setComponent(component);
         verify(app.getThread(), mode).scheduleReceiverList(
-            manifestReceiver(filterEquals(targetedIntent),
-                    null, null, null, null, null, null, UserHandle.USER_SYSTEM, null));
+                manifestReceiver(filterEquals(targetedIntent),
+                        null, null, null, null, null, null, null, UserHandle.USER_SYSTEM, null));
     }
 
     private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app,
             Intent intent, int userId) throws Exception {
         verify(app.getThread(), mode).scheduleReceiverList(
-            manifestReceiver(filterEqualsIgnoringComponent(intent),
-                    null, null, null, null, null, null, userId, null));
+                manifestReceiver(filterEqualsIgnoringComponent(intent),
+                        null, null, null, null, null, null, null, userId, null));
     }
 
     private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app,
             int userId) throws Exception {
         verify(app.getThread(), mode).scheduleReceiverList(
                 manifestReceiver(null,
-                        null, null, null, null, null, null, userId, null));
+                        null, null, null, null, null, null, null, userId, null));
     }
 
     private void verifyScheduleRegisteredReceiver(ProcessRecord app,
             Intent intent) throws Exception {
         verify(app.getThread()).scheduleReceiverList(
-            registeredReceiver(null, filterEqualsIgnoringComponent(intent),
-                    null, null, null, null, null, UserHandle.USER_SYSTEM, null));
+                registeredReceiver(null, filterEqualsIgnoringComponent(intent),
+                        null, null, null, null, null, null, UserHandle.USER_SYSTEM, null));
     }
 
     private void verifyScheduleRegisteredReceiver(VerificationMode mode, ProcessRecord app,
             int userId) throws Exception {
         verify(app.getThread(), mode).scheduleReceiverList(
                 registeredReceiver(null, null,
-                        null, null, null, null, null, userId, null));
+                        null, null, null, null, null, null, userId, null));
     }
 
     static final int USER_GUEST = 11;
@@ -1118,10 +1127,11 @@
     }
 
     /**
-     * Verify that we detect and ANR a wedged process.
+     * Verify that we detect and ANR a wedged process when delivering to a
+     * manifest receiver.
      */
     @Test
-    public void testWedged() throws Exception {
+    public void testWedged_Manifest() throws Exception {
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
                 ProcessBehavior.WEDGE);
@@ -1135,6 +1145,77 @@
     }
 
     /**
+     * Verify that we detect and ANR a wedged process when delivering an ordered
+     * broadcast, and that we deliver final result.
+     */
+    @Test
+    public void testWedged_Registered_Ordered() throws Exception {
+        // Legacy stack doesn't detect these ANRs; likely an oversight
+        Assume.assumeTrue(mImpl == Impl.MODERN);
+
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
+                ProcessBehavior.WEDGE);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        final IIntentReceiver resultTo = mock(IIntentReceiver.class);
+        enqueueBroadcast(makeOrderedBroadcastRecord(airplane, callerApp,
+                List.of(makeRegisteredReceiver(receiverApp)), resultTo, null));
+
+        waitForIdle();
+        verify(mAms).appNotResponding(eq(receiverApp), any());
+        verifyScheduleRegisteredReceiver(callerApp, airplane);
+    }
+
+    /**
+     * Verify that we detect and ANR a wedged process when delivering an
+     * unordered broadcast with a {@code resultTo}.
+     */
+    @Test
+    public void testWedged_Registered_ResultTo() throws Exception {
+        // Legacy stack doesn't detect these ANRs; likely an oversight
+        Assume.assumeTrue(mImpl == Impl.MODERN);
+
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
+                ProcessBehavior.WEDGE);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        final IIntentReceiver resultTo = mock(IIntentReceiver.class);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+                List.of(makeRegisteredReceiver(receiverApp)), resultTo));
+
+        waitForIdle();
+        verify(mAms).appNotResponding(eq(receiverApp), any());
+        verifyScheduleRegisteredReceiver(callerApp, airplane);
+    }
+
+    /**
+     * Verify that we detect and ANR a wedged process when delivering a
+     * broadcast with more than one priority tranche.
+     */
+    @Test
+    public void testWedged_Registered_Prioritized() throws Exception {
+        // Legacy stack doesn't detect these ANRs; likely an oversight
+        Assume.assumeTrue(mImpl == Impl.MODERN);
+
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN,
+                ProcessBehavior.WEDGE);
+        final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE,
+                ProcessBehavior.NORMAL);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+                List.of(makeRegisteredReceiver(receiverGreenApp, 10),
+                        makeRegisteredReceiver(receiverBlueApp, 5))));
+
+        waitForIdle();
+        verify(mAms).appNotResponding(eq(receiverGreenApp), any());
+        verifyScheduleRegisteredReceiver(receiverBlueApp, airplane);
+    }
+
+    /**
      * Verify that we handle registered receivers in a process that always
      * responds with {@link DeadObjectException}, recovering to restart the
      * process and deliver their next broadcast.
@@ -1344,11 +1425,11 @@
 
         // Confirm that we saw no registered receiver traffic
         final IApplicationThread oldThread = oldApp.getThread();
-        verify(oldThread, never()).scheduleRegisteredReceiver(any(),
-                any(), anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
+        verify(oldThread, never()).scheduleRegisteredReceiver(any(), any(), anyInt(), any(), any(),
+                anyBoolean(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
         final IApplicationThread newThread = newApp.getThread();
-        verify(newThread, never()).scheduleRegisteredReceiver(any(),
-                any(), anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
+        verify(newThread, never()).scheduleRegisteredReceiver(any(), any(), anyInt(), any(), any(),
+                anyBoolean(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
 
         // Confirm that we saw final manifest broadcast
         verifyScheduleReceiver(times(1), newApp, airplane,
@@ -1470,22 +1551,22 @@
         expectedExtras.putBoolean(PACKAGE_RED, true);
         inOrder.verify(greenThread).scheduleReceiverList(manifestReceiver(
                 filterEqualsIgnoringComponent(airplane), null, null,
-                Activity.RESULT_OK, null, bundleEquals(expectedExtras), true,
+                Activity.RESULT_OK, null, bundleEquals(expectedExtras), true, false,
                 UserHandle.USER_SYSTEM, null));
         inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
                 filterEqualsIgnoringComponent(airplane), null, null,
-                Activity.RESULT_OK, null, bundleEquals(expectedExtras), true,
+                Activity.RESULT_OK, null, bundleEquals(expectedExtras), true, false,
                 UserHandle.USER_SYSTEM, null));
         expectedExtras.putBoolean(PACKAGE_BLUE, true);
         inOrder.verify(yellowThread).scheduleReceiverList(manifestReceiver(
                 filterEqualsIgnoringComponent(airplane), null, null,
-                Activity.RESULT_OK, null, bundleEquals(expectedExtras), true,
+                Activity.RESULT_OK, null, bundleEquals(expectedExtras), true, false,
                 UserHandle.USER_SYSTEM, null));
         expectedExtras.putBoolean(PACKAGE_YELLOW, true);
         inOrder.verify(redThread).scheduleReceiverList(registeredReceiver(
                 null, filterEquals(airplane),
                 Activity.RESULT_OK, null, bundleEquals(expectedExtras), false,
-                null, UserHandle.USER_SYSTEM, null));
+                null, true, UserHandle.USER_SYSTEM, null));
 
         // Finally, verify that we thawed the final receiver
         verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(callerApp),
@@ -1550,22 +1631,22 @@
         final InOrder inOrder = inOrder(greenThread, blueThread, redThread);
         inOrder.verify(greenThread).scheduleReceiverList(manifestReceiver(
                 filterEqualsIgnoringComponent(intent), null, null,
-                Activity.RESULT_OK, null, null, true, UserHandle.USER_SYSTEM,
+                Activity.RESULT_OK, null, null, true, false, UserHandle.USER_SYSTEM,
                 null));
         if ((intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0) {
             inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
                     filterEqualsIgnoringComponent(intent), null, null,
-                    Activity.RESULT_OK, null, null, true, UserHandle.USER_SYSTEM,
+                    Activity.RESULT_OK, null, null, true, false, UserHandle.USER_SYSTEM,
                     null));
         } else {
             inOrder.verify(blueThread, never()).scheduleReceiverList(manifestReceiver(
-                    null, null, null, null,
+                    null, null, null, null, null,
                     null, null, null, null, null));
         }
         inOrder.verify(redThread).scheduleReceiverList(registeredReceiver(
                 null, filterEquals(intent),
                 Activity.RESULT_OK, null, bundleEquals(expectedExtras),
-                false, null, UserHandle.USER_SYSTEM, null));
+                false, null, true, UserHandle.USER_SYSTEM, null));
     }
 
     /**
@@ -1588,7 +1669,7 @@
         verify(callerThread).scheduleReceiverList(registeredReceiver(
                 null, filterEquals(airplane),
                 Activity.RESULT_OK, null, bundleEquals(orderedExtras), false,
-                null, UserHandle.USER_SYSTEM, null));
+                null, true, UserHandle.USER_SYSTEM, null));
     }
 
     /**
@@ -1609,7 +1690,7 @@
         verify(callerThread).scheduleReceiverList(registeredReceiver(
                 null, filterEquals(airplane),
                 Activity.RESULT_OK, null, null, false,
-                null, UserHandle.USER_SYSTEM, null));
+                null, true, UserHandle.USER_SYSTEM, null));
     }
 
     /**
@@ -1775,26 +1856,26 @@
         // First broadcast is canceled
         inOrder.verify(callerThread).scheduleReceiverList(registeredReceiver(null,
                 filterAndExtrasEquals(timezoneFirst), Activity.RESULT_CANCELED, null,
-                null, false, null, UserHandle.USER_SYSTEM, null));
+                null, false, null, true, UserHandle.USER_SYSTEM, null));
 
         // We deliver second broadcast to app
         timezoneSecond.setClassName(PACKAGE_BLUE, CLASS_GREEN);
         inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
                 filterAndExtrasEquals(timezoneSecond),
-                null, null, null, null, null, true, null, null));
+                null, null, null, null, null, true, false, null, null));
 
         // Second broadcast is finished
         timezoneSecond.setComponent(null);
         inOrder.verify(callerThread).scheduleReceiverList(registeredReceiver(null,
                 filterAndExtrasEquals(timezoneSecond), Activity.RESULT_OK, null,
-                null, false, null, UserHandle.USER_SYSTEM, null));
+                null, false, null, true, UserHandle.USER_SYSTEM, null));
 
         // Since we "replaced" the first broadcast in its original position,
         // only now do we see the airplane broadcast
         airplane.setClassName(PACKAGE_BLUE, CLASS_RED);
         inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
                 filterEquals(airplane),
-                null, null, null, null, null, false, null, null));
+                null, null, null, null, null, false, false, null, null));
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 1367af8..f13de12 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -20,25 +20,31 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.content.Context;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.provider.Settings;
 import android.util.Log;
 import android.util.SparseArray;
 
 import androidx.test.annotation.UiThreadTest;
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.ExtendedMockitoTestCase;
 import com.android.server.LocalServices;
 import com.android.server.am.UserState;
 import com.android.server.pm.UserManagerService.UserData;
+import com.android.server.storage.DeviceStorageMonitorInternal;
 
 import org.junit.After;
 import org.junit.Before;
@@ -86,6 +92,10 @@
     private @Mock PackageManagerService mMockPms;
     private @Mock UserDataPreparer mMockUserDataPreparer;
     private @Mock ActivityManagerInternal mActivityManagerInternal;
+    private @Mock DeviceStorageMonitorInternal mDeviceStorageMonitorInternal;
+    private @Mock StorageManager mStorageManager;
+    private @Mock LockSettingsInternal mLockSettingsInternal;
+    private @Mock PackageManagerInternal mPackageManagerInternal;
 
     /**
      * Reference to the {@link UserManagerService} being tested.
@@ -101,7 +111,8 @@
     protected void initializeSession(StaticMockitoSessionBuilder builder) {
         builder
                 .spyStatic(UserManager.class)
-                .spyStatic(LocalServices.class);
+                .spyStatic(LocalServices.class)
+                .mockStatic(Settings.Global.class);
     }
 
     @Before
@@ -112,6 +123,14 @@
         // Called when WatchedUserStates is constructed
         doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache());
 
+        // Called when creating new users
+        when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false);
+        mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal);
+        when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
+        mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal);
+        mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal);
+        doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
+
         // Must construct UserManagerService in the UiThread
         mUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
                 mPackagesLock, mRealContext.getDataDir(), mUsers);
@@ -223,6 +242,87 @@
                 .that(mUms.isUserRunning(PROFILE_USER_ID)).isFalse();
     }
 
+    @Test
+    public void testSetBootUser_SuppliedUserIsSwitchable() throws Exception {
+        addUser(USER_ID);
+        addUser(OTHER_USER_ID);
+
+        mUms.setBootUser(OTHER_USER_ID);
+
+        assertWithMessage("getBootUser")
+                .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+    }
+
+    @Test
+    public void testSetBootUser_NotHeadless_SuppliedUserIsNotSwitchable() throws Exception {
+        setSystemUserHeadless(false);
+        addUser(USER_ID);
+        addUser(OTHER_USER_ID);
+        addDefaultProfileAndParent();
+
+        mUms.setBootUser(PROFILE_USER_ID);
+
+        assertWithMessage("getBootUser")
+                .that(mUmi.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+    }
+
+    @Test
+    public void testSetBootUser_Headless_SuppliedUserIsNotSwitchable() throws Exception {
+        setSystemUserHeadless(true);
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+        addDefaultProfileAndParent();
+
+        mUms.setBootUser(PROFILE_USER_ID);
+
+        // Boot user not switchable so return most recently in foreground.
+        assertWithMessage("getBootUser")
+                .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+    }
+
+    @Test
+    public void testGetBootUser_NotHeadless_ReturnsSystemUser() throws Exception {
+        setSystemUserHeadless(false);
+        addUser(USER_ID);
+        addUser(OTHER_USER_ID);
+
+        assertWithMessage("getBootUser")
+                .that(mUmi.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+    }
+
+    @Test
+    public void testGetBootUser_Headless_ReturnsMostRecentlyInForeground() throws Exception {
+        setSystemUserHeadless(true);
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        assertWithMessage("getBootUser")
+                .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+    }
+
+    @Test
+    public void testGetBootUser_Headless_UserCreatedIfOnlySystemUserExists() throws Exception {
+        setSystemUserHeadless(true);
+
+        int bootUser = mUmi.getBootUser();
+
+        assertWithMessage("getStartingUser")
+                .that(bootUser).isNotEqualTo(UserHandle.USER_SYSTEM);
+
+        UserData newUser = mUsers.get(bootUser);
+        assertWithMessage("New boot user is a full user")
+                .that(newUser.info.isFull()).isTrue();
+        assertWithMessage("New boot user is an admin user")
+                .that(newUser.info.isAdmin()).isTrue();
+        assertWithMessage("New boot user is the main user")
+                .that(newUser.info.isMain()).isTrue();
+    }
+
     private void mockCurrentUser(@UserIdInt int userId) {
         mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
 
@@ -248,7 +348,7 @@
 
     private void addUser(@UserIdInt int userId) {
         TestUserData userData = new TestUserData(userId);
-
+        userData.info.flags = UserInfo.FLAG_FULL;
         addUserData(userData);
     }
 
@@ -277,6 +377,23 @@
         mUsers.put(userData.info.id, userData);
     }
 
+    private void setSystemUserHeadless(boolean headless) {
+        UserData systemUser = mUsers.get(UserHandle.USER_SYSTEM);
+        if (headless) {
+            systemUser.info.flags &= ~UserInfo.FLAG_FULL;
+            systemUser.info.userType = UserManager.USER_TYPE_SYSTEM_HEADLESS;
+        } else {
+            systemUser.info.flags |= UserInfo.FLAG_FULL;
+            systemUser.info.userType = UserManager.USER_TYPE_FULL_SYSTEM;
+        }
+        doReturn(headless).when(() -> UserManager.isHeadlessSystemUserMode());
+    }
+
+    private void setLastForegroundTime(@UserIdInt int userId, long timeMillis) {
+        UserData userData = mUsers.get(userId);
+        userData.mLastEnteredForegroundTimeMillis = timeMillis;
+    }
+
     private static final class TestUserData extends UserData {
 
         @SuppressWarnings("deprecation")
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index c08f6bf..a924207 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -306,12 +306,13 @@
         verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
         verifyCurrentSystemData(testUserId);
 
-        spyOn(mService.mLastWallpaper.connection);
-        doReturn(true).when(mService.mLastWallpaper.connection).isUsableDisplay(any());
+        spyOn(mService.mWallpaperDisplayHelper);
+        doReturn(true).when(mService.mWallpaperDisplayHelper)
+                .isUsableDisplay(any(Display.class), mService.mLastWallpaper.connection.mClientUid);
         mService.mLastWallpaper.connection.attachEngine(mock(IWallpaperEngine.class),
                 DEFAULT_DISPLAY);
 
-        WallpaperManagerService.WallpaperConnection.DisplayConnector connector =
+        WallpaperManagerService.DisplayConnector connector =
                 mService.mLastWallpaper.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY);
         mService.setWallpaperComponent(sDefaultWallpaperComponent, FLAG_SYSTEM, testUserId);
 
@@ -521,7 +522,7 @@
     }
 
     private void verifyDisplayData() {
-        mService.forEachDisplayData(data -> {
+        mService.mWallpaperDisplayHelper.forEachDisplayData(data -> {
             assertTrue("Display width must larger than maximum screen size",
                     data.mWidth >= DISPLAY_SIZE_DIMENSION);
             assertTrue("Display height must larger than maximum screen size",
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index e54a48b..1298e7b 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -1292,10 +1292,11 @@
         unlockSystemUser();
         try {
             mAms.hasFeatures(
-                null, // response
-                AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
-                new String[] {"feature1", "feature2"}, // features
-                "testPackage"); // opPackageName
+                    null, // response
+                    AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                    new String[] {"feature1", "feature2"}, // features
+                    0, // userId
+                    "testPackage"); // opPackageName
             fail("IllegalArgumentException expected. But no exception was thrown.");
         } catch (IllegalArgumentException e) {
             // IllegalArgumentException is expected.
@@ -1307,10 +1308,11 @@
         unlockSystemUser();
         try {
             mAms.hasFeatures(
-                mMockAccountManagerResponse, // response
-                null, // account
-                new String[] {"feature1", "feature2"}, // features
-                "testPackage"); // opPackageName
+                    mMockAccountManagerResponse, // response
+                    null, // account
+                    new String[] {"feature1", "feature2"}, // features
+                    0, // userId
+                    "testPackage"); // opPackageName
             fail("IllegalArgumentException expected. But no exception was thrown.");
         } catch (IllegalArgumentException e) {
             // IllegalArgumentException is expected.
@@ -1325,6 +1327,7 @@
                     mMockAccountManagerResponse, // response
                     AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account
                     null, // features
+                    0, // userId
                     "testPackage"); // opPackageName
             fail("IllegalArgumentException expected. But no exception was thrown.");
         } catch (IllegalArgumentException e) {
@@ -1341,6 +1344,7 @@
                 response, // response
                 AccountManagerServiceTestFixtures.ACCOUNT_ERROR, // account
                 AccountManagerServiceTestFixtures.ACCOUNT_FEATURES, // features
+                0, // userId
                 "testPackage"); // opPackageName
         waitForLatch(latch);
         verify(mMockAccountManagerResponse).onError(
@@ -1357,6 +1361,7 @@
                 response, // response
                 AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account
                 AccountManagerServiceTestFixtures.ACCOUNT_FEATURES, // features
+                0, // userId
                 "testPackage"); // opPackageName
         waitForLatch(latch);
         verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
index d4e3d44..0dd60b8 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
@@ -24,6 +24,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
@@ -95,7 +96,7 @@
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
         doNothing().when(mAccountManagerInternal).addOnAppPermissionChangeListener(any());
         when(mJobSchedulerInternal.getSystemScheduledPendingJobs()).thenReturn(new ArrayList<>());
-        mSyncManager = new SyncManagerWithMockedServices(mContext, true);
+        mSyncManager = spy(new SyncManagerWithMockedServices(mContext, true));
     }
 
     public void testSyncExtrasEquals_WithNull() throws Exception {
@@ -233,6 +234,7 @@
     }
 
     public void testShouldDisableSync() {
+        doReturn(true).when(mSyncManager).isContactSharingAllowedForCloneProfile();
         UserInfo primaryUserInfo = createUserInfo("primary", 0 /* id */, 0 /* groupId */,
                 UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN);
         UserInfo cloneUserInfo = createUserInfo("clone", 10 /* id */, 0 /* groupId */,
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 61c3f13..210aeef 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -7396,7 +7396,7 @@
         verify(getServices().alarmManager, times(1)).set(anyInt(), eq(PROFILE_OFF_DEADLINE), any());
         // Now the user should see a warning notification.
         verify(getServices().notificationManager, times(1))
-                .notify(anyInt(), any());
+                .notifyAsUser(any(), anyInt(), any(), any());
         // Apps shouldn't be suspended yet.
         verifyZeroInteractions(getServices().ipackageManager);
         clearInvocations(getServices().alarmManager);
@@ -7410,7 +7410,7 @@
         verifyZeroInteractions(getServices().alarmManager);
         // Now the user should see a notification about suspended apps.
         verify(getServices().notificationManager, times(1))
-                .notify(anyInt(), any());
+                .notifyAsUser(any(), anyInt(), any(), any());
         // Verify that the apps are suspended.
         verify(getServices().ipackageManager, times(1)).setPackagesSuspendedAsUser(
                 any(), eq(true), any(), any(), any(), any(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
index 96302b9..299f153 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
@@ -25,6 +25,7 @@
 
 import android.database.Cursor;
 import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteException;
 import android.net.Uri;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.Contacts;
@@ -63,6 +64,7 @@
     private MatrixCursor mContactsLookupCursor;
     private MatrixCursor mPhoneCursor;
     private ContactsQueryHelper mHelper;
+    private ContactsContentProvider contentProvider;
 
     @Before
     public void setUp() {
@@ -73,7 +75,7 @@
         mPhoneCursor = new MatrixCursor(PHONE_COLUMNS);
 
         MockContentResolver contentResolver = new MockContentResolver();
-        ContactsContentProvider contentProvider = new ContactsContentProvider();
+        contentProvider = new ContactsContentProvider();
         contentProvider.registerCursor(Contacts.CONTENT_URI, mContactsCursor);
         contentProvider.registerCursor(
                 ContactsContract.PhoneLookup.CONTENT_FILTER_URI, mContactsLookupCursor);
@@ -89,6 +91,14 @@
     }
 
     @Test
+    public void testQueryException_returnsFalse() {
+        contentProvider.setThrowException(true);
+
+        Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY);
+        assertFalse(mHelper.query(contactUri.toString()));
+    }
+
+    @Test
     public void testQueryWithUri() {
         mContactsCursor.addRow(new Object[] {
                 /* id= */ 11, CONTACT_LOOKUP_KEY, /* starred= */ 1, /* hasPhoneNumber= */ 1,
@@ -168,10 +178,15 @@
     private class ContactsContentProvider extends MockContentProvider {
 
         private Map<Uri, Cursor> mUriPrefixToCursorMap = new ArrayMap<>();
+        private boolean throwException = false;
 
         @Override
         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                 String sortOrder) {
+            if (throwException) {
+                throw new SQLiteException();
+            }
+
             for (Uri prefixUri : mUriPrefixToCursorMap.keySet()) {
                 if (uri.isPathPrefixMatch(prefixUri)) {
                     return mUriPrefixToCursorMap.get(prefixUri);
@@ -180,6 +195,10 @@
             return mUriPrefixToCursorMap.get(uri);
         }
 
+        public void setThrowException(boolean throwException) {
+            this.throwException = throwException;
+        }
+
         private void registerCursor(Uri uriPrefix, Cursor cursor) {
             mUriPrefixToCursorMap.put(uriPrefix, cursor);
         }
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index bf2faac..3135215 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -137,6 +137,13 @@
         return this;
     }
 
+    public BatteryUsageStatsRule setPerUidModemModel(int perUidModemModel) {
+        synchronized (mBatteryStats) {
+            mBatteryStats.setPerUidModemModel(perUidModemModel);
+        }
+        return this;
+    }
+
     /** Call only after setting the power profile information. */
     public BatteryUsageStatsRule initMeasuredEnergyStatsLocked() {
         return initMeasuredEnergyStatsLocked(new String[0]);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
index 65e6486..d059472 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
@@ -574,8 +574,10 @@
     }
 
     @Test
-    public void testMeasuredEnergyBasedModel() {
+    public void testMeasuredEnergyBasedModel_mobileRadioActiveTimeModel() {
         mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+                .setPerUidModemModel(
+                        BatteryStatsImpl.PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME)
                 .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
@@ -637,6 +639,263 @@
                 .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
+
+
+    @Test
+    public void testMeasuredEnergyBasedModel_modemActivityInfoRxTxModel() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator_multiactive)
+                .setPerUidModemModel(
+                        BatteryStatsImpl.PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX)
+                .initMeasuredEnergyStatsLocked();
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+        // Scan for a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+                TelephonyManager.SIM_STATE_READY,
+                2000, 2000);
+
+        // Found a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+                5000, 5000);
+
+        ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+        CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        perRatCellStrength.add(gsmSignalStrength);
+
+        // Note cell signal strength
+        SignalStrength signalStrength = mock(SignalStrength.class);
+        when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                8_000_000_000L, APP_UID, 8000, 8000);
+
+        // Note established network
+        stats.noteNetworkInterfaceForTransports("cellular",
+                new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+        // Spend some time in each signal strength level. It doesn't matter how long.
+        // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+        // timers.
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                9_500_000_000L, APP_UID2, 9500, 9500);
+
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
+        // Note application network activity
+        NetworkStats networkStats = new NetworkStats(10000, 1)
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 300, 10, 100))
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 2000, 30, 111));
+        mStatsRule.setNetworkStats(networkStats);
+
+        ActivityStatsTechSpecificInfo cdmaInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.CDMA2000, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+                new int[]{10, 11, 12, 13, 14}, 15);
+        ActivityStatsTechSpecificInfo lteInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.EUTRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+                new int[]{20, 21, 22, 23, 24}, 25);
+        ActivityStatsTechSpecificInfo nrLowFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_LOW,
+                new int[]{30, 31, 32, 33, 34}, 35);
+        ActivityStatsTechSpecificInfo nrMidFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MID,
+                new int[]{40, 41, 42, 43, 44}, 45);
+        ActivityStatsTechSpecificInfo nrHighFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH,
+                new int[]{50, 51, 52, 53, 54}, 55);
+        ActivityStatsTechSpecificInfo nrMmwaveFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE,
+                new int[]{60, 61, 62, 63, 64}, 65);
+
+        ActivityStatsTechSpecificInfo[] ratInfos =
+                new ActivityStatsTechSpecificInfo[]{cdmaInfo, lteInfo, nrLowFreqInfo, nrMidFreqInfo,
+                        nrHighFreqInfo, nrMmwaveFreqInfo};
+
+        ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000, ratInfos);
+        stats.noteModemControllerActivity(mai, 10_000_000, 10000, 10000,
+                mNetworkStatsManager);
+
+        mStatsRule.setTime(10_000, 10_000);
+
+        MobileRadioPowerCalculator calculator =
+                new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(calculator);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(2.77778);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+
+        // CDMA2000 [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [720, 1080, 1440, 1800, 2160, 1440] mA . [10, 11, 12, 13, 14, 15] ms = 111600 mA-ms
+        // LTE [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [800, 1200, 1600, 2000, 2400, 2000] mA . [20, 21, 22, 23, 24, 25] ms = 230000 mA-ms
+        // 5G Low Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        // (nrFrequency="LOW" values was not defined so fall back to nrFrequency="DEFAULT")
+        //   [999, 1333, 1888, 2222, 2666, 2222] mA . [30, 31, 32, 33, 34, 35] ms = 373449 mA-ms
+        // 5G Mid Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        // (nrFrequency="MID" values was not defined so fall back to nrFrequency="DEFAULT")
+        //   [999, 1333, 1888, 2222, 2666, 2222] mA . [40, 41, 42, 43, 44, 45] ms = 486749 mA-ms
+        // 5G High Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [1818, 2727, 3636, 4545, 5454, 2727] mA . [50, 51, 52, 53, 54, 55] ms = 1104435 mA-ms
+        // 5G Mmwave Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [2345, 3456, 4567, 5678, 6789, 3456] mA . [60, 61, 62, 63, 64, 65] ms = 1651520 mA-ms
+        // _________________
+        // =    3957753 mA-ms estimated active consumption
+        //
+        // Idle drain rate * idle duration
+        //   360 mA * 3000 ms = 1080000 mA-ms
+        // Sleep drain rate * sleep duration
+        //   70 mA * 2000 ms = 140000 mA-ms
+        // _________________
+        // =    5177753 mA-ms estimated total consumption
+        //
+        // 2.77778 mA-h measured total consumption * 3957753 / 5177753 = 2.123268 mA-h
+        BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(2.123268);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+
+        // 240 ms Rx Time, 1110 ms Tx Time, 1350 ms active time
+        // 150 App 1 Rx Packets, 10 App 1 Tx packets
+        // 50 App 2 Rx Packets, 30 App 2 Tx packets
+        // 200 total Rx Packets, 40 total Tx packets
+        // 623985 mA-ms Rx consumption, 3333768 mA-ms Tx consumption
+        //
+        // Rx Power consumption * Ratio of App1 / Total Rx Packets:
+        // 623985 * 150 / 200 = 467988.75 mA-ms App 1 Rx Power Consumption
+        //
+        // App 1 Tx Packets + App 1 Rx Packets * Ratio of Tx / Total active time
+        // 10 + 150 * 1110 / 1350 = 133.3333 Estimated App 1 Rx/Tx Packets during Tx
+        // Total Tx Packets + Total Rx Packets * Ratio of Tx / Total active time
+        // 40 + 200 * 1110 / 1350 = 204.44444 Estimated Total Rx/Tx Packets during Tx
+        // Tx Power consumption * Ratio of App 1 / Total Estimated Tx Packets:
+        // 3333768 * 133.33333 / 204.44444 = 2174196.52174 mA-ms App 1 Tx Power Consumption
+        //
+        // Total App Power consumption * Ratio of App 1 / Total Estimated Power Consumption
+        // 2.123268 * (467988.75 + 2174196.52174) / 3957753 = 1.41749 App 1 Power Consumption
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.41749);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+
+        // Rest should go to the other app
+        UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.705778);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+    }
+
+    @Test
+    public void testMeasuredEnergyBasedModel_modemActivityInfoRxTxModel_legacyPowerProfile() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+                .setPerUidModemModel(
+                        BatteryStatsImpl.PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX)
+                .initMeasuredEnergyStatsLocked();
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+        // Scan for a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+                TelephonyManager.SIM_STATE_READY,
+                2000, 2000);
+
+        ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+        CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        perRatCellStrength.add(gsmSignalStrength);
+
+        // Found a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+                5000, 5000);
+
+        // Note cell signal strength
+        SignalStrength signalStrength = mock(SignalStrength.class);
+        when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                8_000_000_000L, APP_UID, 8000, 8000);
+
+        // Note established network
+        stats.noteNetworkInterfaceForTransports("cellular",
+                new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+        // Spend some time in each signal strength level. It doesn't matter how long.
+        // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+        // timers.
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
+        // Note application network activity
+        NetworkStats networkStats = new NetworkStats(10000, 1)
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100));
+        mStatsRule.setNetworkStats(networkStats);
+
+        ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
+                new int[]{100, 200, 300, 400, 500}, 600);
+        stats.noteModemControllerActivity(mai, 10_000_000, 10000, 10000, mNetworkStatsManager);
+
+        mStatsRule.setTime(12_000, 12_000);
+
+        MobileRadioPowerCalculator calculator =
+                new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(calculator);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        // 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(2.77778);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+
+        BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+        // Estimated Rx/Tx modem consumption = 0.94 mAh
+        // Estimated total modem consumption = 1.27888 mAh
+        // 2.77778 * 0.94 / 1.27888 = 2.04170 mAh
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(2.04170);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(2.04170);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+    }
+
     @Test
     public void testMeasuredEnergyBasedModel_byProcessState() {
         mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 19d2639..f803355 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -213,6 +213,13 @@
         return this;
     }
 
+    @GuardedBy("this")
+    public MockBatteryStatsImpl setPerUidModemModel(int perUidModemModel) {
+        mConstants.PER_UID_MODEM_MODEL = perUidModemModel;
+        mConstants.onChange();
+        return this;
+    }
+
     public int getAndClearExternalStatsSyncFlags() {
         final int flags = mExternalStatsSync.flags;
         mExternalStatsSync.flags = 0;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index 4ad8516..8cc362c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -280,7 +280,7 @@
     }
 
     @Test
-    public void testStartRecording_notifiesCallback() {
+    public void testStartRecording_notifiesCallback_taskSession() {
         // WHEN a recording is ongoing.
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
@@ -292,6 +292,18 @@
     }
 
     @Test
+    public void testStartRecording_notifiesCallback_displaySession() {
+        // WHEN a recording is ongoing.
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        // THEN the visibility change callback is notified.
+        verify(mMediaProjectionManagerWrapper)
+                .notifyActiveProjectionCapturedContentVisibilityChanged(true);
+    }
+
+    @Test
     public void testOnVisibleRequestedChanged_notifiesCallback() {
         // WHEN a recording is ongoing.
         mContentRecorder.setContentRecordingSession(mTaskSession);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 3eb7fe3..352e498 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -201,27 +201,6 @@
     }
 
     @Test
-    public void testNotApplyStrategyToTranslucentActivitiesWithDifferentUid() {
-        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
-        setUpDisplaySizeWithApp(2000, 1000);
-        prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
-        mActivity.info.setMinAspectRatio(1.2f);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-        // Translucent Activity
-        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
-                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
-                .setMinAspectRatio(1.1f)
-                .setMaxAspectRatio(3f)
-                .build();
-        doReturn(false).when(translucentActivity).fillsParent();
-        mTask.addChild(translucentActivity);
-        // We check bounds
-        final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
-        final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds();
-        assertNotEquals(opaqueBounds, translucentRequestedBounds);
-    }
-
-    @Test
     public void testApplyStrategyToMultipleTranslucentActivities() {
         mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
         setUpDisplaySizeWithApp(2000, 1000);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 035d73d..dbd7a4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -819,6 +819,38 @@
     }
 
     @Test
+    public void testApplyTransaction_createTaskFragment_overrideBounds() {
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord activityAtBottom = createActivityRecord(task);
+        final int uid = Binder.getCallingUid();
+        activityAtBottom.info.applicationInfo.uid = uid;
+        activityAtBottom.getTask().effectiveUid = uid;
+        mTaskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .setFragmentToken(mFragmentToken)
+                .createActivityCount(1)
+                .build();
+        mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+        final IBinder fragmentToken1 = new Binder();
+        final Rect bounds = new Rect(100, 100, 500, 1000);
+        final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+                mOrganizerToken, fragmentToken1, activityAtBottom.token)
+                .setPairedActivityToken(activityAtBottom.token)
+                .setInitialBounds(bounds)
+                .build();
+        mTransaction.setTaskFragmentOrganizer(mIOrganizer);
+        mTransaction.createTaskFragment(params);
+        assertApplyTransactionAllowed(mTransaction);
+
+        // Successfully created a TaskFragment.
+        final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(
+                fragmentToken1);
+        assertNotNull(taskFragment);
+        // The relative embedded bounds is updated to the initial requested bounds.
+        assertEquals(bounds, taskFragment.getRelativeEmbeddedBounds());
+    }
+
+    @Test
     public void testApplyTransaction_createTaskFragment_withPairedActivityToken() {
         final Task task = createTask(mDisplayContent);
         final ActivityRecord activityAtBottom = createActivityRecord(task);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 6bce959..1f60e79 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -552,7 +552,12 @@
         win.applyWithNextDraw(t -> handledT[0] = t);
         assertTrue(win.useBLASTSync());
         final SurfaceControl.Transaction drawT = new StubTransaction();
+        final SurfaceControl.Transaction currT = win.getSyncTransaction();
+        clearInvocations(currT);
+        win.mWinAnimator.mLastHidden = true;
         assertTrue(win.finishDrawing(drawT, Integer.MAX_VALUE));
+        // The draw transaction should be merged to current transaction even if the state is hidden.
+        verify(currT).merge(eq(drawT));
         assertEquals(drawT, handledT[0]);
         assertFalse(win.useBLASTSync());
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index 4d6d320..b8536f9 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
@@ -205,9 +205,15 @@
         mVoiceInteractionServiceUid = voiceInteractionServiceUid;
         mVoiceInteractorIdentity = voiceInteractorIdentity;
         mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
-        mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, getDetectorType(),
-                mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
-                mVoiceInteractorIdentity.attributionTag);
+        if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+            mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager,
+                    getDetectorType(),
+                    mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+                    mVoiceInteractorIdentity.attributionTag);
+        } else {
+            mHotwordAudioStreamCopier = null;
+        }
+
         mScheduledExecutorService = scheduledExecutorService;
         mDebugHotwordLogging = logging;
 
@@ -236,9 +242,12 @@
                     future.complete(null);
                     if (mUpdateStateAfterStartFinished.getAndSet(true)) {
                         Slog.w(TAG, "call callback after timeout");
-                        HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                        if (getDetectorType()
+                                != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                            HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
                                 HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT,
                                 mVoiceInteractionServiceUid);
+                        }
                         return;
                     }
                     Pair<Integer, Integer> statusResultPair = getInitStatusAndMetricsResult(bundle);
@@ -246,27 +255,37 @@
                     int initResultMetricsResult = statusResultPair.second;
                     try {
                         mCallback.onStatusReported(status);
-                        HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
-                                initResultMetricsResult, mVoiceInteractionServiceUid);
+                        if (getDetectorType()
+                                != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                            HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
+                                    initResultMetricsResult, mVoiceInteractionServiceUid);
+                        }
                     } catch (RemoteException e) {
                         Slog.w(TAG, "Failed to report initialization status: " + e);
-                        HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
-                                METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
-                                mVoiceInteractionServiceUid);
+                        if (getDetectorType()
+                                != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                            HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                                    METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
+                                    mVoiceInteractionServiceUid);
+                        }
                     }
                 }
             };
             try {
                 service.updateState(options, sharedMemory, statusCallback);
-                HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
-                        HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE,
-                        mVoiceInteractionServiceUid);
+                if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                    HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                            HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE,
+                            mVoiceInteractionServiceUid);
+                }
             } catch (RemoteException e) {
                 // TODO: (b/181842909) Report an error to voice interactor
                 Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e);
-                HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
-                        HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION,
-                        mVoiceInteractionServiceUid);
+                if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                    HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                            HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION,
+                            mVoiceInteractionServiceUid);
+                }
             }
             return future.orTimeout(MAX_UPDATE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         }).whenComplete((res, err) -> {
@@ -277,13 +296,17 @@
                 }
                 try {
                     mCallback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN);
-                    HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
-                            METRICS_INIT_UNKNOWN_TIMEOUT, mVoiceInteractionServiceUid);
+                    if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                        HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
+                                METRICS_INIT_UNKNOWN_TIMEOUT, mVoiceInteractionServiceUid);
+                    }
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed to report initialization status UNKNOWN", e);
-                    HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
-                            METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
-                            mVoiceInteractionServiceUid);
+                    if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                        HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                                METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
+                                mVoiceInteractionServiceUid);
+                    }
                 }
             } else if (err != null) {
                 Slog.w(TAG, "Failed to update state: " + err);
@@ -315,9 +338,11 @@
     @SuppressWarnings("GuardedBy")
     void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory,
             Instant lastRestartInstant) {
-        HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
-                HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE,
-                mVoiceInteractionServiceUid);
+        if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+            HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                    HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE,
+                    mVoiceInteractionServiceUid);
+        }
         // Prevent doing the init late, so restart is handled equally to a clean process start.
         // TODO(b/191742511): this logic needs a test
         if (!mUpdateStateAfterStartFinished.get() && Instant.now().minus(
@@ -399,9 +424,11 @@
                     callback.onError();
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to report onError status: " + ex);
-                    HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
-                            HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
-                            mVoiceInteractionServiceUid);
+                    if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                        HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                                HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+                                mVoiceInteractionServiceUid);
+                    }
                 }
             } finally {
                 synchronized (mLock) {
@@ -427,7 +454,8 @@
                                         throws RemoteException {
                                     synchronized (mLock) {
                                         mPerformingExternalSourceHotwordDetection = false;
-                                        HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                                        HotwordMetricsLogger.writeDetectorEvent(
+                                                getDetectorType(),
                                                 METRICS_EXTERNAL_SOURCE_REJECTED,
                                                 mVoiceInteractionServiceUid);
                                         mScheduledExecutorService.schedule(
@@ -454,7 +482,8 @@
                                         throws RemoteException {
                                     synchronized (mLock) {
                                         mPerformingExternalSourceHotwordDetection = false;
-                                        HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                                        HotwordMetricsLogger.writeDetectorEvent(
+                                                getDetectorType(),
                                                 METRICS_EXTERNAL_SOURCE_DETECTED,
                                                 mVoiceInteractionServiceUid);
                                         mScheduledExecutorService.schedule(
@@ -540,9 +569,11 @@
             mCallback.onError(status);
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to report onError status: " + e);
-            HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
-                    HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
-                    mVoiceInteractionServiceUid);
+            if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                        HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+                        mVoiceInteractionServiceUid);
+            }
         }
     }
 
@@ -679,6 +710,8 @@
             return HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP;
         } else if (this instanceof SoftwareTrustedHotwordDetectorSession) {
             return HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE;
+        } else if (this instanceof VisualQueryDetectorSession) {
+            return HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR;
         }
         Slog.v(TAG, "Unexpected detector type");
         return -1;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
index ad84f00..cb5b930 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
@@ -71,6 +71,8 @@
 
     @GuardedBy("mLock")
     private boolean mValidatingDspTrigger = false;
+    @GuardedBy("mLock")
+    private HotwordRejectedResult mLastHotwordRejectedResult = null;
 
     DspTrustedHotwordDetectorSession(
             @NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService,
@@ -110,7 +112,8 @@
                             HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED,
                             mVoiceInteractionServiceUid);
                     if (!mValidatingDspTrigger) {
-                        Slog.i(TAG, "Ignoring #onDetected due to a process restart");
+                        Slog.i(TAG, "Ignoring #onDetected due to a process restart or previous"
+                                + " #onRejected result = " + mLastHotwordRejectedResult);
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
                                 METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK,
@@ -173,6 +176,7 @@
                     }
                     mValidatingDspTrigger = false;
                     externalCallback.onRejected(result);
+                    mLastHotwordRejectedResult = result;
                     if (mDebugHotwordLogging && result != null) {
                         Slog.i(TAG, "Egressed rejected result: " + result);
                     }
@@ -181,6 +185,7 @@
         };
 
         mValidatingDspTrigger = true;
+        mLastHotwordRejectedResult = null;
         mRemoteDetectionService.run(service -> {
             // We use the VALIDATION_TIMEOUT_MILLIS to inform that the client needs to invoke
             // the callback before timeout value. In order to reduce the latency impact between
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index d501af7..665d5e7 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -52,6 +52,8 @@
 import android.service.voice.HotwordDetector;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 import android.service.voice.ISandboxedDetectionService;
+import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
+import android.service.voice.VisualQueryDetectionService;
 import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
 import android.speech.IRecognitionServiceManager;
 import android.util.Slog;
@@ -74,7 +76,8 @@
 import java.util.function.Function;
 
 /**
- * A class that provides the communication with the HotwordDetectionService.
+ * A class that provides the communication with the {@link HotwordDetectionService} and
+ * {@link VisualQueryDetectionService}.
  */
 final class HotwordDetectionConnection {
     private static final String TAG = "HotwordDetectionConnection";
@@ -90,7 +93,8 @@
     @Nullable private final ScheduledFuture<?> mCancellationTaskFuture;
     private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
     @NonNull private final ServiceConnectionFactory mHotwordDetectionServiceConnectionFactory;
-    private final int mDetectorType;
+    @NonNull private final ServiceConnectionFactory mVisualQueryDetectionServiceConnectionFactory;
+    private int mDetectorType;
     /**
      * Time after which each HotwordDetectionService process is stopped and replaced by a new one.
      * 0 indicates no restarts.
@@ -100,9 +104,11 @@
     final Object mLock;
     final int mVoiceInteractionServiceUid;
     final ComponentName mHotwordDetectionComponentName;
+    final ComponentName mVisualQueryDetectionComponentName;
     final int mUser;
     final Context mContext;
     volatile HotwordDetectionServiceIdentity mIdentity;
+    //TODO: add similar identity for visual query service for the use of content capturing
     private Instant mLastRestartInstant;
 
     private ScheduledFuture<?> mDebugHotwordLoggingTimeoutFuture = null;
@@ -112,6 +118,7 @@
     @Nullable
     private final Identity mVoiceInteractorIdentity;
     @NonNull private ServiceConnection mRemoteHotwordDetectionService;
+    @NonNull private ServiceConnection mRemoteVisualQueryDetectionService;
     private IBinder mAudioFlinger;
     @GuardedBy("mLock")
     private boolean mDebugHotwordLogging = false;
@@ -126,26 +133,39 @@
             new SparseArray<>();
 
     HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
-            Identity voiceInteractorIdentity, ComponentName hotwordDetectionServiceName, int userId,
+            Identity voiceInteractorIdentity, ComponentName hotwordDetectionServiceName,
+            ComponentName visualQueryDetectionServiceName, int userId,
             boolean bindInstantServiceAllowed, int detectorType) {
         mLock = lock;
         mContext = context;
         mVoiceInteractionServiceUid = voiceInteractionServiceUid;
         mVoiceInteractorIdentity = voiceInteractorIdentity;
         mHotwordDetectionComponentName = hotwordDetectionServiceName;
+        mVisualQueryDetectionComponentName = visualQueryDetectionServiceName;
         mUser = userId;
         mDetectorType = detectorType;
         mReStartPeriodSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION,
                 KEY_RESTART_PERIOD_IN_SECONDS, 0);
+
         final Intent hotwordDetectionServiceIntent =
                 new Intent(HotwordDetectionService.SERVICE_INTERFACE);
         hotwordDetectionServiceIntent.setComponent(mHotwordDetectionComponentName);
+
+        final Intent visualQueryDetectionServiceIntent =
+                new Intent(VisualQueryDetectionService.SERVICE_INTERFACE);
+        visualQueryDetectionServiceIntent.setComponent(mVisualQueryDetectionComponentName);
+
         initAudioFlingerLocked();
 
         mHotwordDetectionServiceConnectionFactory =
                 new ServiceConnectionFactory(hotwordDetectionServiceIntent,
                         bindInstantServiceAllowed);
-        mRemoteHotwordDetectionService = mHotwordDetectionServiceConnectionFactory.createLocked();
+
+        mVisualQueryDetectionServiceConnectionFactory =
+                new ServiceConnectionFactory(visualQueryDetectionServiceIntent,
+                        bindInstantServiceAllowed);
+
+
         mLastRestartInstant = Instant.now();
 
         if (mReStartPeriodSeconds <= 0) {
@@ -157,9 +177,11 @@
                 Slog.v(TAG, "Time to restart the process, TTL has passed");
                 synchronized (mLock) {
                     restartProcessLocked();
-                    HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
-                            HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE,
-                            mVoiceInteractionServiceUid);
+                    if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                        HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
+                                HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE,
+                                mVoiceInteractionServiceUid);
+                    }
                 }
             }, mReStartPeriodSeconds, mReStartPeriodSeconds, TimeUnit.SECONDS);
         }
@@ -193,9 +215,11 @@
             // We restart the process instead of simply sending over the new binder, to avoid race
             // conditions with audio reading in the service.
             restartProcessLocked();
-            HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
-                    HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED,
-                    mVoiceInteractionServiceUid);
+            if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
+                        HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED,
+                        mVoiceInteractionServiceUid);
+            }
         }
     }
 
@@ -208,9 +232,8 @@
         });
         mDetectorSessions.clear();
         mDebugHotwordLogging = false;
-        mRemoteHotwordDetectionService.unbind();
-        LocalServices.getService(PermissionManagerServiceInternal.class)
-                .setHotwordDetectionServiceProvider(null);
+        unbindVisualQueryDetectionService();
+        unbindHotwordDetectionService();
         if (mIdentity != null) {
             removeServiceUidForAudioPolicy(mIdentity.getIsolatedUid());
         }
@@ -223,6 +246,21 @@
         }
     }
 
+    private void unbindVisualQueryDetectionService() {
+        if (mRemoteVisualQueryDetectionService != null) {
+            mRemoteVisualQueryDetectionService.unbind();
+            //TODO: Set visual query detection service provider to null
+        }
+    }
+
+    private void unbindHotwordDetectionService() {
+        if (mRemoteHotwordDetectionService != null) {
+            mRemoteHotwordDetectionService.unbind();
+            LocalServices.getService(PermissionManagerServiceInternal.class)
+                .setHotwordDetectionServiceProvider(null);
+        }
+    }
+
     @SuppressWarnings("GuardedBy")
     void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory,
             @NonNull IBinder token) {
@@ -253,6 +291,34 @@
         session.startListeningFromMicLocked(audioFormat, callback);
     }
 
+    /**
+     * This method is only used by VisualQueryDetector.
+     */
+    void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+        if (DEBUG) {
+            Slog.d(TAG, "startPerceivingLocked");
+        }
+        final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
+        if (session == null) {
+            return;
+        }
+        session.startPerceivingLocked(callback);
+    }
+
+    /**
+     * This method is only used by VisaulQueryDetector.
+     */
+    void stopPerceivingLocked() {
+        if (DEBUG) {
+            Slog.d(TAG, "stopPerceivingLocked");
+        }
+        final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
+        if (session == null) {
+            return;
+        }
+        session.stopPerceivingLocked();
+    }
+
     public void startListeningFromExternalSourceLocked(
             ParcelFileDescriptor audioStream,
             AudioFormat audioFormat,
@@ -342,6 +408,10 @@
         }
     }
 
+    void setDetectorType(int detectorType) {
+        mDetectorType = detectorType;
+    }
+
     private void clearDebugHotwordLoggingTimeoutLocked() {
         if (mDebugHotwordLoggingTimeoutFuture != null) {
             mDebugHotwordLoggingTimeoutFuture.cancel(/* mayInterruptIfRunning= */ true);
@@ -353,24 +423,41 @@
     private void restartProcessLocked() {
         // TODO(b/244598068): Check HotwordAudioStreamManager first
         Slog.v(TAG, "Restarting hotword detection process");
+
         ServiceConnection oldHotwordConnection = mRemoteHotwordDetectionService;
+        ServiceConnection oldVisualQueryDetectionConnection = mRemoteVisualQueryDetectionService;
         HotwordDetectionServiceIdentity previousIdentity = mIdentity;
+        //TODO: Add previousIdentity for visual query detection service
 
         mLastRestartInstant = Instant.now();
         // Recreate connection to reset the cache.
+
         mRemoteHotwordDetectionService = mHotwordDetectionServiceConnectionFactory.createLocked();
+        mRemoteVisualQueryDetectionService =
+                mVisualQueryDetectionServiceConnectionFactory.createLocked();
 
         Slog.v(TAG, "Started the new process, dispatching processRestarted to detector");
         runForEachDetectorSessionLocked((session) -> {
-            session.updateRemoteSandboxedDetectionServiceLocked(mRemoteHotwordDetectionService);
+            HotwordDetectionConnection.ServiceConnection newRemoteService =
+                    (session instanceof VisualQueryDetectorSession)
+                            ? mRemoteVisualQueryDetectionService : mRemoteHotwordDetectionService;
+            session.updateRemoteSandboxedDetectionServiceLocked(newRemoteService);
             session.informRestartProcessLocked();
         });
         if (DEBUG) {
             Slog.i(TAG, "processRestarted is dispatched done, unbinding from the old process");
         }
 
-        oldHotwordConnection.ignoreConnectionStatusEvents();
-        oldHotwordConnection.unbind();
+        if (oldHotwordConnection != null) {
+            oldHotwordConnection.ignoreConnectionStatusEvents();
+            oldHotwordConnection.unbind();
+        }
+
+        if (oldVisualQueryDetectionConnection != null) {
+            oldVisualQueryDetectionConnection.ignoreConnectionStatusEvents();
+            oldVisualQueryDetectionConnection.unbind();
+        }
+
         if (previousIdentity != null) {
             removeServiceUidForAudioPolicy(previousIdentity.getIsolatedUid());
         }
@@ -438,9 +525,14 @@
         synchronized (mLock) {
             pw.print(prefix); pw.print("mReStartPeriodSeconds="); pw.println(mReStartPeriodSeconds);
             pw.print(prefix); pw.print("mBound=");
-            pw.println(mRemoteHotwordDetectionService.isBound());
+            pw.println(mRemoteHotwordDetectionService != null
+                    && mRemoteHotwordDetectionService.isBound());
+            pw.println(mRemoteVisualQueryDetectionService != null
+                    && mRemoteHotwordDetectionService != null
+                    && mRemoteHotwordDetectionService.isBound());
             pw.print(prefix); pw.print("mRestartCount=");
             pw.println(mHotwordDetectionServiceConnectionFactory.mRestartCount);
+            pw.println(mVisualQueryDetectionServiceConnectionFactory.mRestartCount);
             pw.print(prefix); pw.print("mLastRestartInstant="); pw.println(mLastRestartInstant);
             pw.print(prefix); pw.print("mDetectorType=");
             pw.println(HotwordDetector.detectorTypeToString(mDetectorType));
@@ -489,11 +581,11 @@
         private boolean mIsLoggedFirstConnect = false;
 
         ServiceConnection(@NonNull Context context,
-                @NonNull Intent intent, int bindingFlags, int userId,
+                @NonNull Intent serviceIntent, int bindingFlags, int userId,
                 @Nullable Function<IBinder, ISandboxedDetectionService> binderAsInterface,
                 int instanceNumber) {
-            super(context, intent, bindingFlags, userId, binderAsInterface);
-            this.mIntent = intent;
+            super(context, serviceIntent, bindingFlags, userId, binderAsInterface);
+            this.mIntent = serviceIntent;
             this.mBindingFlags = bindingFlags;
             this.mInstanceNumber = instanceNumber;
         }
@@ -512,14 +604,18 @@
                 mIsBound = connected;
 
                 if (!connected) {
-                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                            HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED,
-                            mVoiceInteractionServiceUid);
+                    if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                                HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED,
+                                mVoiceInteractionServiceUid);
+                    }
                 } else if (!mIsLoggedFirstConnect) {
                     mIsLoggedFirstConnect = true;
-                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                            HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED,
-                            mVoiceInteractionServiceUid);
+                    if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                                HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED,
+                                mVoiceInteractionServiceUid);
+                    }
                 }
             }
         }
@@ -539,6 +635,7 @@
                     return;
                 }
             }
+            //TODO(b265535257): report error to either service only.
             synchronized (HotwordDetectionConnection.this.mLock) {
                 runForEachDetectorSessionLocked((session) -> {
                     session.reportErrorLocked(
@@ -546,35 +643,46 @@
                 });
             }
             // Can improve to log exit reason if needed
-            HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                    mDetectorType,
-                    HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH,
-                    mVoiceInteractionServiceUid);
+            if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                        mDetectorType,
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH,
+                        mVoiceInteractionServiceUid);
+            }
         }
 
         @Override
         protected boolean bindService(
                 @NonNull android.content.ServiceConnection serviceConnection) {
             try {
-                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                        HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE,
-                        mVoiceInteractionServiceUid);
+                if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                            HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE,
+                            mVoiceInteractionServiceUid);
+                }
+                String instancePrefix =
+                        mIntent.getAction().equals(HotwordDetectionService.SERVICE_INTERFACE)
+                                ? "hotword_detector_" : "visual_query_detector_";
                 boolean bindResult = mContext.bindIsolatedService(
                         mIntent,
                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE | mBindingFlags,
-                        "hotword_detector_" + mInstanceNumber,
+                        instancePrefix + mInstanceNumber,
                         mExecutor,
                         serviceConnection);
                 if (!bindResult) {
+                    if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                                HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
+                                mVoiceInteractionServiceUid);
+                    }
+                }
+                return bindResult;
+            } catch (IllegalArgumentException e) {
+                if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
                     HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
                             HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
                             mVoiceInteractionServiceUid);
                 }
-                return bindResult;
-            } catch (IllegalArgumentException e) {
-                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                        HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
-                        mVoiceInteractionServiceUid);
                 Slog.wtf(TAG, "Can't bind to the hotword detection service!", e);
                 return false;
             }
@@ -609,10 +717,27 @@
         }
         final DetectorSession session;
         if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP) {
+            if (mRemoteHotwordDetectionService == null) {
+                mRemoteHotwordDetectionService =
+                        mHotwordDetectionServiceConnectionFactory.createLocked();
+            }
             session = new DspTrustedHotwordDetectorSession(mRemoteHotwordDetectionService,
                     mLock, mContext, token, callback, mVoiceInteractionServiceUid,
                     mVoiceInteractorIdentity, mScheduledExecutorService, mDebugHotwordLogging);
+        } else if (detectorType == HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+            if (mRemoteVisualQueryDetectionService == null) {
+                mRemoteVisualQueryDetectionService =
+                        mVisualQueryDetectionServiceConnectionFactory.createLocked();
+            }
+            session = new VisualQueryDetectorSession(
+                    mRemoteVisualQueryDetectionService, mLock, mContext, token, callback,
+                    mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
+                    mScheduledExecutorService, mDebugHotwordLogging);
         } else {
+            if (mRemoteHotwordDetectionService == null) {
+                mRemoteHotwordDetectionService =
+                        mHotwordDetectionServiceConnectionFactory.createLocked();
+            }
             session = new SoftwareTrustedHotwordDetectorSession(
                     mRemoteHotwordDetectionService, mLock, mContext, token, callback,
                     mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
@@ -625,13 +750,23 @@
     @SuppressWarnings("GuardedBy")
     void destroyDetectorLocked(@NonNull IBinder token) {
         final DetectorSession session = getDetectorSessionByTokenLocked(token);
-        if (session != null) {
-            session.destroyLocked();
-            final int index = mDetectorSessions.indexOfValue(session);
-            if (index < 0 || index > mDetectorSessions.size() - 1) {
-                return;
-            }
-            mDetectorSessions.removeAt(index);
+        if (session == null) {
+            return;
+        }
+        session.destroyLocked();
+        final int index = mDetectorSessions.indexOfValue(session);
+        if (index < 0 || index > mDetectorSessions.size() - 1) {
+            return;
+        }
+        mDetectorSessions.removeAt(index);
+        if (session instanceof VisualQueryDetectorSession) {
+            unbindVisualQueryDetectionService();
+        }
+        // Handle case where all hotword detector sessions are destroyed with only the visual
+        // detector session left
+        if (mDetectorSessions.size() == 1
+                && mDetectorSessions.get(0) instanceof VisualQueryDetectorSession) {
+            unbindHotwordDetectionService();
         }
     }
 
@@ -672,6 +807,15 @@
     }
 
     @SuppressWarnings("GuardedBy")
+    private VisualQueryDetectorSession getVisualQueryDetectorSessionLocked() {
+        final DetectorSession session = mDetectorSessions.get(
+                HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR);
+        if (session == null || session.isDestroyed()) {
+            Slog.v(TAG, "Not found the look and talk perceiver");
+            return null;
+        }
+        return (VisualQueryDetectorSession) session;
+    }
     private void runForEachDetectorSessionLocked(
             @NonNull Consumer<DetectorSession> action) {
         for (int i = 0; i < mDetectorSessions.size(); i++) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
new file mode 100644
index 0000000..621c3de
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2023 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.voiceinteraction;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioFormat;
+import android.media.permission.Identity;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.service.voice.IDetectorSessionVisualQueryDetectionCallback;
+import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.ISandboxedDetectionService;
+import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
+import android.util.Slog;
+
+import com.android.internal.app.IHotwordRecognitionStatusCallback;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * A class that provides visual query detector to communicate with the {@link
+ * android.service.voice.VisualQueryDetectionService}.
+ *
+ * This class can handle the visual query detection whose detector is created by using
+ * {@link android.service.voice.VoiceInteractionService#createVisualQueryDetector(PersistableBundle
+ * ,SharedMemory, HotwordDetector.Callback)}.
+ */
+final class VisualQueryDetectorSession extends DetectorSession {
+
+    private static final String TAG = "VisualQueryDetectorSession";
+    private boolean mEgressingData;
+    private boolean mQueryStreaming;
+
+    //TODO(b/261783819): Determines actual functionalities, e.g., startRecognition etc.
+    VisualQueryDetectorSession(
+            @NonNull HotwordDetectionConnection.ServiceConnection remoteService,
+            @NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
+            @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
+            Identity voiceInteractorIdentity,
+            @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
+        super(remoteService, lock, context, token, callback,
+                voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService,
+                logging);
+        mEgressingData = false;
+        mQueryStreaming = false;
+    }
+
+    @Override
+    @SuppressWarnings("GuardedBy")
+    void informRestartProcessLocked() {
+        Slog.v(TAG, "informRestartProcessLocked");
+        mUpdateStateAfterStartFinished.set(false);
+        //TODO(b/261783819): Starts detection in VisualQueryDetectionService.
+    }
+
+    @SuppressWarnings("GuardedBy")
+    void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+        if (DEBUG) {
+            Slog.d(TAG, "startPerceivingLocked");
+        }
+
+        IDetectorSessionVisualQueryDetectionCallback internalCallback =
+                new IDetectorSessionVisualQueryDetectionCallback.Stub(){
+
+            @Override
+            public void onAttentionGained() {
+                Slog.v(TAG, "BinderCallback#onAttentionGained");
+                //TODO check to see if there is an active SysUI listener registered
+                mEgressingData = true;
+            }
+
+            @Override
+            public void onAttentionLost() {
+                Slog.v(TAG, "BinderCallback#onAttentionLost");
+                //TODO check to see if there is an active SysUI listener registered
+                mEgressingData = false;
+            }
+
+            @Override
+            public void onQueryDetected(@NonNull String partialQuery) throws RemoteException {
+                Objects.requireNonNull(partialQuery);
+                Slog.v(TAG, "BinderCallback#onQueryDetected");
+                if (!mEgressingData) {
+                    Slog.v(TAG, "Query should not be egressed within the unattention state.");
+                    return;
+                }
+                mQueryStreaming = true;
+                callback.onQueryDetected(partialQuery);
+                Slog.i(TAG, "Egressed from visual query detection process.");
+            }
+
+            @Override
+            public void onQueryFinished() throws RemoteException {
+                Slog.v(TAG, "BinderCallback#onQueryFinished");
+                if (!mQueryStreaming) {
+                    Slog.v(TAG, "Query streaming state signal FINISHED is block since there is"
+                            + " no active query being streamed.");
+                    return;
+                }
+                callback.onQueryFinished();
+                mQueryStreaming = false;
+            }
+
+            @Override
+            public void onQueryRejected() throws RemoteException {
+                Slog.v(TAG, "BinderCallback#onQueryRejected");
+                if (!mQueryStreaming) {
+                    Slog.v(TAG, "Query streaming state signal REJECTED is block since there is"
+                            + " no active query being streamed.");
+                    return;
+                }
+                callback.onQueryRejected();
+                mQueryStreaming = false;
+            }
+        };
+        mRemoteDetectionService.run(service -> service.detectWithVisualSignals(internalCallback));
+    }
+
+    @SuppressWarnings("GuardedBy")
+    void stopPerceivingLocked() {
+        if (DEBUG) {
+            Slog.d(TAG, "stopPerceivingLocked");
+        }
+        mRemoteDetectionService.run(ISandboxedDetectionService::stopDetection);
+    }
+
+    @Override
+     void startListeningFromExternalSourceLocked(
+            ParcelFileDescriptor audioStream,
+            AudioFormat audioFormat,
+            @Nullable PersistableBundle options,
+            IMicrophoneHotwordDetectionVoiceInteractionCallback callback)
+             throws UnsupportedOperationException {
+        throw new UnsupportedOperationException("HotwordDetectionService method"
+                + " should not be called from VisualQueryDetectorSession.");
+    }
+
+
+    @SuppressWarnings("GuardedBy")
+    public void dumpLocked(String prefix, PrintWriter pw) {
+        super.dumpLocked(prefix, pw);
+        pw.print(prefix);
+    }
+}
+
+
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 9a02188..38bf9c2 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -72,6 +72,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
 import android.service.voice.IVoiceInteractionSession;
 import android.service.voice.VoiceInteractionManagerInternal;
 import android.service.voice.VoiceInteractionService;
@@ -330,6 +331,12 @@
         @GuardedBy("this")
         private boolean mTemporarilyDisabled;
 
+        /** The start value of showSessionId */
+        private static final int SHOW_SESSION_START_ID = 0;
+
+        @GuardedBy("this")
+        private int mShowSessionId = SHOW_SESSION_START_ID;
+
         private final boolean mEnableService;
         // TODO(b/226201975): remove reference once RoleService supports pre-created users
         private final RoleObserver mRoleObserver;
@@ -349,6 +356,24 @@
             }
         }
 
+        int getNextShowSessionId() {
+            synchronized (this) {
+                // Reset the showSessionId to SHOW_SESSION_START_ID to avoid the value exceeds
+                // Integer.MAX_VALUE
+                if (mShowSessionId == Integer.MAX_VALUE - 1) {
+                    mShowSessionId = SHOW_SESSION_START_ID;
+                }
+                mShowSessionId++;
+                return mShowSessionId;
+            }
+        }
+
+        int getShowSessionId() {
+            synchronized (this) {
+                return mShowSessionId;
+            }
+        }
+
         @Override
         public @NonNull IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator(
                 @NonNull Identity originatorIdentity, IBinder client) {
@@ -1314,6 +1339,46 @@
         }
 
         @Override
+        public void startPerceiving(
+                IVisualQueryDetectionVoiceInteractionCallback callback)
+                throws RemoteException {
+            enforceCallingPermission(Manifest.permission.RECORD_AUDIO);
+            enforceCallingPermission(Manifest.permission.CAMERA);
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService();
+
+                if (mImpl == null) {
+                    Slog.w(TAG, "startPerceiving without running voice interaction service");
+                    return;
+                }
+                final long caller = Binder.clearCallingIdentity();
+                try {
+                    mImpl.startPerceivingLocked(callback);
+                } finally {
+                    Binder.restoreCallingIdentity(caller);
+                }
+            }
+        }
+
+        @Override
+        public void stopPerceiving() throws RemoteException {
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService();
+
+                if (mImpl == null) {
+                    Slog.w(TAG, "stopPerceiving without running voice interaction service");
+                    return;
+                }
+                final long caller = Binder.clearCallingIdentity();
+                try {
+                    mImpl.stopPerceivingLocked();
+                } finally {
+                    Binder.restoreCallingIdentity(caller);
+                }
+            }
+        }
+
+        @Override
         public void startListeningFromMic(
                 AudioFormat audioFormat,
                 IMicrophoneHotwordDetectionVoiceInteractionCallback callback)
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 13c0f17..04c8f8f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -21,6 +21,7 @@
 import static android.app.ActivityManager.START_VOICE_HIDDEN_SESSION;
 import static android.app.ActivityManager.START_VOICE_NOT_ACTIVE_SESSION;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.service.voice.VoiceInteractionService.KEY_SHOW_SESSION_ID;
 
 import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
 
@@ -58,7 +59,9 @@
 import android.os.ServiceManager;
 import android.os.SharedMemory;
 import android.os.UserHandle;
+import android.service.voice.HotwordDetector;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
 import android.service.voice.IVoiceInteractionService;
 import android.service.voice.IVoiceInteractionSession;
 import android.service.voice.VoiceInteractionService;
@@ -109,6 +112,7 @@
     final ComponentName mSessionComponentName;
     final IWindowManager mIWindowManager;
     final ComponentName mHotwordDetectionComponentName;
+    final ComponentName mVisualQueryDetectionComponentName;
     boolean mBound = false;
     IVoiceInteractionService mService;
     volatile HotwordDetectionConnection mHotwordDetectionConnection;
@@ -211,6 +215,7 @@
             mInfo = null;
             mSessionComponentName = null;
             mHotwordDetectionComponentName = null;
+            mVisualQueryDetectionComponentName = null;
             mIWindowManager = null;
             mValid = false;
             return;
@@ -220,6 +225,7 @@
             Slog.w(TAG, "Bad voice interaction service: " + mInfo.getParseError());
             mSessionComponentName = null;
             mHotwordDetectionComponentName = null;
+            mVisualQueryDetectionComponentName = null;
             mIWindowManager = null;
             mValid = false;
             return;
@@ -230,6 +236,9 @@
         final String hotwordDetectionServiceName = mInfo.getHotwordDetectionService();
         mHotwordDetectionComponentName = hotwordDetectionServiceName != null
                 ? new ComponentName(service.getPackageName(), hotwordDetectionServiceName) : null;
+        final String visualQueryDetectionServiceName = mInfo.getVisualQueryDetectionService();
+        mVisualQueryDetectionComponentName = visualQueryDetectionServiceName != null ? new
+                ComponentName(service.getPackageName(), visualQueryDetectionServiceName) : null;
         mIWindowManager = IWindowManager.Stub.asInterface(
                 ServiceManager.getService(Context.WINDOW_SERVICE));
         IntentFilter filter = new IntentFilter();
@@ -247,13 +256,17 @@
                 /* direct= */ true);
     }
 
-    public boolean showSessionLocked(@NonNull Bundle args, int flags,
+    public boolean showSessionLocked(@Nullable Bundle args, int flags,
             @Nullable String attributionTag,
             @Nullable IVoiceInteractionSessionShowCallback showCallback,
             @Nullable IBinder activityToken) {
+        final int sessionId = mServiceStub.getNextShowSessionId();
+        final Bundle newArgs = args == null ? new Bundle() : args;
+        newArgs.putInt(KEY_SHOW_SESSION_ID, sessionId);
+
         try {
             if (mService != null) {
-                mService.prepareToShowSession(args, flags);
+                mService.prepareToShowSession(newArgs, flags);
             }
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException while calling prepareToShowSession", e);
@@ -267,7 +280,9 @@
         if (!mActiveSession.mBound) {
             try {
                 if (mService != null) {
-                    mService.showSessionFailed();
+                    Bundle failedArgs = new Bundle();
+                    failedArgs.putInt(KEY_SHOW_SESSION_ID, sessionId);
+                    mService.showSessionFailed(failedArgs);
                 }
             } catch (RemoteException e) {
                 Slog.w(TAG, "RemoteException while calling showSessionFailed", e);
@@ -292,7 +307,7 @@
         } else {
             visibleActivities = allVisibleActivities;
         }
-        return mActiveSession.showLocked(args, flags, attributionTag, mDisabledShowContext,
+        return mActiveSession.showLocked(newArgs, flags, attributionTag, mDisabledShowContext,
                 showCallback, visibleActivities);
     }
 
@@ -591,14 +606,11 @@
         }
     }
 
-    public void initAndVerifyDetectorLocked(
-            @NonNull Identity voiceInteractorIdentity,
-            @Nullable PersistableBundle options,
+    private void verifyDetectorForHotwordDetectionLocked(
             @Nullable SharedMemory sharedMemory,
-            @NonNull IBinder token,
             IHotwordRecognitionStatusCallback callback,
             int detectorType) {
-        Slog.v(TAG, "initAndVerifyDetectorLocked");
+        Slog.v(TAG, "verifyDetectorForHotwordDetectionLocked");
         int voiceInteractionServiceUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (mHotwordDetectionComponentName == null) {
             Slog.w(TAG, "Hotword detection service name not found");
@@ -649,11 +661,70 @@
 
         logDetectorCreateEventIfNeeded(callback, detectorType, true,
                 voiceInteractionServiceUid);
+    }
+
+    private void verifyDetectorForVisualQueryDetectionLocked(@Nullable SharedMemory sharedMemory) {
+        Slog.v(TAG, "verifyDetectorForVisualQueryDetectionLocked");
+
+        if (mVisualQueryDetectionComponentName == null) {
+            Slog.w(TAG, "Visual query detection service name not found");
+            throw new IllegalStateException("Visual query detection service name not found");
+        }
+        ServiceInfo visualQueryDetectionServiceInfo = getServiceInfoLocked(
+                mVisualQueryDetectionComponentName, mUser);
+        if (visualQueryDetectionServiceInfo == null) {
+            Slog.w(TAG, "Visual query detection service info not found");
+            throw new IllegalStateException("Visual query detection service name not found");
+        }
+        if (!isIsolatedProcessLocked(visualQueryDetectionServiceInfo)) {
+            Slog.w(TAG, "Visual query detection service not in isolated process");
+            throw new IllegalStateException("Visual query detection not in isolated process");
+        }
+        if (!Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE.equals(
+                visualQueryDetectionServiceInfo.permission)) {
+            Slog.w(TAG, "Visual query detection does not require permission "
+                    + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
+            throw new SecurityException("Visual query detection does not require permission "
+                    + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
+        }
+        if (mContext.getPackageManager().checkPermission(
+                Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE,
+                mInfo.getServiceInfo().packageName) == PackageManager.PERMISSION_GRANTED) {
+            Slog.w(TAG, "Voice interaction service should not hold permission "
+                    + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
+            throw new SecurityException("Voice interaction service should not hold permission "
+                    + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
+        }
+        if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) {
+            Slog.w(TAG, "Can't set sharedMemory to be read-only");
+            throw new IllegalStateException("Can't set sharedMemory to be read-only");
+        }
+    }
+
+    public void initAndVerifyDetectorLocked(
+            @NonNull Identity voiceInteractorIdentity,
+            @Nullable PersistableBundle options,
+            @Nullable SharedMemory sharedMemory,
+            @NonNull IBinder token,
+            IHotwordRecognitionStatusCallback callback,
+            int detectorType) {
+
+        if (detectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+            verifyDetectorForHotwordDetectionLocked(sharedMemory, callback, detectorType);
+        } else {
+            verifyDetectorForVisualQueryDetectionLocked(sharedMemory);
+        }
+
         if (mHotwordDetectionConnection == null) {
             mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext,
                     mInfo.getServiceInfo().applicationInfo.uid, voiceInteractorIdentity,
-                    mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false,
-                    detectorType);
+                    mHotwordDetectionComponentName, mVisualQueryDetectionComponentName, mUser,
+                    /* bindInstantServiceAllowed= */ false, detectorType);
+        } else if (detectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+            // TODO: Logger events should be handled in session instead. Temporary adding the
+            //  checking to prevent confusion so VisualQueryDetection events won't be logged if the
+            //  connection is instantiated by the VisualQueryDetector.
+            mHotwordDetectionConnection.setDetectorType(detectorType);
         }
         mHotwordDetectionConnection.createDetectorLocked(options, sharedMemory, token, callback,
                 detectorType);
@@ -689,6 +760,32 @@
         mHotwordDetectionConnection = null;
     }
 
+    public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+        if (DEBUG) {
+            Slog.d(TAG, "startPerceivingLocked");
+        }
+
+        if (mHotwordDetectionConnection == null) {
+            // TODO: callback.onError();
+            return;
+        }
+
+        mHotwordDetectionConnection.startPerceivingLocked(callback);
+    }
+
+    public void stopPerceivingLocked() {
+        if (DEBUG) {
+            Slog.d(TAG, "stopPerceivingLocked");
+        }
+
+        if (mHotwordDetectionConnection == null) {
+            Slog.w(TAG, "stopPerceivingLocked() called but connection isn't established");
+            return;
+        }
+
+        mHotwordDetectionConnection.stopPerceivingLocked();
+    }
+
     public void startListeningFromMicLocked(
             AudioFormat audioFormat,
             IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
diff --git a/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
index 5aa58c1..ae677ca 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
@@ -24,4 +24,5 @@
     void onSendSmsResult(int token, int messageRef, int status, int reason, int networkErrorCode);
     void onSmsStatusReportReceived(int token, in String format, in byte[] pdu);
     void onSmsReceived(int token, in String format, in byte[] pdu);
+    void onMemoryAvailableResult(int token, int status, int networkErrorCode);
 }
diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
index 17cb3b4..e7e0ec2 100644
--- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
@@ -196,6 +196,8 @@
      *
      * @param token unique token generated in {@link ImsSmsDispatcher#onMemoryAvailable(void)} that
      *  should be used when triggering callbacks for this specific message.
+     *
+     * @hide
      */
     public void onMemoryAvailable(int token) {
         // Base Implementation - Should be overridden
@@ -403,6 +405,38 @@
     }
 
     /**
+     * This API is used to report the result of sending
+     * RP-SMMA to framework based on received network responses(RP-ACK,
+     * RP-ERROR or SIP error).
+     *
+     * @param token provided in {@link #onMemoryAvailable()}.
+     * @param result based on RP-ACK or RP_ERROR
+     * @param networkErrorCode the error code reported by the carrier
+     * network if sending this SMS has resulted in an error or
+     * {@link #RESULT_NO_NETWORK_ERROR} if no network error was generated. See
+     * 3GPP TS 24.011 Section 7.3.4 for valid error codes and more
+     * information.
+     *
+     * @hide
+     */
+    public final void onMemoryAvailableResult(int token, @SendStatusResult int result,
+            int networkErrorCode) throws RuntimeException {
+        IImsSmsListener listener = null;
+        synchronized (mLock) {
+            listener = mListener;
+        }
+
+        if (listener == null) {
+            throw new RuntimeException("Feature not ready.");
+        }
+        try {
+            listener.onMemoryAvailableResult(token, result, networkErrorCode);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * This method should be triggered by the IMS providers when the status report of the sent
      * message is received. The platform will handle the report and notify the IMS provider of the
      * result by calling {@link #acknowledgeSmsReport(int, int, int)}.
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index 84781b4..707b522 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -22,6 +22,10 @@
         <!-- Ensure output directory is empty at the start -->
         <option name="run-command" value="rm -rf /sdcard/flicker" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
+        <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
+    </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
         <option name="test-file-name" value="FlickerTests.apk"/>
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
index 33e9574..7d1f6cb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
@@ -46,7 +46,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowAndCloseTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class OpenImeWindowAndCloseTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val simpleApp = SimpleAppHelper(instrumentation)
     private val testApp = ImeAppHelper(instrumentation)
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTestCfArm.kt
new file mode 100644
index 0000000..c40dfae
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTestCfArm.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.ime
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class OpenImeWindowAndCloseTestCfArm(flicker: FlickerTest) :
+    OpenImeWindowAndCloseTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index c097511..5b830e5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -37,7 +37,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class OpenImeWindowTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val testApp = ImeAppHelper(instrumentation)
 
     /** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTestCfArm.kt
new file mode 100644
index 0000000..34bd455
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTestCfArm.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.ime
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenImeWindowTestCfArm(flicker: FlickerTest) : OpenImeWindowTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
index b05beba..d95af9d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
@@ -46,7 +46,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowToOverViewTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class OpenImeWindowToOverViewTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTestCfArm.kt
new file mode 100644
index 0000000..8e6d7dc
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTestCfArm.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.ime
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenImeWindowToOverViewTestCfArm(flicker: FlickerTest) : OpenImeWindowToOverViewTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTestCfArm.kt
new file mode 100644
index 0000000..1d3658e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTestCfArm.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.ime
+
+import android.platform.test.annotations.Presubmit
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Presubmit
+open class SwitchImeWindowsFromGestureNavTestCfArm(flicker: FlickerTest) :
+    SwitchImeWindowsFromGestureNavTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index 3d1342c..1baff37 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
@@ -74,11 +73,6 @@
         }
     }
 
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 206753786)
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
     /**
      * Checks that the [ActivityOptions.LaunchNewActivity] activity is visible at the start of the
      * transition, that [ActivityOptions.SimpleActivity] becomes visible during the transition, and
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
index 4ca9d5fa..baa2750 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
@@ -21,10 +21,7 @@
 import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.CameraAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.Assume
-import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -42,11 +39,6 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class OpenAppAfterCameraTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
-    @Before
-    open fun before() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-    }
-
     private val cameraApp = CameraAppHelper(instrumentation)
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
deleted file mode 100644
index a9f9204..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
+++ /dev/null
@@ -1,162 +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.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.Assume
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launching an app after cold opening camera (with shell transitions)
- *
- * To run this test: `atest FlickerTests:OpenAppAfterCameraTest_ShellTransit`
- *
- * Notes: Some default assertions are inherited [OpenAppTransition]
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppAfterCameraTest_ShellTransit(flicker: FlickerTest) : OpenAppAfterCameraTest(flicker) {
-    @Before
-    override fun before() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-    }
-
-    @FlakyTest
-    @Test
-    override fun appLayerReplacesLauncher() {
-        super.appLayerReplacesLauncher()
-    }
-
-    @FlakyTest
-    @Test
-    override fun appLayerBecomesVisible() {
-        super.appLayerBecomesVisible()
-    }
-
-    @FlakyTest
-    @Test
-    override fun appWindowBecomesTopWindow() {
-        super.appWindowBecomesTopWindow()
-    }
-
-    @FlakyTest
-    @Test
-    override fun appWindowBecomesVisible() {
-        super.appWindowBecomesVisible()
-    }
-
-    @FlakyTest
-    @Test
-    override fun appWindowIsTopWindowAtEnd() {
-        super.appWindowIsTopWindowAtEnd()
-    }
-
-    @FlakyTest
-    @Test
-    override fun appWindowReplacesLauncherAsTopWindow() {
-        super.appWindowReplacesLauncherAsTopWindow()
-    }
-
-    @FlakyTest
-    @Test
-    override fun entireScreenCovered() {
-        super.entireScreenCovered()
-    }
-
-    @FlakyTest
-    @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() {
-        super.navBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    @FlakyTest
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() {
-        super.navBarLayerPositionAtStartAndEnd()
-    }
-
-    @FlakyTest
-    @Test
-    override fun navBarWindowIsAlwaysVisible() {
-        super.navBarWindowIsAlwaysVisible()
-    }
-
-    @FlakyTest
-    @Test
-    override fun statusBarLayerIsVisibleAtStartAndEnd() {
-        super.statusBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    @FlakyTest
-    @Test
-    override fun statusBarLayerPositionAtStartAndEnd() {
-        super.statusBarLayerPositionAtStartAndEnd()
-    }
-
-    @FlakyTest
-    @Test
-    override fun statusBarWindowIsAlwaysVisible() {
-        super.statusBarWindowIsAlwaysVisible()
-    }
-
-    @FlakyTest
-    @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() {
-        super.taskBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    @FlakyTest
-    @Test
-    override fun taskBarWindowIsAlwaysVisible() {
-        super.taskBarWindowIsAlwaysVisible()
-    }
-
-    @FlakyTest
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-    }
-
-    @FlakyTest
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
-        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-    }
-
-    @FlakyTest
-    @Test
-    override fun focusChanges() {
-        super.focusChanges()
-    }
-
-    @FlakyTest
-    @Test
-    override fun appWindowAsTopWindowAtEnd() {
-        super.appWindowAsTopWindowAtEnd()
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index 242f457..9d86f8c8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerBuilder
 import com.android.server.wm.flicker.FlickerTest
@@ -25,7 +24,6 @@
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
 import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -77,103 +75,6 @@
             teardown { testApp.exit(wmHelper) }
         }
 
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appWindowReplacesLauncherAsTopWindow() =
-        super.appWindowReplacesLauncherAsTopWindow()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028) @Test override fun focusChanges() = super.focusChanges()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun statusBarLayerIsVisibleAtStartAndEnd() =
-        super.statusBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
-        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
-
     companion object {
         /**
          * Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index a4f09c0..9fbec97 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerBuilder
@@ -71,11 +70,6 @@
         }
 
     /** {@inheritDoc} */
-    @FlakyTest(bugId = 206753786)
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
-    /** {@inheritDoc} */
     @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
 
     companion object {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
index 56d7d5e..991cd1c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -113,9 +113,6 @@
     override fun navBarWindowIsAlwaysVisible() {}
 
     /** {@inheritDoc} */
-    @Postsubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
-
-    /** {@inheritDoc} */
     @Postsubmit
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
index 6c833c4..90c18c4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
@@ -61,9 +61,9 @@
             }
         }
 
-    @Postsubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
+    @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
 
-    @Postsubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart()
+    @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart()
 
     /** {@inheritDoc} */
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
index d582931..efca6ab 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.view.WindowInsets
@@ -112,9 +111,9 @@
             teardown { testApp.exit(wmHelper) }
         }
 
-    @FlakyTest @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart()
+    @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart()
 
-    @Postsubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart()
+    @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart()
 
     @Presubmit
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index db4baa0..2b16ef0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -87,9 +87,6 @@
         }
 
     /** {@inheritDoc} */
-    @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
-
-    /** {@inheritDoc} */
     @FlakyTest
     @Test
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index 7a7990f..93bf099 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -79,9 +79,6 @@
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
-
-    /** {@inheritDoc} */
     @Presubmit
     @Test
     override fun appLayerBecomesVisible() = super.appLayerBecomesVisible_warmStart()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
new file mode 100644
index 0000000..41316d8
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.wm.flicker.launch
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import android.view.KeyEvent
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
+import com.android.server.wm.flicker.helpers.CameraAppHelper
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching camera from launcher by double pressing power button
+ *
+ * To run this test: `atest FlickerTests:OpenCameraOnDoubleClickPowerButton`
+ *
+ * Actions:
+ * ```
+ *     Make sure no apps are running on the device
+ *     Launch an app [testApp] and wait animation to complete
+ * ```
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited [OpenAppTransition]
+ *     2. Part of the test setup occurs automatically via
+ *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenCameraOnDoubleClickPowerButton(flicker: FlickerTest) :
+    OpenAppFromLauncherTransition(flicker) {
+    private val cameraApp = CameraAppHelper(instrumentation)
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                RemoveAllTasksButHomeRule.removeAllTasksButHome()
+                this.setRotation(flicker.scenario.startRotation)
+            }
+            transitions {
+                device.pressKeyCode(KeyEvent.KEYCODE_POWER)
+                device.pressKeyCode(KeyEvent.KEYCODE_POWER)
+                wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(cameraApp).waitForAndVerify()
+            }
+            teardown { RemoveAllTasksButHomeRule.removeAllTasksButHome() }
+        }
+
+    @Postsubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
+
+    @Postsubmit @Test override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
+
+    @Postsubmit @Test override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
+
+    @Postsubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+
+    @Postsubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+
+    @Postsubmit @Test override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
+
+    @Postsubmit
+    @Test
+    override fun appWindowReplacesLauncherAsTopWindow() =
+        super.appWindowReplacesLauncherAsTopWindow()
+
+    @Postsubmit @Test override fun focusChanges() = super.focusChanges()
+
+    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+    @Postsubmit
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+    @Postsubmit
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+    @Postsubmit
+    @Test
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+    @Postsubmit
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    @Postsubmit
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+    @Postsubmit
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+    @Postsubmit
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+    @Postsubmit
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    @Postsubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 31babb8..959ab3d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -20,7 +20,6 @@
 import android.app.WallpaperManager
 import android.content.res.Resources
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
@@ -92,7 +91,7 @@
      * Checks that the [wallpaper] layer is never visible when performing task transitions. A solid
      * color background should be shown instead.
      */
-    @FlakyTest(bugId = 253617416)
+    @Presubmit
     @Test
     fun wallpaperLayerIsNeverVisible() {
         flicker.assertLayers {
@@ -192,7 +191,7 @@
      * Checks that we start with the LaunchNewTask activity on top and then open up the
      * SimpleActivity and then go back to the LaunchNewTask activity.
      */
-    @Postsubmit
+    @Presubmit
     @Test
     fun newTaskOpensOnTopAndThenCloses() {
         flicker.assertWm {
@@ -208,11 +207,6 @@
         }
     }
 
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
     companion object {
         private fun getWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher {
             val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext)
diff --git a/tests/Input/src/com/android/test/input/MotionPredictorTest.kt b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
index 46aad9f..bc363b0 100644
--- a/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
+++ b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
@@ -31,15 +31,14 @@
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when`
-
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
 
 import java.time.Duration
 
@@ -49,7 +48,6 @@
         x: Float,
         y: Float,
         ): MotionEvent{
-    // One-time: send a DOWN event
     val pointerCount = 1
     val properties = arrayOfNulls<MotionEvent.PointerProperties>(pointerCount)
     val coords = arrayOfNulls<MotionEvent.PointerCoords>(pointerCount)
diff --git a/tests/MotionPrediction/Android.bp b/tests/MotionPrediction/Android.bp
index b9d01da..6cda8f0 100644
--- a/tests/MotionPrediction/Android.bp
+++ b/tests/MotionPrediction/Android.bp
@@ -26,6 +26,5 @@
 android_app {
     name: "MotionPrediction",
     srcs: ["**/*.kt"],
-    platform_apis: true,
-    certificate: "platform",
+    sdk_version: "current",
 }
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index f2234fb..21007ef 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -74,6 +74,7 @@
     ],
     test_suites: ["general-tests"],
     test_config: "MultiUserRollbackTest.xml",
+    data : [":RollbackTest"],
 }
 
 java_library_host {
diff --git a/tools/processors/immutability/Android.bp b/tools/processors/immutability/Android.bp
index 2ce785f..a7d69039 100644
--- a/tools/processors/immutability/Android.bp
+++ b/tools/processors/immutability/Android.bp
@@ -60,6 +60,9 @@
     java_resources: [":ImmutabilityAnnotationJavaSource"],
 
     test_suites: ["general-tests"],
+    test_options: {
+        unit_test: true,
+    },
     javacflags: [
         "--add-modules=jdk.compiler",
         "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",