Merge "Dedup MediaSessionService foreground service handling code" into main
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 9a1796f..c7797c7 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -64,3 +64,10 @@
     bug: "296829976"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "allow_resolver_sheet_for_private_space"
+    namespace: "profile_experiences"
+    description: "Add support for Private Space in resolver sheet"
+    bug: "307515485"
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 40e03db..60ad8e8 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -86,6 +86,8 @@
     private static native long nativeCreate(String opPackageName);
     private static native boolean nativeGetSensorAtIndex(long nativeInstance,
             Sensor sensor, int index);
+    private static native boolean nativeGetDefaultDeviceSensorAtIndex(long nativeInstance,
+            Sensor sensor, int index);
     private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
     private static native void nativeGetRuntimeSensors(
             long nativeInstance, int deviceId, List<Sensor> list);
@@ -162,11 +164,14 @@
         // initialize the sensor list
         for (int index = 0;; ++index) {
             Sensor sensor = new Sensor();
-            if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+            if (android.companion.virtual.flags.Flags.enableNativeVdm()) {
+                if (!nativeGetDefaultDeviceSensorAtIndex(mNativeInstance, sensor, index)) break;
+            } else {
+                if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+            }
             mFullSensorsList.add(sensor);
             mHandleToSensor.put(sensor.getHandle(), sensor);
         }
-
     }
 
     /** @hide */
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index ffd7212..64a62a9 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -751,6 +751,23 @@
          */
         boolean blockScreenOn(Runnable unblocker);
 
+        /**
+         * Get the brightness levels used to determine automatic brightness based on lux levels.
+         * @param mode The auto-brightness mode
+         *             (AutomaticBrightnessController.AutomaticBrightnessMode)
+         * @return The brightness levels for the specified mode. The values are between
+         * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+         */
+        float[] getAutoBrightnessLevels(int mode);
+
+        /**
+         * Get the lux levels used to determine automatic brightness.
+         * @param mode The auto-brightness mode
+         *             (AutomaticBrightnessController.AutomaticBrightnessMode)
+         * @return The lux levels for the specified mode
+         */
+        float[] getAutoBrightnessLuxLevels(int mode);
+
         /** Returns whether displayoffload supports the given display state. */
         static boolean isSupportedOffloadState(int displayState) {
             return Display.isSuspendedState(displayState);
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index bd087f9..41dee3a 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -21,10 +21,10 @@
 package android.nfc.cardemulation;
 
 import android.annotation.FlaggedApi;
-import android.compat.annotation.UnsupportedAppUsage;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -374,7 +374,7 @@
         // Set uid
         mUid = si.applicationInfo.uid;
 
-        mCategoryOtherServiceEnabled = false;    // support other category
+        mCategoryOtherServiceEnabled = true;    // support other category
 
     }
 
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index 17e0427..0d073cc 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -48,3 +48,10 @@
     description: "Enable NFC Polling Loop Notifications ST shim"
     bug: "294217286"
 }
+
+flag {
+    name: "enable_tag_detection_broadcasts"
+    namespace: "nfc"
+    description: "Enable sending broadcasts to Wallet role holder when a tag enters/leaves the field."
+    bug: "306203494"
+}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index b56bef3..30524a1 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -50,3 +50,11 @@
     description: "Collect sepolicy hash from sysfs"
     bug: "308471499"
 }
+
+flag {
+    name: "frp_enforcement"
+    namespace: "android_hw_security"
+    description: "This flag controls whether PDB enforces FRP"
+    bug: "290312729"
+    is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 7534d29..7dcbbea 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -249,6 +249,7 @@
 
     private UserHandle mCloneProfileUserHandle;
     private UserHandle mTabOwnerUserHandleForLaunch;
+    private UserHandle mPrivateProfileUserHandle;
 
     protected final LatencyTracker mLatencyTracker = getLatencyTracker();
 
@@ -441,6 +442,7 @@
         mPersonalProfileUserHandle = fetchPersonalProfileUserHandle();
         mWorkProfileUserHandle = fetchWorkProfileUserProfile();
         mCloneProfileUserHandle = fetchCloneProfileUserHandle();
+        mPrivateProfileUserHandle = fetchPrivateProfileUserHandle();
         mTabOwnerUserHandleForLaunch = fetchTabOwnerUserHandleForLaunch();
 
         // The last argument of createResolverListAdapter is whether to do special handling
@@ -648,7 +650,8 @@
                 initialIntents,
                 rList,
                 filterLastUsed,
-                /* userHandle */ getPersonalProfileUserHandle());
+                getPersonalProfileUserHandle());
+
         QuietModeManager quietModeManager = createQuietModeManager();
         return new ResolverMultiProfilePagerAdapter(
                 /* context */ this,
@@ -747,6 +750,9 @@
     }
 
     protected UserHandle getPersonalProfileUserHandle() {
+        if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()){
+            return mPrivateProfileUserHandle;
+        }
         return mPersonalProfileUserHandle;
     }
     protected @Nullable UserHandle getWorkProfileUserHandle() {
@@ -761,6 +767,10 @@
         return mTabOwnerUserHandleForLaunch;
     }
 
+    protected UserHandle getPrivateProfileUserHandle() {
+        return mPrivateProfileUserHandle;
+    }
+
     protected UserHandle fetchPersonalProfileUserHandle() {
         // ActivityManager.getCurrentUser() refers to the current Foreground user. When clone/work
         // profile is active, we always make the personal tab from the foreground user.
@@ -795,12 +805,28 @@
         return mCloneProfileUserHandle;
     }
 
+    protected @Nullable UserHandle fetchPrivateProfileUserHandle() {
+        mPrivateProfileUserHandle = null;
+        UserManager userManager = getSystemService(UserManager.class);
+        for (final UserInfo userInfo :
+                userManager.getProfiles(mPersonalProfileUserHandle.getIdentifier())) {
+            if (userInfo.isPrivateProfile()) {
+                mPrivateProfileUserHandle = userInfo.getUserHandle();
+                break;
+            }
+        }
+        return mPrivateProfileUserHandle;
+    }
+
     private UserHandle fetchTabOwnerUserHandleForLaunch() {
-        // If we are in work profile's process, return WorkProfile user as owner, otherwise we
-        // always return PersonalProfile user as owner
-        return UserHandle.of(UserHandle.myUserId()).equals(getWorkProfileUserHandle())
-                ? getWorkProfileUserHandle()
-                : getPersonalProfileUserHandle();
+        // If we are in work or private profile's process, return WorkProfile/PrivateProfile user
+        // as owner, otherwise we always return PersonalProfile user as owner
+        if (UserHandle.of(UserHandle.myUserId()).equals(getWorkProfileUserHandle())) {
+            return getWorkProfileUserHandle();
+        } else if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()) {
+            return getPrivateProfileUserHandle();
+        }
+        return getPersonalProfileUserHandle();
     }
 
     private boolean hasWorkProfile() {
@@ -816,7 +842,15 @@
                 && (UserHandle.myUserId() == getCloneProfileUserHandle().getIdentifier());
     }
 
+    protected final boolean isLaunchedAsPrivateProfile() {
+        return getPrivateProfileUserHandle() != null
+                && (UserHandle.myUserId() == getPrivateProfileUserHandle().getIdentifier());
+    }
+
     protected boolean shouldShowTabs() {
+        if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()) {
+            return false;
+        }
         return hasWorkProfile() && ENABLE_TABBED_VIEW;
     }
 
@@ -2619,6 +2653,11 @@
         return resolveInfo.userHandle;
     }
 
