Moves quick affordances into domain layer.

After much discussion, it became clear that it would be better to move
quic affordance config definitions out of the data layer and into the
domain layer.

This CL does the following:
1. Moves config definitions to the domain layer
2. Moves the "configs" class to the domain layer and renames it to
   "registry"
3. Eliminates the quick affordance repository from the data layer,
   merging its logic into the observe quick affordance use-case
4. Updates the documentation

Fix: 241681067
Test: Unit tests updated. Manually verified the bottom area quick
affordances still work properly when locked, unlocked, and dozing.
Played with the settings under Display > Lock screen to turn the on and
off.

Change-Id: I4b293eeef02e5546fc834008b096e4a7935657fe
diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
index a96e5339..38d636d7 100644
--- a/packages/SystemUI/docs/device-entry/quickaffordance.md
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -6,16 +6,16 @@
 
 ## Adding a new Quick Affordance
 ### Step 1: create a new quick affordance config
-* Create a new class under the [systemui/keyguard/data/quickaffordance](../../src/com/android/systemui/keyguard/data/quickaffordance) directory
+* Create a new class under the [systemui/keyguard/domain/quickaffordance](../../src/com/android/systemui/keyguard/domain/quickaffordance) directory
 * Please make sure that the class is injected through the Dagger dependency injection system by using the `@Inject` annotation on its main constructor and the `@SysUISingleton` annotation at class level, to make sure only one instance of the class is ever instantiated
-* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
+* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
   * The `state` Flow property must emit `State.Hidden` when the feature is not enabled!
   * It is safe to assume that `onQuickAffordanceClicked` will not be invoked if-and-only-if the previous rule is followed
   * When implementing `onQuickAffordanceClicked`, the implementation can do something or it can ask the framework to start an activity using an `Intent` provided by the implementation
-* Please include a unit test for your new implementation under [the correct directory](../../tests/src/com/android/systemui/keyguard/data/quickaffordance)
+* Please include a unit test for your new implementation under [the correct directory](../../tests/src/com/android/systemui/keyguard/domain/quickaffordance)
 
 ### Step 2: choose a position and priority
