Merge "Split up KeyguardBottomArea" into udc-qpr-dev
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index a336252..d4b73a4 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -65,17 +65,17 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
+    <include layout="@layout/status_bar_expanded"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:visibility="invisible" />
+
     <!-- Root for all keyguard content. It was previously located within the shade. -->
     <com.android.systemui.keyguard.ui.view.KeyguardRootView
         android:id="@id/keyguard_root_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
-    <include layout="@layout/status_bar_expanded"
-             android:layout_width="match_parent"
-             android:layout_height="match_parent"
-             android:visibility="invisible" />
-
     <!-- Shared container for the notification stack. Can be positioned by either
          the keyguard_root_view or notification_panel -->
     <com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9588498..de8287e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -823,6 +823,12 @@
     <dimen name="keyguard_affordance_fixed_radius">24dp</dimen>
     <dimen name="keyguard_affordance_fixed_padding">12dp</dimen>
 
+    <!--  The width/height/padding of the keyguard settings popup menu  -->
+    <dimen name="keyguard_settings_popup_menu_icon_height">24dp</dimen>
+    <dimen name="keyguard_settings_popup_menu_icon_width">24dp</dimen>
+    <dimen name="keyguard_settings_popup_menu_icon_end_margin">8dp</dimen>
+    <dimen name="keyguard_settings_popup_menu_padding">12dp</dimen>
+
     <!-- Amount the button should shake when it's not long-pressed for long enough. -->
     <dimen name="keyguard_affordance_shake_amplitude">8dp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 03a270e..4495943 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard
 
+import android.content.res.Configuration
 import android.view.View
 import android.view.ViewGroup
 import com.android.systemui.CoreStartable
@@ -24,18 +25,29 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.binder.KeyguardAmbientIndicationAreaViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel
 import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager
 import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommandListener
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.shade.NotificationShadeWindowView
 import com.android.systemui.statusbar.KeyguardIndicationController
 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.statusbar.VibratorHelper
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
@@ -49,26 +61,44 @@
 constructor(
     private val keyguardRootView: KeyguardRootView,
     private val sharedNotificationContainer: SharedNotificationContainer,
+    private val keyguardRootViewModel: KeyguardRootViewModel,
     private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
     private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+    private val keyguardAmbientIndicationViewModel: KeyguardAmbientIndicationViewModel,
     private val notificationShadeWindowView: NotificationShadeWindowView,
     private val featureFlags: FeatureFlags,
     private val indicationController: KeyguardIndicationController,
     private val keyguardLayoutManager: KeyguardLayoutManager,
     private val keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener,
+    private val keyguardQuickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
+    private val falsingManager: FalsingManager,
+    private val vibratorHelper: VibratorHelper,
+    private val keyguardStateController: KeyguardStateController,
+    private val keyguardSettingsMenuViewModel: KeyguardSettingsMenuViewModel,
+    private val activityStarter: ActivityStarter,
     private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
     private val chipbarCoordinator: ChipbarCoordinator,
 ) : CoreStartable {
 
+    private var rootViewHandle: DisposableHandle? = null
     private var indicationAreaHandle: DisposableHandle? = null
+    private var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
+    private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
+    private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null
+    private var settingsPopupMenuHandle: DisposableHandle? = null
 
     override fun start() {
         bindKeyguardRootView()
         val notificationPanel =
             notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
-        bindIndicationArea(notificationPanel)
+        unbindKeyguardBottomArea(notificationPanel)
+        bindIndicationArea()
         bindLockIconView(notificationPanel)
         setupNotificationStackScrollLayout(notificationPanel)
+        bindLeftShortcut()
+        bindRightShortcut()
+        bindAmbientIndicationArea()
+        bindSettingsPopupMenu()
 
         keyguardLayoutManager.layoutViews()
         keyguardLayoutManagerCommandListener.start()
@@ -90,16 +120,21 @@
         }
     }
 
-    fun bindIndicationArea(legacyParent: ViewGroup) {
+    override fun onConfigurationChanged(newConfig: Configuration?) {
+        super.onConfigurationChanged(newConfig)
+        leftShortcutHandle?.onConfigurationChanged()
+        rightShortcutHandle?.onConfigurationChanged()
+        ambientIndicationAreaHandle?.onConfigurationChanged()
+
+        keyguardLayoutManager.layoutViews()
+    }
+
+    fun bindIndicationArea() {
         indicationAreaHandle?.dispose()
 
         // At startup, 2 views with the ID `R.id.keyguard_indication_area` will be available.
         // Disable one of them
-        if (featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) {
-            legacyParent.findViewById<View>(R.id.keyguard_indication_area)?.let {
-                legacyParent.removeView(it)
-            }
-        } else {
+        if (!featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) {
             keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let {
                 keyguardRootView.removeView(it)
             }
@@ -109,10 +144,24 @@
             KeyguardIndicationAreaBinder.bind(
                 notificationShadeWindowView,
                 keyguardIndicationAreaViewModel,
-                indicationController
+                keyguardRootViewModel,
+                indicationController,
+                featureFlags,
             )
     }
 
+    private fun bindKeyguardRootView() {
+        rootViewHandle?.dispose()
+        rootViewHandle = KeyguardRootViewBinder.bind(
+            keyguardRootView,
+            keyguardRootViewModel,
+            featureFlags,
+            occludingAppDeviceEntryMessageViewModel,
+            chipbarCoordinator,
+            keyguardStateController,
+        )
+    }
+
     private fun bindLockIconView(legacyParent: ViewGroup) {
         if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
             legacyParent.requireViewById<View>(R.id.lock_icon_view).let {
@@ -125,12 +174,84 @@
         }
     }
 
-    private fun bindKeyguardRootView() {
-        KeyguardRootViewBinder.bind(
-            keyguardRootView,
-            featureFlags,
-            occludingAppDeviceEntryMessageViewModel,
-            chipbarCoordinator,
-        )
+    private fun bindAmbientIndicationArea() {
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            ambientIndicationAreaHandle?.destroy()
+            ambientIndicationAreaHandle =
+                KeyguardAmbientIndicationAreaViewBinder.bind(
+                    notificationShadeWindowView,
+                    keyguardAmbientIndicationViewModel,
+                    keyguardRootViewModel,
+                )
+        } else {
+            keyguardRootView.findViewById<View?>(R.id.ambient_indication_container)?.let {
+                keyguardRootView.removeView(it)
+            }
+        }
+    }
+
+    private fun bindSettingsPopupMenu() {
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            settingsPopupMenuHandle?.dispose()
+            settingsPopupMenuHandle =
+                KeyguardSettingsViewBinder.bind(
+                    keyguardRootView,
+                    keyguardSettingsMenuViewModel,
+                    vibratorHelper,
+                    activityStarter,
+                )
+        } else {
+            keyguardRootView.findViewById<View?>(R.id.keyguard_settings_button)?.let {
+                keyguardRootView.removeView(it)
+            }
+        }
+    }
+
+    private fun unbindKeyguardBottomArea(legacyParent: ViewGroup) {
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            legacyParent.requireViewById<View>(R.id.keyguard_bottom_area).let {
+                legacyParent.removeView(it)
+            }
+        }
+    }
+
+    private fun bindLeftShortcut() {
+        leftShortcutHandle?.destroy()
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            leftShortcutHandle =
+                KeyguardQuickAffordanceViewBinder.bind(
+                    keyguardRootView.requireViewById(R.id.start_button),
+                    keyguardQuickAffordancesCombinedViewModel.startButton,
+                    keyguardRootViewModel.alpha,
+                    falsingManager,
+                    vibratorHelper,
+                ) {
+                    indicationController.showTransientIndication(it)
+                }
+        } else {
+            keyguardRootView.findViewById<View?>(R.id.start_button)?.let {
+                keyguardRootView.removeView(it)
+            }
+        }
+    }
+
+    private fun bindRightShortcut() {
+        rightShortcutHandle?.destroy()
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            rightShortcutHandle =
+                KeyguardQuickAffordanceViewBinder.bind(
+                    keyguardRootView.requireViewById(R.id.end_button),
+                    keyguardQuickAffordancesCombinedViewModel.endButton,
+                    keyguardRootViewModel.alpha,
+                    falsingManager,
+                    vibratorHelper,
+                ) {
+                    indicationController.showTransientIndication(it)
+                }
+        } else {
+            keyguardRootView.findViewById<View?>(R.id.end_button)?.let {
+                keyguardRootView.removeView(it)
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 7475c42..9ee9902 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
 import com.android.systemui.keyguard.shared.model.ScreenModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
@@ -76,6 +77,8 @@
      */
     val bottomAreaAlpha: StateFlow<Float>
 
+    val keyguardAlpha: StateFlow<Float>
+
     /**
      * Observable of the relative offset of the lock-screen clock from its natural position on the
      * screen.
@@ -170,6 +173,9 @@
     /** Whether quick settings or quick-quick settings is visible. */
     val isQuickSettingsVisible: Flow<Boolean>
 
+    /** Represents the current state of the KeyguardRootView visibility */
+    val keyguardRootViewVisibility: Flow<KeyguardRootViewVisibilityState>
+
     /** Receive an event for doze time tick */
     val dozeTimeTick: Flow<Unit>
 
@@ -194,8 +200,14 @@
     fun setAnimateDozingTransitions(animate: Boolean)
 
     /** Sets the current amount of alpha that should be used for rendering the bottom area. */
+    @Deprecated("Deprecated as part of b/278057014")
     fun setBottomAreaAlpha(alpha: Float)
 
+    /** Sets the current amount of alpha that should be used for rendering the keyguard. */
+    fun setKeyguardAlpha(alpha: Float)
+
+    fun setKeyguardVisibility(statusBarState: Int, goingToFullShade: Boolean, occlusionTransitionRunning: Boolean)
+
     /**
      * Sets the relative offset of the lock-screen clock from its natural position on the screen.
      */
@@ -244,6 +256,9 @@
     private val _bottomAreaAlpha = MutableStateFlow(1f)
     override val bottomAreaAlpha = _bottomAreaAlpha.asStateFlow()
 
+    private val _keyguardAlpha = MutableStateFlow(1f)
+    override val keyguardAlpha = _keyguardAlpha.asStateFlow()
+
     private val _clockPosition = MutableStateFlow(Position(0, 0))
     override val clockPosition = _clockPosition.asStateFlow()
 
@@ -686,6 +701,17 @@
     private val _isActiveDreamLockscreenHosted = MutableStateFlow(false)
     override val isActiveDreamLockscreenHosted = _isActiveDreamLockscreenHosted.asStateFlow()
 
+    private val _keyguardRootViewVisibility =
+        MutableStateFlow(
+            KeyguardRootViewVisibilityState(
+                com.android.systemui.statusbar.StatusBarState.SHADE,
+                goingToFullShade = false,
+                occlusionTransitionRunning = false,
+            )
+        )
+    override val keyguardRootViewVisibility: Flow<KeyguardRootViewVisibilityState> =
+        _keyguardRootViewVisibility.asStateFlow()
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.value = animate
     }
@@ -694,6 +720,23 @@
         _bottomAreaAlpha.value = alpha
     }
 
+    override fun setKeyguardAlpha(alpha: Float) {
+        _keyguardAlpha.value = alpha
+    }
+
+    override fun setKeyguardVisibility(
+        statusBarState: Int,
+        goingToFullShade: Boolean,
+        occlusionTransitionRunning: Boolean
+    ) {
+        _keyguardRootViewVisibility.value =
+            KeyguardRootViewVisibilityState(
+                statusBarState,
+                goingToFullShade,
+                occlusionTransitionRunning
+            )
+    }
+
     override fun setClockPosition(x: Int, y: Int) {
         _clockPosition.value = Position(x, y)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 1553525..cc15916 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.Position
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
@@ -32,14 +33,17 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -63,6 +67,18 @@
     bouncerRepository: KeyguardBouncerRepository,
     configurationRepository: ConfigurationRepository,
 ) {
+
+    data class PreviewMode(
+        val isInPreviewMode: Boolean = false,
+        val shouldHighlightSelectedAffordance: Boolean = false,
+    )
+
+    /**
+     * Whether this view-model instance is powering the preview experience that renders exclusively
+     * in the wallpaper picker application. This should _always_ be `false` for the real lock screen
+     * experience.
+     */
+    val previewMode = MutableStateFlow(PreviewMode())
     /**
      * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
      * all.
@@ -142,8 +158,6 @@
     val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
     /** Observable for the [StatusBarState] */
     val statusBarState: Flow<StatusBarState> = repository.statusBarState
-    /** Whether or not quick settings or quick quick settings are showing. */
-    val isQuickSettingsVisible: Flow<Boolean> = repository.isQuickSettingsVisible
     /**
      * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
      * side, under display) is used to unlock the device.
@@ -182,6 +196,18 @@
     /** Notifies when a new configuration is set */
     val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
 
+    /** Represents the current state of the KeyguardRootView visibility */
+    val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
+        repository.keyguardRootViewVisibility
+
+    /** The position of the keyguard clock. */
+    val clockPosition: Flow<Position> = repository.clockPosition
+
+    val keyguardAlpha: Flow<Float> = repository.keyguardAlpha
+
+    /** Whether to animate the next doze mode transition. */
+    val animateDozingTransitions: Flow<Boolean> = repository.animateBottomAreaDozingTransitions
+
     fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
         return dozeTransitionModel.filter { states.contains(it.to) }
     }