+    private boolean privateSpaceEnabled() {
+        return mIsIntentPicker && android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.allowResolverSheetForPrivateSpace();
+    }
+
     /**
      * An a11y delegate that expands resolver drawer when gesture navigation reaches a partially
      * invisible target in the list.
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index c89cfc4..5705b7e 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -37,6 +37,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 
+import java.lang.ref.WeakReference;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -63,6 +64,8 @@
 
     PackageMonitorCallback mPackageMonitorCallback;
 
+    private Executor mExecutor;
+
     @UnsupportedAppUsage
     public PackageMonitor() {
         final boolean isCore = UserHandle.isCore(android.os.Process.myUid());
@@ -106,8 +109,8 @@
         if (mPackageMonitorCallback == null) {
             PackageManager pm = mRegisteredContext.getPackageManager();
             if (pm != null) {
-                mPackageMonitorCallback = new PackageMonitorCallback(this,
-                        new HandlerExecutor(mRegisteredHandler));
+                mExecutor = new HandlerExecutor(mRegisteredHandler);
+                mPackageMonitorCallback = new PackageMonitorCallback(this);
                 int userId = user != null ? user.getIdentifier() : mRegisteredContext.getUserId();
                 pm.registerPackageMonitorCallback(mPackageMonitorCallback, userId);
             }
@@ -131,6 +134,7 @@
         }
         mPackageMonitorCallback = null;
         mRegisteredContext = null;
+        mExecutor = null;
     }
 
     public void onBeginPackageChanges() {
@@ -362,6 +366,13 @@
         doHandlePackageEvent(intent);
     }
 
+
+    private void postHandlePackageEvent(Intent intent) {
+        if (mExecutor != null) {
+            mExecutor.execute(() -> doHandlePackageEvent(intent));
+        }
+    }
+
     /**
      * Handle the package related event
      * @param intent the intent that contains package related event information
@@ -516,13 +527,10 @@
     }
 
     private static final class PackageMonitorCallback extends IRemoteCallback.Stub {
+        private final WeakReference<PackageMonitor> mMonitorWeakReference;
 
-        private final PackageMonitor mPackageMonitor;
-        private final Executor mExecutor;
-
-        PackageMonitorCallback(PackageMonitor monitor, Executor executor) {
-            mPackageMonitor = monitor;
-            mExecutor = executor;
+        PackageMonitorCallback(PackageMonitor monitor) {
+            mMonitorWeakReference = new WeakReference<>(monitor);
         }
 
         @Override
@@ -537,7 +545,10 @@
                 Log.w(TAG, "No intent is set for PackageMonitorCallback");
                 return;
             }
-            mExecutor.execute(() -> mPackageMonitor.doHandlePackageEvent(intent));
+            PackageMonitor monitor = mMonitorWeakReference.get();
+            if (monitor != null) {
+                monitor.postHandlePackageEvent(intent);
+            }
         }
     }
 }
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 9c883d1..56ea52d 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -225,6 +225,19 @@
     return translateNativeSensorToJavaSensor(env, sensor, *sensorList[index]) != NULL;
 }
 
+static jboolean nativeGetDefaultDeviceSensorAtIndex(JNIEnv *env, jclass clazz, jlong sensorManager,
+                                                    jobject sensor, jint index) {
+    SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+
+    Vector<Sensor> sensorList;
+    ssize_t count = mgr->getDefaultDeviceSensorList(sensorList);
+    if (ssize_t(index) >= count) {
+        return false;
+    }
+
+    return translateNativeSensorToJavaSensor(env, sensor, sensorList[index]) != NULL;
+}
+
 static void
 nativeGetDynamicSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jobject sensorList) {
 
@@ -539,6 +552,9 @@
         {"nativeGetSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
          (void *)nativeGetSensorAtIndex},
 
+        {"nativeGetDefaultDeviceSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
+         (void *)nativeGetDefaultDeviceSensorAtIndex},
+
         {"nativeGetDynamicSensors", "(JLjava/util/List;)V", (void *)nativeGetDynamicSensors},
 
         {"nativeGetRuntimeSensors", "(JILjava/util/List;)V", (void *)nativeGetRuntimeSensors},
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index b6813ff..b209c7c 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -30,6 +30,7 @@
 import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo;
 import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
 import static org.hamcrest.CoreMatchers.allOf;
@@ -46,6 +47,7 @@
 import android.net.Uri;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.text.TextUtils;
 import android.view.View;
 import android.widget.RelativeLayout;
@@ -88,7 +90,8 @@
     public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
             new ActivityTestRule<>(ResolverWrapperActivity.class, false,
                     false);
-
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     @Before
     public void cleanOverrideData() {
         sOverrides.reset();
@@ -1156,6 +1159,97 @@
                 sOverrides.cloneProfileUserHandle)));
     }
 
+    @Test
+    public void testTriggerFromPrivateProfile_withoutWorkProfile() throws RemoteException {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+        markPrivateProfileUserAvailable();
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> privateResolvedComponentInfos =
+                createResolvedComponentsForTest(3, sOverrides.privateProfileUserHandle);
+        setupResolverControllers(privateResolvedComponentInfos);
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+        assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+        onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
+        onView(withId(R.id.button_always)).check(matches(not(isEnabled())));
+        for (ResolvedComponentInfo resolvedInfo : privateResolvedComponentInfos) {
+            assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+                    sOverrides.privateProfileUserHandle);
+        }
+    }
+
+    @Test
+    public void testTriggerFromPrivateProfile_withWorkProfilePresent(){
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+        ResolverActivity.ENABLE_TABBED_VIEW = false;
+        markPrivateProfileUserAvailable();
+        markWorkProfileUserAvailable();
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> privateResolvedComponentInfos =
+                createResolvedComponentsForTest(3, sOverrides.privateProfileUserHandle);
+        setupResolverControllers(privateResolvedComponentInfos);
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+        onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+        assertEquals(activity.getMultiProfilePagerAdapterCount(), 1);
+        for (ResolvedComponentInfo resolvedInfo : privateResolvedComponentInfos) {
+            assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+                    sOverrides.privateProfileUserHandle);
+        }
+    }
+
+    @Test
+    public void testPrivateProfile_triggerFromPrimaryUser_withWorkProfilePresent(){
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+        markPrivateProfileUserAvailable();
+        markWorkProfileUserAvailable();
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        assertThat(activity.getAdapter().getCount(), is(2));
+        assertThat(activity.getWorkListAdapter().getCount(), is(4));
+        onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+        for (ResolvedComponentInfo resolvedInfo : personalResolvedComponentInfos) {
+            assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+                    activity.getPersonalProfileUserHandle());
+        }
+    }
+
+    @Test
+    public void testPrivateProfile_triggerFromWorkProfile(){
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+        markPrivateProfileUserAvailable();
+        markWorkProfileUserAvailable();
+        Intent sendIntent = createSendImageIntent();
+
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        assertThat(activity.getAdapter().getCount(), is(2));
+        assertThat(activity.getWorkListAdapter().getCount(), is(4));
+        onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+        for (ResolvedComponentInfo resolvedInfo : personalResolvedComponentInfos) {
+            assertTrue(resolvedInfo.getResolveInfoAt(0).userHandle.equals(
+                    activity.getPersonalProfileUserHandle()) || resolvedInfo.getResolveInfoAt(
+                    0).userHandle.equals(activity.getWorkProfileUserHandle()));
+        }
+    }
+
     private Intent createSendImageIntent() {
         Intent sendIntent = new Intent();
         sendIntent.setAction(Intent.ACTION_SEND);
@@ -1237,6 +1331,10 @@
         ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(11);
     }
 
+    private void markPrivateProfileUserAvailable() {
+        ResolverWrapperActivity.sOverrides.privateProfileUserHandle = UserHandle.of(12);
+    }
+
     private void setupResolverControllers(
             List<ResolvedComponentInfo> personalResolvedComponentInfos,
             List<ResolvedComponentInfo> workResolvedComponentInfos) {
@@ -1256,4 +1354,13 @@
                 eq(UserHandle.SYSTEM)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
     }
+
+    private void setupResolverControllers(
+            List<ResolvedComponentInfo> resolvedComponentInfos) {
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(resolvedComponentInfos));
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index e193de0..862cbd5 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -88,6 +88,10 @@
         return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1));
     }
 
+    int getMultiProfilePagerAdapterCount(){
+        return mMultiProfilePagerAdapter.getCount();
+    }
+
     @Override
     public boolean isVoiceInteraction() {
         if (sOverrides.isVoiceInteraction != null) {
@@ -144,6 +148,11 @@
     }
 
     @Override
+    protected UserHandle getPrivateProfileUserHandle() {
+        return sOverrides.privateProfileUserHandle;
+    }
+
+    @Override
     protected UserHandle getTabOwnerUserHandleForLaunch() {
         if (sOverrides.tabOwnerUserHandleForLaunch == null) {
             return super.getTabOwnerUserHandleForLaunch();
@@ -176,6 +185,7 @@
         public Boolean isVoiceInteraction;
         public UserHandle workProfileUserHandle;
         public UserHandle cloneProfileUserHandle;
+        public UserHandle privateProfileUserHandle;
         public UserHandle tabOwnerUserHandleForLaunch;
         public Integer myUserId;
         public boolean hasCrossProfileIntents;
@@ -191,6 +201,7 @@
             workResolverListController = mock(ResolverListController.class);
             workProfileUserHandle = null;
             cloneProfileUserHandle = null;
+            privateProfileUserHandle = null;
             tabOwnerUserHandleForLaunch = null;
             myUserId = null;
             hasCrossProfileIntents = true;
diff --git a/core/tests/overlaytests/Android.mk b/core/tests/overlaytests/Android.mk
deleted file mode 100644
index b798d87..0000000
--- a/core/tests/overlaytests/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-include $(call all-subdir-makefiles)
diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk
deleted file mode 100644
index d58d939..0000000
--- a/core/tests/overlaytests/host/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-# Include to build test-apps.
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/core/tests/overlaytests/host/test-apps/Android.mk b/core/tests/overlaytests/host/test-apps/Android.mk
deleted file mode 100644
index 5c7187e..0000000
--- a/core/tests/overlaytests/host/test-apps/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-include $(call all-subdir-makefiles)
-
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp
new file mode 100644
index 0000000..bb7d63e
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp
@@ -0,0 +1,57 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Error: Cannot get the name of the license module in the
+// ./Android.bp file.
+// If no such license module exists, please add one there first.
+// Then reset the default_applicable_licenses property below with the license module name.
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_NonPlatformSignatureOverlay",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    aaptflags: [
+        "--custom-package com.android.server.om.hosttest.signature_overlay_bad",
+    ],
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_PlatformSignatureStaticOverlay",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    certificate: "platform",
+    manifest: "static/AndroidManifest.xml",
+    aaptflags: [
+        "--custom-package com.android.server.om.hosttest.signature_overlay_static",
+    ],
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_PlatformSignatureOverlay",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    certificate: "platform",
+    aaptflags: [
+        "--custom-package",
+        "com.android.server.om.hosttest.signature_overlay_v1",
+        "--version-code",
+        "1",
+        "--version-name",
+        "v1",
+    ],
+}
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
deleted file mode 100644
index b453cde9..0000000
--- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
+++ /dev/null
@@ -1,56 +0,0 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-my_package_prefix := com.android.server.om.hosttest.signature_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_NonPlatformSignatureOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureStaticOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_MANIFEST_FILE := static/AndroidManifest.xml
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_static
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-include $(BUILD_PACKAGE)
-
-my_package_prefix :=
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp
new file mode 100644
index 0000000..ee0c0e5
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp
@@ -0,0 +1,97 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Error: Cannot get the name of the license module in the
+// ./Android.bp file.
+// If no such license module exists, please add one there first.
+// Then reset the default_applicable_licenses property below with the license module name.
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_UpdateOverlay",
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    static_libs: ["androidx.test.rules"],
+    aaptflags: ["--no-resource-removal"],
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_FrameworkOverlayV1",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    certificate: "platform",
+    aaptflags: [
+        "--custom-package",
+        "com.android.server.om.hosttest.framework_overlay_v1",
+        "--version-code",
+        "1",
+        "--version-name",
+        "v1",
+    ],
+    resource_dirs: ["framework/v1/res"],
+    manifest: "framework/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_FrameworkOverlayV2",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    certificate: "platform",
+    aaptflags: [
+        "--custom-package",
+        "com.android.server.om.hosttest.framework_overlay_v2",
+        "--version-code",
+        "2",
+        "--version-name",
+        "v2",
+    ],
+    resource_dirs: ["framework/v2/res"],
+    manifest: "framework/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_AppOverlayV1",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    aaptflags: [
+        "--custom-package",
+        "com.android.server.om.hosttest.app_overlay_v1",
+        "--version-code",
+        "1",
+        "--version-name",
+        "v1",
+    ],
+    resource_dirs: ["app/v1/res"],
+    manifest: "app/v1/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_AppOverlayV2",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    aaptflags: [
+        "--custom-package",
+        "com.android.server.om.hosttest.app_overlay_v2",
+        "--version-code",
+        "2",
+        "--version-name",
+        "v2",
+    ],
+    resource_dirs: ["app/v2/res"],
+    manifest: "app/v2/AndroidManifest.xml",
+}
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
deleted file mode 100644
index 77fc887..0000000
--- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_FLAGS := --no-resource-removal
-include $(BUILD_PACKAGE)
-
-my_package_prefix := com.android.server.om.hosttest.framework_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v1/res
-LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v2/res
-LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-my_package_prefix := com.android.server.om.hosttest.app_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res
-LOCAL_MANIFEST_FILE := app/v1/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res
-LOCAL_MANIFEST_FILE := app/v2/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-my_package_prefix :=
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index c4530f6..13d38d2 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -347,7 +347,9 @@
     <!-- Allow IMS service entitlement app to schedule jobs to run when app in background. -->
     <allow-in-power-save-except-idle package="com.android.imsserviceentitlement" />
 
-    <!-- Allow device lock controller app to schedule jobs and alarms when app in background,
-        otherwise, it may not be able to enforce provision for managed devices. -->
+    <!-- Allow device lock controller app to schedule jobs and alarms, and have network access
+         when app in background; otherwise, it may not be able to enforce provision for managed
+         devices. -->
     <allow-in-power-save package="com.android.devicelockcontroller" />
+    <allow-in-data-usage-save package="com.android.devicelockcontroller" />
 </permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index c6f920f..b9efe65 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -575,6 +575,7 @@
         <permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
         <permission name="android.permission.BIND_WALLPAPER"/>
         <permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/>
+        <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.dynsystem">
diff --git a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
index db8ebb4..1ac5db6 100644
--- a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
@@ -18,54 +18,63 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    style="@style/ScrollViewStyle">
+    style="@style/ScrollViewStyle"
+    android:importantForAccessibility="no">
 
     <LinearLayout
-        android:id="@+id/data_transfer_confirmation"
-        style="@style/ContainerLayout">
-
-        <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
-
-        <ImageView
-            android:id="@+id/header_icon"
-            android:layout_width="match_parent"
-            android:layout_height="32dp"
-            android:gravity="center"
-            android:layout_marginTop="18dp"
-            android:src="@drawable/ic_warning"
-            android:contentDescription="@null" />
-
-        <LinearLayout style="@style/Description">
-
-            <TextView
-                android:id="@+id/title"
-                style="@style/DescriptionTitle" />
-
-            <TextView
-                android:id="@+id/summary"
-                style="@style/DescriptionSummary" />
-
-        </LinearLayout>
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:baselineAligned="false"
+        android:importantForAccessibility="no">
 
         <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:orientation="vertical"
-            android:layout_marginTop="12dp"
-            android:layout_marginBottom="18dp">
+            android:id="@+id/data_transfer_confirmation"
+            style="@style/ContainerLayout">
 
-            <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+            <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
 
-            <Button
-                android:id="@+id/btn_positive"
-                style="@style/PositiveButton"
-                android:text="@string/consent_yes" />
+            <ImageView
+                android:id="@+id/header_icon"
+                android:layout_width="match_parent"
+                android:layout_height="32dp"
+                android:gravity="center"
+                android:layout_marginTop="18dp"
+                android:src="@drawable/ic_warning"
+                android:contentDescription="@null" />
 
-            <Button
-                android:id="@+id/btn_negative"
-                style="@style/NegativeButton"
-                android:text="@string/consent_no" />
+            <LinearLayout style="@style/Description">
+
+                <TextView
+                    android:id="@+id/title"
+                    style="@style/DescriptionTitle" />
+
+                <TextView
+                    android:id="@+id/summary"
+                    style="@style/DescriptionSummary" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+                android:orientation="vertical"
+                android:layout_marginTop="12dp"
+                android:layout_marginBottom="18dp">
+
+                <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+
+                <Button
+                    android:id="@+id/btn_positive"
+                    style="@style/PositiveButton"
+                    android:text="@string/consent_yes" />
+
+                <Button
+                    android:id="@+id/btn_negative"
+                    style="@style/NegativeButton"
+                    android:text="@string/consent_no" />
+
+            </LinearLayout>
 
         </LinearLayout>
 
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 933be11..6a4bb21 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -134,8 +134,7 @@
     @UsesReflection({
             // As the runtime class name is used to generate the returned name, and the returned
             // name may be used used with reflection, generate the necessary keep rules.
-            @KeepTarget(classConstant = LocalTransport.class),
-            @KeepTarget(extendsClassConstant = LocalTransport.class)
+            @KeepTarget(instanceOfClassConstant = LocalTransport.class)
     })
     @Override
     public String name() {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
new file mode 100644
index 0000000..472484a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package com.android.systemui.keyguard.ui.composable
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.input.pointer.pointerInput
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+
+/** Container for lockscreen content that handles long-press to bring up the settings menu. */
+@Composable
+fun LockscreenLongPress(
+    viewModel: KeyguardLongPressViewModel,
+    modifier: Modifier = Modifier,
+    content: @Composable BoxScope.(onSettingsMenuPlaces: (coordinates: Rect?) -> Unit) -> Unit,
+) {
+    val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
+    val (settingsMenuBounds, setSettingsMenuBounds) = remember { mutableStateOf<Rect?>(null) }
+    val interactionSource = remember { MutableInteractionSource() }
+
+    Box(
+        modifier =
+            modifier
+                .combinedClickable(
+                    enabled = isEnabled,
+                    onLongClick = viewModel::onLongPress,
+                    onClick = {},
+                    interactionSource = interactionSource,
+                    // Passing null for the indication removes the ripple effect.
+                    indication = null,
+                )
+                .pointerInput(settingsMenuBounds) {
+                    awaitEachGesture {
+                        val pointerInputChange = awaitFirstDown()
+                        if (settingsMenuBounds?.contains(pointerInputChange.position) == false) {
+                            viewModel.onTouchedOutside()
+                        }
+                    }
+                },
+    ) {
+        content(setSettingsMenuBounds)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 93f31ec..67a6820 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -14,36 +14,15 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalFoundationApi::class)
-
 package com.android.systemui.keyguard.ui.composable
 
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.toComposeRect
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.core.view.isVisible
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.qualifiers.KeyguardRootView
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
-import com.android.systemui.notifications.ui.composable.NotificationStack
-import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.Edge
 import com.android.systemui.scene.shared.model.SceneKey
@@ -68,8 +47,8 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     private val viewModel: LockscreenSceneViewModel,
-    @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
     private val lockscreenContent: Lazy<LockscreenContent>,
+    private val viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
 ) : ComposableScene {
     override val key = SceneKey.Lockscreen
 
@@ -93,9 +72,8 @@
         modifier: Modifier,
     ) {
         LockscreenScene(
-            viewProvider = viewProvider,
-            viewModel = viewModel,
             lockscreenContent = lockscreenContent,
+            viewBasedLockscreenContent = viewBasedLockscreenContent,
             modifier = modifier,
         )
     }