-* Add the new class as a dependency in the constructor of [KeyguardQuickAffordanceConfigs](../../src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt)
+* Add the new class as a dependency in the constructor of [KeyguardQuickAffordanceRegistry](../../src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt)
 * Place the new class in one of the available positions in the `configsByPosition` property, note:
   * In each position, there is a list. The order matters. The order of that list is the priority order in which the framework considers each config. The first config whose state property returns `State.Visible` determines the button that is shown for that position
   * Please only add to one position. The framework treats each position individually and there is no good way to prevent the same config from making its button appear in more than one position at the same time
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 4ff008f..430b59c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -43,6 +43,7 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
 import com.android.systemui.keyguard.domain.usecase.KeyguardUseCaseModule;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -70,6 +71,7 @@
         KeyguardUserSwitcherComponent.class},
         includes = {
             FalsingModule.class,
+            KeyguardQuickAffordanceModule.class,
             KeyguardRepositoryModule.class,
             KeyguardUseCaseModule.class,
         })
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
deleted file mode 100644
index 43c4fa0..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.State
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-
-/** Defines interface for classes that encapsulate quick affordance state for the keyguard. */
-interface KeyguardQuickAffordanceRepository {
-    fun affordance(position: KeyguardQuickAffordancePosition): Flow<KeyguardQuickAffordanceModel>
-}
-
-/** Real implementation of [KeyguardQuickAffordanceRepository] */
-@SysUISingleton
-class KeyguardQuickAffordanceRepositoryImpl
-@Inject
-constructor(
-    private val configs: KeyguardQuickAffordanceConfigs,
-) : KeyguardQuickAffordanceRepository {
-
-    /** Returns an observable for the quick affordance model in the given position. */
-    override fun affordance(
-        position: KeyguardQuickAffordancePosition
-    ): Flow<KeyguardQuickAffordanceModel> {
-        val configs = configs.getAll(position)
-        return combine(configs.map { config -> config.state }) { states ->
-            val index = states.indexOfFirst { state -> state is State.Visible }
-            val visibleState =
-                if (index != -1) {
-                    states[index] as State.Visible
-                } else {
-                    null
-                }
-            if (visibleState != null) {
-                KeyguardQuickAffordanceModel.Visible(
-                    configKey = configs[index]::class,
-                    icon = visibleState.icon,
-                    contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
-                )
-            } else {
-                KeyguardQuickAffordanceModel.Hidden
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 1a5670c..d15d7f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -16,22 +16,10 @@
 
 package com.android.systemui.keyguard.data.repository
 
-import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
-import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigsImpl
 import dagger.Binds
 import dagger.Module
 
 @Module
 interface KeyguardRepositoryModule {
     @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
-
-    @Binds
-    fun keyguardQuickAffordanceRepository(
-        impl: KeyguardQuickAffordanceRepositoryImpl
-    ): KeyguardQuickAffordanceRepository
-
-    @Binds
-    fun keyguardQuickAffordanceConfigs(
-        impl: KeyguardQuickAffordanceConfigsImpl
-    ): KeyguardQuickAffordanceConfigs
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index 09785df..411a2ca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -15,11 +15,11 @@
  *
  */
 
-package com.android.systemui.keyguard.shared.model
+package com.android.systemui.keyguard.domain.model
 
 import androidx.annotation.StringRes
 import com.android.systemui.containeddrawable.ContainedDrawable
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
 import kotlin.reflect.KClass
 
 /**
@@ -27,7 +27,6 @@
  * lock-screen).
  */
 sealed class KeyguardQuickAffordanceModel {
-
     /** No affordance should show up. */
     object Hidden : KeyguardQuickAffordanceModel()
 
@@ -43,4 +42,21 @@
          */
         @StringRes val contentDescriptionResourceId: Int,
     ) : KeyguardQuickAffordanceModel()
+
+    companion object {
+        fun from(
+            state: KeyguardQuickAffordanceConfig.State?,
+            configKey: KClass<out KeyguardQuickAffordanceConfig>,
+        ): KeyguardQuickAffordanceModel {
+            return when (state) {
+                is KeyguardQuickAffordanceConfig.State.Visible ->
+                    Visible(
+                        configKey = configKey,
+                        icon = state.icon,
+                        contentDescriptionResourceId = state.contentDescriptionResourceId,
+                    )
+                else -> Hidden
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
index b71e15d..581dafa3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.shared.model
+package com.android.systemui.keyguard.domain.model
 
 /** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
 enum class KeyguardQuickAffordancePosition {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index df44957..8f32ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import android.content.Context
 import android.content.Intent
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 67a776e..8fb952c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import android.content.Intent
 import androidx.annotation.StringRes
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
new file mode 100644
index 0000000..a7b3828
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
@@ -0,0 +1,29 @@
+/*
+ *  Copyright (C) 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.quickaffordance
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface KeyguardQuickAffordanceModule {
+    @Binds
+    fun keyguardQuickAffordanceRegistry(
+        impl: KeyguardQuickAffordanceRegistryImpl
+    ): KeyguardQuickAffordanceRegistry
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
index 7164215..2c37f93 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
@@ -15,29 +15,25 @@
  *
  */
 
-package com.android.systemui.keyguard.data.config
+package com.android.systemui.keyguard.domain.quickaffordance
 
-import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
 import javax.inject.Inject
 import kotlin.reflect.KClass
 
-/** Injectable provider of the positioning of the known quick affordance configs. */
-interface KeyguardQuickAffordanceConfigs {
+/** Central registry of all known quick affordance configs. */
+interface KeyguardQuickAffordanceRegistry {
     fun getAll(position: KeyguardQuickAffordancePosition): List<KeyguardQuickAffordanceConfig>
     fun get(configClass: KClass<out KeyguardQuickAffordanceConfig>): KeyguardQuickAffordanceConfig
 }
 
-class KeyguardQuickAffordanceConfigsImpl
+class KeyguardQuickAffordanceRegistryImpl
 @Inject
 constructor(
     homeControls: HomeControlsKeyguardQuickAffordanceConfig,
     quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
     qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
-) : KeyguardQuickAffordanceConfigs {
+) : KeyguardQuickAffordanceRegistry {
     private val configsByPosition =
         mapOf(
             KeyguardQuickAffordancePosition.BOTTOM_START to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index ea6497e..c8e5e4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index cc5a997..885af33 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import android.graphics.drawable.Drawable
 import android.service.quickaccesswallet.GetWalletCardsError
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
index c44c2c9..403d343 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
@@ -26,4 +26,9 @@
     fun launchQuickAffordance(
         impl: LaunchKeyguardQuickAffordanceUseCaseImpl
     ): LaunchKeyguardQuickAffordanceUseCase
+
+    @Binds
+    fun observeKeyguardQuickAffordance(
+        impl: ObserveKeyguardQuickAffordanceUseCaseImpl
+    ): ObserveKeyguardQuickAffordanceUseCase
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
index eef8ec3..8dee8b3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
@@ -16,26 +16,33 @@
 
 package com.android.systemui.keyguard.domain.usecase
 
-import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 
-/** Use-case for observing the model of a quick affordance in the keyguard. */
-class ObserveKeyguardQuickAffordanceUseCase
+/** Defines interface for use-case for observing the model of a quick affordance in the keyguard. */
+interface ObserveKeyguardQuickAffordanceUseCase {
+    operator fun invoke(
+        position: KeyguardQuickAffordancePosition
+    ): Flow<KeyguardQuickAffordanceModel>
+}
+
+class ObserveKeyguardQuickAffordanceUseCaseImpl
 @Inject
 constructor(
-    private val repository: KeyguardQuickAffordanceRepository,
+    private val registry: KeyguardQuickAffordanceRegistry,
     private val isDozingUseCase: ObserveIsDozingUseCase,
     private val isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase,
-) {
-    operator fun invoke(
+) : ObserveKeyguardQuickAffordanceUseCase {
+    override fun invoke(
         position: KeyguardQuickAffordancePosition
     ): Flow<KeyguardQuickAffordanceModel> {
         return combine(
-            repository.affordance(position),
+            affordance(position),
             isDozingUseCase(),
             isKeyguardShowingUseCase(),
         ) { affordance, isDozing, isKeyguardShowing ->
@@ -46,4 +53,23 @@
             }
         }
     }
+
+    private fun affordance(
+        position: KeyguardQuickAffordancePosition
+    ): Flow<KeyguardQuickAffordanceModel> {
+        val configs = registry.getAll(position)
+        return combine(configs.map { config -> config.state }) { states ->
+            val index =
+                states.indexOfFirst { state ->
+                    state is KeyguardQuickAffordanceConfig.State.Visible
+                }
+            val visibleState =
+                if (index != -1) {
+                    states[index] as KeyguardQuickAffordanceConfig.State.Visible
+                } else {
+                    null
+                }
+            KeyguardQuickAffordanceModel.from(visibleState, configs[index]::class)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
index f8db90f..9315339 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.keyguard.domain.usecase
 
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
 import javax.inject.Inject
 import kotlin.reflect.KClass
 
@@ -27,7 +27,7 @@
 class OnKeyguardQuickAffordanceClickedUseCase
 @Inject
 constructor(
-    private val configs: KeyguardQuickAffordanceConfigs,
+    private val registry: KeyguardQuickAffordanceRegistry,
     private val launchAffordanceUseCase: LaunchKeyguardQuickAffordanceUseCase,
 ) {
     operator fun invoke(
@@ -35,7 +35,7 @@
         animationController: ActivityLaunchAnimator.Controller?,
     ) {
         @Suppress("UNCHECKED_CAST")
-        val config = configs.get(configKey as KClass<out KeyguardQuickAffordanceConfig>)
+        val config = registry.get(configKey as KClass<out KeyguardQuickAffordanceConfig>)
         when (val result = config.onQuickAffordanceClicked(animationController)) {
             is OnClickedResult.StartActivity ->
                 launchAffordanceUseCase(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 4b69a81..d296e76 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
 import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
 import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
@@ -24,8 +26,6 @@
 import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
 import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
 import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt
deleted file mode 100644
index 6fff440..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.data.repository
-
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.yield
-
-/**
- * Fake implementation of a quick affordance data source.
- *
- * This class is abstract to force tests to provide extensions of it as the system that references
- * these configs uses each implementation's class type to refer to them.
- */
-abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig {
-
-    private val _onClickedInvocations = mutableListOf<ActivityLaunchAnimator.Controller?>()
-    val onClickedInvocations: List<ActivityLaunchAnimator.Controller?> = _onClickedInvocations
-
-    var onClickedResult: OnClickedResult = OnClickedResult.Handled
-
-    private val _state =
-        MutableStateFlow<KeyguardQuickAffordanceConfig.State>(
-            KeyguardQuickAffordanceConfig.State.Hidden
-        )
-    override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
-
-    override fun onQuickAffordanceClicked(
-        animationController: ActivityLaunchAnimator.Controller?,
-    ): OnClickedResult {
-        _onClickedInvocations.add(animationController)
-        return onClickedResult
-    }
-
-    suspend fun setState(state: KeyguardQuickAffordanceConfig.State) {
-        _state.value = state
-        // Yield to allow the test's collection coroutine to "catch up" and collect this value
-        // before the test continues to the next line.
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
-        yield()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt
deleted file mode 100644
index a24fc93..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.data.repository
-
-import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import kotlin.reflect.KClass
-
-/** Fake implementation of [KeyguardQuickAffordanceConfigs], for tests. */
-class FakeKeyguardQuickAffordanceConfigs(
-    private val configsByPosition:
-        Map<KeyguardQuickAffordancePosition, List<KeyguardQuickAffordanceConfig>>,
-) : KeyguardQuickAffordanceConfigs {
-
-    override fun getAll(
-        position: KeyguardQuickAffordancePosition
-    ): List<KeyguardQuickAffordanceConfig> {
-        return configsByPosition.getValue(position)
-    }
-
-    override fun get(
-        configClass: KClass<out KeyguardQuickAffordanceConfig>
-    ): KeyguardQuickAffordanceConfig {
-        return configsByPosition.values
-            .flatten()
-            .associateBy { config -> config::class }
-            .getValue(configClass)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt
deleted file mode 100644
index 10d2e4d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.data.repository
-
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.yield
-
-/** Fake implementation of [KeyguardQuickAffordanceRepository], for tests. */
-class FakeKeyguardQuickAffordanceRepository : KeyguardQuickAffordanceRepository {
-
-    private val modelByPosition =
-        mutableMapOf<
-            KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
-
-    init {
-        KeyguardQuickAffordancePosition.values().forEach { value ->
-            modelByPosition[value] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden)
-        }
-    }
-
-    override fun affordance(
-        position: KeyguardQuickAffordancePosition
-    ): Flow<KeyguardQuickAffordanceModel> {
-        return modelByPosition.getValue(position)
-    }
-
-    suspend fun setModel(
-        position: KeyguardQuickAffordancePosition,
-        model: KeyguardQuickAffordanceModel
-    ) {
-        modelByPosition.getValue(position).value = model
-        // Yield to allow the test's collection coroutine to "catch up" and collect this value
-        // before the test continues to the next line.
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
-        yield()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index d40b985..38a3375 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -20,6 +20,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.yield
 
 /** Fake implementation of [KeyguardRepository] */
 class FakeKeyguardRepository : KeyguardRepository {
@@ -43,11 +44,6 @@
     private val _dozeAmount = MutableStateFlow(0f)
     override val dozeAmount: Flow<Float> = _dozeAmount
 
-    init {
-        setDozeAmount(0f)
-        setDozing(false)
-    }
-
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.tryEmit(animate)
     }
@@ -60,15 +56,30 @@
         _clockPosition.value = Position(x, y)
     }
 
-    fun setKeyguardShowing(isShowing: Boolean) {
+    suspend fun setKeyguardShowing(isShowing: Boolean) {
         _isKeyguardShowing.value = isShowing
+        // Yield to allow the test's collection coroutine to "catch up" and collect this value
+        // before the test continues to the next line.
+        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+        // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+        yield()
     }
 
-    fun setDozing(isDozing: Boolean) {
+    suspend fun setDozing(isDozing: Boolean) {
         _isDozing.value = isDozing
+        // Yield to allow the test's collection coroutine to "catch up" and collect this value
+        // before the test continues to the next line.
+        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+        // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+        yield()
     }
 
-    fun setDozeAmount(dozeAmount: Float) {
+    suspend fun setDozeAmount(dozeAmount: Float) {
         _dozeAmount.value = dozeAmount
+        // Yield to allow the test's collection coroutine to "catch up" and collect this value
+        // before the test continues to the next line.
+        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+        // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+        yield()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt
deleted file mode 100644
index dc0e6f7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.data.repository
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlin.reflect.KClass
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runBlockingTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardQuickAffordanceRepositoryImplTest : SysuiTestCase() {
-
-    private lateinit var underTest: KeyguardQuickAffordanceRepository
-
-    private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
-    private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
-    private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
-        quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
-        qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
-
-        underTest =
-            KeyguardQuickAffordanceRepositoryImpl(
-                configs =
-                    FakeKeyguardQuickAffordanceConfigs(
-                        mapOf(
-                            KeyguardQuickAffordancePosition.BOTTOM_START to
-                                listOf(
-                                    homeControls,
-                                ),
-                            KeyguardQuickAffordancePosition.BOTTOM_END to
-                                listOf(
-                                    quickAccessWallet,
-                                    qrCodeScanner,
-                                ),
-                        ),
-                    ),
-            )
-    }
-
-    @Test
-    fun `bottom start affordance - none`() = runBlockingTest {
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest
-                .affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
-                .onEach { latest = it }
-                .launchIn(this)
-
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
-        job.cancel()
-    }
-
-    @Test
-    fun `bottom start affordance - home controls`() = runBlockingTest {
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest
-                .affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
-                .onEach { latest = it }
-                .launchIn(this)
-
-        val state =
-            KeyguardQuickAffordanceConfig.State.Visible(
-                icon = mock(),
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-            )
-        homeControls.setState(state)
-
-        assertThat(latest).isEqualTo(state.toModel(homeControls::class))
-        job.cancel()
-    }
-
-    @Test
-    fun `bottom end affordance - none`() = runBlockingTest {
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest
-                .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
-                .onEach { latest = it }
-                .launchIn(this)
-
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
-        job.cancel()
-    }
-
-    @Test
-    fun `bottom end affordance - quick access wallet`() = runBlockingTest {
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest
-                .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
-                .onEach { latest = it }
-                .launchIn(this)
-
-        val quickAccessWalletState =
-            KeyguardQuickAffordanceConfig.State.Visible(
-                icon = mock(),
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-            )
-        quickAccessWallet.setState(quickAccessWalletState)
-        val qrCodeScannerState =
-            KeyguardQuickAffordanceConfig.State.Visible(
-                icon = mock(),
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-            )
-        qrCodeScanner.setState(qrCodeScannerState)
-
-        assertThat(latest).isEqualTo(quickAccessWalletState.toModel(quickAccessWallet::class))
-        job.cancel()
-    }
-
-    @Test
-    fun `bottom end affordance - qr code scanner`() = runBlockingTest {
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest
-                .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
-                .onEach { latest = it }
-                .launchIn(this)
-
-        val state =
-            KeyguardQuickAffordanceConfig.State.Visible(
-                icon = mock(),
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-            )
-        qrCodeScanner.setState(state)
-
-        assertThat(latest).isEqualTo(state.toModel(qrCodeScanner::class))
-        job.cancel()
-    }
-
-    private fun KeyguardQuickAffordanceConfig.State?.toModel(
-        configKey: KClass<out KeyguardQuickAffordanceConfig>,
-    ): KeyguardQuickAffordanceModel? {
-        return when (this) {
-            is KeyguardQuickAffordanceConfig.State.Visible ->
-                KeyguardQuickAffordanceModel.Visible(
-                    configKey = configKey,
-                    icon = icon,
-                    contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-                )
-            is KeyguardQuickAffordanceConfig.State.Hidden -> KeyguardQuickAffordanceModel.Hidden
-            null -> null
-        }
-    }
-
-    companion object {
-        private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..6ea1daa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,56 @@
+/*
+ *  Copyright (C) 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.quickaffordance
+
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.yield
+
+/**
+ * Fake implementation of a quick affordance data source.
+ *
+ * This class is abstract to force tests to provide extensions of it as the system that references
+ * these configs uses each implementation's class type to refer to them.
+ */
+abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig {
+
+    var onClickedResult: OnClickedResult = OnClickedResult.Handled
+
+    private val _state =
+        MutableStateFlow<KeyguardQuickAffordanceConfig.State>(
+            KeyguardQuickAffordanceConfig.State.Hidden
+        )
+    override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
+
+    override fun onQuickAffordanceClicked(
+        animationController: ActivityLaunchAnimator.Controller?,
+    ): OnClickedResult {
+        return onClickedResult
+    }
+
+    suspend fun setState(state: KeyguardQuickAffordanceConfig.State) {
+        _state.value = state
+        // Yield to allow the test's collection coroutine to "catch up" and collect this value
+        // before the test continues to the next line.
+        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+        // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+        yield()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
new file mode 100644
index 0000000..1c9902b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
@@ -0,0 +1,43 @@
+/*
+ *  Copyright (C) 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.quickaffordance
+
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import kotlin.reflect.KClass
+
+/** Fake implementation of [FakeKeyguardQuickAffordanceRegistry], for tests. */
+class FakeKeyguardQuickAffordanceRegistry(
+    private val configsByPosition:
+        Map<KeyguardQuickAffordancePosition, List<KeyguardQuickAffordanceConfig>>,
+) : KeyguardQuickAffordanceRegistry {
+
+    override fun getAll(
+        position: KeyguardQuickAffordancePosition
+    ): List<KeyguardQuickAffordanceConfig> {
+        return configsByPosition.getValue(position)
+    }
+
+    override fun get(
+        configClass: KClass<out KeyguardQuickAffordanceConfig>
+    ): KeyguardQuickAffordanceConfig {
+        return configsByPosition.values
+            .flatten()
+            .associateBy { config -> config::class }
+            .getValue(configClass)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
similarity index 87%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index 810c6dc..9acd21c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -1,20 +1,21 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ *  Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ *  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
+ *       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.
+ *  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.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index ef588f5..059487d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
@@ -23,7 +23,7 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.dagger.ControlsComponent
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 6fd04de..d4fba41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -15,12 +15,12 @@
  *
  */
 
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import android.content.Intent
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 345c51f..5a3a78e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import android.graphics.drawable.Drawable
 import android.service.quickaccesswallet.GetWalletCardsResponse
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt
new file mode 100644
index 0000000..8982752
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt
@@ -0,0 +1,46 @@
+/*
+ *  Copyright (C) 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeObserveKeyguardQuickAffordanceUseCase : ObserveKeyguardQuickAffordanceUseCase {
+
+    private val affordanceByPosition =
+        mutableMapOf<
+            KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
+
+    init {
+        KeyguardQuickAffordancePosition.values().forEach { position ->
+            affordanceByPosition[position] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden)
+        }
+    }
+
+    override fun invoke(
+        position: KeyguardQuickAffordancePosition
+    ): Flow<KeyguardQuickAffordanceModel> {
+        return affordanceByPosition[position] ?: error("Flow unexpectedly missing!")
+    }
+
+    fun setModel(position: KeyguardQuickAffordancePosition, model: KeyguardQuickAffordanceModel) {
+        affordanceByPosition[position]?.value = model
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt
new file mode 100644
index 0000000..63eb68f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
+
+    private lateinit var underTest: ObserveKeyguardQuickAffordanceUseCase
+
+    private lateinit var repository: FakeKeyguardRepository
+    private lateinit var isDozingUseCase: ObserveIsDozingUseCase
+    private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
+    private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+    private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
+    private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
+
+    @Before
+    fun setUp() = runBlockingTest {
+        repository = FakeKeyguardRepository()
+        repository.setKeyguardShowing(true)
+        isDozingUseCase = ObserveIsDozingUseCase(repository)
+        isKeyguardShowingUseCase = ObserveIsKeyguardShowingUseCase(repository)
+
+        homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
+        quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
+        qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
+
+        underTest =
+            ObserveKeyguardQuickAffordanceUseCaseImpl(
+                registry =
+                    FakeKeyguardQuickAffordanceRegistry(
+                        mapOf(
+                            KeyguardQuickAffordancePosition.BOTTOM_START to
+                                listOf(
+                                    homeControls,
+                                ),
+                            KeyguardQuickAffordancePosition.BOTTOM_END to
+                                listOf(
+                                    quickAccessWallet,
+                                    qrCodeScanner,
+                                ),
+                        ),
+                    ),
+                isDozingUseCase = isDozingUseCase,
+                isKeyguardShowingUseCase = isKeyguardShowingUseCase,
+            )
+    }
+
+    @Test
+    fun `invoke - bottom start affordance is visible`() = runBlockingTest {
+        val configKey = homeControls::class
+        homeControls.setState(
+            KeyguardQuickAffordanceConfig.State.Visible(
+                icon = ICON,
+                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+            )
+        )
+
+        var latest: KeyguardQuickAffordanceModel? = null
+        val job =
+            underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+        val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
+        assertThat(visibleModel.configKey).isEqualTo(configKey)
+        assertThat(visibleModel.icon).isEqualTo(ICON)
+        assertThat(visibleModel.contentDescriptionResourceId)
+            .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+        job.cancel()
+    }
+
+    @Test
+    fun `invoke - bottom end affordance is visible`() = runBlockingTest {
+        val configKey = quickAccessWallet::class
+        quickAccessWallet.setState(
+            KeyguardQuickAffordanceConfig.State.Visible(
+                icon = ICON,
+                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+            )
+        )
+
+        var latest: KeyguardQuickAffordanceModel? = null
+        val job =
+            underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+        val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
+        assertThat(visibleModel.configKey).isEqualTo(configKey)
+        assertThat(visibleModel.icon).isEqualTo(ICON)
+        assertThat(visibleModel.contentDescriptionResourceId)
+            .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+        job.cancel()
+    }
+
+    @Test
+    fun `invoke - bottom start affordance hidden while dozing`() = runBlockingTest {
+        repository.setDozing(true)
+        homeControls.setState(
+            KeyguardQuickAffordanceConfig.State.Visible(
+                icon = ICON,
+                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+            )
+        )
+
+        var latest: KeyguardQuickAffordanceModel? = null
+        val job =
+            underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+                .onEach { latest = it }
+                .launchIn(this)
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+        job.cancel()
+    }
+
+    @Test
+    fun `invoke - bottom start affordance hidden when lockscreen is not showing`() =
+        runBlockingTest {
+            repository.setKeyguardShowing(false)
+            homeControls.setState(
+                KeyguardQuickAffordanceConfig.State.Visible(
+                    icon = ICON,
+                    contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+                )
+            )
+
+            var latest: KeyguardQuickAffordanceModel? = null
+            val job =
+                underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+                    .onEach { latest = it }
+                    .launchIn(this)
+            assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+            job.cancel()
+        }
+
+    companion object {
+        private val ICON: ContainedDrawable = mock()
+        private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt
deleted file mode 100644
index b90400be..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.containeddrawable.ContainedDrawable
-import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runBlockingTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@SmallTest
-@RunWith(JUnit4::class)
-class ObserveKeyguardQuickAffordanceUseCaseTest : SysuiTestCase() {
-
-    private lateinit var underTest: ObserveKeyguardQuickAffordanceUseCase
-
-    private lateinit var repository: FakeKeyguardRepository
-    private lateinit var quickAffordanceRepository: FakeKeyguardQuickAffordanceRepository
-    private lateinit var isDozingUseCase: ObserveIsDozingUseCase
-    private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
-
-    @Before
-    fun setUp() {
-        repository = FakeKeyguardRepository()
-        repository.setKeyguardShowing(true)
-        isDozingUseCase = ObserveIsDozingUseCase(repository)
-        isKeyguardShowingUseCase = ObserveIsKeyguardShowingUseCase(repository)
-        quickAffordanceRepository = FakeKeyguardQuickAffordanceRepository()
-
-        underTest =
-            ObserveKeyguardQuickAffordanceUseCase(
-                repository = quickAffordanceRepository,
-                isDozingUseCase = isDozingUseCase,
-                isKeyguardShowingUseCase = isKeyguardShowingUseCase,
-            )
-    }
-
-    @Test
-    fun `invoke - affordance is visible`() = runBlockingTest {
-        val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
-        val model =
-            KeyguardQuickAffordanceModel.Visible(
-                configKey = configKey,
-                icon = ICON,
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-            )
-        quickAffordanceRepository.setModel(
-            KeyguardQuickAffordancePosition.BOTTOM_END,
-            model,
-        )
-
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
-                .onEach { latest = it }
-                .launchIn(this)
-
-        assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
-        val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
-        assertThat(visibleModel.configKey).isEqualTo(configKey)
-        assertThat(visibleModel.icon).isEqualTo(ICON)
-        assertThat(visibleModel.contentDescriptionResourceId)
-            .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
-        job.cancel()
-    }
-
-    @Test
-    fun `invoke - affordance not visible while dozing`() = runBlockingTest {
-        repository.setDozing(true)
-        val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
-        val model =
-            KeyguardQuickAffordanceModel.Visible(
-                configKey = configKey,
-                icon = ICON,
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-            )
-        quickAffordanceRepository.setModel(
-            KeyguardQuickAffordancePosition.BOTTOM_END,
-            model,
-        )
-
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
-                .onEach { latest = it }
-                .launchIn(this)
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
-        job.cancel()
-    }
-
-    @Test
-    fun `invoke - affordance not visible when lockscreen is not showing`() = runBlockingTest {
-        repository.setKeyguardShowing(false)
-        val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
-        val model =
-            KeyguardQuickAffordanceModel.Visible(
-                configKey = configKey,
-                icon = ICON,
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-            )
-        quickAffordanceRepository.setModel(
-            KeyguardQuickAffordancePosition.BOTTOM_END,
-            model,
-        )
-
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
-                .onEach { latest = it }
-                .launchIn(this)
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
-        job.cancel()
-    }
-
-    @Test
-    fun `invoke - affordance is none`() = runBlockingTest {
-        quickAffordanceRepository.setModel(
-            KeyguardQuickAffordancePosition.BOTTOM_START,
-            KeyguardQuickAffordanceModel.Hidden,
-        )
-
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
-                .onEach { latest = it }
-                .launchIn(this)
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
-        job.cancel()
-    }
-
-    companion object {
-        private val ICON: ContainedDrawable = mock()
-        private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 00dd58e..8758ce5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -22,22 +22,20 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.containeddrawable.ContainedDrawable
 import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfigs
-import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.domain.usecase.FakeLaunchKeyguardQuickAffordanceUseCase
+import com.android.systemui.keyguard.domain.usecase.FakeObserveKeyguardQuickAffordanceUseCase
 import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
 import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
 import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
 import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
 import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveIsKeyguardShowingUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
 import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
@@ -63,14 +61,14 @@
 
     private lateinit var underTest: KeyguardBottomAreaViewModel
 
-    private lateinit var affordanceRepository: FakeKeyguardQuickAffordanceRepository
     private lateinit var repository: FakeKeyguardRepository
+    private lateinit var registry: FakeKeyguardQuickAffordanceRegistry
     private lateinit var isDozingUseCase: ObserveIsDozingUseCase
-    private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
     private lateinit var launchQuickAffordanceUseCase: FakeLaunchKeyguardQuickAffordanceUseCase
     private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
     private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
     private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+    private lateinit var observeQuickAffordanceUseCase: FakeObserveKeyguardQuickAffordanceUseCase
 
     @Before
     fun setUp() {
@@ -78,33 +76,38 @@
         whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
             .thenReturn(RETURNED_BURN_IN_OFFSET)
 
-        affordanceRepository = FakeKeyguardQuickAffordanceRepository()
+        homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+        quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+        qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+        registry =
+            FakeKeyguardQuickAffordanceRegistry(
+                mapOf(
+                    KeyguardQuickAffordancePosition.BOTTOM_START to
+                        listOf(
+                            homeControlsQuickAffordanceConfig,
+                        ),
+                    KeyguardQuickAffordancePosition.BOTTOM_END to
+                        listOf(
+                            quickAccessWalletAffordanceConfig,
+                            qrCodeScannerAffordanceConfig,
+                        ),
+                ),
+            )
         repository = FakeKeyguardRepository()
         isDozingUseCase =
             ObserveIsDozingUseCase(
                 repository = repository,
             )
-        isKeyguardShowingUseCase =
-            ObserveIsKeyguardShowingUseCase(
-                repository = repository,
-            )
         launchQuickAffordanceUseCase = FakeLaunchKeyguardQuickAffordanceUseCase()
-        homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
-        quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
-        qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+        observeQuickAffordanceUseCase = FakeObserveKeyguardQuickAffordanceUseCase()
 
         underTest =
             KeyguardBottomAreaViewModel(
-                observeQuickAffordanceUseCase =
-                    ObserveKeyguardQuickAffordanceUseCase(
-                        repository = affordanceRepository,
-                        isDozingUseCase = isDozingUseCase,
-                        isKeyguardShowingUseCase = isKeyguardShowingUseCase,
-                    ),
+                observeQuickAffordanceUseCase = observeQuickAffordanceUseCase,
                 onQuickAffordanceClickedUseCase =
                     OnKeyguardQuickAffordanceClickedUseCase(
-                        configs =
-                            FakeKeyguardQuickAffordanceConfigs(
+                        registry =
+                            FakeKeyguardQuickAffordanceRegistry(
                                 mapOf(
                                     KeyguardQuickAffordancePosition.BOTTOM_START to
                                         listOf(
@@ -141,98 +144,11 @@
     }
 
     @Test
-    fun `startButton - present - not dozing - lockscreen showing - visible model - starts activity on click`() = // ktlint-disable max-line-length
-        runBlockingTest {
-            var latest: KeyguardQuickAffordanceViewModel? = null
-            val job = underTest.startButton.onEach { latest = it }.launchIn(this)
-
-            repository.setDozing(false)
-            repository.setKeyguardShowing(true)
-            val testConfig =
-                TestConfig(
-                    isVisible = true,
-                    icon = mock(),
-                    canShowWhileLocked = false,
-                    intent = Intent("action"),
-                )
-            val configKey =
-                setUpQuickAffordanceModel(
-                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
-                    testConfig = testConfig,
-                )
-
-            assertQuickAffordanceViewModel(
-                viewModel = latest,
-                testConfig = testConfig,
-                configKey = configKey,
-            )
-            job.cancel()
-        }
-
-    @Test
-    fun `endButton - present - not dozing - lockscreen showing - visible model - do nothing on click`() = // ktlint-disable max-line-length
-        runBlockingTest {
-            var latest: KeyguardQuickAffordanceViewModel? = null
-            val job = underTest.endButton.onEach { latest = it }.launchIn(this)
-
-            repository.setDozing(false)
-            repository.setKeyguardShowing(true)
-            val config =
-                TestConfig(
-                    isVisible = true,
-                    icon = mock(),
-                    canShowWhileLocked = false,
-                    intent =
-                        null, // This will cause it to tell the system that the click was handled.
-                )
-            val configKey =
-                setUpQuickAffordanceModel(
-                    position = KeyguardQuickAffordancePosition.BOTTOM_END,
-                    testConfig = config,
-                )
-
-            assertQuickAffordanceViewModel(
-                viewModel = latest,
-                testConfig = config,
-                configKey = configKey,
-            )
-            job.cancel()
-        }
-
-    @Test
-    fun `startButton - not present - not dozing - lockscreen showing - model is none`() =
-        runBlockingTest {
-            var latest: KeyguardQuickAffordanceViewModel? = null
-            val job = underTest.startButton.onEach { latest = it }.launchIn(this)
-
-            repository.setDozing(false)
-            repository.setKeyguardShowing(true)
-            val config =
-                TestConfig(
-                    isVisible = false,
-                )
-            val configKey =
-                setUpQuickAffordanceModel(
-                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
-                    testConfig = config,
-                )
-
-            assertQuickAffordanceViewModel(
-                viewModel = latest,
-                testConfig = config,
-                configKey = configKey,
-            )
-            job.cancel()
-        }
-
-    @Test
-    fun `startButton - present - dozing - lockscreen showing - model is none`() = runBlockingTest {
+    fun `startButton - present - visible model - starts activity on click`() = runBlockingTest {
         var latest: KeyguardQuickAffordanceViewModel? = null
         val job = underTest.startButton.onEach { latest = it }.launchIn(this)
 
-        repository.setDozing(true)
-        repository.setKeyguardShowing(true)
-        val config =
+        val testConfig =
             TestConfig(
                 isVisible = true,
                 icon = mock(),
@@ -242,45 +158,65 @@
         val configKey =
             setUpQuickAffordanceModel(
                 position = KeyguardQuickAffordancePosition.BOTTOM_START,
-                testConfig = config,
+                testConfig = testConfig,
             )
 
         assertQuickAffordanceViewModel(
             viewModel = latest,
-            testConfig = TestConfig(isVisible = false),
+            testConfig = testConfig,
             configKey = configKey,
         )
         job.cancel()
     }
 
     @Test
-    fun `startButton - present - not dozing - lockscreen not showing - model is none`() =
-        runBlockingTest {
-            var latest: KeyguardQuickAffordanceViewModel? = null
-            val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+    fun `endButton - present - visible model - do nothing on click`() = runBlockingTest {
+        var latest: KeyguardQuickAffordanceViewModel? = null
+        val job = underTest.endButton.onEach { latest = it }.launchIn(this)
 
-            repository.setDozing(false)
-            repository.setKeyguardShowing(false)
-            val config =
-                TestConfig(
-                    isVisible = true,
-                    icon = mock(),
-                    canShowWhileLocked = false,
-                    intent = Intent("action"),
-                )
-            val configKey =
-                setUpQuickAffordanceModel(
-                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
-                    testConfig = config,
-                )
-
-            assertQuickAffordanceViewModel(
-                viewModel = latest,
-                testConfig = TestConfig(isVisible = false),
-                configKey = configKey,
+        val config =
+            TestConfig(
+                isVisible = true,
+                icon = mock(),
+                canShowWhileLocked = false,
+                intent = null, // This will cause it to tell the system that the click was handled.
             )
-            job.cancel()
-        }
+        val configKey =
+            setUpQuickAffordanceModel(
+                position = KeyguardQuickAffordancePosition.BOTTOM_END,
+                testConfig = config,
+            )
+
+        assertQuickAffordanceViewModel(
+            viewModel = latest,
+            testConfig = config,
+            configKey = configKey,
+        )
+        job.cancel()
+    }
+
+    @Test
+    fun `startButton - not present - model is hidden`() = runBlockingTest {
+        var latest: KeyguardQuickAffordanceViewModel? = null
+        val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+        val config =
+            TestConfig(
+                isVisible = false,
+            )
+        val configKey =
+            setUpQuickAffordanceModel(
+                position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                testConfig = config,
+            )
+
+        assertQuickAffordanceViewModel(
+            viewModel = latest,
+            testConfig = config,
+            configKey = configKey,
+        )
+        job.cancel()
+    }
 
     @Test
     fun animateButtonReveal() = runBlockingTest {
@@ -413,7 +349,7 @@
         job.cancel()
     }
 
-    private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+    private suspend fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
         repository.setDozeAmount(dozeAmount)
         return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
     }
@@ -428,27 +364,31 @@
                 KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
             }
 
-        affordanceRepository.setModel(
-            position = position,
-            model =
-                if (testConfig.isVisible) {
-                    if (testConfig.intent != null) {
-                        config.onClickedResult =
-                            KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
-                                intent = testConfig.intent,
-                                canShowWhileLocked = testConfig.canShowWhileLocked,
-                            )
-                    }
-                    KeyguardQuickAffordanceModel.Visible(
-                        configKey = config::class,
-                        icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
-                        contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-                    )
-                } else {
-                    KeyguardQuickAffordanceModel.Hidden
+        val state =
+            if (testConfig.isVisible) {
+                if (testConfig.intent != null) {
+                    config.onClickedResult =
+                        KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+                            intent = testConfig.intent,
+                            canShowWhileLocked = testConfig.canShowWhileLocked,
+                        )
                 }
+                KeyguardQuickAffordanceConfig.State.Visible(
+                    icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
+                    contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+                )
+            } else {
+                KeyguardQuickAffordanceConfig.State.Hidden
+            }
+        config.setState(state)
+
+        val configKey = config::class
+        observeQuickAffordanceUseCase.setModel(
+            position,
+            KeyguardQuickAffordanceModel.from(state, configKey)
         )
-        return config::class
+
+        return configKey
     }
 
     private fun assertQuickAffordanceViewModel(