@@ -211,6 +237,22 @@
         repository.setQuickSettingsVisible(isVisible)
     }
 
+    fun setKeyguardRootVisibility(statusBarState: Int, goingToFullShade: Boolean, isOcclusionTransitionRunning: Boolean) {
+        repository.setKeyguardVisibility(statusBarState, goingToFullShade, isOcclusionTransitionRunning)
+    }
+
+    fun setClockPosition(x: Int, y: Int) {
+        repository.setClockPosition(x, y)
+    }
+
+    fun setAlpha(alpha: Float) {
+        repository.setKeyguardAlpha(alpha)
+    }
+
+    fun setAnimateDozingTransitions(animate: Boolean) {
+        repository.setAnimateDozingTransitions(animate)
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 40e0604..a486843 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -101,10 +101,9 @@
             quickAffordanceAlwaysVisible(position),
             keyguardInteractor.isDozing,
             keyguardInteractor.isKeyguardShowing,
-            keyguardInteractor.isQuickSettingsVisible,
             biometricSettingsRepository.isCurrentUserInLockdown,
-        ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
-            if (!isDozing && isKeyguardShowing && !isQuickSettingsVisible && !isUserInLockdown) {
+        ) { affordance, isDozing, isKeyguardShowing, isUserInLockdown ->
+            if (!isDozing && isKeyguardShowing && !isUserInLockdown) {
                 affordance
             } else {
                 KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardRootViewVisibilityState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardRootViewVisibilityState.kt
new file mode 100644
index 0000000..9a57aef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardRootViewVisibilityState.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.shared.model
+
+/**
+ * Provides a stateful representation of the visibility of the KeyguardRootView
+ *
+ * @param statusBarState State of the status bar represented by [StatusBarState]
+ * @param goingToFullShade Whether status bar is going to full shade
+ * @param occlusionTransitionRunning Whether the occlusion transition is running in this instant
+ */
+data class KeyguardRootViewVisibilityState(
+    val statusBarState: Int,
+    val goingToFullShade: Boolean,
+    val occlusionTransitionRunning: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
new file mode 100644
index 0000000..d6883dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.binder
+
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewPropertyAnimator
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+object KeyguardAmbientIndicationAreaViewBinder {
+    /**
+     * Defines interface for an object that acts as the binding between the view and its view-model.
+     *
+     * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
+     * it is bound.
+     */
+    interface Binding {
+        /**
+         * Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the
+         * indication areas.
+         */
+        fun getIndicationAreaAnimators(): List<ViewPropertyAnimator>
+
+        /** Notifies that device configuration has changed. */
+        fun onConfigurationChanged()
+
+        /** Destroys this binding, releases resources, and cancels any coroutines. */
+        fun destroy()
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    fun bind(
+        view: ViewGroup,
+        viewModel: KeyguardAmbientIndicationViewModel,
+        keyguardRootViewModel: KeyguardRootViewModel,
+    ): Binding {
+        val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
+        val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
+
+        val disposableHandle =
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch {
+                        keyguardRootViewModel.alpha.collect { alpha ->
+                            view.importantForAccessibility =
+                                if (alpha == 0f) {
+                                    View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                } else {
+                                    View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                }
+
+                            ambientIndicationArea?.alpha = alpha
+                        }
+                    }
+
+                    launch {
+                        viewModel.indicationAreaTranslationX.collect { translationX ->
+                            ambientIndicationArea?.translationX = translationX
+                        }
+                    }
+
+                    launch {
+                        configurationBasedDimensions
+                            .map { it.defaultBurnInPreventionYOffsetPx }
+                            .flatMapLatest { defaultBurnInOffsetY ->
+                                viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
+                            }
+                            .collect { translationY ->
+                                ambientIndicationArea?.translationY = translationY
+                            }
+                    }
+
+                }
+            }
+
+
+        return object : Binding {
+            override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> {
+                return listOf(ambientIndicationArea).mapNotNull { it?.animate() }
+            }
+
+            override fun onConfigurationChanged() {
+                configurationBasedDimensions.value = loadFromResources(view)
+            }
+
+            override fun destroy() {
+                disposableHandle.dispose()
+            }
+        }
+    }
+
+    private fun loadFromResources(view: View): ConfigurationBasedDimensions {
+        return ConfigurationBasedDimensions(
+            defaultBurnInPreventionYOffsetPx =
+                view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
+        )
+    }
+
+    private data class ConfigurationBasedDimensions(
+        val defaultBurnInPreventionYOffsetPx: Int,
+    )
+}
\ No newline at end of file
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 db84268..a0a2abe 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
@@ -63,6 +63,7 @@
  * view-model to be reused for multiple view/view-binder bindings.
  */
 @OptIn(ExperimentalCoroutinesApi::class)
+@Deprecated("Deprecated as part of b/278057014")
 object KeyguardBottomAreaViewBinder {
 
     private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
@@ -75,6 +76,8 @@
      * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
      * it is bound.
      */
+    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
+    @Deprecated("Deprecated as part of b/278057014")
     interface Binding {
         /**
          * Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the
@@ -96,6 +99,7 @@
     }
 
     /** Binds the view to the view-model, continuing to update the former based on the latter. */
+    @Deprecated("Deprecated as part of b/278057014")
     @SuppressLint("ClickableViewAccessibility")
     @JvmStatic
     fun bind(
@@ -132,6 +136,8 @@
         val disposableHandle =
             view.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
+
+                    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
                     launch {
                         viewModel.startButton.collect { buttonModel ->
                             updateButton(
@@ -144,6 +150,7 @@
                         }
                     }
 
+                    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
                     launch {
                         viewModel.endButton.collect { buttonModel ->
                             updateButton(
@@ -180,6 +187,7 @@
                         }
                     }
 
+                    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
                     launch {
                         updateButtonAlpha(
                             view = startButton,
@@ -188,6 +196,7 @@
                         )
                     }
 
+                    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
                     launch {
                         updateButtonAlpha(
                             view = endButton,
@@ -213,6 +222,7 @@
                             }
                     }
 
+                    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
                     launch {
                         configurationBasedDimensions.collect { dimensions ->
                             startButton.updateLayoutParams<ViewGroup.LayoutParams> {
@@ -287,6 +297,8 @@
         }
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
+    // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
     @SuppressLint("ClickableViewAccessibility")
     private fun updateButton(
         view: ImageView,
@@ -394,6 +406,8 @@
         view.isSelected = viewModel.isSelected
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
+    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
     private suspend fun updateButtonAlpha(
         view: View,
         viewModel: Flow<KeyguardQuickAffordanceViewModel>,
@@ -405,6 +419,7 @@
             .collect { view.alpha = it }
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
     private fun View.animateVisibility(visible: Boolean) {
         animate()
             .withStartAction {
@@ -422,6 +437,8 @@
             .start()
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
+    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
     private class OnLongClickListener(
         private val falsingManager: FalsingManager?,
         private val viewModel: KeyguardQuickAffordanceViewModel,
@@ -455,9 +472,10 @@
         }
 
         override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
-
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
+    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
     private class OnClickListener(
         private val viewModel: KeyguardQuickAffordanceViewModel,
         private val falsingManager: FalsingManager,
@@ -479,6 +497,7 @@
         }
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
     private fun loadFromResources(view: View): ConfigurationBasedDimensions {
         return ConfigurationBasedDimensions(
             defaultBurnInPreventionYOffsetPx =
@@ -491,6 +510,7 @@
         )
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
     /** Opens the wallpaper picker screen after the device is unlocked by the user. */
     private fun navigateToLockScreenSettings(
         activityStarter: ActivityStarter,
@@ -510,6 +530,8 @@
         )
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
+    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
     private data class ConfigurationBasedDimensions(
         val defaultBurnInPreventionYOffsetPx: Int,
         val buttonSizePx: Size,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 02e6765..a385a0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -23,7 +23,10 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.KeyguardIndicationController
 import kotlinx.coroutines.DisposableHandle
@@ -49,7 +52,9 @@
     fun bind(
         view: ViewGroup,
         viewModel: KeyguardIndicationAreaViewModel,
+        keyguardRootViewModel: KeyguardRootViewModel,
         indicationController: KeyguardIndicationController,
+        featureFlags: FeatureFlags,
     ): DisposableHandle {
         val indicationArea: ViewGroup = view.requireViewById(R.id.keyguard_indication_area)
         indicationController.setIndicationArea(indicationArea)
@@ -66,15 +71,28 @@
             view.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
                     launch {
-                        viewModel.alpha.collect { alpha ->
-                            view.importantForAccessibility =
-                                if (alpha == 0f) {
-                                    View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                                } else {
-                                    View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                                }
+                        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+                            keyguardRootViewModel.alpha.collect { alpha ->
+                                view.importantForAccessibility =
+                                    if (alpha == 0f) {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                    } else {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                    }
 
-                            indicationArea.alpha = alpha
+                                indicationArea.alpha = alpha
+                            }
+                        } else {
+                            viewModel.alpha.collect { alpha ->
+                                view.importantForAccessibility =
+                                    if (alpha == 0f) {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                    } else {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                    }
+
+                                indicationArea.alpha = alpha
+                            }
                         }
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
new file mode 100644
index 0000000..63a6791
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -0,0 +1,315 @@
+/*
+ * 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.binder
+
+import android.annotation.SuppressLint
+import android.graphics.drawable.Animatable2
+import android.util.Size
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.core.view.isInvisible
+import androidx.core.view.isVisible
+import androidx.core.view.updateLayoutParams
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.animation.view.LaunchableImageView
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * This is only for a SINGLE Quick affordance
+ */
+object KeyguardQuickAffordanceViewBinder {
+
+    private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
+    private const val SCALE_SELECTED_BUTTON = 1.23f
+    private const val DIM_ALPHA = 0.3f
+
+    /**
+     * Defines interface for an object that acts as the binding between the view and its view-model.
+     *
+     * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
+     * it is bound.
+     */
+    interface Binding {
+        /** Notifies that device configuration has changed. */
+        fun onConfigurationChanged()
+
+        /** Destroys this binding, releases resources, and cancels any coroutines. */
+        fun destroy()
+    }
+
+    fun bind(
+        view: LaunchableImageView,
+        viewModel: Flow<KeyguardQuickAffordanceViewModel>,
+        alpha: Flow<Float>,
+        falsingManager: FalsingManager?,
+        vibratorHelper: VibratorHelper?,
+        messageDisplayer: (Int) -> Unit,
+    ): Binding {
+        val button = view as ImageView
+        val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
+        val disposableHandle =
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch {
+                        viewModel.collect { buttonModel ->
+                            updateButton(
+                                view = button,
+                                viewModel = buttonModel,
+                                falsingManager = falsingManager,
+                                messageDisplayer = messageDisplayer,
+                                vibratorHelper = vibratorHelper,
+                            )
+                        }
+                    }
+
+                    launch {
+                        updateButtonAlpha(
+                            view = button,
+                            viewModel = viewModel,
+                            alphaFlow = alpha,
+                        )
+                    }
+
+                    launch {
+                        configurationBasedDimensions.collect { dimensions ->
+                            button.updateLayoutParams<ViewGroup.LayoutParams> {
+                                width = dimensions.buttonSizePx.width
+                                height = dimensions.buttonSizePx.height
+                            }
+                        }
+                    }
+                }
+            }
+
+        return object : Binding {
+            override fun onConfigurationChanged() {
+                configurationBasedDimensions.value = loadFromResources(view)
+            }
+
+            override fun destroy() {
+                disposableHandle.dispose()
+            }
+        }
+    }
+
+    @SuppressLint("ClickableViewAccessibility")
+    private fun updateButton(
+        view: ImageView,
+        viewModel: KeyguardQuickAffordanceViewModel,
+        falsingManager: FalsingManager?,
+        messageDisplayer: (Int) -> Unit,
+        vibratorHelper: VibratorHelper?,
+    ) {
+        if (!viewModel.isVisible) {
+            view.isInvisible = true
+            return
+        }
+
+        if (!view.isVisible) {
+            view.isVisible = true
+            if (viewModel.animateReveal) {
+                view.alpha = 0f
+                view.translationY = view.height / 2f
+                view
+                    .animate()
+                    .alpha(1f)
+                    .translationY(0f)
+                    .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+                    .setDuration(EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS)
+                    .start()
+            }
+        }
+
+        IconViewBinder.bind(viewModel.icon, view)
+
+        (view.drawable as? Animatable2)?.let { animatable ->
+            (viewModel.icon as? Icon.Resource)?.res?.let { iconResourceId ->
+                // Always start the animation (we do call stop() below, if we need to skip it).
+                animatable.start()
+
+                if (view.tag != iconResourceId) {
+                    // Here when we haven't run the animation on a previous update.
+                    //
+                    // Save the resource ID for next time, so we know not to re-animate the same
+                    // animation again.
+                    view.tag = iconResourceId
+                } else {
+                    // Here when we've already done this animation on a previous update and want to
+                    // skip directly to the final frame of the animation to avoid running it.
+                    //
+                    // By calling stop after start, we go to the final frame of the animation.
+                    animatable.stop()
+                }
+            }
+        }
+
+        view.isActivated = viewModel.isActivated
+        view.drawable.setTint(
+            Utils.getColorAttrDefaultColor(
+                view.context,
+                if (viewModel.isActivated) {
+                    com.android.internal.R.attr.materialColorOnPrimaryFixed
+                } else {
+                    com.android.internal.R.attr.materialColorOnSurface
+                },
+            )
+        )
+
+        view.backgroundTintList =
+            if (!viewModel.isSelected) {
+                Utils.getColorAttr(
+                    view.context,
+                    if (viewModel.isActivated) {
+                        com.android.internal.R.attr.materialColorPrimaryFixed
+                    } else {
+                        com.android.internal.R.attr.materialColorSurfaceContainerHigh
+                    }
+                )
+            } else {
+                null
+            }
+        view
+            .animate()
+            .scaleX(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
+            .scaleY(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
+            .start()
+
+        view.isClickable = viewModel.isClickable
+        if (viewModel.isClickable) {
+            if (viewModel.useLongPress) {
+                val onTouchListener = KeyguardQuickAffordanceOnTouchListener(
+                    view,
+                    viewModel,
+                    messageDisplayer,
+                    vibratorHelper,
+                    falsingManager,
+                )
+                view.setOnTouchListener(onTouchListener)
+                view.onLongClickListener =
+                    OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener)
+            } else {
+                view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
+            }
+        } else {
+            view.onLongClickListener = null
+            view.setOnClickListener(null)
+            view.setOnTouchListener(null)
+        }
+
+        view.isSelected = viewModel.isSelected
+    }
+
+    private suspend fun updateButtonAlpha(
+        view: View,
+        viewModel: Flow<KeyguardQuickAffordanceViewModel>,
+        alphaFlow: Flow<Float>,
+    ) {
+        combine(viewModel.map { it.isDimmed }, alphaFlow) { isDimmed, alpha ->
+            if (isDimmed) DIM_ALPHA else alpha
+        }
+            .collect { view.alpha = it }
+    }
+
+    private fun loadFromResources(view: View): ConfigurationBasedDimensions {
+        return ConfigurationBasedDimensions(
+            buttonSizePx =
+            Size(
+                view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
+                view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+            ),
+        )
+    }
+
+    private class OnClickListener(
+        private val viewModel: KeyguardQuickAffordanceViewModel,
+        private val falsingManager: FalsingManager,
+    ) : View.OnClickListener {
+        override fun onClick(view: View) {
+            if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                return
+            }
+
+            if (viewModel.configKey != null) {
+                viewModel.onClicked(
+                    KeyguardQuickAffordanceViewModel.OnClickedParameters(
+                        configKey = viewModel.configKey,
+                        expandable = Expandable.fromView(view),
+                        slotId = viewModel.slotId,
+                    )
+                )
+            }
+        }
+    }
+
+    private class OnLongClickListener(
+        private val falsingManager: FalsingManager?,
+        private val viewModel: KeyguardQuickAffordanceViewModel,
+        private val vibratorHelper: VibratorHelper?,
+        private val onTouchListener: KeyguardQuickAffordanceOnTouchListener
+    ) : View.OnLongClickListener {
+        override fun onLongClick(view: View): Boolean {
+            if (falsingManager?.isFalseLongTap(FalsingManager.MODERATE_PENALTY) == true) {
+                return true
+            }
+
+            if (viewModel.configKey != null) {
+                viewModel.onClicked(
+                    KeyguardQuickAffordanceViewModel.OnClickedParameters(
+                        configKey = viewModel.configKey,
+                        expandable = Expandable.fromView(view),
+                        slotId = viewModel.slotId,
+                    )
+                )
+                vibratorHelper?.vibrate(
+                    if (viewModel.isActivated) {
+                        KeyguardBottomAreaVibrations.Activated
+                    } else {
+                        KeyguardBottomAreaVibrations.Deactivated
+                    }
+                )
+            }
+
+            onTouchListener.cancel()
+            return true
+        }
+
+        override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
+
+    }
+
+    private data class ConfigurationBasedDimensions(
+        val buttonSizePx: Size,
+    )
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 1db596b..19f622b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -17,20 +17,26 @@
 package com.android.systemui.keyguard.ui.binder
 
 import android.annotation.DrawableRes
+import android.view.View
 import android.view.ViewGroup
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.shared.model.TintedIcon
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.temporarydisplay.ViewPriority
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
+import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 
@@ -40,31 +46,74 @@
     @JvmStatic
     fun bind(
         view: ViewGroup,
+        viewModel: KeyguardRootViewModel,
         featureFlags: FeatureFlags,
         occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
         chipbarCoordinator: ChipbarCoordinator,
-    ) {
-        if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
+        keyguardStateController: KeyguardStateController,
+    ): DisposableHandle {
+        val disposableHandle =
             view.repeatWhenAttached {
-                repeatOnLifecycle(Lifecycle.State.CREATED) {
-                    launch {
-                        occludingAppDeviceEntryMessageViewModel.message.collect { biometricMessage
-                            ->
-                            if (biometricMessage?.message != null) {
-                                chipbarCoordinator.displayView(
-                                    createChipbarInfo(
-                                        biometricMessage.message,
-                                        R.drawable.ic_lock,
+                if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
+                    repeatOnLifecycle(Lifecycle.State.CREATED) {
+                        launch {
+                            occludingAppDeviceEntryMessageViewModel.message.collect {
+                                biometricMessage ->
+                                if (biometricMessage?.message != null) {
+                                    chipbarCoordinator.displayView(
+                                        createChipbarInfo(
+                                            biometricMessage.message,
+                                            R.drawable.ic_lock,
+                                        )
                                     )
-                                )
-                            } else {
-                                chipbarCoordinator.removeView(ID, "occludingAppMsgNull")
+                                } else {
+                                    chipbarCoordinator.removeView(ID, "occludingAppMsgNull")
+                                }
+                            }
+                        }
+                    }
+                }
+
+                if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+                    repeatOnLifecycle(Lifecycle.State.STARTED) {
+                        launch {
+                            viewModel.keyguardRootViewVisibilityState.collect { visibilityState ->
+                                view.animate().cancel()
+                                val goingToFullShade = visibilityState.goingToFullShade
+                                val statusBarState = visibilityState.statusBarState
+                                val isOcclusionTransitionRunning =
+                                    visibilityState.occlusionTransitionRunning
+                                if (goingToFullShade) {
+                                    view.animate().alpha(0f).setStartDelay(
+                                        keyguardStateController.keyguardFadingAwayDelay
+                                    ).setDuration(
+                                        keyguardStateController.shortenedFadingAwayDuration
+                                    ).setInterpolator(
+                                        Interpolators.ALPHA_OUT
+                                    ).withEndAction { view.visibility = View.GONE }.start()
+                                } else if (
+                                    statusBarState == StatusBarState.KEYGUARD ||
+                                    statusBarState == StatusBarState.SHADE_LOCKED
+                                ) {
+                                    view.visibility = View.VISIBLE
+                                    if (!isOcclusionTransitionRunning) {
+                                        view.alpha = 1f
+                                    }
+                                } else {
+                                    view.visibility = View.GONE
+                                }
+                            }
+                        }
+
+                        launch {
+                            viewModel.alpha.collect { alpha ->
+                                view.alpha = alpha
                             }
                         }
                     }
                 }
             }
-        }
+        return disposableHandle
     }
 
     /**
@@ -73,10 +122,10 @@
     private fun createChipbarInfo(message: String, @DrawableRes icon: Int): ChipbarInfo {
         return ChipbarInfo(
             startIcon =
-                TintedIcon(
-                    Icon.Resource(icon, null),
-                    ChipbarInfo.DEFAULT_ICON_TINT,
-                ),
+            TintedIcon(
+                Icon.Resource(icon, null),
+                ChipbarInfo.DEFAULT_ICON_TINT,
+            ),
             text = Text.Loaded(message),
             endItem = null,
             vibrationEffect = null,
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
new file mode 100644
index 0000000..162c109
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.binder
+
+import android.content.Intent
+import android.view.View
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+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.KeyguardSettingsMenuViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.VibratorHelper
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.launch
+
+object KeyguardSettingsViewBinder {
+    fun bind(
+        parentView: View,
+        viewModel: KeyguardSettingsMenuViewModel,
+        vibratorHelper: VibratorHelper,
+        activityStarter: ActivityStarter
+    ): DisposableHandle {
+        val view = parentView.findViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
+
+        val disposableHandle =
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch {
+                        viewModel.isVisible.distinctUntilChanged().collect { isVisible ->
+                            view.animateVisibility(visible = isVisible)
+                            if (isVisible) {
+                                vibratorHelper.vibrate(KeyguardBottomAreaVibrations.Activated)
+                                view.setOnTouchListener(
+                                    KeyguardSettingsButtonOnTouchListener(
+                                        view = view,
+                                        viewModel = viewModel,
+                                    )
+                                )
+                                IconViewBinder.bind(
+                                    icon = viewModel.icon,
+                                    view = view.requireViewById(R.id.icon),
+                                )
+                                TextViewBinder.bind(
+                                    view = view.requireViewById(R.id.text),
+                                    viewModel = viewModel.text,
+                                )
+                            }
+                        }
+                    }
+
+                    // activityStarter will only be null when rendering the preview that
+                    // shows up in the Wallpaper Picker app. If we do that, then the
+                    // settings menu should never be visible.
+                    if (activityStarter != null) {
+                        launch {
+                            viewModel.shouldOpenSettings
+                                .filter { it }
+                                .collect {
+                                    navigateToLockScreenSettings(
+                                        activityStarter = activityStarter,
+                                        view = view,
+                                    )
+                                    viewModel.onSettingsShown()
+                                }
+                        }
+                    }
+                }
+            }
+        return disposableHandle
+    }
+
+    /** Opens the wallpaper picker screen after the device is unlocked by the user. */
+    private fun navigateToLockScreenSettings(
+        activityStarter: ActivityStarter,
+        view: View,
+    ) {
+        activityStarter.postStartActivityDismissingKeyguard(
+            Intent(Intent.ACTION_SET_WALLPAPER).apply {
+                flags = Intent.FLAG_ACTIVITY_NEW_TASK
+                view.context
+                    .getString(R.string.config_wallpaperPickerPackage)
+                    .takeIf { it.isNotEmpty() }
+                    ?.let { packageName -> setPackage(packageName) }
+            },
+            /* delay= */ 0,
+            /* animationController= */ ActivityLaunchAnimator.Controller.fromView(view),
+            /* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls)
+        )
+    }
+
+    private fun View.animateVisibility(visible: Boolean) {
+        animate()
+            .withStartAction {
+                if (visible) {
+                    alpha = 0f
+                    isVisible = true
+                }
+            }
+            .alpha(if (visible) 1f else 0f)
+            .withEndAction {
+                if (!visible) {
+                    isVisible = false
+                }
+            }
+            .start()
+    }
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index b92d104..3e6e158 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -42,17 +42,27 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.shared.clocks.DefaultClockController
 import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
+import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
 import dagger.assisted.Assisted
@@ -71,6 +81,7 @@
     private val clockViewModel: KeyguardPreviewClockViewModel,
     private val smartspaceViewModel: KeyguardPreviewSmartspaceViewModel,
     private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
+    private val quickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
     displayManager: DisplayManager,
     private val windowManager: WindowManager,
     private val clockController: ClockEventController,
@@ -78,6 +89,12 @@
     private val broadcastDispatcher: BroadcastDispatcher,
     private val lockscreenSmartspaceController: LockscreenSmartspaceController,
     private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
+    private val featureFlags: FeatureFlags,
+    private val keyguardLayoutManager: KeyguardLayoutManager,
+    private val falsingManager: FalsingManager,
+    private val vibratorHelper: VibratorHelper,
+    private val indicationController: KeyguardIndicationController,
+    private val keyguardRootViewModel: KeyguardRootViewModel,
     @Assisted bundle: Bundle,
 ) {
 
@@ -106,14 +123,26 @@
     private val disposables = mutableSetOf<DisposableHandle>()
     private var isDestroyed = false
 
+    private val shortcutsBindings = mutableSetOf<KeyguardQuickAffordanceViewBinder.Binding>()
+
     init {
-        bottomAreaViewModel.enablePreviewMode(
-            initiallySelectedSlotId =
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            keyguardRootViewModel.enablePreviewMode(
+                initiallySelectedSlotId =
                 bundle.getString(
                     KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
                 ),
-            shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
-        )
+                shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
+            )
+        } else {
+            bottomAreaViewModel.enablePreviewMode(
+                initiallySelectedSlotId =
+                bundle.getString(
+                    KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
+                ),
+                shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
+            )
+        }
         runBlocking(mainDispatcher) {
             host =
                 SurfaceControlViewHost(
@@ -130,7 +159,20 @@
         mainHandler.post {
             val rootView = FrameLayout(context)
 
-            setUpBottomArea(rootView)
+            if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+                val keyguardRootView = KeyguardRootView(context, null)
+                rootView.addView(
+                    keyguardRootView,
+                    FrameLayout.LayoutParams(
+                        FrameLayout.LayoutParams.MATCH_PARENT,
+                        FrameLayout.LayoutParams.MATCH_PARENT,
+                    ),
+                )
+                setupShortcuts(keyguardRootView)
+                keyguardLayoutManager.layoutViews(keyguardRootView)
+            } else {
+                setUpBottomArea(rootView)
+            }
 
             setUpSmartspace(rootView)
             smartSpaceView?.let {
@@ -182,13 +224,20 @@
     }
 
     fun onSlotSelected(slotId: String) {
-        bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            quickAffordancesCombinedViewModel.onPreviewSlotSelected(slotId = slotId)
+        } else {
+            bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
+        }
     }
 
     fun destroy() {
         isDestroyed = true
         lockscreenSmartspaceController.disconnect()
         disposables.forEach { it.dispose() }
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            shortcutsBindings.forEach { it.destroy() }
+        }
     }
 
     /**
@@ -250,6 +299,7 @@
         smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
     private fun setUpBottomArea(parentView: ViewGroup) {
         val bottomAreaView =
             LayoutInflater.from(context)
@@ -259,7 +309,7 @@
                     false,
                 ) as KeyguardBottomAreaView
         bottomAreaView.init(
-            viewModel = bottomAreaViewModel,
+                viewModel = bottomAreaViewModel,
         )
         parentView.addView(
             bottomAreaView,
@@ -270,6 +320,32 @@
         )
     }
 
+    private fun setupShortcuts(keyguardRootView: KeyguardRootView) {
+        shortcutsBindings.add(
+            KeyguardQuickAffordanceViewBinder.bind(
+                keyguardRootView.requireViewById(R.id.start_button),
+                quickAffordancesCombinedViewModel.startButton,
+                keyguardRootViewModel.alpha,
+                falsingManager,
+                vibratorHelper,
+            ) {
+                indicationController.showTransientIndication(it)
+            }
+        )
+
+        shortcutsBindings.add(
+            KeyguardQuickAffordanceViewBinder.bind(
+                keyguardRootView.requireViewById(R.id.end_button),
+                quickAffordancesCombinedViewModel.endButton,
+                keyguardRootViewModel.alpha,
+                falsingManager,
+                vibratorHelper,
+            ) {
+                indicationController.showTransientIndication(it)
+            }
+        )
+    }
+
     private fun setUpUdfps(parentView: ViewGroup) {
         val sensorBounds = udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
index 0077f2d..65fe990 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
@@ -20,8 +20,13 @@
 import android.content.Context
 import android.util.AttributeSet
 import androidx.constraintlayout.widget.ConstraintLayout
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.ImageView
+import androidx.core.content.res.ResourcesCompat
 import com.android.keyguard.LockIconView
 import com.android.systemui.R
+import com.android.systemui.animation.view.LaunchableImageView
 
 /** Provides a container for all keyguard ui content. */
 class KeyguardRootView(
@@ -36,6 +41,10 @@
     init {
         addIndicationTextArea()
         addLockIconView()
+        addAmbientIndicationArea()
+        addLeftShortcut()
+        addRightShortcut()
+        addSettingsPopupMenu()
     }
 
     private fun addIndicationTextArea() {
@@ -47,4 +56,65 @@
         val view = LockIconView(context, attrs).apply { id = R.id.lock_icon_view }
         addView(view)
     }
+
+    private fun addAmbientIndicationArea() {
+        LayoutInflater.from(context).inflate(R.layout.ambient_indication, this)
+    }
+
+    private fun addLeftShortcut() {
+        val view = LaunchableImageView(context, attrs)
+            .apply {
+                id = R.id.start_button
+                scaleType = ImageView.ScaleType.FIT_CENTER
+                background =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_bg,
+                        context.theme
+                    )
+                foreground =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_selected_border,
+                        context.theme
+                    )
+                visibility = View.INVISIBLE
+            }
+        addView(view)
+    }
+
+    private fun addRightShortcut() {
+        val view = LaunchableImageView(context, attrs)
+            .apply {
+                id = R.id.end_button
+                scaleType = ImageView.ScaleType.FIT_CENTER
+                background =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_bg,
+                        context.theme
+                    )
+                foreground =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_selected_border,
+                        context.theme
+                    )
+                visibility = View.INVISIBLE
+            }
+        addView(view)
+    }
+
+    private fun addSettingsPopupMenu() {
+        val view = LayoutInflater.from(context).inflate(
+            R.layout.keyguard_settings_popup_menu,
+            this,
+            false
+        )
+        .apply {
+            id = R.id.keyguard_settings_button
+            visibility = GONE
+        }
+        addView(view)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt
index baaeb60..6be45c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt
@@ -24,17 +24,27 @@
 import android.view.View
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.ViewGroup.MarginLayoutParams
 import android.view.WindowManager
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
 import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.LEFT
+import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.RIGHT
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.core.view.setPadding
+import androidx.core.view.updateLayoutParams
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
+import com.android.systemui.animation.view.LaunchableLinearLayout
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
@@ -136,15 +146,139 @@
         }
     }
 
+    override fun layoutShortcuts(rootView: KeyguardRootView) {
+        val leftShortcut = rootView.findViewById<View>(R.id.start_button) ?: return
+        val rightShortcut = rootView.findViewById<View>(R.id.end_button) ?: return
+        val width =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
+        val height =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
+        val horizontalOffsetMargin =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
+        val verticalOffsetMargin =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
+        val padding =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_padding)
+
+        leftShortcut.apply {
+            updateLayoutParams<MarginLayoutParams> {
+                marginStart = horizontalOffsetMargin
+                bottomMargin = verticalOffsetMargin
+            }
+            setPadding(padding)
+        }
+
+        rightShortcut.apply {
+            updateLayoutParams<MarginLayoutParams> {
+                marginEnd = horizontalOffsetMargin
+                bottomMargin = verticalOffsetMargin
+            }
+            setPadding(padding)
+        }
+
+        rootView.getConstraintSet().apply {
+            constrainWidth(leftShortcut.id, width)
+            constrainHeight(leftShortcut.id, height)
+            connect(leftShortcut.id, LEFT, PARENT_ID, LEFT)
+            connect(leftShortcut.id, BOTTOM, PARENT_ID, BOTTOM)
+
+            constrainWidth(rightShortcut.id, width)
+            constrainHeight(rightShortcut.id, height)
+            connect(rightShortcut.id, RIGHT, PARENT_ID, RIGHT)
+            connect(rightShortcut.id, BOTTOM, PARENT_ID, BOTTOM)
+            applyTo(rootView)
+        }
+    }
+
+    override fun layoutAmbientIndicationArea(rootView: KeyguardRootView) {
+        val ambientIndicationContainer =
+            rootView.findViewById<View>(R.id.ambient_indication_container) ?: return
+        val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
+        val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return
+
+        rootView.getConstraintSet().apply {
+            constrainWidth(ambientIndicationContainer.id, MATCH_PARENT)
+
+            if (keyguardUpdateMonitor.isUdfpsSupported) {
+                //constrain below udfps and above indication area
+                constrainHeight(ambientIndicationContainer.id, MATCH_CONSTRAINT)
+                connect(ambientIndicationContainer.id, TOP, lockIconView.id, BOTTOM)
+                connect(ambientIndicationContainer.id, BOTTOM, indicationArea.id, TOP)
+                connect(ambientIndicationContainer.id, LEFT, PARENT_ID, LEFT)
+                connect(ambientIndicationContainer.id, RIGHT, PARENT_ID, RIGHT)
+            } else {
+                //constrain above lock icon
+                constrainHeight(ambientIndicationContainer.id, WRAP_CONTENT)
+                connect(ambientIndicationContainer.id, BOTTOM, lockIconView.id, TOP)
+                connect(ambientIndicationContainer.id, LEFT, PARENT_ID, LEFT)
+                connect(ambientIndicationContainer.id, RIGHT, PARENT_ID, RIGHT)
+            }
+
+            applyTo(rootView)
+        }
+    }
+
+    override fun layoutSettingsPopupMenu(rootView: KeyguardRootView) {
+        val popupMenu =
+            rootView.findViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button) ?: return
+        val icon = popupMenu.findViewById<ImageView>(R.id.icon) ?: return
+        val textView = popupMenu.findViewById<TextView>(R.id.text) ?: return
+        val horizontalOffsetMargin =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
+
+        icon.updateLayoutParams<LinearLayout.LayoutParams> {
+            height =
+                context
+                    .resources
+                    .getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_height)
+            width =
+                context
+                    .resources
+                    .getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_width)
+            marginEnd =
+                context
+                    .resources
+                    .getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_end_margin)
+        }
+
+        textView.updateLayoutParams<LinearLayout.LayoutParams> {
+            height = WRAP_CONTENT
+            width = WRAP_CONTENT
+        }
+
+        popupMenu.updateLayoutParams<MarginLayoutParams> {
+            bottomMargin =
+                context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
+            marginStart = horizontalOffsetMargin
+            marginEnd = horizontalOffsetMargin
+        }
+        popupMenu.setPadding(
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_padding)
+        )
+
+        rootView.getConstraintSet().apply {
+            constrainWidth(popupMenu.id, WRAP_CONTENT)
+            constrainHeight(popupMenu.id, WRAP_CONTENT)
+            constrainMinHeight(
+                popupMenu.id,
+                context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
+            )
+            connect(popupMenu.id, LEFT, PARENT_ID, LEFT)
+            connect(popupMenu.id, RIGHT, PARENT_ID, RIGHT)
+            connect(popupMenu.id, BOTTOM, PARENT_ID, BOTTOM)
+
+            applyTo(rootView)
+        }
+    }
+
     private fun Int.dp(): Int {
         return context.resources.getDimensionPixelSize(this)
     }
 
-    private fun ConstraintLayout.getConstraintSet(): ConstraintSet {
-        val cs = ConstraintSet()
-        cs.clone(this)
-        return cs
-    }
+    private fun ConstraintLayout.getConstraintSet(): ConstraintSet =
+        ConstraintSet().also {
+            it.clone(this)
+        }
 
     companion object {
         const val DEFAULT = "default"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt
index 9bc6302..6973cd9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt
@@ -63,8 +63,8 @@
         return true
     }
 
-    fun layoutViews() {
-        layout?.layoutViews(keyguardRootView)
+    fun layoutViews(rootView: KeyguardRootView = keyguardRootView) {
+        layout?.layoutViews(rootView)
     }
 
     companion object {
@@ -85,7 +85,13 @@
             .applyTo(rootView)
         layoutIndicationArea(rootView)
         layoutLockIcon(rootView)
+        layoutShortcuts(rootView)
+        layoutAmbientIndicationArea(rootView)
+        layoutSettingsPopupMenu(rootView)
     }
     fun layoutIndicationArea(rootView: KeyguardRootView)
     fun layoutLockIcon(rootView: KeyguardRootView)
+    fun layoutShortcuts(rootView: KeyguardRootView)
+    fun layoutAmbientIndicationArea(rootView: KeyguardRootView)
+    fun layoutSettingsPopupMenu(rootView: KeyguardRootView)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt
index 00f93e3..c0447af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt
@@ -28,4 +28,10 @@
     abstract fun bindDefaultLayout(
         defaultLockscreenLayout: DefaultLockscreenLayout
     ): LockscreenLayout
+
+    @Binds
+    @IntoSet
+    abstract fun bindShortcutsBesideUdfpsLockscreenLayout(
+        shortcutsBesideUdfpsLockscreenLayout: ShortcutsBesideUdfpsLockscreenLayout
+    ): LockscreenLayout
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/ShortcutsBesideUdfpsLockscreenLayout.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/ShortcutsBesideUdfpsLockscreenLayout.kt
new file mode 100644
index 0000000..569762d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/ShortcutsBesideUdfpsLockscreenLayout.kt
@@ -0,0 +1,305 @@
+/*
+ * 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.view.layout
+
+import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
+import android.util.DisplayMetrics
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.WindowManager
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.annotation.VisibleForTesting
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.LEFT
+import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.RIGHT
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.core.view.setPadding
+import androidx.core.view.updateLayoutParams
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.animation.view.LaunchableLinearLayout
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import javax.inject.Inject
+
+/**
+ * Positions elements of the lockscreen to the default position.
+ *
+ * This will be the most common use case for phones in portrait mode.
+ */
+@SysUISingleton
+class ShortcutsBesideUdfpsLockscreenLayout
+@Inject
+constructor(
+    private val authController: AuthController,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val windowManager: WindowManager,
+    private val context: Context,
+) : LockscreenLayout {
+    override val id: String = SHORTCUTS_BESIDE_UDFPS
+
+    override fun layoutIndicationArea(rootView: KeyguardRootView) {
+        val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return
+
+        rootView.getConstraintSet().apply {
+            constrainWidth(indicationArea.id, MATCH_PARENT)
+            constrainHeight(indicationArea.id, WRAP_CONTENT)
+            connect(
+                indicationArea.id,
+                BOTTOM,
+                PARENT_ID,
+                BOTTOM,
+                R.dimen.keyguard_indication_margin_bottom.dp()
+            )
+            connect(indicationArea.id, START, PARENT_ID, START)
+            connect(indicationArea.id, END, PARENT_ID, END)
+            applyTo(rootView)
+        }
+    }
+
+    override fun layoutLockIcon(rootView: KeyguardRootView) {
+        val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
+        val scaleFactor: Float = authController.scaleFactor
+        val mBottomPaddingPx = R.dimen.lock_icon_margin_bottom.dp()
+        val mDefaultPaddingPx = R.dimen.lock_icon_padding.dp()
+        val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt()
+        val bounds = windowManager.currentWindowMetrics.bounds
+        val widthPixels = bounds.right.toFloat()
+        val heightPixels = bounds.bottom.toFloat()
+        val defaultDensity =
+            DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+                DisplayMetrics.DENSITY_DEFAULT.toFloat()
+        val lockIconRadiusPx = (defaultDensity * 36).toInt()
+
+        if (isUdfpsSupported) {
+            authController.udfpsLocation?.let { udfpsLocation ->
+                centerLockIcon(udfpsLocation, authController.udfpsRadius, scaledPadding, rootView)
+            }
+        } else {
+            centerLockIcon(
+                Point(
+                    (widthPixels / 2).toInt(),
+                    (heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
+                ),
+                lockIconRadiusPx * scaleFactor,
+                scaledPadding,
+                rootView
+            )
+        }
+    }
+
+    @VisibleForTesting
+    internal fun centerLockIcon(
+        center: Point,
+        radius: Float,
+        drawablePadding: Int,
+        rootView: KeyguardRootView,
+    ) {
+        val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
+        val lockIcon = lockIconView.findViewById<View>(R.id.lock_icon) ?: return
+        lockIcon.setPadding(drawablePadding, drawablePadding, drawablePadding, drawablePadding)
+
+        val sensorRect =
+            Rect().apply {
+                set(
+                    center.x - radius.toInt(),
+                    center.y - radius.toInt(),
+                    center.x + radius.toInt(),
+                    center.y + radius.toInt(),
+                )
+            }
+
+        rootView.getConstraintSet().apply {
+            constrainWidth(lockIconView.id, sensorRect.right - sensorRect.left)
+            constrainHeight(lockIconView.id, sensorRect.bottom - sensorRect.top)
+            connect(lockIconView.id, TOP, PARENT_ID, TOP, sensorRect.top)
+            connect(lockIconView.id, START, PARENT_ID, START, sensorRect.left)
+            applyTo(rootView)
+        }
+    }
+
+    override fun layoutShortcuts(rootView: KeyguardRootView) {
+        val leftShortcut = rootView.findViewById<View>(R.id.start_button) ?: return
+        val rightShortcut = rootView.findViewById<View>(R.id.end_button) ?: return
+        val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
+        val udfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
+        val width =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
+        val height =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
+        val horizontalOffsetMargin =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
+        val verticalOffsetMargin =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
+        val padding =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_padding)
+
+        if (!udfpsSupported) {
+            leftShortcut.apply {
+                updateLayoutParams<ViewGroup.MarginLayoutParams> {
+                    marginStart = horizontalOffsetMargin
+                    bottomMargin = verticalOffsetMargin
+                }
+                setPadding(padding)
+            }
+
+            rightShortcut.apply {
+                updateLayoutParams<ViewGroup.MarginLayoutParams> {
+                    marginEnd = horizontalOffsetMargin
+                    bottomMargin = verticalOffsetMargin
+                }
+                setPadding(padding)
+            }
+        }
+
+        rootView.getConstraintSet().apply {
+            if (udfpsSupported) {
+                constrainWidth(leftShortcut.id, width)
+                constrainHeight(leftShortcut.id, height)
+                connect(leftShortcut.id, LEFT, PARENT_ID, LEFT)
+                connect(leftShortcut.id, RIGHT, lockIconView.id, LEFT)
+                connect(leftShortcut.id, TOP, lockIconView.id, TOP)
+                connect(leftShortcut.id, BOTTOM, lockIconView.id, BOTTOM)
+
+                constrainWidth(rightShortcut.id, width)
+                constrainHeight(rightShortcut.id, height)
+                connect(rightShortcut.id, RIGHT, PARENT_ID, RIGHT)
+                connect(rightShortcut.id, LEFT, lockIconView.id, RIGHT)
+                connect(rightShortcut.id, TOP, lockIconView.id, TOP)
+                connect(rightShortcut.id, BOTTOM, lockIconView.id, BOTTOM)
+            } else {
+                constrainWidth(leftShortcut.id, width)
+                constrainHeight(leftShortcut.id, height)
+                connect(leftShortcut.id, LEFT, PARENT_ID, LEFT)
+                connect(leftShortcut.id, BOTTOM, PARENT_ID, BOTTOM)
+
+                constrainWidth(rightShortcut.id, width)
+                constrainHeight(rightShortcut.id, height)
+                connect(rightShortcut.id, RIGHT, PARENT_ID, RIGHT)
+                connect(rightShortcut.id, BOTTOM, PARENT_ID, BOTTOM)
+            }
+            applyTo(rootView)
+        }
+    }
+
+    override fun layoutAmbientIndicationArea(rootView: KeyguardRootView) {
+        val ambientIndicationContainer =
+            rootView.findViewById<View>(R.id.ambient_indication_container) ?: return
+        val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
+        val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return
+
+        rootView.getConstraintSet().apply {
+            constrainWidth(ambientIndicationContainer.id, MATCH_PARENT)
+
+            if (keyguardUpdateMonitor.isUdfpsSupported) {
+                //constrain below udfps and above indication area
+                constrainHeight(ambientIndicationContainer.id, MATCH_CONSTRAINT)
+                connect(ambientIndicationContainer.id, TOP, lockIconView.id, BOTTOM)
+                connect(ambientIndicationContainer.id, BOTTOM, indicationArea.id, TOP)
+                connect(ambientIndicationContainer.id, LEFT, PARENT_ID, LEFT)
+                connect(ambientIndicationContainer.id, RIGHT, PARENT_ID, RIGHT)
+            } else {
+                //constrain above lock icon
+                constrainHeight(ambientIndicationContainer.id, WRAP_CONTENT)
+                connect(ambientIndicationContainer.id, BOTTOM, lockIconView.id, TOP)
+                connect(ambientIndicationContainer.id, LEFT, PARENT_ID, LEFT)
+                connect(ambientIndicationContainer.id, RIGHT, PARENT_ID, RIGHT)
+            }
+            applyTo(rootView)
+        }
+    }
+
+    override fun layoutSettingsPopupMenu(rootView: KeyguardRootView) {
+        val popupMenu =
+            rootView.findViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button) ?: return
+        val icon = popupMenu.findViewById<ImageView>(R.id.icon) ?: return
+        val textView = popupMenu.findViewById<TextView>(R.id.text) ?: return
+        val horizontalOffsetMargin =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
+
+        icon.updateLayoutParams<LinearLayout.LayoutParams> {
+            height =
+                context
+                    .resources
+                    .getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_height)
+            width =
+                context
+                    .resources
+                    .getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_width)
+            marginEnd =
+                context
+                    .resources
+                    .getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_end_margin)
+        }
+
+        textView.updateLayoutParams<LinearLayout.LayoutParams> {
+            height = WRAP_CONTENT
+            width = WRAP_CONTENT
+        }
+
+        popupMenu.updateLayoutParams<ViewGroup.MarginLayoutParams> {
+            bottomMargin =
+                context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
+            marginStart = horizontalOffsetMargin
+            marginEnd = horizontalOffsetMargin
+        }
+        popupMenu.setPadding(
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_padding)
+        )
+
+        rootView.getConstraintSet().apply {
+            constrainWidth(popupMenu.id, WRAP_CONTENT)
+            constrainHeight(popupMenu.id, WRAP_CONTENT)
+            constrainMinHeight(
+                popupMenu.id,
+                context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
+            )
+            connect(popupMenu.id, LEFT, PARENT_ID, LEFT)
+            connect(popupMenu.id, RIGHT, PARENT_ID, RIGHT)
+            connect(popupMenu.id, BOTTOM, PARENT_ID, BOTTOM)
+
+            applyTo(rootView)
+        }
+    }
+
+    private fun Int.dp(): Int {
+        return context.resources.getDimensionPixelSize(this)
+    }
+
+    private fun ConstraintLayout.getConstraintSet(): ConstraintSet =
+        ConstraintSet().also {
+            it.clone(this)
+        }
+
+    companion object {
+        const val SHORTCUTS_BESIDE_UDFPS = "shortcutsBesideUdfps"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt
new file mode 100644
index 0000000..dd3967a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.viewmodel
+
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+class KeyguardAmbientIndicationViewModel
+@Inject
+constructor(
+    private val keyguardInteractor: KeyguardInteractor,
+    private val burnInHelperWrapper: BurnInHelperWrapper,
+) {
+
+    /** An observable for the x-offset by which the indication area should be translated. */
+    val indicationAreaTranslationX: Flow<Float> =
+        keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
+
+    /** Returns an observable for the y-offset by which the indication area should be translated. */
+    fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
+        return keyguardInteractor.dozeAmount
+            .map { dozeAmount ->
+                dozeAmount *
+                    (burnInHelperWrapper.burnInOffset(
+                        /* amplitude = */ defaultBurnInOffset * 2,
+                        /* xAxis= */ false,
+                    ) - defaultBurnInOffset)
+            }
+            .distinctUntilChanged()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
index f1ceaaa..980cc1b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import javax.inject.Inject
@@ -33,6 +35,8 @@
     bottomAreaInteractor: KeyguardBottomAreaInteractor,
     keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
     private val burnInHelperWrapper: BurnInHelperWrapper,
+    private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
+    private val featureFlags: FeatureFlags,
 ) {
 
     /** Notifies when a new configuration is set */
@@ -43,15 +47,28 @@
 
     /** An observable for whether the indication area should be padded. */
     val isIndicationAreaPadded: Flow<Boolean> =
-        combine(keyguardBottomAreaViewModel.startButton, keyguardBottomAreaViewModel.endButton) {
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            combine(shortcutsCombinedViewModel.startButton, shortcutsCombinedViewModel.endButton) {
                 startButtonModel,
                 endButtonModel ->
                 startButtonModel.isVisible || endButtonModel.isVisible
             }
-            .distinctUntilChanged()
+                .distinctUntilChanged()
+        } else {
+            combine(keyguardBottomAreaViewModel.startButton, keyguardBottomAreaViewModel.endButton) {
+                startButtonModel,
+                endButtonModel ->
+                startButtonModel.isVisible || endButtonModel.isVisible
+            }
+                .distinctUntilChanged()
+        }
     /** An observable for the x-offset by which the indication area should be translated. */
     val indicationAreaTranslationX: Flow<Float> =
-        bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
+        } else {
+            bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
+        }
 
     /** Returns an observable for the y-offset by which the indication area should be translated. */
     fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
new file mode 100644
index 0000000..56a98455
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.viewmodel
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class KeyguardQuickAffordancesCombinedViewModel
+@Inject
+constructor(
+    private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
+    private val keyguardInteractor: KeyguardInteractor,
+) {
+
+    /**
+     * ID of the slot that's currently selected in the preview that renders exclusively in the
+     * wallpaper picker application. This is ignored for the actual, real lock screen experience.
+     */
+    private val selectedPreviewSlotId =
+        MutableStateFlow(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
+
+    /**
+     * Whether quick affordances are "opaque enough" to be considered visible to and interactive by
+     * the user. If they are not interactive, user input should not be allowed on them.
+     *
+     * Note that there is a margin of error, where we allow very, very slightly transparent views to
+     * be considered "fully opaque" for the purpose of being interactive. This is to accommodate the
+     * error margin of floating point arithmetic.
+     *
+     * A view that is visible but with an alpha of less than our threshold either means it's not
+     * fully done fading in or is fading/faded out. Either way, it should not be
+     * interactive/clickable unless "fully opaque" to avoid issues like in b/241830987.
+     */
+    private val areQuickAffordancesFullyOpaque: Flow<Boolean> =
+        keyguardInteractor.keyguardAlpha
+            .map { alpha -> alpha >= AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD }
+            .distinctUntilChanged()
+
+    /** An observable for the view-model of the "start button" quick affordance. */
+    val startButton: Flow<KeyguardQuickAffordanceViewModel> =
+        button(KeyguardQuickAffordancePosition.BOTTOM_START)
+
+    /** An observable for the view-model of the "end button" quick affordance. */
+    val endButton: Flow<KeyguardQuickAffordanceViewModel> =
+        button(KeyguardQuickAffordancePosition.BOTTOM_END)
+
+    /**
+     * Notifies that a slot with the given ID has been selected in the preview experience that is
+     * rendering in the wallpaper picker. This is ignored for the real lock screen experience.
+     *
+     * @see [KeyguardRootViewModel.enablePreviewMode]
+     */
+    fun onPreviewSlotSelected(slotId: String) {
+        selectedPreviewSlotId.value = slotId
+    }
+
+    private fun button(
+        position: KeyguardQuickAffordancePosition
+    ): Flow<KeyguardQuickAffordanceViewModel> {
+        return keyguardInteractor.previewMode.flatMapLatest { previewMode ->
+            combine(
+                if (previewMode.isInPreviewMode) {
+                    quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position)
+                } else {
+                    quickAffordanceInteractor.quickAffordance(position = position)
+                },
+                keyguardInteractor.animateDozingTransitions.distinctUntilChanged(),
+                areQuickAffordancesFullyOpaque,
+                selectedPreviewSlotId,
+                quickAffordanceInteractor.useLongPress(),
+            ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId, useLongPress ->
+                val slotId = position.toSlotId()
+                val isSelected = selectedPreviewSlotId == slotId
+                model.toViewModel(
+                    animateReveal = !previewMode.isInPreviewMode && animateReveal,
+                    isClickable = isFullyOpaque && !previewMode.isInPreviewMode,
+                    isSelected =
+                    previewMode.isInPreviewMode &&
+                        previewMode.shouldHighlightSelectedAffordance &&
+                        isSelected,
+                    isDimmed =
+                    previewMode.isInPreviewMode &&
+                        previewMode.shouldHighlightSelectedAffordance &&
+                        !isSelected,
+                    forceInactive = previewMode.isInPreviewMode,
+                    slotId = slotId,
+                    useLongPress = useLongPress,
+                )
+            }
+                .distinctUntilChanged()
+        }
+    }
+
+    private fun KeyguardQuickAffordanceModel.toViewModel(
+        animateReveal: Boolean,
+        isClickable: Boolean,
+        isSelected: Boolean,
+        isDimmed: Boolean,
+        forceInactive: Boolean,
+        slotId: String,
+        useLongPress: Boolean,
+    ): KeyguardQuickAffordanceViewModel {
+        return when (this) {
+            is KeyguardQuickAffordanceModel.Visible ->
+                KeyguardQuickAffordanceViewModel(
+                    configKey = configKey,
+                    isVisible = true,
+                    animateReveal = animateReveal,
+                    icon = icon,
+                    onClicked = { parameters ->
+                        quickAffordanceInteractor.onQuickAffordanceTriggered(
+                            configKey = parameters.configKey,
+                            expandable = parameters.expandable,
+                            slotId = parameters.slotId,
+                        )
+                    },
+                    isClickable = isClickable,
+                    isActivated = !forceInactive && activationState is ActivationState.Active,
+                    isSelected = isSelected,
+                    useLongPress = useLongPress,
+                    isDimmed = isDimmed,
+                    slotId = slotId,
+                )
+            is KeyguardQuickAffordanceModel.Hidden ->
+                KeyguardQuickAffordanceViewModel(
+                    slotId = slotId,
+                )
+        }
+    }
+
+    companion object {
+        // We select a value that's less than 1.0 because we want floating point math precision to
+        // not be a factor in determining whether the affordance UI is fully opaque. The number we
+        // choose needs to be close enough 1.0 such that the user can't easily tell the difference
+        // between the UI with an alpha at the threshold and when the alpha is 1.0. At the same
+        // time, we don't want the number to be too close to 1.0 such that there is a chance that we
+        // never treat the affordance UI as "fully opaque" as that would risk making it forever not
+        // clickable.
+        @VisibleForTesting
+        const val AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD = 0.95f
+    }
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
new file mode 100644
index 0000000..316ca77
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import javax.inject.Inject
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class KeyguardRootViewModel
+@Inject
+constructor(
+    private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardQuickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
+)
+{
+    /** Represents the current state of the KeyguardRootView visibility */
+    val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
+        keyguardInteractor.keyguardRootViewVisibilityState
+
+    /** An observable for the alpha level for the entire keyguard root view. */
+    val alpha: Flow<Float> =
+        keyguardInteractor.previewMode.flatMapLatest {
+            if (it.isInPreviewMode) {
+                flowOf(1f)
+            } else {
+                keyguardInteractor.keyguardAlpha.distinctUntilChanged()
+            }
+        }
+
+    /**
+     * Puts this view-model in "preview mode", which means it's being used for UI that is rendering
+     * the lock screen preview in wallpaper picker / settings and not the real experience on the
+     * lock screen.
+     *
+     * @param initiallySelectedSlotId The ID of the initial slot to render as the selected one.
+     * @param shouldHighlightSelectedAffordance Whether the selected quick affordance should be
+     *   highlighted (while all others are dimmed to make the selected one stand out).
+     */
+    fun enablePreviewMode(
+        initiallySelectedSlotId: String?,
+        shouldHighlightSelectedAffordance: Boolean,
+    ) {
+        keyguardInteractor.previewMode.value =
+            KeyguardInteractor.PreviewMode(
+                isInPreviewMode = true,
+                shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
+            )
+        keyguardQuickAffordancesCombinedViewModel.onPreviewSlotSelected(
+            initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+        )
+    }
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ed7cbff..773f35e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -138,6 +138,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.keyguard.shared.model.WakefulnessModel;
 import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
+import com.android.systemui.keyguard.ui.view.KeyguardRootView;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
@@ -348,6 +349,7 @@
     private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
     private final NotificationGutsManager mGutsManager;
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
+    private final KeyguardRootView mKeyguardRootView;
     private final QuickSettingsController mQsController;
     private final TouchHandler mTouchHandler = new TouchHandler();
 
@@ -363,6 +365,7 @@
     private boolean mTracking;
     private boolean mIsTrackingExpansionFromStatusBar;
     private boolean mHintAnimationRunning;
+    @Deprecated
     private KeyguardBottomAreaView mKeyguardBottomArea;
     private boolean mExpanding;
     private boolean mSplitShadeEnabled;
@@ -739,7 +742,8 @@
             KeyguardInteractor keyguardInteractor,
             ActivityStarter activityStarter,
             KeyguardViewConfigurator keyguardViewConfigurator,
-            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
+            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
+            KeyguardRootView keyguardRootView) {
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onKeyguardFadingAwayChanged() {
@@ -939,6 +943,7 @@
                     }
                 });
         mAlternateBouncerInteractor = alternateBouncerInteractor;
+        mKeyguardRootView = keyguardRootView;
         dumpManager.registerDumpable(this);
     }
 
@@ -1029,7 +1034,9 @@
         mShadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
         mShadeHeadsUpTracker.addTrackingHeadsUpListener(
                 mNotificationStackScrollLayoutController::setTrackingHeadsUp);
-        setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
+        }
 
         initBottomArea();
 
@@ -1311,14 +1318,17 @@
         updateViewControllers(mView.findViewById(R.id.keyguard_status_view), userAvatarView,
                 keyguardUserSwitcherView);
 
-        // Update keyguard bottom area
-        int index = mView.indexOfChild(mKeyguardBottomArea);
-        mView.removeView(mKeyguardBottomArea);
-        KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
-        setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView());
-        mKeyguardBottomArea.initFrom(oldBottomArea);
-        mView.addView(mKeyguardBottomArea, index);
-        initBottomArea();
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            // Update keyguard bottom area
+            int index = mView.indexOfChild(mKeyguardBottomArea);
+            mView.removeView(mKeyguardBottomArea);
+            KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
+            setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView());
+            mKeyguardBottomArea.initFrom(oldBottomArea);
+            mView.addView(mKeyguardBottomArea, index);
+
+            initBottomArea();
+        }
         mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
                 mStatusBarStateController.getInterpolatedDozeAmount());
 
@@ -1341,7 +1351,12 @@
                     false,
                     mBarState);
         }
-        setKeyguardBottomAreaVisibility(mBarState, false);
+
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            setKeyguardVisibility(mBarState, false);
+        } else {
+            setKeyguardBottomAreaVisibility(mBarState, false);
+        }
 
         mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
         mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView));
