Changes quick affordance key to string.

Before this change, the key that uniquely identifies each quick
affordance was the class of that key. Since we're about to embark on a
project where we'll have many implementations that share the same class,
this is no longer sufficient.

Previously, I was reluctant to use a string because there was no easy
way to guarantee uniqueness. Now that we see that we can't guarantee
uniqueness through the class typing system, we have to compromise.

Bug: 254858696
Test: unit tests updated and all passing. Manually verified that
affordances work and that clicking on one doesn't trigger the other.

Change-Id: I2d1ec382c04604c8478737720eb92410eec479f8
This commit is contained in:
Alejandro Nijamkin
2022-10-21 12:19:59 -07:00
parent 04cbb0e201
commit 61274aba37
14 changed files with 115 additions and 40 deletions

View File

@@ -29,7 +29,6 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlin.reflect.KClass
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onStart
@@ -70,10 +69,10 @@ constructor(
* @param expandable An optional [Expandable] for the activity- or dialog-launch animation
*/
fun onQuickAffordanceClicked(
configKey: KClass<out KeyguardQuickAffordanceConfig>,
configKey: String,
expandable: Expandable?,
) {
@Suppress("UNCHECKED_CAST") val config = registry.get(configKey as KClass<Nothing>)
@Suppress("UNCHECKED_CAST") val config = registry.get(configKey)
when (val result = config.onQuickAffordanceClicked(expandable)) {
is KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity ->
launchQuickAffordance(
@@ -102,7 +101,7 @@ constructor(
if (index != -1) {
val visibleState = states[index] as KeyguardQuickAffordanceConfig.State.Visible
KeyguardQuickAffordanceModel.Visible(
configKey = configs[index]::class,
configKey = configs[index].key,
icon = visibleState.icon,
toggle = visibleState.toggle,
)

View File

@@ -18,9 +18,7 @@
package com.android.systemui.keyguard.domain.model
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
import kotlin.reflect.KClass
/**
* Models a "quick affordance" in the keyguard bottom area (for example, a button on the
@@ -33,7 +31,7 @@ sealed class KeyguardQuickAffordanceModel {
/** A affordance is visible. */
data class Visible(
/** Identifier for the affordance this is modeling. */
val configKey: KClass<out KeyguardQuickAffordanceConfig>,
val configKey: String,
/** An icon for the affordance. */
val icon: Icon,
/** The toggle state for the affordance. */

View File

@@ -0,0 +1,31 @@
/*
* 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
/**
* Unique identifier keys for all known built-in quick affordances.
*
* Please ensure uniqueness by never associating more than one class with each key.
*/
object BuiltInKeyguardQuickAffordanceKeys {
// Please keep alphabetical order of const names to simplify future maintenance.
const val HOME_CONTROLS = "home"
const val QR_CODE_SCANNER = "qr_code_scanner"
const val QUICK_ACCESS_WALLET = "wallet"
// Please keep alphabetical order of const names to simplify future maintenance.
}

View File

@@ -51,6 +51,8 @@ constructor(
private val appContext = context.applicationContext
override val key: String = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
override val state: Flow<KeyguardQuickAffordanceConfig.State> =
component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked ->
if (canShowWhileLocked) {

View File

@@ -26,8 +26,18 @@ import kotlinx.coroutines.flow.Flow
/** Defines interface that can act as data source for a single quick affordance model. */
interface KeyguardQuickAffordanceConfig {
/** Unique identifier for this quick affordance. It must be globally unique. */
val key: String
/** The observable [State] of the affordance. */
val state: Flow<State>
/**
* Notifies that the affordance was clicked by the user.
*
* @param expandable An [Expandable] to use when animating dialogs or activities
* @return An [OnClickedResult] telling the caller what to do next
*/
fun onQuickAffordanceClicked(expandable: Expandable?): OnClickedResult
/**

View File

@@ -19,12 +19,11 @@ package com.android.systemui.keyguard.domain.quickaffordance
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import javax.inject.Inject
import kotlin.reflect.KClass
/** Central registry of all known quick affordance configs. */
interface KeyguardQuickAffordanceRegistry<T : KeyguardQuickAffordanceConfig> {
fun getAll(position: KeyguardQuickAffordancePosition): List<T>
fun get(configClass: KClass<out T>): T
fun get(key: String): T
}
class KeyguardQuickAffordanceRegistryImpl
@@ -46,8 +45,8 @@ constructor(
qrCodeScanner,
),
)
private val configByClass =
configsByPosition.values.flatten().associateBy { config -> config::class }
private val configByKey =
configsByPosition.values.flatten().associateBy { config -> config.key }
override fun getAll(
position: KeyguardQuickAffordancePosition,
@@ -56,8 +55,8 @@ constructor(
}
override fun get(
configClass: KClass<out KeyguardQuickAffordanceConfig>
key: String,
): KeyguardQuickAffordanceConfig {
return configByClass.getValue(configClass)
return configByKey.getValue(key)
}
}

View File

@@ -37,6 +37,8 @@ constructor(
private val controller: QRCodeScannerController,
) : KeyguardQuickAffordanceConfig {
override val key: String = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
val callback =
object : QRCodeScannerController.Callback {

View File

@@ -44,6 +44,8 @@ constructor(
private val activityStarter: ActivityStarter,
) : KeyguardQuickAffordanceConfig {
override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
val callback =
object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {

View File

@@ -18,12 +18,10 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import kotlin.reflect.KClass
/** Models the UI state of a keyguard quick affordance button. */
data class KeyguardQuickAffordanceViewModel(
val configKey: KClass<out KeyguardQuickAffordanceConfig>? = null,
val configKey: String? = null,
val isVisible: Boolean = false,
/** Whether to animate the transition of the quick affordance from invisible to visible. */
val animateReveal: Boolean = false,
@@ -33,7 +31,7 @@ data class KeyguardQuickAffordanceViewModel(
val isActivated: Boolean = false,
) {
data class OnClickedParameters(
val configKey: KClass<out KeyguardQuickAffordanceConfig>,
val configKey: String,
val expandable: Expandable?,
)
}

View File

@@ -27,6 +27,7 @@ import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.domain.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
@@ -211,7 +212,11 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
whenever(expandable.activityLaunchController()).thenReturn(animationController)
homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
homeControls =
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
) {}
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
@@ -224,8 +229,14 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
),
KeyguardQuickAffordancePosition.BOTTOM_END to
listOf(
object : FakeKeyguardQuickAffordanceConfig() {},
object : FakeKeyguardQuickAffordanceConfig() {},
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
) {},
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
) {},
),
),
),
@@ -260,7 +271,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
}
underTest.onQuickAffordanceClicked(
configKey = homeControls::class,
configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
expandable = expandable,
)

View File

@@ -25,6 +25,7 @@ import com.android.systemui.common.shared.model.Icon
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.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
@@ -69,9 +70,21 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
repository = FakeKeyguardRepository()
repository.setKeyguardShowing(true)
homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
homeControls =
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
) {}
quickAccessWallet =
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
) {}
qrCodeScanner =
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
) {}
underTest =
KeyguardQuickAffordanceInteractor(
@@ -99,7 +112,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
@Test
fun `quickAffordance - bottom start affordance is visible`() = runBlockingTest {
val configKey = homeControls::class
val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible(
icon = ICON,
@@ -130,7 +143,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
@Test
fun `quickAffordance - bottom end affordance is visible`() = runBlockingTest {
val configKey = quickAccessWallet::class
val configKey = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
quickAccessWallet.setState(
KeyguardQuickAffordanceConfig.State.Visible(
icon = ICON,

View File

@@ -29,7 +29,9 @@ import kotlinx.coroutines.yield
* 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 {
abstract class FakeKeyguardQuickAffordanceConfig(
override val key: String,
) : KeyguardQuickAffordanceConfig {
var onClickedResult: OnClickedResult = OnClickedResult.Handled

View File

@@ -18,7 +18,6 @@
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(
@@ -33,11 +32,8 @@ class FakeKeyguardQuickAffordanceRegistry(
}
override fun get(
configClass: KClass<out FakeKeyguardQuickAffordanceConfig>
key: String,
): FakeKeyguardQuickAffordanceConfig {
return configsByPosition.values
.flatten()
.associateBy { config -> config::class }
.getValue(configClass)
return configsByPosition.values.flatten().associateBy { config -> config.key }.getValue(key)
}
}

View File

@@ -28,6 +28,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInterac
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.domain.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
@@ -40,7 +41,6 @@ import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
import kotlin.reflect.KClass
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
@@ -81,9 +81,21 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
.thenReturn(RETURNED_BURN_IN_OFFSET)
homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
homeControlsQuickAffordanceConfig =
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
) {}
quickAccessWalletAffordanceConfig =
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
) {}
qrCodeScannerAffordanceConfig =
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
) {}
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
@@ -489,7 +501,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
private suspend fun setUpQuickAffordanceModel(
position: KeyguardQuickAffordancePosition,
testConfig: TestConfig,
): KClass<out FakeKeyguardQuickAffordanceConfig> {
): String {
val config =
when (position) {
KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
@@ -518,13 +530,13 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
KeyguardQuickAffordanceConfig.State.Hidden
}
config.setState(state)
return config::class
return config.key
}
private fun assertQuickAffordanceViewModel(
viewModel: KeyguardQuickAffordanceViewModel?,
testConfig: TestConfig,
configKey: KClass<out FakeKeyguardQuickAffordanceConfig>,
configKey: String,
) {
checkNotNull(viewModel)
assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)