@@ -116,98 +94,21 @@
 
 @Composable
 private fun SceneScope.LockscreenScene(
-    viewProvider: () -> View,
-    viewModel: LockscreenSceneViewModel,
     lockscreenContent: Lazy<LockscreenContent>,
+    viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
     modifier: Modifier = Modifier,
 ) {
-    fun findSettingsMenu(): View {
-        return viewProvider().requireViewById(R.id.keyguard_settings_button)
-    }
-
-    Box(
-        modifier = modifier,
-    ) {
-        LongPressSurface(
-            viewModel = viewModel.longPress,
-            isSettingsMenuVisible = { findSettingsMenu().isVisible },
-            settingsMenuBounds = {
-                val bounds = android.graphics.Rect()
-                findSettingsMenu().getHitRect(bounds)
-                bounds.toComposeRect()
-            },
-            modifier = Modifier.fillMaxSize(),
-        )
-
-        if (UseLockscreenContent) {
-            lockscreenContent
-                .get()
-                .Content(
-                    modifier = Modifier.fillMaxSize(),
-                )
-        } else {
-            AndroidView(
-                factory = { _ ->
-                    val keyguardRootView = viewProvider()
-                    // Remove the KeyguardRootView from any parent it might already have in legacy
-                    // code just in case (a view can't have two parents).
-                    (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
-                    keyguardRootView
-                },
-                modifier = Modifier.fillMaxSize(),
+    if (UseLockscreenContent) {
+        lockscreenContent
+            .get()
+            .Content(
+                modifier = modifier.fillMaxSize(),
+            )
+    } else {
+        with(viewBasedLockscreenContent.get()) {
+            Content(
+                modifier = modifier.fillMaxSize(),
             )
         }
-
-        val notificationStackPosition by viewModel.keyguardRoot.notificationBounds.collectAsState()
-
-        Layout(
-            modifier = Modifier.fillMaxSize(),
-            content = {
-                NotificationStack(
-                    viewModel = viewModel.notifications,
-                    isScrimVisible = false,
-                )
-            }
-        ) { measurables, constraints ->
-            check(measurables.size == 1)
-            val height = notificationStackPosition.height.toInt()
-            val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
-            val placeable = measurables[0].measure(childConstraints)
-            layout(constraints.maxWidth, constraints.maxHeight) {
-                val start = (constraints.maxWidth - placeable.measuredWidth) / 2
-                placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt())
-            }
-        }
     }
 }
-
-@Composable
-private fun LongPressSurface(
-    viewModel: KeyguardLongPressViewModel,
-    isSettingsMenuVisible: () -> Boolean,
-    settingsMenuBounds: () -> Rect,
-    modifier: Modifier = Modifier,
-) {
-    val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
-
-    Box(
-        modifier =
-            modifier
-                .combinedClickable(
-                    enabled = isEnabled,
-                    onLongClick = viewModel::onLongPress,
-                    onClick = {},
-                )
-                .pointerInput(Unit) {
-                    awaitEachGesture {
-                        val pointerInputChange = awaitFirstDown()
-                        if (
-                            isSettingsMenuVisible() &&
-                                !settingsMenuBounds().contains(pointerInputChange.position)
-                        ) {
-                            viewModel.onTouchedOutside()
-                        }
-                    }
-                },
-    )
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
new file mode 100644
index 0000000..976161b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.keyguard.ui.composable
+
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toComposeRect
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.qualifiers.KeyguardRootView
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/**
+ * Renders the content of the lockscreen.
+ *
+ * This is different from [LockscreenContent] (which is pure compose) and uses a view-based
+ * implementation of the lockscreen scene content that relies on [KeyguardRootView].
+ *
+ * TODO(b/316211368): remove this once [LockscreenContent] is feature complete.
+ */
+class ViewBasedLockscreenContent
+@Inject
+constructor(
+    private val viewModel: LockscreenSceneViewModel,
+    @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
+) {
+    @Composable
+    fun SceneScope.Content(
+        modifier: Modifier = Modifier,
+    ) {
+        fun findSettingsMenu(): View {
+            return viewProvider().requireViewById(R.id.keyguard_settings_button)
+        }
+
+        LockscreenLongPress(
+            viewModel = viewModel.longPress,
+            modifier = modifier,
+        ) { onSettingsMenuPlaced ->
+            AndroidView(
+                factory = { _ ->
+                    val keyguardRootView = viewProvider()
+                    // Remove the KeyguardRootView from any parent it might already have in legacy
+                    // code just in case (a view can't have two parents).
+                    (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
+                    keyguardRootView
+                },
+                modifier = Modifier.fillMaxSize(),
+            )
+
+            val notificationStackPosition by
+                viewModel.keyguardRoot.notificationBounds.collectAsState()
+
+            Layout(
+                modifier =
+                    Modifier.fillMaxSize().onPlaced {
+                        val settingsMenuView = findSettingsMenu()
+                        onSettingsMenuPlaced(
+                            if (settingsMenuView.isVisible) {
+                                val bounds = Rect()
+                                settingsMenuView.getHitRect(bounds)
+                                bounds.toComposeRect()
+                            } else {
+                                null
+                            }
+                        )
+                    },
+                content = {
+                    NotificationStack(
+                        viewModel = viewModel.notifications,
+                        isScrimVisible = false,
+                    )
+                }
+            ) { measurables, constraints ->
+                check(measurables.size == 1)
+                val height = notificationStackPosition.height.toInt()
+                val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
+                val placeable = measurables[0].measure(childConstraints)
+                layout(constraints.maxWidth, constraints.maxHeight) {
+                    val start = (constraints.maxWidth - placeable.measuredWidth) / 2
+                    placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt())
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
index 7eddaaf..86124c6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
@@ -24,24 +24,35 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
 import javax.inject.Inject
 
 /** Renders the lockscreen scene when showing the communal glanceable hub. */
-class CommunalBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+class CommunalBlueprint
+@Inject
+constructor(
+    private val viewModel: LockscreenContentViewModel,
+) : LockscreenSceneBlueprint {
 
     override val id: String = "communal"
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        Box(modifier.background(Color.Black)) {
-            Text(
-                text = "TODO(b/316211368): communal blueprint",
-                color = Color.White,
-                modifier = Modifier.align(Alignment.Center),
-            )
+        LockscreenLongPress(
+            viewModel = viewModel.longPress,
+            modifier = modifier,
+        ) { _ ->
+            Box(modifier.background(Color.Black)) {
+                Text(
+                    text = "TODO(b/316211368): communal blueprint",
+                    color = Color.White,
+                    modifier = Modifier.align(Alignment.Center),
+                )
+            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 7314453..d9d98cb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -17,17 +17,20 @@
 package com.android.systemui.keyguard.ui.composable.blueprint
 
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
 import com.android.systemui.keyguard.ui.composable.section.ClockSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
 import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
 import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
@@ -51,6 +54,7 @@
     private val lockSection: LockSection,
     private val ambientIndicationSection: AmbientIndicationSection,
     private val bottomAreaSection: BottomAreaSection,
+    private val settingsMenuSection: SettingsMenuSection,
 ) : LockscreenSceneBlueprint {
 
     override val id: String = "default"
@@ -59,102 +63,116 @@
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
 
-        Layout(
-            content = {
-                // Constrained to above the lock icon.
-                Column(
-                    modifier = Modifier.fillMaxWidth(),
-                ) {
-                    with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                    with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
-                    with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
-                    with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
-                    with(notificationSection) {
-                        Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
-                    }
-                    if (!isUdfpsVisible) {
-                        with(ambientIndicationSection) {
-                            AmbientIndication(modifier = Modifier.fillMaxWidth())
-                        }
-                    }
-                }
-
-                with(lockSection) { LockIcon() }
-
-                // Aligned to bottom and constrained to below the lock icon.
-                Column(modifier = Modifier.fillMaxWidth()) {
-                    if (isUdfpsVisible) {
-                        with(ambientIndicationSection) {
-                            AmbientIndication(modifier = Modifier.fillMaxWidth())
-                        }
-                    }
-
-                    with(bottomAreaSection) { IndicationArea(modifier = Modifier.fillMaxWidth()) }
-                }
-
-                // Aligned to bottom and NOT constrained by the lock icon.
-                with(bottomAreaSection) {
-                    Shortcut(isStart = true, applyPadding = true)
-                    Shortcut(isStart = false, applyPadding = true)
-                }
-            },
+        LockscreenLongPress(
+            viewModel = viewModel.longPress,
             modifier = modifier,
-        ) { measurables, constraints ->
-            check(measurables.size == 5)
-            val (
-                aboveLockIconMeasurable,
-                lockIconMeasurable,
-                belowLockIconMeasurable,
-                startShortcutMeasurable,
-                endShortcutMeasurable,
-            ) = measurables
+        ) { onSettingsMenuPlaced ->
+            Layout(
+                content = {
+                    // Constrained to above the lock icon.
+                    Column(
+                        modifier = Modifier.fillMaxWidth(),
+                    ) {
+                        with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+                        with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+                        with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+                        with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+                        with(notificationSection) {
+                            Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+                        }
+                        if (!isUdfpsVisible) {
+                            with(ambientIndicationSection) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
+                            }
+                        }
+                    }
 
-            val noMinConstraints =
-                constraints.copy(
-                    minWidth = 0,
-                    minHeight = 0,
-                )
-            val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
-            val lockIconBounds =
-                IntRect(
-                    left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
-                    top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
-                    right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
-                    bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
-                )
+                    with(lockSection) { LockIcon() }
 
-            val aboveLockIconPlaceable =
-                aboveLockIconMeasurable.measure(
-                    noMinConstraints.copy(maxHeight = lockIconBounds.top)
-                )
-            val belowLockIconPlaceable =
-                belowLockIconMeasurable.measure(
-                    noMinConstraints.copy(maxHeight = constraints.maxHeight - lockIconBounds.bottom)
-                )
-            val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
-            val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+                    // Aligned to bottom and constrained to below the lock icon.
+                    Column(modifier = Modifier.fillMaxWidth()) {
+                        if (isUdfpsVisible) {
+                            with(ambientIndicationSection) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
+                            }
+                        }
 
-            layout(constraints.maxWidth, constraints.maxHeight) {
-                aboveLockIconPlaceable.place(
-                    x = 0,
-                    y = 0,
-                )
-                lockIconPlaceable.place(
-                    x = lockIconBounds.left,
-                    y = lockIconBounds.top,
-                )
-                belowLockIconPlaceable.place(
-                    x = 0,
-                    y = constraints.maxHeight - belowLockIconPlaceable.height,
-                )
-                startShortcutPleaceable.place(
-                    x = 0,
-                    y = constraints.maxHeight - startShortcutPleaceable.height,
-                )
-                endShortcutPleaceable.place(
-                    x = constraints.maxWidth - endShortcutPleaceable.width,
-                    y = constraints.maxHeight - endShortcutPleaceable.height,
-                )
+                        with(bottomAreaSection) {
+                            IndicationArea(modifier = Modifier.fillMaxWidth())
+                        }
+                    }
+
+                    // Aligned to bottom and NOT constrained by the lock icon.
+                    with(bottomAreaSection) {
+                        Shortcut(isStart = true, applyPadding = true)
+                        Shortcut(isStart = false, applyPadding = true)
+                    }
+                    with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+                },
+                modifier = Modifier.fillMaxSize(),
+            ) { measurables, constraints ->
+                check(measurables.size == 6)
+                val aboveLockIconMeasurable = measurables[0]
+                val lockIconMeasurable = measurables[1]
+                val belowLockIconMeasurable = measurables[2]
+                val startShortcutMeasurable = measurables[3]
+                val endShortcutMeasurable = measurables[4]
+                val settingsMenuMeasurable = measurables[5]
+
+                val noMinConstraints =
+                    constraints.copy(
+                        minWidth = 0,
+                        minHeight = 0,
+                    )
+                val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+                val lockIconBounds =
+                    IntRect(
+                        left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+                        top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+                        right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+                        bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+                    )
+
+                val aboveLockIconPlaceable =
+                    aboveLockIconMeasurable.measure(
+                        noMinConstraints.copy(maxHeight = lockIconBounds.top)
+                    )
+                val belowLockIconPlaceable =
+                    belowLockIconMeasurable.measure(
+                        noMinConstraints.copy(
+                            maxHeight = constraints.maxHeight - lockIconBounds.bottom
+                        )
+                    )
+                val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
+                val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+                val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+                layout(constraints.maxWidth, constraints.maxHeight) {
+                    aboveLockIconPlaceable.place(
+                        x = 0,
+                        y = 0,
+                    )
+                    lockIconPlaceable.place(
+                        x = lockIconBounds.left,
+                        y = lockIconBounds.top,
+                    )
+                    belowLockIconPlaceable.place(
+                        x = 0,
+                        y = constraints.maxHeight - belowLockIconPlaceable.height,
+                    )
+                    startShortcutPleaceable.place(
+                        x = 0,
+                        y = constraints.maxHeight - startShortcutPleaceable.height,
+                    )
+                    endShortcutPleaceable.place(
+                        x = constraints.maxWidth - endShortcutPleaceable.width,
+                        y = constraints.maxHeight - endShortcutPleaceable.height,
+                    )
+                    settingsMenuPlaceable.place(
+                        x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+                        y = constraints.maxHeight - settingsMenuPlaceable.height,
+                    )
+                }
             }
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 4c119c7..4704f5c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -17,17 +17,20 @@
 package com.android.systemui.keyguard.ui.composable.blueprint
 
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
 import com.android.systemui.keyguard.ui.composable.section.ClockSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
 import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
 import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
@@ -51,6 +54,7 @@
     private val lockSection: LockSection,
     private val ambientIndicationSection: AmbientIndicationSection,
     private val bottomAreaSection: BottomAreaSection,
+    private val settingsMenuSection: SettingsMenuSection,
 ) : LockscreenSceneBlueprint {
 
     override val id: String = "shortcuts-besides-udfps"
@@ -59,105 +63,123 @@
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
 
-        Layout(
-            content = {
-                // Constrained to above the lock icon.
-                Column(
-                    modifier = Modifier.fillMaxWidth(),
-                ) {
-                    with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                    with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
-                    with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
-                    with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
-                    with(notificationSection) {
-                        Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
-                    }
-                    if (!isUdfpsVisible) {
-                        with(ambientIndicationSection) {
-                            AmbientIndication(modifier = Modifier.fillMaxWidth())
-                        }
-                    }
-                }
-
-                // Constrained to the left of the lock icon (in left-to-right layouts).
-                with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
-
-                with(lockSection) { LockIcon() }
-
-                // Constrained to the right of the lock icon (in left-to-right layouts).
-                with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
-
-                // Aligned to bottom and constrained to below the lock icon.
-                Column(modifier = Modifier.fillMaxWidth()) {
-                    if (isUdfpsVisible) {
-                        with(ambientIndicationSection) {
-                            AmbientIndication(modifier = Modifier.fillMaxWidth())
-                        }
-                    }
-
-                    with(bottomAreaSection) { IndicationArea(modifier = Modifier.fillMaxWidth()) }
-                }
-            },
+        LockscreenLongPress(
+            viewModel = viewModel.longPress,
             modifier = modifier,
-        ) { measurables, constraints ->
-            check(measurables.size == 5)
-            val (
-                aboveLockIconMeasurable,
-                startSideShortcutMeasurable,
-                lockIconMeasurable,
-                endSideShortcutMeasurable,
-                belowLockIconMeasurable,
-            ) = measurables
+        ) { onSettingsMenuPlaced ->
+            Layout(
+                content = {
+                    // Constrained to above the lock icon.
+                    Column(
+                        modifier = Modifier.fillMaxWidth(),
+                    ) {
+                        with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+                        with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+                        with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+                        with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+                        with(notificationSection) {
+                            Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+                        }
+                        if (!isUdfpsVisible) {
+                            with(ambientIndicationSection) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
+                            }
+                        }
+                    }
 
-            val noMinConstraints =
-                constraints.copy(
-                    minWidth = 0,
-                    minHeight = 0,
-                )
+                    // Constrained to the left of the lock icon (in left-to-right layouts).
+                    with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
 
-            val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
-            val lockIconBounds =
-                IntRect(
-                    left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
-                    top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
-                    right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
-                    bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
-                )
+                    with(lockSection) { LockIcon() }
 
-            val aboveLockIconPlaceable =
-                aboveLockIconMeasurable.measure(
-                    noMinConstraints.copy(maxHeight = lockIconBounds.top)
-                )
-            val startSideShortcutPlaceable = startSideShortcutMeasurable.measure(noMinConstraints)
-            val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
-            val belowLockIconPlaceable =
-                belowLockIconMeasurable.measure(
-                    noMinConstraints.copy(maxHeight = constraints.maxHeight - lockIconBounds.bottom)
-                )
+                    // Constrained to the right of the lock icon (in left-to-right layouts).
+                    with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
 
-            layout(constraints.maxWidth, constraints.maxHeight) {
-                aboveLockIconPlaceable.place(
-                    x = 0,
-                    y = 0,
-                )
-                startSideShortcutPlaceable.placeRelative(
-                    x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
-                    y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
-                )
-                lockIconPlaceable.place(
-                    x = lockIconBounds.left,
-                    y = lockIconBounds.top,
-                )
-                endSideShortcutPlaceable.placeRelative(
-                    x =
-                        lockIconBounds.right + (constraints.maxWidth - lockIconBounds.right) / 2 -
-                            endSideShortcutPlaceable.width / 2,
-                    y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
-                )
-                belowLockIconPlaceable.place(
-                    x = 0,
-                    y = constraints.maxHeight - belowLockIconPlaceable.height,
-                )
+                    // Aligned to bottom and constrained to below the lock icon.
+                    Column(modifier = Modifier.fillMaxWidth()) {
+                        if (isUdfpsVisible) {
+                            with(ambientIndicationSection) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
+                            }
+                        }
+
+                        with(bottomAreaSection) {
+                            IndicationArea(modifier = Modifier.fillMaxWidth())
+                        }
+                    }
+
+                    // Aligned to bottom and NOT constrained by the lock icon.
+                    with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+                },
+                modifier = Modifier.fillMaxSize(),
+            ) { measurables, constraints ->
+                check(measurables.size == 6)
+                val aboveLockIconMeasurable = measurables[0]
+                val startSideShortcutMeasurable = measurables[1]
+                val lockIconMeasurable = measurables[2]
+                val endSideShortcutMeasurable = measurables[3]
+                val belowLockIconMeasurable = measurables[4]
+                val settingsMenuMeasurable = measurables[5]
+
+                val noMinConstraints =
+                    constraints.copy(
+                        minWidth = 0,
+                        minHeight = 0,
+                    )
+
+                val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+                val lockIconBounds =
+                    IntRect(
+                        left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+                        top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+                        right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+                        bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+                    )
+
+                val aboveLockIconPlaceable =
+                    aboveLockIconMeasurable.measure(
+                        noMinConstraints.copy(maxHeight = lockIconBounds.top)
+                    )
+                val startSideShortcutPlaceable =
+                    startSideShortcutMeasurable.measure(noMinConstraints)
+                val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
+                val belowLockIconPlaceable =
+                    belowLockIconMeasurable.measure(
+                        noMinConstraints.copy(
+                            maxHeight = constraints.maxHeight - lockIconBounds.bottom
+                        )
+                    )
+                val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+                layout(constraints.maxWidth, constraints.maxHeight) {
+                    aboveLockIconPlaceable.place(
+                        x = 0,
+                        y = 0,
+                    )
+                    startSideShortcutPlaceable.placeRelative(
+                        x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
+                        y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
+                    )
+                    lockIconPlaceable.place(
+                        x = lockIconBounds.left,
+                        y = lockIconBounds.top,
+                    )
+                    endSideShortcutPlaceable.placeRelative(
+                        x =
+                            lockIconBounds.right +
+                                (constraints.maxWidth - lockIconBounds.right) / 2 -
+                                endSideShortcutPlaceable.width / 2,
+                        y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
+                    )
+                    belowLockIconPlaceable.place(
+                        x = 0,
+                        y = constraints.maxHeight - belowLockIconPlaceable.height,
+                    )
+                    settingsMenuPlaceable.place(
+                        x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+                        y = constraints.maxHeight - settingsMenuPlaceable.height,
+                    )
+                }
             }
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index 7545d5f..fdf1166 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -24,6 +24,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
@@ -33,18 +35,27 @@
  * Renders the lockscreen scene when showing with a split shade (e.g. unfolded foldable and/or
  * tablet form factor).
  */
-class SplitShadeBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+class SplitShadeBlueprint
+@Inject
+constructor(
+    private val viewModel: LockscreenContentViewModel,
+) : LockscreenSceneBlueprint {
 
     override val id: String = "split-shade"
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        Box(modifier.background(Color.Black)) {
-            Text(
-                text = "TODO(b/316211368): split shade blueprint",
-                color = Color.White,
-                modifier = Modifier.align(Alignment.Center),
-            )
+        LockscreenLongPress(
+            viewModel = viewModel.longPress,
+            modifier = modifier,
+        ) { _ ->
+            Box(modifier.background(Color.Black)) {
+                Text(
+                    text = "TODO(b/316211368): split shade blueprint",
+                    color = Color.White,
+                    modifier = Modifier.align(Alignment.Center),
+                )
+            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index ebc346d..c547e2b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -16,76 +16,25 @@
 
 package com.android.systemui.keyguard.ui.composable.section
 
-import android.view.View
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.onPlaced
-import androidx.compose.ui.layout.positionInWindow
-import androidx.compose.ui.viewinterop.AndroidView
-import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
-import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
-import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
-import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 
 class NotificationSection
 @Inject
 constructor(
-    private val rootViewModel: KeyguardRootViewModel,
-    private val sharedNotificationContainer: SharedNotificationContainer,
-    private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
-    private val controller: NotificationStackScrollLayoutController,
-    private val notificationStackSizeCalculator: NotificationStackSizeCalculator,
-    @Main private val dispatcher: CoroutineDispatcher,
-    private val sceneContainerFlags: SceneContainerFlags,
+    private val viewModel: NotificationsPlaceholderViewModel,
 ) {
     @Composable
     fun SceneScope.Notifications(modifier: Modifier = Modifier) {
-        MovableElement(
-            key = NotificationsElementKey,
+        NotificationStack(
+            viewModel = viewModel,
+            isScrimVisible = false,
             modifier = modifier,
-        ) {
-            val (bounds, onBoundsChanged) = remember { mutableStateOf<Pair<Float, Float>?>(null) }
-            LaunchedEffect(bounds) {
-                bounds?.let {
-                    rootViewModel.onNotificationContainerBoundsChanged(bounds.first, bounds.second)
-                }
-            }
-
-            AndroidView(
-                factory = { context ->
-                    View(context, null).apply {
-                        id = R.id.nssl_placeholder
-                        SharedNotificationContainerBinder.bind(
-                            view = sharedNotificationContainer,
-                            viewModel = sharedNotificationContainerViewModel,
-                            sceneContainerFlags = sceneContainerFlags,
-                            controller = controller,
-                            notificationStackSizeCalculator = notificationStackSizeCalculator,
-                            mainImmediateDispatcher = dispatcher,
-                        )
-                    }
-                },
-                modifier =
-                    modifier.onPlaced {
-                        val positionInRoot = it.positionInWindow()
-                        val size = it.size
-                        onBoundsChanged(positionInRoot.y to positionInRoot.y + size.height)
-                    },
-            )
-        }
+        )
     }
 }
-
-private val NotificationsElementKey = ElementKey("Notifications")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
new file mode 100644
index 0000000..44b0535
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.keyguard.ui.composable.section
+
+import android.view.LayoutInflater
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.positionInParent
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.toSize
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
+import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+
+class SettingsMenuSection
+@Inject
+constructor(
+    private val viewModel: KeyguardSettingsMenuViewModel,
+    private val longPressViewModel: KeyguardLongPressViewModel,
+    private val vibratorHelper: VibratorHelper,
+    private val activityStarter: ActivityStarter,
+) {
+    @Composable
+    @SuppressWarnings("InflateParams") // null is passed into the inflate call, on purpose.
+    fun SettingsMenu(
+        onPlaced: (Rect?) -> Unit,
+        modifier: Modifier = Modifier,
+    ) {
+        val (disposableHandle, setDisposableHandle) =
+            remember { mutableStateOf<DisposableHandle?>(null) }
+        AndroidView(
+            factory = { context ->
+                LayoutInflater.from(context)
+                    .inflate(
+                        R.layout.keyguard_settings_popup_menu,
+                        null,
+                    )
+                    .apply {
+                        isVisible = false
+                        alpha = 0f
+
+                        setDisposableHandle(
+                            KeyguardSettingsViewBinder.bind(
+                                view = this,
+                                viewModel = viewModel,
+                                longPressViewModel = longPressViewModel,
+                                rootViewModel = null,
+                                vibratorHelper = vibratorHelper,
+                                activityStarter = activityStarter,
+                            )
+                        )
+                    }
+            },
+            onRelease = { disposableHandle?.dispose() },
+            modifier =
+                modifier
+                    .padding(
+                        bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset),
+                    )
+                    .padding(
+                        horizontal =
+                            dimensionResource(R.dimen.keyguard_affordance_horizontal_offset),
+                    )
+                    .onPlaced { coordinates ->
+                        onPlaced(
+                            if (!coordinates.size.toSize().isEmpty()) {
+                                Rect(coordinates.positionInParent(), coordinates.size.toSize())
+                            } else {
+                                null
+                            }
+                        )
+                    },
+        )
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 92f66902..387f2e1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -101,7 +101,7 @@
     }
 
     /** Alerts listener and plugin that the plugin has been created. */
-    public void onCreate() {
+    public synchronized void onCreate() {
         boolean loadPlugin = mListener.onPluginAttached(this);
         if (!loadPlugin) {
             if (mPlugin != null) {
@@ -128,7 +128,7 @@
     }
 
     /** Alerts listener and plugin that the plugin is being shutdown. */
-    public void onDestroy() {
+    public synchronized void onDestroy() {
         logDebug("onDestroy");
         unloadPlugin();
         mListener.onPluginDetached(this);
@@ -143,12 +143,13 @@
     /**
      * Loads and creates the plugin if it does not exist.
      */
-    public void loadPlugin() {
+    public synchronized void loadPlugin() {
         if (mPlugin != null) {
             logDebug("Load request when already loaded");
             return;
         }
 
+        // Both of these calls take about 1 - 1.5 seconds in test runs
         mPlugin = mPluginFactory.createPlugin();
         mPluginContext = mPluginFactory.createPluginContext();
         if (mPlugin == null || mPluginContext == null) {
@@ -171,7 +172,7 @@
      *
      * This will free the associated memory if there are not other references.
      */
-    public void unloadPlugin() {
+    public synchronized void unloadPlugin() {
         if (mPlugin == null) {
             logDebug("Unload request when already unloaded");
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
rename to packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index b1de127..49e0df6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -31,7 +31,7 @@
  * Controls the interaction between {@link MagnetizedObject} and
  * {@link MagnetizedObject.MagneticTarget}.
  */
-class DismissAnimationController {
+class DragToInteractAnimationController {
     private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false;
     private static final float COMPLETELY_OPAQUE = 1.0f;
     private static final float COMPLETELY_TRANSPARENT = 0.0f;
@@ -45,7 +45,7 @@
     private float mMinDismissSize;
     private float mSizePercent;
 
-    DismissAnimationController(DismissView dismissView, MenuView menuView) {
+    DragToInteractAnimationController(DismissView dismissView, MenuView menuView) {
         mDismissView = dismissView;
         mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
         mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
@@ -127,7 +127,7 @@
      * @param event that move the magnetized object which is also the menu list view.
      * @return true if the location of the motion events moves within the magnetic field of a
      * target, but false if didn't set
-     * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+     * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
      */
     boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
         return mMagnetizedObject.maybeConsumeMotionEvent(event);
@@ -140,7 +140,7 @@
      * @param event that move the magnetized object which is also the menu list view.
      * @return true if the location of the motion events moves within the magnetic field of a
      * target, but false if didn't set
-     * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+     * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
      */
     boolean maybeConsumeUpMotionEvent(MotionEvent event) {
         return mMagnetizedObject.maybeConsumeMotionEvent(event);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index 34d7cec..a270558 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -73,7 +73,7 @@
     private final ValueAnimator mFadeOutAnimator;
     private final Handler mHandler;
     private boolean mIsFadeEffectEnabled;
-    private DismissAnimationController.DismissCallback mDismissCallback;
+    private DragToInteractAnimationController.DismissCallback mDismissCallback;
     private Runnable mSpringAnimationsEndAction;
 
     // Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
@@ -171,7 +171,7 @@
     }
 
     void setDismissCallback(
-            DismissAnimationController.DismissCallback dismissCallback) {
+            DragToInteractAnimationController.DismissCallback dismissCallback) {
         mDismissCallback = dismissCallback;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index d01590f..52e7b91 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -40,13 +40,13 @@
     private final PointF mMenuTranslationDown = new PointF();
     private boolean mIsDragging = false;
     private float mTouchSlop;
-    private final DismissAnimationController mDismissAnimationController;
+    private final DragToInteractAnimationController mDragToInteractAnimationController;
     private Optional<Runnable> mOnActionDownEnd = Optional.empty();
 
     MenuListViewTouchHandler(MenuAnimationController menuAnimationController,
-            DismissAnimationController dismissAnimationController) {
+            DragToInteractAnimationController dragToInteractAnimationController) {
         mMenuAnimationController = menuAnimationController;
-        mDismissAnimationController = dismissAnimationController;
+        mDragToInteractAnimationController = dragToInteractAnimationController;
     }
 
     @Override
@@ -67,7 +67,7 @@
                 mMenuTranslationDown.set(menuView.getTranslationX(), menuView.getTranslationY());
 
                 mMenuAnimationController.cancelAnimations();
-                mDismissAnimationController.maybeConsumeDownMotionEvent(motionEvent);
+                mDragToInteractAnimationController.maybeConsumeDownMotionEvent(motionEvent);
 
                 mOnActionDownEnd.ifPresent(Runnable::run);
                 break;
@@ -78,9 +78,10 @@
                         mMenuAnimationController.onDraggingStart();
                     }
 
-                    mDismissAnimationController.showDismissView(/* show= */ true);
+                    mDragToInteractAnimationController.showDismissView(/* show= */ true);
 
-                    if (!mDismissAnimationController.maybeConsumeMoveMotionEvent(motionEvent)) {
+                    if (!mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(
+                            motionEvent)) {
                         mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
                         mMenuAnimationController.moveToPositionYIfNeeded(
                                 mMenuTranslationDown.y + dy);
@@ -94,17 +95,18 @@
                     mIsDragging = false;
 
                     if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
-                        mDismissAnimationController.showDismissView(/* show= */ false);
+                        mDragToInteractAnimationController.showDismissView(/* show= */ false);
                         mMenuAnimationController.fadeOutIfEnabled();
 
                         return true;
                     }
 
-                    if (!mDismissAnimationController.maybeConsumeUpMotionEvent(motionEvent)) {
+                    if (!mDragToInteractAnimationController.maybeConsumeUpMotionEvent(
+                            motionEvent)) {
                         mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
                         mMenuAnimationController.flingMenuThenSpringToEdge(endX,
                                 mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-                        mDismissAnimationController.showDismissView(/* show= */ false);
+                        mDragToInteractAnimationController.showDismissView(/* show= */ false);
                     }
 
                     // Avoid triggering the listener of the item.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index ff3a9e3..62d5feb 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -94,7 +94,7 @@
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final IAccessibilityFloatingMenu mFloatingMenu;
     private final SecureSettings mSecureSettings;
-    private final DismissAnimationController mDismissAnimationController;
+    private final DragToInteractAnimationController mDragToInteractAnimationController;
     private final MenuViewModel mMenuViewModel;
     private final Observer<Boolean> mDockTooltipObserver =
             this::onDockTooltipVisibilityChanged;
@@ -188,29 +188,30 @@
         mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
         mDismissView = new DismissView(context);
         DismissViewUtils.setup(mDismissView);
-        mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
-        mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
+        mDragToInteractAnimationController = new DragToInteractAnimationController(
+                mDismissView, mMenuView);
+        mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
             @Override
             public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
-                mDismissAnimationController.animateDismissMenu(/* scaleUp= */ true);
+                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ true);
             }
 
             @Override
             public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
                     float velocityX, float velocityY, boolean wasFlungOut) {
-                mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
             }
 
             @Override
             public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
                 hideMenuAndShowMessage();
                 mDismissView.hide();
-                mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
             }
         });
 
         mMenuListViewTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
-                mDismissAnimationController);
+                mDragToInteractAnimationController);
         mMenuView.addOnItemTouchListenerToList(mMenuListViewTouchHandler);
         mMenuView.setMoveToTuckedListener(this);
 
@@ -243,7 +244,7 @@
     @Override
     public void onConfigurationChanged(@NonNull Configuration newConfig) {
         mDismissView.updateResources();
-        mDismissAnimationController.updateResources();
+        mDragToInteractAnimationController.updateResources();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
index 629b361..cfa5294 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
@@ -65,4 +65,11 @@
             SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, previousEvent.currentProgress)
         }
     }
+
+    /** The arrow navigation that was operating the slider has stopped. */
+    fun onArrowUp() {
+        _currentEvent.update { previousEvent ->
+            SliderEvent(SliderEventType.ARROW_UP, previousEvent.currentProgress)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
index d89cf63..10098fa 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
@@ -58,7 +58,7 @@
 
     override suspend fun iterateState(event: SliderEvent) {
         when (currentState) {
-            SliderState.IDLE -> handleIdle(event.type)
+            SliderState.IDLE -> handleIdle(event.type, event.currentProgress)
             SliderState.WAIT -> handleWait(event.type, event.currentProgress)
             SliderState.DRAG_HANDLE_ACQUIRED_BY_TOUCH -> handleAcquired(event.type)
             SliderState.DRAG_HANDLE_DRAGGING -> handleDragging(event.type, event.currentProgress)
@@ -67,17 +67,26 @@
             SliderState.DRAG_HANDLE_RELEASED_FROM_TOUCH -> setState(SliderState.IDLE)
             SliderState.JUMP_TRACK_LOCATION_SELECTED -> handleJumpToTrack(event.type)
             SliderState.JUMP_BOOKEND_SELECTED -> handleJumpToBookend(event.type)
+            SliderState.ARROW_HANDLE_MOVED_ONCE -> handleArrowOnce(event.type)
+            SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY ->
+                handleArrowContinuous(event.type, event.currentProgress)
+            SliderState.ARROW_HANDLE_REACHED_BOOKEND -> handleArrowBookend()
         }
         latestProgress = event.currentProgress
     }
 
-    private fun handleIdle(newEventType: SliderEventType) {
+    private fun handleIdle(newEventType: SliderEventType, currentProgress: Float) {
         if (newEventType == SliderEventType.STARTED_TRACKING_TOUCH) {
             timerJob = launchTimer()
             // The WAIT state will wait for the timer to complete or a slider progress to occur.
             // This will disambiguate between an imprecise touch that acquires the slider handle,
             // and a select and jump operation in the slider track.
             setState(SliderState.WAIT)
+        } else if (newEventType == SliderEventType.PROGRESS_CHANGE_BY_PROGRAM) {
+            val state =
+                if (bookendReached(currentProgress)) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+                else SliderState.ARROW_HANDLE_MOVED_ONCE
+            setState(state)
         }
     }
 
@@ -176,6 +185,13 @@
             SliderState.DRAG_HANDLE_REACHED_BOOKEND -> executeOnBookend()
             SliderState.JUMP_TRACK_LOCATION_SELECTED ->
                 sliderListener.onProgressJump(latestProgress)
+            SliderState.ARROW_HANDLE_MOVED_ONCE -> sliderListener.onSelectAndArrow(latestProgress)
+            SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY -> sliderListener.onProgress(latestProgress)
+            SliderState.ARROW_HANDLE_REACHED_BOOKEND -> {
+                executeOnBookend()
+                // This transitory execution must also reset the state
+                resetState()
+            }
             else -> {}
         }
     }
@@ -204,6 +220,43 @@
             currentProgress <= config.lowerBookendThreshold
     }
 
+    private fun handleArrowOnce(newEventType: SliderEventType) {
+        val nextState =
+            when (newEventType) {
+                SliderEventType.STARTED_TRACKING_TOUCH -> {
+                    // Launching the timer and going to WAIT
+                    timerJob = launchTimer()
+                    SliderState.WAIT
+                }
+                SliderEventType.PROGRESS_CHANGE_BY_PROGRAM ->
+                    SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+                SliderEventType.ARROW_UP -> SliderState.IDLE
+                else -> SliderState.ARROW_HANDLE_MOVED_ONCE
+            }
+        setState(nextState)
+    }
+
+    private fun handleArrowContinuous(newEventType: SliderEventType, currentProgress: Float) {
+        val reachedBookend = bookendReached(currentProgress)
+        val nextState =
+            when (newEventType) {
+                SliderEventType.ARROW_UP -> SliderState.IDLE
+                SliderEventType.STARTED_TRACKING_TOUCH -> {
+                    // Launching the timer and going to WAIT
+                    timerJob = launchTimer()
+                    SliderState.WAIT
+                }
+                SliderEventType.PROGRESS_CHANGE_BY_PROGRAM -> {
+                    if (reachedBookend) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+                    else SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+                }
+                else -> SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+            }
+        setState(nextState)
+    }
+
+    private fun handleArrowBookend() = setState(SliderState.IDLE)
+
     @VisibleForTesting
     fun setState(state: SliderState) {
         currentState = state
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
index 413e277..4a63941 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
@@ -29,5 +29,5 @@
     /* The slider has stopped tracking touch events. */
     STOPPED_TRACKING_TOUCH,
     /* The external (not touch) stimulus that was modifying the slider progress has stopped. */
-    EXTERNAL_STIMULUS_RELEASE,
+    ARROW_UP,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
index fe092e6..de6ddd7 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
@@ -32,6 +32,12 @@
     DRAG_HANDLE_REACHED_BOOKEND,
     /* A location in the slider track has been selected. */
     JUMP_TRACK_LOCATION_SELECTED,
-    /* The slider handled moved to a bookend after it was selected. */
+    /* The slider handle moved to a bookend after it was selected. */
     JUMP_BOOKEND_SELECTED,
+    /** The slider handle moved due to single select-and-arrow operation */
+    ARROW_HANDLE_MOVED_ONCE,
+    /** The slider handle moves continuously due to constant select-and-arrow operations */
+    ARROW_HANDLE_MOVES_CONTINUOUSLY,
+    /** The slider handle reached a bookend due to a select-and-arrow operation */
+    ARROW_HANDLE_REACHED_BOOKEND,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index eee5206..96e83b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -241,7 +241,6 @@
                                 vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Activated)
                                 settingsMenu.setOnTouchListener(
                                     KeyguardSettingsButtonOnTouchListener(
-                                        view = settingsMenu,
                                         viewModel = viewModel.settingsMenuViewModel,
                                     )
                                 )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
index c54203c..c6dfcb0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
@@ -20,12 +20,10 @@
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewConfiguration
-import com.android.systemui.animation.view.LaunchableLinearLayout
 import com.android.systemui.common.ui.view.rawDistanceFrom
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
 
 class KeyguardSettingsButtonOnTouchListener(
-    private val view: LaunchableLinearLayout,
     private val viewModel: KeyguardSettingsMenuViewModel,
 ) : View.OnTouchListener {
 
@@ -41,8 +39,10 @@
             MotionEvent.ACTION_UP -> {
                 view.isPressed = false
                 val distanceMoved =
-                    motionEvent
-                        .rawDistanceFrom(downPositionDisplayCoords.x, downPositionDisplayCoords.y)
+                    motionEvent.rawDistanceFrom(
+                        downPositionDisplayCoords.x,
+                        downPositionDisplayCoords.y
+                    )
                 val isClick = distanceMoved < ViewConfiguration.getTouchSlop()
                 viewModel.onTouchGestureEnded(isClick)
                 if (isClick) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index 11e63e7..f67cb68 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -23,7 +23,6 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.view.LaunchableLinearLayout
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.common.ui.binder.TextViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
@@ -43,15 +42,13 @@
 
 object KeyguardSettingsViewBinder {
     fun bind(
-        parentView: View,
+        view: View,
         viewModel: KeyguardSettingsMenuViewModel,
         longPressViewModel: KeyguardLongPressViewModel,
-        rootViewModel: KeyguardRootViewModel,
+        rootViewModel: KeyguardRootViewModel?,
         vibratorHelper: VibratorHelper,
         activityStarter: ActivityStarter
     ): DisposableHandle {
-        val view = parentView.requireViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
-
         val disposableHandle =
             view.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -62,7 +59,6 @@
                                 vibratorHelper.vibrate(KeyguardBottomAreaVibrations.Activated)
                                 view.setOnTouchListener(
                                     KeyguardSettingsButtonOnTouchListener(
-                                        view = view,
                                         viewModel = viewModel,
                                     )
                                 )
@@ -96,7 +92,7 @@
                     }
 
                     launch {
-                        rootViewModel.lastRootViewTapPosition.filterNotNull().collect { point ->
+                        rootViewModel?.lastRootViewTapPosition?.filterNotNull()?.collect { point ->
                             if (view.isVisible) {
                                 val hitRect = Rect()
                                 view.getHitRect(hitRect)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 24240df..940d1e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -142,6 +142,11 @@
             return true
         }
 
+        if (renderer == null || onDestroy == null) {
+            Log.wtf(TAG, "Renderer/onDestroy should not be null.")
+            return true
+        }
+
         when (message.what) {
             KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
                 message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index d57e569..36bbe4e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -33,6 +33,7 @@
 constructor(
     private val interactor: KeyguardBlueprintInteractor,
     private val authController: AuthController,
+    val longPress: KeyguardLongPressViewModel,
 ) {
     val isUdfpsVisible: Boolean
         get() = authController.isUdfpsSupported
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index bc5090f..be1fa2b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -227,7 +227,7 @@
                 mListener.onChanged(mTracking, progress, false);
                 SeekableSliderEventProducer eventProducer =
                         mBrightnessSliderHapticPlugin.getSeekableSliderEventProducer();
-                if (eventProducer != null) {
+                if (eventProducer != null && fromUser) {
                     eventProducer.onProgressChanged(seekBar, progress, fromUser);
                 }
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
similarity index 84%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index fd258e3..9bcab57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -40,12 +40,12 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-/** Tests for {@link DismissAnimationController}. */
+/** Tests for {@link DragToInteractAnimationController}. */
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class DismissAnimationControllerTest extends SysuiTestCase {
-    private DismissAnimationController mDismissAnimationController;
+public class DragToInteractAnimationControllerTest extends SysuiTestCase {
+    private DragToInteractAnimationController mDragToInteractAnimationController;
     private DismissView mDismissView;
 
     @Rule
@@ -65,19 +65,20 @@
                 stubMenuViewAppearance);
         mDismissView = spy(new DismissView(mContext));
         DismissViewUtils.setup(mDismissView);
-        mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView);
+        mDragToInteractAnimationController = new DragToInteractAnimationController(
+                mDismissView, stubMenuView);
     }
 
     @Test
     public void showDismissView_success() {
-        mDismissAnimationController.showDismissView(true);
+        mDragToInteractAnimationController.showDismissView(true);
 
         verify(mDismissView).show();
     }
 
     @Test
     public void hideDismissView_success() {
-        mDismissAnimationController.showDismissView(false);
+        mDragToInteractAnimationController.showDismissView(false);
 
         verify(mDismissView).hide();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 7f12c05..9c8de30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -62,7 +62,7 @@
     @Mock
     private SecureSettings mSecureSettings;
     @Mock
-    private DismissAnimationController.DismissCallback mStubDismissCallback;
+    private DragToInteractAnimationController.DismissCallback mStubDismissCallback;
 
     private RecyclerView mStubListView;
     private MenuView mMenuView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 9797f2a..e1522f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -68,7 +68,7 @@
     private MenuView mStubMenuView;
     private MenuListViewTouchHandler mTouchHandler;
     private MenuAnimationController mMenuAnimationController;
-    private DismissAnimationController mDismissAnimationController;
+    private DragToInteractAnimationController mDragToInteractAnimationController;
     private RecyclerView mStubListView;
     private DismissView mDismissView;
 
@@ -92,10 +92,10 @@
                 mStubMenuView, stubMenuViewAppearance));
         mDismissView = spy(new DismissView(mContext));
         DismissViewUtils.setup(mDismissView);
-        mDismissAnimationController =
-                spy(new DismissAnimationController(mDismissView, mStubMenuView));
+        mDragToInteractAnimationController =
+                spy(new DragToInteractAnimationController(mDismissView, mStubMenuView));
         mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
-                mDismissAnimationController);
+                mDragToInteractAnimationController);
         final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
         mStubListView = (RecyclerView) mStubMenuView.getChildAt(0);
         mStubListView.setAdapter(stubAdapter);
@@ -115,7 +115,7 @@
 
     @Test
     public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
-        doReturn(false).when(mDismissAnimationController).maybeConsumeMoveMotionEvent(
+        doReturn(false).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
                 any(MotionEvent.class));
         final int offset = 100;
         final MotionEvent stubDownEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
index 71a56cd..c22d35c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
@@ -123,4 +123,25 @@
 
             assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0.5F), latest)
         }
+
+    @Test
+    fun onArrowUp_afterStartTrackingTouch_ArrowUpProduced() = runTest {
+        val latest by collectLastValue(eventFlow)
+
+        eventProducer.onStartTrackingTouch(seekBar)
+        eventProducer.onArrowUp()
+
+        assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0f), latest)
+    }
+
+    @Test
+    fun onArrowUp_afterChangeByProgram_ArrowUpProduced_withProgress() = runTest {
+        val progress = 50
+        val latest by collectLastValue(eventFlow)
+
+        eventProducer.onProgressChanged(seekBar, progress, false)
+        eventProducer.onArrowUp()
+
+        assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0.5f), latest)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
index 8d12e49..db04962 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
@@ -528,6 +528,194 @@
         verifyNoMoreInteractions(sliderStateListener)
     }
 
+    @Test
+    fun onProgressChangeByProgram_atTheMiddle_onIdle_movesToArrowHandleMovedOnce() = runTest {
+        // GIVEN an initialized tracker in the IDLE state
+        initTracker(testScheduler)
+
+        // GIVEN a progress due to an external source that lands at the middle of the slider
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the state moves to ARROW_HANDLE_MOVED_ONCE and the listener is called to play
+        // haptics
+        assertThat(mSeekableSliderTracker.currentState)
+            .isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE)
+        verify(sliderStateListener).onSelectAndArrow(progress)
+    }
+
+    @Test
+    fun onProgressChangeByProgram_atUpperBookend_onIdle_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the IDLE state
+        val config = SeekableSliderTrackerConfig()
+        initTracker(testScheduler, config)
+
+        // GIVEN a progress due to an external source that lands at the upper bookend
+        val progress = config.upperBookendThreshold + 0.01f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker executes upper bookend haptics before moving back to IDLE
+        verify(sliderStateListener).onUpperBookend()
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+    }
+
+    @Test
+    fun onProgressChangeByProgram_atLowerBookend_onIdle_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the IDLE state
+        val config = SeekableSliderTrackerConfig()
+        initTracker(testScheduler, config)
+
+        // WHEN a progress is recorded due to an external source that lands at the lower bookend
+        val progress = config.lowerBookendThreshold - 0.01f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker executes lower bookend haptics before moving to IDLE
+        verify(sliderStateListener).onLowerBookend()
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+    }
+
+    @Test
+    fun onArrowUp_onArrowMovedOnce_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+        // WHEN the external stimulus is released
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+        // THEN the tracker moves back to IDLE and there are no haptics
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+        verifyZeroInteractions(sliderStateListener)
+    }
+
+    @Test
+    fun onStartTrackingTouch_onArrowMovedOnce_movesToWait() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+        // WHEN the slider starts tracking touch
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+        // THEN the tracker moves back to WAIT and starts the waiting job. Also, there are no
+        // haptics
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+        assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+        verifyZeroInteractions(sliderStateListener)
+    }
+
+    @Test
+    fun onProgressChangeByProgram_onArrowMovedOnce_movesToArrowMovesContinuously() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+        // WHEN the slider gets an external progress change
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker moves to ARROW_HANDLE_MOVES_CONTINUOUSLY and calls the appropriate
+        // haptics
+        assertThat(mSeekableSliderTracker.currentState)
+            .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+        verify(sliderStateListener).onProgress(progress)
+    }
+
+    @Test
+    fun onArrowUp_onArrowMovesContinuously_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+        // WHEN the external stimulus is released
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+        // THEN the tracker moves to IDLE and no haptics are played
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+        verifyZeroInteractions(sliderStateListener)
+    }
+
+    @Test
+    fun onStartTrackingTouch_onArrowMovesContinuously_movesToWait() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+        // WHEN the slider starts tracking touch
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+        // THEN the tracker moves to WAIT and the wait job starts.
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+        assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+        verifyZeroInteractions(sliderStateListener)
+    }
+
+    @Test
+    fun onProgressChangeByProgram_onArrowMovesContinuously_preservesState() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+        // WHEN the slider changes progress programmatically at the middle
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker stays in the same state and haptics are delivered appropriately
+        assertThat(mSeekableSliderTracker.currentState)
+            .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+        verify(sliderStateListener).onProgress(progress)
+    }
+
+    @Test
+    fun onProgramProgress_atLowerBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+        val config = SeekableSliderTrackerConfig()
+        initTracker(testScheduler, config)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+        // WHEN the slider reaches the lower bookend programmatically
+        val progress = config.lowerBookendThreshold - 0.01f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker executes lower bookend haptics before moving to IDLE
+        verify(sliderStateListener).onLowerBookend()
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+    }
+
+    @Test
+    fun onProgramProgress_atUpperBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+        val config = SeekableSliderTrackerConfig()
+        initTracker(testScheduler, config)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+        // WHEN the slider reaches the lower bookend programmatically
+        val progress = config.upperBookendThreshold + 0.01f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker executes upper bookend haptics before moving to IDLE
+        verify(sliderStateListener).onUpperBookend()
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+    }
+
     @OptIn(ExperimentalCoroutinesApi::class)
     private fun initTracker(
         scheduler: TestCoroutineScheduler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 6eabf44..5e57c83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -17,13 +17,17 @@
 package com.android.systemui.shared.plugins;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
 
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -40,7 +44,11 @@
 
 import java.lang.ref.WeakReference;
 import java.util.Collections;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -104,6 +112,7 @@
         mPluginInstance = mPluginInstanceFactory.create(
                 mContext, mAppInfo, TEST_PLUGIN_COMPONENT_NAME,
                 TestPlugin.class, mPluginListener);
+        mPluginInstance.setIsDebug(true);
         mPluginContext = new WeakReference<>(mPluginInstance.getPluginContext());
     }
 
@@ -158,7 +167,7 @@
 
     @Test
     public void testOnAttach_SkipLoad() {
-        mPluginListener.mAttachReturn = false;
+        mPluginListener.mOnAttach = () -> false;
         mPluginInstance.onCreate();
         assertEquals(1, mPluginListener.mAttachedCount);
         assertEquals(0, mPluginListener.mLoadCount);
@@ -166,6 +175,65 @@
         assertInstances(0, 0);
     }
 
+    @Test
+    public void testLoadUnloadSimultaneous_HoldsUnload() throws Exception {
+        final Semaphore loadLock = new Semaphore(1);
+        final Semaphore unloadLock = new Semaphore(1);
+
+        mPluginListener.mOnAttach = () -> false;
+        mPluginListener.mOnLoad = () -> {
+            assertNotNull(mPluginInstance.getPlugin());
+
+            // Allow the bg thread the opportunity to delete the plugin
+            loadLock.release();
+            Thread.yield();
+            boolean isLocked = getLock(unloadLock, 1000);
+
+            // Ensure the bg thread failed to do delete the plugin
+            assertNotNull(mPluginInstance.getPlugin());
+            // We expect that bgThread deadlocked holding the semaphore
+            assertFalse(isLocked);
+        };
+
+        AtomicBoolean isBgThreadFailed = new AtomicBoolean(false);
+        Thread bgThread = new Thread(() -> {
+            assertTrue(getLock(unloadLock, 10));
+            assertTrue(getLock(loadLock, 3000)); // Wait for the foreground thread
+            assertNotNull(mPluginInstance.getPlugin());
+            // Attempt to delete the plugin, this should block until the load completes
+            mPluginInstance.unloadPlugin();
+            assertNull(mPluginInstance.getPlugin());
+            unloadLock.release();
+            loadLock.release();
+        });
+
+        // This protects the test suite from crashing due to the uncaught exception.
+        bgThread.setUncaughtExceptionHandler((Thread t, Throwable ex) -> {
+            Log.e("testLoadUnloadSimultaneous_HoldsUnload", "Exception from BG Thread", ex);
+            isBgThreadFailed.set(true);
+        });
+
+        loadLock.acquire();
+        mPluginInstance.onCreate();
+
+        assertNull(mPluginInstance.getPlugin());
+        bgThread.start();
+        mPluginInstance.loadPlugin();
+
+        bgThread.join(5000);
+        assertFalse(isBgThreadFailed.get());
+        assertNull(mPluginInstance.getPlugin());
+    }
+
+    private boolean getLock(Semaphore lock, long millis) {
+        try {
+            return lock.tryAcquire(millis, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException ex) {
+            fail();
+            return false;
+        }
+    }
+
     // This target class doesn't matter, it just needs to have a Requires to hit the flow where
     // the mock version info is called.
     @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
@@ -226,7 +294,10 @@
     }
 
     public class FakeListener implements PluginListener<TestPlugin> {
-        public boolean mAttachReturn = true;
+        public Supplier<Boolean> mOnAttach = null;
+        public Runnable mOnDetach = null;
+        public Runnable mOnLoad = null;
+        public Runnable mOnUnload = null;
         public int mAttachedCount = 0;
         public int mDetachedCount = 0;
         public int mLoadCount = 0;
@@ -236,13 +307,16 @@
         public boolean onPluginAttached(PluginLifecycleManager<TestPlugin> manager) {
             mAttachedCount++;
             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
-            return mAttachReturn;
+            return mOnAttach != null ? mOnAttach.get() : true;
         }
 
         @Override
         public void onPluginDetached(PluginLifecycleManager<TestPlugin> manager) {
             mDetachedCount++;
             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+            if (mOnDetach != null) {
+                mOnDetach.run();
+            }
         }
 
         @Override
@@ -261,6 +335,9 @@
                 assertEquals(expectedContext, pluginContext);
             }
             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+            if (mOnLoad != null) {
+                mOnLoad.run();
+            }
         }
 
         @Override
@@ -274,6 +351,9 @@
                 assertEquals(expectedPlugin, plugin);
             }
             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+            if (mOnUnload != null) {
+                mOnUnload.run();
+            }
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 7558974..1236fcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -38,6 +38,7 @@
 import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
 
 import android.metrics.LogMaker;
+import android.platform.test.annotations.DisableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -84,6 +85,7 @@
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -218,6 +220,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_notificationsVisible_zenHiding() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true);
         initController(/* viewIsAttached= */ true);
@@ -238,6 +241,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_notificationsHidden_zenNotHiding() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -258,6 +262,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_splitShadeMode_alwaysShowEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -285,6 +290,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_bouncerShowing_flagOff_hideEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -306,6 +312,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_bouncerShowing_flagOn_hideEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -327,6 +334,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_bouncerNotShowing_flagOff_showEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -348,6 +356,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_bouncerNotShowing_flagOn_showEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -504,6 +513,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
         initController(/* viewIsAttached= */ true);
         mSeenNotificationsInteractor.setHasFilteredOutSeenNotifications(true);
@@ -545,6 +555,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateImportantForAccessibility_noChild_onKeyGuard_notImportantForA11y() {
         // GIVEN: Controller is attached, active notifications is empty,
         // and mNotificationStackScrollLayout.onKeyguard() is true
@@ -561,6 +572,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateImportantForAccessibility_hasChild_onKeyGuard_importantForA11y() {
         // GIVEN: Controller is attached, active notifications is not empty,
         // and mNotificationStackScrollLayout.onKeyguard() is true
@@ -584,6 +596,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateImportantForAccessibility_hasChild_notOnKeyGuard_importantForA11y() {
         // GIVEN: Controller is attached, active notifications is not empty,
         // and mNotificationStackScrollLayout.onKeyguard() is false
@@ -607,6 +620,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateImportantForAccessibility_noChild_notOnKeyGuard_importantForA11y() {
         // GIVEN: Controller is attached, active notifications is empty,
         // and mNotificationStackScrollLayout.onKeyguard() is false
@@ -623,6 +637,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateEmptyShadeView_onKeyguardTransitionToAod_hidesView() {
         initController(/* viewIsAttached= */ true);
         mController.onKeyguardTransitionChanged(
@@ -633,6 +648,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateEmptyShadeView_onKeyguardOccludedTransitionToAod_hidesView() {
         initController(/* viewIsAttached= */ true);
         mController.onKeyguardTransitionChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index ad7dee3..83ba684 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -51,6 +51,8 @@
 
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
@@ -81,6 +83,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -191,7 +194,7 @@
         mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper,
                 mNotificationStackSizeCalculator);
         mStackScroller = spy(mStackScrollerInternal);
-        mStackScroller.setResetUserExpandedStatesRunnable(()->{});
+        mStackScroller.setResetUserExpandedStatesRunnable(() -> {});
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
         when(mStackScrollLayoutController.getNotificationRoundnessManager())
@@ -309,7 +312,9 @@
     public void updateEmptyView_dndSuppressing() {
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
 
-        mStackScroller.updateEmptyShadeView(true, true);
+        mStackScroller.updateEmptyShadeView(/* visible = */ true,
+                /* areNotificationsHiddenInShade = */ true,
+                /* hasFilteredOutSeenNotifications = */ false);
 
         verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
     }
@@ -319,7 +324,9 @@
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
 
-        mStackScroller.updateEmptyShadeView(true, false);
+        mStackScroller.updateEmptyShadeView(/* visible = */ true,
+                /* areNotificationsHiddenInShade = */ false,
+                /* hasFilteredOutSeenNotifications = */ false);
 
         verify(mEmptyShadeView).setText(R.string.empty_shade_text);
     }
@@ -328,10 +335,14 @@
     public void updateEmptyView_noNotificationsToDndSuppressing() {
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
-        mStackScroller.updateEmptyShadeView(true, false);
+        mStackScroller.updateEmptyShadeView(/* visible = */ true,
+                /* areNotificationsHiddenInShade = */ false,
+                /* hasFilteredOutSeenNotifications = */ false);
         verify(mEmptyShadeView).setText(R.string.empty_shade_text);
 
-        mStackScroller.updateEmptyShadeView(true, true);
+        mStackScroller.updateEmptyShadeView(/* visible = */ true,
+                /* areNotificationsHiddenInShade = */ true,
+                /* hasFilteredOutSeenNotifications = */ false);
         verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
     }
 
@@ -385,8 +396,8 @@
         mStackScroller.setExpandedHeight(100f);
     }
 
-
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void manageNotifications_visible() {
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
@@ -399,6 +410,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void clearAll_visible() {
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
@@ -411,6 +423,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testInflateFooterView() {
         mStackScroller.inflateFooterView();
         ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class);
@@ -444,7 +457,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(false, true, true);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
     }
 
     @Test
@@ -459,7 +472,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(false, false, true);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(false, false, true);
     }
 
     @Test
@@ -474,7 +487,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(true, true, true);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, true);
     }
 
     @Test
@@ -490,7 +503,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(true, true, false);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, false);
     }
 
     @Test
@@ -505,7 +518,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(false, true, true);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
     }
 
     @Test
@@ -521,7 +534,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(true, false, true);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(true, false, true);
     }
 
     @Test
@@ -529,7 +542,8 @@
         mStackScroller.setCurrentUserSetup(true);
 
         // add footer
-        mStackScroller.inflateFooterView();
+        FooterView view = mock(FooterView.class);
+        mStackScroller.setFooterView(view);
 
         // add notification
         ExpandableNotificationRow row = createClearableRow();
@@ -545,6 +559,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testReInflatesFooterViews() {
         when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
         clearInvocations(mStackScroller);
@@ -554,6 +569,16 @@
     }
 
     @Test
+    @EnableFlags(FooterViewRefactor.FLAG_NAME)
+    public void testReInflatesEmptyShadeView() {
+        when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
+        clearInvocations(mStackScroller);
+        mStackScroller.reinflateViews();
+        verify(mStackScroller, never()).setFooterView(any());
+        verify(mStackScroller).setEmptyShadeView(any());
+    }
+
+    @Test
     public void testSetIsBeingDraggedResetsExposedMenu() {
         mStackScroller.setIsBeingDragged(true);
         verify(mNotificationSwipeHelper).resetExposedMenuView(true, true);
@@ -601,6 +626,8 @@
 
     @Test
     public void testClearNotifications_clearAllInProgress() {
+        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
         ExpandableNotificationRow row = createClearableRow();
         when(row.getEntry().hasFinishedInitialization()).thenReturn(true);
         doReturn(true).when(mStackScroller).isVisible(row);
@@ -645,6 +672,8 @@
 
     @Test
     public void testAddNotificationUpdatesSpeedBumpIndex() {
+        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
         // initial state calculated == 0
         assertEquals(0, mStackScroller.getSpeedBumpIndex());
 
@@ -661,6 +690,8 @@
 
     @Test
     public void testAddAmbientNotificationNoSpeedBumpUpdate() {
+        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
         // initial state calculated  == 0
         assertEquals(0, mStackScroller.getSpeedBumpIndex());
 
@@ -677,6 +708,8 @@
 
     @Test
     public void testRemoveNotificationUpdatesSpeedBump() {
+        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
         // initial state calculated == 0
         assertEquals(0, mStackScroller.getSpeedBumpIndex());
 
@@ -872,6 +905,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void hasFilteredOutSeenNotifs_updateFooter() {
         mStackScroller.setCurrentUserSetup(true);
 
@@ -887,6 +921,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void hasFilteredOutSeenNotifs_updateEmptyShadeView() {
         mStackScroller.setHasFilteredOutSeenNotifications(true);
         mStackScroller.updateEmptyShadeView(true, false);
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 3024dd2..8910b6e 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -82,6 +82,7 @@
     public static final int AUTO_BRIGHTNESS_MODE_DEFAULT = 0;
     public static final int AUTO_BRIGHTNESS_MODE_IDLE = 1;
     public static final int AUTO_BRIGHTNESS_MODE_DOZE = 2;
+    public static final int AUTO_BRIGHTNESS_MODE_MAX = AUTO_BRIGHTNESS_MODE_DOZE;
 
     // How long the current sensor reading is assumed to be valid beyond the current time.
     // This provides a bit of prediction, as well as ensures that the weight for the last sample is
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e38d08f..bc3f9dd 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4573,8 +4573,10 @@
                     if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
                         final DisplayPowerControllerInterface displayPowerController =
                                 mDisplayPowerControllers.get(id);
-                        ready &= displayPowerController.requestPowerState(request,
-                                waitForNegativeProximity);
+                        if (displayPowerController != null) {
+                            ready &= displayPowerController.requestPowerState(request,
+                                    waitForNegativeProximity);
+                        }
                     }
                 }
 
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 4e341a9..a43f93a 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_MAX;
+
 import android.annotation.Nullable;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.PowerManager;
@@ -65,6 +67,22 @@
         return true;
     }
 
+    @Override
+    public float[] getAutoBrightnessLevels(int mode) {
+        if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) {
+            throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+        }
+        return mDisplayPowerController.getAutoBrightnessLevels(mode);
+    }
+
+    @Override
+    public float[] getAutoBrightnessLuxLevels(int mode) {
+        if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) {
+            throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+        }
+        return mDisplayPowerController.getAutoBrightnessLuxLevels(mode);
+    }
+
     /**
      * Start the offload session. The method returns if the session is already active.
      * @return Whether the session was started successfully
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 06e5f99..734381b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2214,6 +2214,20 @@
     }
 
     @Override
+    public float[] getAutoBrightnessLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+        // The old DPC is no longer supported
+        return null;
+    }
+
+    @Override
+    public float[] getAutoBrightnessLuxLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+        // The old DPC is no longer supported
+        return null;
+    }
+
+    @Override
     public BrightnessInfo getBrightnessInfo() {
         synchronized (mCachedBrightnessInfo) {
             return new BrightnessInfo(
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 519224a..7df6114 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1886,6 +1886,24 @@
     }
 
     @Override
+    public float[] getAutoBrightnessLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+        int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+        return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
+    }
+
+    @Override
+    public float[] getAutoBrightnessLuxLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+        int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+        return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
+    }
+
+    @Override
     public BrightnessInfo getBrightnessInfo() {
         synchronized (mCachedBrightnessInfo) {
             return new BrightnessInfo(
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index c279184..13acb3f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -237,4 +237,21 @@
      * Indicate that boot has been completed and the screen is ready to update.
      */
     void onBootCompleted();
+
+    /**
+     * Get the brightness levels used to determine automatic brightness based on lux levels.
+     * @param mode The auto-brightness mode
+     * @return The brightness levels for the specified mode. The values are between
+     * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+     */
+    float[] getAutoBrightnessLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
+
+    /**
+     * Get the lux levels used to determine automatic brightness.
+     * @param mode The auto-brightness mode
+     * @return The lux levels for the specified mode
+     */
+    float[] getAutoBrightnessLuxLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
 }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 38f0df4..9088cb9 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2700,11 +2700,8 @@
                 // session info from them.
                 sessionInfo = mSystemProvider.getDefaultSessionInfo();
             }
-            // TODO: b/279555229 - replace with matchingRequest.mRouterRecord.notifySessionCreated.
-            notifySessionCreatedToRouter(
-                    matchingRequest.mRouterRecord,
-                    toOriginalRequestId(uniqueRequestId),
-                    sessionInfo);
+            matchingRequest.mRouterRecord.notifySessionCreated(
+                    toOriginalRequestId(uniqueRequestId), sessionInfo);
         }
 
         private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
@@ -2812,11 +2809,6 @@
             return true;
         }
 
-        private void notifySessionCreatedToRouter(@NonNull RouterRecord routerRecord,
-                int requestId, @NonNull RoutingSessionInfo sessionInfo) {
-            routerRecord.notifySessionCreated(requestId, sessionInfo);
-        }
-
         private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
                 int requestId) {
             try {
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index b96b704..c920ca8 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -829,6 +829,9 @@
                         int returnCodeOfChild;
                         for (int childId : childUserIds) {
                             if (childId == userId) continue;
+                            if (mUserManagerInternal.getProfileParentId(childId) != userId) {
+                                continue;
+                            }
 
                             // If package is not present in child then don't attempt to delete.
                             if (!packageState.getUserStateOrDefault(childId).isInstalled()) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index edae273..b286b12 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -64,6 +64,7 @@
 import android.os.Message;
 import android.os.PatternMatcher;
 import android.os.PersistableBundle;
+import android.os.Process;
 import android.os.SELinux;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -3189,6 +3190,9 @@
             pkg.isScannedAsStoppedSystemApp());
         if (!pkg.hasSharedUser()) {
             serializer.attributeInt(null, "userId", pkg.getAppId());
+
+            serializer.attributeBoolean(null, "isSdkLibrary",
+                    pkg.getAndroidPackage() != null && pkg.getAndroidPackage().isSdkLibrary());
         } else {
             serializer.attributeInt(null, "sharedUserId", pkg.getAppId());
         }
@@ -4039,10 +4043,12 @@
         int targetSdkVersion = 0;
         byte[] restrictUpdateHash = null;
         boolean isScannedAsStoppedSystemApp = false;
+        boolean isSdkLibrary = false;
         try {
             name = parser.getAttributeValue(null, ATTR_NAME);
             realName = parser.getAttributeValue(null, "realName");
             appId = parseAppId(parser);
+            isSdkLibrary = parser.getAttributeBoolean(null, "isSdkLibrary", false);
             sharedUserAppId = parseSharedUserAppId(parser);
             codePathStr = parser.getAttributeValue(null, "codePath");
 
@@ -4157,7 +4163,8 @@
                 PackageManagerService.reportSettingsProblem(Log.WARN,
                         "Error in package manager settings: <package> has no codePath at "
                                 + parser.getPositionDescription());
-            } else if (appId > 0) {
+            } else if (appId > 0 || (appId == Process.INVALID_UID && isSdkLibrary
+                    && Flags.disallowSdkLibsToBeApps())) {
                 packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
                         appId, pkgFlags, pkgPrivateFlags, domainSetId);
                 if (PackageManagerService.DEBUG_SETTINGS)
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 59e95e7..1185a4e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2734,11 +2734,7 @@
 
         // AdServicesManagerService (PP API service)
         t.traceBegin("StartAdServicesManagerService");
-        SystemService adServices = mSystemServiceManager
-                .startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
-        if (adServices instanceof Dumpable) {
-            mDumper.addDumpable((Dumpable) adServices);
-        }
+        mSystemServiceManager.startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
         t.traceEnd();
 
         // OnDevicePersonalizationSystemService
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
index 25d208d..5cbb1aa 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -77,7 +77,9 @@
             .autoFix()
             .build()
 
-        return LintFix.create().composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
+        return LintFix.create()
+            .name(annotateFix.getDisplayName())
+            .composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
     }
 
     private val annotation: String