@@ -1352,7 +1367,8 @@
     }
 
     private void initBottomArea() {
-        mKeyguardBottomArea.init(
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            mKeyguardBottomArea.init(
                 mKeyguardBottomAreaViewModel,
                 mFalsingManager,
                 mLockIconViewController,
@@ -1361,8 +1377,9 @@
                 mVibratorHelper,
                 mActivityStarter);
 
-        // Rebind (for now), as a new bottom area and indication area may have been created
-        mKeyguardViewConfigurator.bindIndicationArea(mKeyguardBottomArea);
+            // Rebind (for now), as a new bottom area and indication area may have been created
+            mKeyguardViewConfigurator.bindIndicationArea();
+        }
     }
 
     @VisibleForTesting
@@ -1398,6 +1415,7 @@
         return mHintAnimationRunning || mUnlockedScreenOffAnimationController.isAnimationPlaying();
     }
 
+    @Deprecated
     private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) {
         mKeyguardBottomArea = keyguardBottomArea;
     }
@@ -1521,8 +1539,13 @@
         mClockPositionAlgorithm.run(mClockPositionResult);
         mKeyguardStatusViewController.setLockscreenClockY(
                 mClockPositionAlgorithm.getExpandedPreferredClockY());
-        mKeyguardBottomAreaInteractor.setClockPosition(
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            mKeyguardInteractor.setClockPosition(
                 mClockPositionResult.clockX, mClockPositionResult.clockY);
+        } else {
+            mKeyguardBottomAreaInteractor.setClockPosition(
+                mClockPositionResult.clockX, mClockPositionResult.clockY);
+        }
         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
         boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
         mKeyguardStatusViewController.updatePosition(
@@ -2169,6 +2192,15 @@
         }
     }
 
+    private void setKeyguardVisibility(int statusBarState, boolean goingToFullShade) {
+        mKeyguardInteractor.setKeyguardRootVisibility(
+            statusBarState,
+            goingToFullShade,
+            mIsOcclusionTransitionRunning
+        );
+    }
+
+    @Deprecated
     private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
         mKeyguardBottomArea.animate().cancel();
         if (goingToFullShade) {
@@ -2177,8 +2209,7 @@
                     mKeyguardStateController.getShortenedFadingAwayDuration()).setInterpolator(
                     Interpolators.ALPHA_OUT).withEndAction(
                     mAnimateKeyguardBottomAreaInvisibleEndRunnable).start();
-        } else if (statusBarState == KEYGUARD
-                || statusBarState == StatusBarState.SHADE_LOCKED) {
+        } else if (statusBarState == KEYGUARD || statusBarState == StatusBarState.SHADE_LOCKED) {
             mKeyguardBottomArea.setVisibility(View.VISIBLE);
             if (!mIsOcclusionTransitionRunning) {
                 mKeyguardBottomArea.setAlpha(1f);
@@ -2514,7 +2545,11 @@
                 getExpandedFraction());
         float alpha = Math.min(expansionAlpha, 1 - mQsController.computeExpansionFraction());
         alpha *= mBottomAreaShadeAlpha;
-        mKeyguardBottomAreaInteractor.setAlpha(alpha);
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            mKeyguardInteractor.setAlpha(alpha);
+        } else {
+            mKeyguardBottomAreaInteractor.setAlpha(alpha);
+        }
         mLockIconViewController.setAlpha(alpha);
     }
 
@@ -2773,7 +2808,11 @@
     }
 
     private void updateDozingVisibilities(boolean animate) {
-        mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            mKeyguardInteractor.setAnimateDozingTransitions(animate);
+        } else {
+            mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
+        }
         if (!mDozing && animate) {
             mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
         }
@@ -2977,7 +3016,11 @@
         mDozing = dozing;
         // TODO (b/) make listeners for this
         mNotificationStackScrollLayoutController.setDozing(mDozing, animate);
-        mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            mKeyguardInteractor.setAnimateDozingTransitions(animate);
+        } else {
+            mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
+        }
         mKeyguardStatusBarViewController.setDozing(mDozing);
         mQsController.setDozing(mDozing);
 
@@ -3898,20 +3941,37 @@
         animator.start();
         setAnimator(animator);
 
-        final List<ViewPropertyAnimator> indicationAnimators =
-                mKeyguardBottomArea.getIndicationAreaAnimators();
-        for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) {
-            indicationAreaAnimator
+
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            final ViewPropertyAnimator mKeyguardRootViewAnimator = mKeyguardRootView.animate();
+            mKeyguardRootViewAnimator
                     .translationY(-mHintDistance)
                     .setDuration(250)
                     .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                    .withEndAction(() -> indicationAreaAnimator
+                    .withEndAction(() -> mKeyguardRootViewAnimator
                             .translationY(0)
                             .setDuration(450)
                             .setInterpolator(mBounceInterpolator)
                             .start())
                     .start();
+        } else {
+            final List<ViewPropertyAnimator> indicationAnimators =
+                    mKeyguardBottomArea.getIndicationAreaAnimators();
+
+            for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) {
+                indicationAreaAnimator
+                    .translationY(-mHintDistance)
+                    .setDuration(250)
+                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                    .withEndAction(() -> indicationAreaAnimator
+                        .translationY(0)
+                        .setDuration(450)
+                        .setInterpolator(mBounceInterpolator)
+                        .start())
+                    .start();
+            }
         }
+
     }
 
     private void setAnimator(ValueAnimator animator) {
@@ -4352,7 +4412,11 @@
                     goingToFullShade,
                     mBarState);
 
-            setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
+            if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+                setKeyguardVisibility(statusBarState, goingToFullShade);
+            } else {
+                setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
+            }
 
             // TODO: maybe add a listener for barstate
             mBarState = statusBarState;
@@ -4589,7 +4653,11 @@
             mKeyguardStatusViewController.setAlpha(alpha);
             stackScroller.setAlpha(alpha);
 
-            mKeyguardBottomAreaInteractor.setAlpha(alpha);
+            if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+                mKeyguardInteractor.setAlpha(alpha);
+            } else {
+                mKeyguardBottomAreaInteractor.setAlpha(alpha);
+            }
             mLockIconViewController.setAlpha(alpha);
 
             if (mKeyguardQsUserSwitchController != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index d433814..34bbd13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -38,6 +38,7 @@
  * elements. A secondary concern is the interaction of the quick affordance elements with the
  * indication area between them, though the indication area is primarily controlled elsewhere.
  */
+@Deprecated("Deprecated as part of b/278057014")
 class KeyguardBottomAreaView
 @JvmOverloads
 constructor(
@@ -53,6 +54,7 @@
         defStyleRes,
     ) {
 
+    @Deprecated("Deprecated as part of b/278057014")
     interface MessageDisplayer {
         fun display(@StringRes stringResourceId: Int)
     }
@@ -62,6 +64,7 @@
     private var lockIconViewController: LockIconViewController? = null
 
     /** Initializes the view. */
+    @Deprecated("Deprecated as part of b/278057014")
     fun init(
         viewModel: KeyguardBottomAreaViewModel,
         falsingManager: FalsingManager? = null,
@@ -88,6 +91,7 @@
      * Initializes this instance of [KeyguardBottomAreaView] based on the given instance of another
      * [KeyguardBottomAreaView]
      */
+    @Deprecated("Deprecated as part of b/278057014")
     fun initFrom(oldBottomArea: KeyguardBottomAreaView) {
         // if it exists, continue to use the original ambient indication container
         // instead of the newly inflated one
@@ -122,9 +126,11 @@
     }
 
     /** Returns a list of animators to use to animate the indication areas. */
+    @Deprecated("Deprecated as part of b/278057014")
     val indicationAreaAnimators: List<ViewPropertyAnimator>
         get() = checkNotNull(binding).getIndicationAreaAnimators()
 
+
     override fun hasOverlappingRendering(): Boolean {
         return false
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 07caf59..e8542aad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -293,24 +293,6 @@
         }
 
     @Test
-    fun quickAffordance_hiddenWhenQuickSettingsIsVisible() =
-        testScope.runTest {
-            repository.setQuickSettingsVisible(true)
-            quickAccessWallet.setState(
-                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
-                    icon = ICON,
-                )
-            )
-
-            val collectedValue =
-                collectLastValue(
-                    underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
-                )
-
-            assertThat(collectedValue()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
-        }
-
-    @Test
     fun quickAffordance_hiddenWhenUserIsInLockdownMode() =
         testScope.runTest {
             biometricSettingsRepository.setIsUserInLockdown(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt
index 2e97208..d97813b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt
@@ -26,6 +26,8 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.android.systemui.monet.utils.ArgbSubject.assertThat
 import org.junit.Before
@@ -42,9 +44,10 @@
     private lateinit var defaultLockscreenLayout: DefaultLockscreenLayout
     private lateinit var rootView: KeyguardRootView
     @Mock private lateinit var authController: AuthController
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
 
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
index dff0f29..34d93fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
@@ -45,6 +46,8 @@
 class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
 
     @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+    @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
+    @Mock private lateinit var featureFlags: FeatureFlags
 
     private lateinit var underTest: KeyguardIndicationAreaViewModel
     private lateinit var repository: FakeKeyguardRepository
@@ -83,6 +86,8 @@
                 bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
                 keyguardBottomAreaViewModel = bottomAreaViewModel,
                 burnInHelperWrapper = burnInHelperWrapper,
+                shortcutsCombinedViewModel = shortcutsCombinedViewModel,
+                featureFlags = featureFlags,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
new file mode 100644
index 0000000..ef38d54
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -0,0 +1,681 @@
+/*
+ * 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.viewmodel
+
+import android.app.admin.DevicePolicyManager
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dock.DockManagerFake
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import kotlin.math.max
+import kotlin.math.min
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
+
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var expandable: Expandable
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var launchAnimator: DialogLaunchAnimator
+    @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+
+    private lateinit var underTest: KeyguardQuickAffordancesCombinedViewModel
+
+    private lateinit var testScope: TestScope
+    private lateinit var repository: FakeKeyguardRepository
+    private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+    private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+    private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+    private lateinit var dockManager: DockManagerFake
+    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+    private lateinit var keyguardInteractor: KeyguardInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
+        overrideResource(
+            R.array.config_keyguardQuickAffordanceDefaults,
+            arrayOf(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + ":" +
+                    BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + ":" +
+                    BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+            )
+        )
+
+        homeControlsQuickAffordanceConfig =
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+        quickAccessWalletAffordanceConfig =
+            FakeKeyguardQuickAffordanceConfig(
+                BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+            )
+        qrCodeScannerAffordanceConfig =
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+        dockManager = DockManagerFake()
+        biometricSettingsRepository = FakeBiometricSettingsRepository()
+        val featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA, true)
+                set(Flags.FACE_AUTH_REFACTOR, true)
+                set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
+                set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
+            }
+
+        val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+        keyguardInteractor = withDeps.keyguardInteractor
+        repository = withDeps.repository
+
+        whenever(userTracker.userHandle).thenReturn(mock())
+        whenever(lockPatternUtils.getStrongAuthForUser(ArgumentMatchers.anyInt()))
+            .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
+        val testDispatcher = StandardTestDispatcher()
+        testScope = TestScope(testDispatcher)
+
+        val localUserSelectionManager =
+            KeyguardQuickAffordanceLocalUserSelectionManager(
+                context = context,
+                userFileManager =
+                mock<UserFileManager>().apply {
+                    whenever(
+                        getSharedPreferences(
+                            ArgumentMatchers.anyString(),
+                            ArgumentMatchers.anyInt(),
+                            ArgumentMatchers.anyInt(),
+                        )
+                    )
+                        .thenReturn(FakeSharedPreferences())
+                },
+                userTracker = userTracker,
+                broadcastDispatcher = fakeBroadcastDispatcher,
+            )
+        val remoteUserSelectionManager =
+            KeyguardQuickAffordanceRemoteUserSelectionManager(
+                scope = testScope.backgroundScope,
+                userTracker = userTracker,
+                clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+                userHandle = UserHandle.SYSTEM,
+            )
+        val quickAffordanceRepository =
+            KeyguardQuickAffordanceRepository(
+                appContext = context,
+                scope = testScope.backgroundScope,
+                localUserSelectionManager = localUserSelectionManager,
+                remoteUserSelectionManager = remoteUserSelectionManager,
+                userTracker = userTracker,
+                legacySettingSyncer =
+                KeyguardQuickAffordanceLegacySettingSyncer(
+                    scope = testScope.backgroundScope,
+                    backgroundDispatcher = testDispatcher,
+                    secureSettings = FakeSettings(),
+                    selectionsManager = localUserSelectionManager,
+                ),
+                configs =
+                setOf(
+                    homeControlsQuickAffordanceConfig,
+                    quickAccessWalletAffordanceConfig,
+                    qrCodeScannerAffordanceConfig,
+                ),
+                dumpManager = mock(),
+                userHandle = UserHandle.SYSTEM,
+            )
+
+        underTest = KeyguardQuickAffordancesCombinedViewModel(
+            quickAffordanceInteractor =
+            KeyguardQuickAffordanceInteractor(
+                keyguardInteractor = keyguardInteractor,
+                lockPatternUtils = lockPatternUtils,
+                keyguardStateController = keyguardStateController,
+                userTracker = userTracker,
+                activityStarter = activityStarter,
+                featureFlags = featureFlags,
+                repository = { quickAffordanceRepository },
+                launchAnimator = launchAnimator,
+                logger = logger,
+                devicePolicyManager = devicePolicyManager,
+                dockManager = dockManager,
+                biometricSettingsRepository = biometricSettingsRepository,
+                backgroundDispatcher = testDispatcher,
+                appContext = mContext,
+            ),
+            keyguardInteractor = keyguardInteractor
+        )
+    }
+
+    @Test
+    fun startButton_present_visibleModel_startsActivityOnClick() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            val latest = collectLastValue(underTest.startButton)
+
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    isActivated = true,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = testConfig,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig = testConfig,
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun startButton_hiddenWhenDevicePolicyDisablesAllKeyguardFeatures() =
+        testScope.runTest {
+            whenever(
+                devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)
+            ).thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
+            repository.setKeyguardShowing(true)
+            val latest by collectLastValue(underTest.startButton)
+
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    isActivated = true,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = testConfig,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest,
+                testConfig =
+                TestConfig(
+                    isVisible = false,
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                ),
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun startButton_inPreviewMode_visibleEvenWhenKeyguardNotShowing() =
+        testScope.runTest {
+            underTest.onPreviewSlotSelected(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+            )
+            keyguardInteractor.previewMode.value =
+                KeyguardInteractor.PreviewMode(
+                    isInPreviewMode = true,
+                    shouldHighlightSelectedAffordance = true,
+                )
+
+            repository.setKeyguardShowing(false)
+            val latest = collectLastValue(underTest.startButton)
+
+            val icon: Icon = mock()
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig =
+                    TestConfig(
+                        isVisible = true,
+                        isClickable = true,
+                        isActivated = true,
+                        icon = icon,
+                        canShowWhileLocked = false,
+                        intent = Intent("action"),
+                        slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                    ),
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = false,
+                    isActivated = false,
+                    icon = icon,
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    isSelected = true,
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                ),
+                configKey = configKey,
+            )
+            Truth.assertThat(latest()?.isSelected).isTrue()
+        }
+
+    @Test
+    fun endButton_inHiglightedPreviewMode_dimmedWhenOtherIsSelected() =
+        testScope.runTest {
+            underTest.onPreviewSlotSelected(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+            )
+            keyguardInteractor.previewMode.value =
+                KeyguardInteractor.PreviewMode(
+                    isInPreviewMode = true,
+                    shouldHighlightSelectedAffordance = true,
+                )
+
+            repository.setKeyguardShowing(false)
+            val endButton = collectLastValue(underTest.endButton)
+
+            val icon: Icon = mock()
+            setUpQuickAffordanceModel(
+                position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    isActivated = true,
+                    icon = icon,
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                ),
+            )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_END,
+                    testConfig =
+                    TestConfig(
+                        isVisible = true,
+                        isClickable = true,
+                        isActivated = true,
+                        icon = icon,
+                        canShowWhileLocked = false,
+                        intent = Intent("action"),
+                        slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
+                    ),
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = endButton(),
+                testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = false,
+                    isActivated = false,
+                    icon = icon,
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    isDimmed = true,
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
+                ),
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun endButton_present_visibleModel_doNothingOnClick() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            val latest = collectLastValue(underTest.endButton)
+
+            val config =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent =
+                    null, // This will cause it to tell the system that the click was handled.
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_END,
+                    testConfig = config,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig = config,
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun startButton_notPresent_modelIsHidden() =
+        testScope.runTest {
+            val latest = collectLastValue(underTest.startButton)
+
+            val config =
+                TestConfig(
+                    isVisible = false,
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = config,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig = config,
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun animateButtonReveal() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+
+            setUpQuickAffordanceModel(
+                position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                testConfig = testConfig,
+            )
+
+            val value = collectLastValue(underTest.startButton.map { it.animateReveal })
+
+            Truth.assertThat(value()).isFalse()
+            repository.setAnimateDozingTransitions(true)
+            Truth.assertThat(value()).isTrue()
+            repository.setAnimateDozingTransitions(false)
+            Truth.assertThat(value()).isFalse()
+        }
+
+    @Test
+    fun isClickable_trueWhenAlphaAtThreshold() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            repository.setKeyguardAlpha(
+                KeyguardQuickAffordancesCombinedViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD
+            )
+
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = testConfig,
+                )
+
+            val latest = collectLastValue(underTest.startButton)
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig = testConfig,
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun isClickable_trueWhenAlphaAboveThreshold() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            val latest = collectLastValue(underTest.startButton)
+            repository.setKeyguardAlpha(
+                min(
+                    1f,
+                    KeyguardQuickAffordancesCombinedViewModel
+                        .AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f
+                ),
+            )
+
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = testConfig,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig = testConfig,
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun isClickable_falseWhenAlphaBelowThreshold() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            val latest = collectLastValue(underTest.startButton)
+            repository.setKeyguardAlpha(
+                max(
+                    0f,
+                    KeyguardQuickAffordancesCombinedViewModel
+                        .AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f
+                ),
+            )
+
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = false,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = testConfig,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig = testConfig,
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun isClickable_falseWhenAlphaAtZero() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            val latest = collectLastValue(underTest.startButton)
+            repository.setKeyguardAlpha(0f)
+
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = false,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = testConfig,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig = testConfig,
+                configKey = configKey,
+            )
+        }
+
+    private suspend fun setUpQuickAffordanceModel(
+        position: KeyguardQuickAffordancePosition,
+        testConfig: TestConfig,
+    ): String {
+        val config =
+            when (position) {
+                KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
+                KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
+            }
+
+        val lockScreenState =
+            if (testConfig.isVisible) {
+                if (testConfig.intent != null) {
+                    config.onTriggeredResult =
+                        KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+                            intent = testConfig.intent,
+                            canShowWhileLocked = testConfig.canShowWhileLocked,
+                        )
+                }
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
+                    activationState =
+                    when (testConfig.isActivated) {
+                        true -> ActivationState.Active
+                        false -> ActivationState.Inactive
+                    }
+                )
+            } else {
+                KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+            }
+        config.setState(lockScreenState)
+        return "${position.toSlotId()}::${config.key}"
+    }
+
+    private fun assertQuickAffordanceViewModel(
+        viewModel: KeyguardQuickAffordanceViewModel?,
+        testConfig: TestConfig,
+        configKey: String,
+    ) {
+        checkNotNull(viewModel)
+        Truth.assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
+        Truth.assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable)
+        Truth.assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated)
+        Truth.assertThat(viewModel.isSelected).isEqualTo(testConfig.isSelected)
+        Truth.assertThat(viewModel.isDimmed).isEqualTo(testConfig.isDimmed)
+        Truth.assertThat(viewModel.slotId).isEqualTo(testConfig.slotId)
+        if (testConfig.isVisible) {
+            Truth.assertThat(viewModel.icon).isEqualTo(testConfig.icon)
+            viewModel.onClicked.invoke(
+                KeyguardQuickAffordanceViewModel.OnClickedParameters(
+                    configKey = configKey,
+                    expandable = expandable,
+                    slotId = viewModel.slotId,
+                )
+            )
+            if (testConfig.intent != null) {
+                Truth.assertThat(
+                    Mockito.mockingDetails(activityStarter).invocations
+                ).hasSize(1)
+            } else {
+                Mockito.verifyZeroInteractions(activityStarter)
+            }
+        } else {
+            Truth.assertThat(viewModel.isVisible).isFalse()
+        }
+    }
+
+    private data class TestConfig(
+        val isVisible: Boolean,
+        val isClickable: Boolean = false,
+        val isActivated: Boolean = false,
+        val icon: Icon? = null,
+        val canShowWhileLocked: Boolean = false,
+        val intent: Intent? = null,
+        val isSelected: Boolean = false,
+        val isDimmed: Boolean = false,
+        val slotId: String = ""
+    ) {
+        init {
+            check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
new file mode 100644
index 0000000..05e933b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardRootViewModelTest : SysuiTestCase() {
+
+    private lateinit var underTest: KeyguardRootViewModel
+    private lateinit var testScope: TestScope
+    private lateinit var repository: FakeKeyguardRepository
+    private lateinit var keyguardInteractor: KeyguardInteractor
+    @Mock private lateinit var keyguardQuickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        val testDispatcher = StandardTestDispatcher()
+        testScope = TestScope(testDispatcher)
+
+        val featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA, true)
+                set(Flags.FACE_AUTH_REFACTOR, true)
+            }
+
+        val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+        keyguardInteractor = withDeps.keyguardInteractor
+        repository = withDeps.repository
+
+        underTest = KeyguardRootViewModel(
+            keyguardInteractor,
+            keyguardQuickAffordancesCombinedViewModel,
+        )
+    }
+
+    @Test
+    fun alpha() =
+        testScope.runTest {
+            val value = collectLastValue(underTest.alpha)
+
+            Truth.assertThat(value()).isEqualTo(1f)
+            repository.setKeyguardAlpha(0.1f)
+            Truth.assertThat(value()).isEqualTo(0.1f)
+            repository.setKeyguardAlpha(0.5f)
+            Truth.assertThat(value()).isEqualTo(0.5f)
+            repository.setKeyguardAlpha(0.2f)
+            Truth.assertThat(value()).isEqualTo(0.2f)
+            repository.setKeyguardAlpha(0f)
+            Truth.assertThat(value()).isEqualTo(0f)
+        }
+
+    @Test
+    fun alpha_inPreviewMode_doesNotChange() =
+        testScope.runTest {
+            val value = collectLastValue(underTest.alpha)
+            underTest.enablePreviewMode(
+                initiallySelectedSlotId = null,
+                shouldHighlightSelectedAffordance = false,
+            )
+
+            Truth.assertThat(value()).isEqualTo(1f)
+            repository.setKeyguardAlpha(0.1f)
+            Truth.assertThat(value()).isEqualTo(1f)
+            repository.setKeyguardAlpha(0.5f)
+            Truth.assertThat(value()).isEqualTo(1f)
+            repository.setKeyguardAlpha(0.2f)
+            Truth.assertThat(value()).isEqualTo(1f)
+            repository.setKeyguardAlpha(0f)
+            Truth.assertThat(value()).isEqualTo(1f)
+        }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 202c7bf..47ca49d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -97,6 +97,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.ui.view.KeyguardRootView;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
@@ -314,6 +315,7 @@
     @Mock private ShadeInteractor mShadeInteractor;
     @Mock private JavaAdapter mJavaAdapter;
     @Mock private CastController mCastController;
+    @Mock private KeyguardRootView mKeyguardRootView;
 
     protected final int mMaxUdfpsBurnInOffsetY = 5;
     protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
@@ -618,7 +620,8 @@
                 mKeyguardInteractor,
                 mActivityStarter,
                 mKeyguardViewConfigurator,
-                mKeyguardFaceAuthInteractor);
+                mKeyguardFaceAuthInteractor,
+                mKeyguardRootView);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
                 null,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 8428566..e6894d7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
 import com.android.systemui.keyguard.shared.model.ScreenModel
 import com.android.systemui.keyguard.shared.model.ScreenState
 import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -115,6 +116,20 @@
     private val _isQuickSettingsVisible = MutableStateFlow(false)
     override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow()
 
+    private val _keyguardAlpha = MutableStateFlow(1f)
+    override val keyguardAlpha: StateFlow<Float> = _keyguardAlpha
+
+    private val _keyguardRootViewVisibility =
+        MutableStateFlow(
+            KeyguardRootViewVisibilityState(
+                0,
+                goingToFullShade = false,
+                occlusionTransitionRunning = false
+            )
+        )
+    override val keyguardRootViewVisibility: Flow<KeyguardRootViewVisibilityState> =
+        _keyguardRootViewVisibility.asStateFlow()
+
     override fun setQuickSettingsVisible(isVisible: Boolean) {
         _isQuickSettingsVisible.value = isVisible
     }
@@ -132,6 +147,8 @@
         _animateBottomAreaDozingTransitions.tryEmit(animate)
     }
 
+
+    @Deprecated("Deprecated as part of b/278057014")
     override fun setBottomAreaAlpha(alpha: Float) {
         _bottomAreaAlpha.value = alpha
     }
@@ -223,4 +240,18 @@
     override fun isUdfpsSupported(): Boolean {
         return _isUdfpsSupported.value
     }
+
+    override fun setKeyguardAlpha(alpha: Float) {
+        _keyguardAlpha.value = alpha
+    }
+
+    override fun setKeyguardVisibility(
+        statusBarState: Int,
+        goingToFullShade: Boolean,
+        occlusionTransitionRunning: Boolean
+    ) {
+        _keyguardRootViewVisibility.value = KeyguardRootViewVisibilityState(
+            statusBarState, goingToFullShade, occlusionTransitionRunning
+        )
+    }
 }