From 6b4f8b1adfd39c344d0d58e259a3d028c51319f7 Mon Sep 17 00:00:00 2001 From: Alejandro Nijamkin Date: Mon, 8 Aug 2022 14:28:57 -0700 Subject: 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 --- .../SystemUI/docs/device-entry/quickaffordance.md | 8 +- .../systemui/keyguard/dagger/KeyguardModule.java | 2 + .../HomeControlsKeyguardQuickAffordanceConfig.kt | 128 ----------- .../KeyguardQuickAffordanceConfig.kt | 73 ------ .../QrCodeScannerKeyguardQuickAffordanceConfig.kt | 90 -------- ...ickAccessWalletKeyguardQuickAffordanceConfig.kt | 114 --------- .../repository/KeyguardQuickAffordanceConfigs.kt | 67 ------ .../KeyguardQuickAffordanceRepository.kt | 65 ------ .../data/repository/KeyguardRepositoryModule.kt | 12 - .../domain/model/KeyguardQuickAffordanceModel.kt | 62 +++++ .../model/KeyguardQuickAffordancePosition.kt | 23 ++ .../HomeControlsKeyguardQuickAffordanceConfig.kt | 128 +++++++++++ .../KeyguardQuickAffordanceConfig.kt | 73 ++++++ .../KeyguardQuickAffordanceModule.kt | 29 +++ .../KeyguardQuickAffordanceRegistry.kt | 63 +++++ .../QrCodeScannerKeyguardQuickAffordanceConfig.kt | 90 ++++++++ ...ickAccessWalletKeyguardQuickAffordanceConfig.kt | 114 +++++++++ .../domain/usecase/KeyguardUseCaseModule.kt | 5 + .../ObserveKeyguardQuickAffordanceUseCase.kt | 44 +++- .../OnKeyguardQuickAffordanceClickedUseCase.kt | 10 +- .../shared/model/KeyguardQuickAffordanceModel.kt | 46 ---- .../model/KeyguardQuickAffordancePosition.kt | 23 -- .../ui/viewmodel/KeyguardBottomAreaViewModel.kt | 4 +- ...dQuickAffordanceConfigParameterizedStateTest.kt | 138 ----------- ...omeControlsKeyguardQuickAffordanceConfigTest.kt | 119 ---------- ...CodeScannerKeyguardQuickAffordanceConfigTest.kt | 148 ------------ ...ccessWalletKeyguardQuickAffordanceConfigTest.kt | 179 --------------- .../FakeKeyguardQuickAffordanceConfig.kt | 60 ----- .../FakeKeyguardQuickAffordanceConfigs.kt | 44 ---- .../FakeKeyguardQuickAffordanceRepository.kt | 55 ----- .../data/repository/FakeKeyguardRepository.kt | 27 ++- .../KeyguardQuickAffordanceRepositoryImplTest.kt | 193 ---------------- .../FakeKeyguardQuickAffordanceConfig.kt | 56 +++++ .../FakeKeyguardQuickAffordanceRegistry.kt | 43 ++++ ...dQuickAffordanceConfigParameterizedStateTest.kt | 139 +++++++++++ ...omeControlsKeyguardQuickAffordanceConfigTest.kt | 119 ++++++++++ ...CodeScannerKeyguardQuickAffordanceConfigTest.kt | 148 ++++++++++++ ...ccessWalletKeyguardQuickAffordanceConfigTest.kt | 179 +++++++++++++++ .../FakeObserveKeyguardQuickAffordanceUseCase.kt | 46 ++++ ...bserveKeyguardQuickAffordanceUseCaseImplTest.kt | 176 ++++++++++++++ .../ObserveKeyguardQuickAffordanceUseCaseTest.kt | 161 ------------- .../viewmodel/KeyguardBottomAreaViewModelTest.kt | 254 ++++++++------------- 42 files changed, 1657 insertions(+), 1900 deletions(-) delete mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md index a96e533933f4..38d636d7ff82 100644 --- a/packages/SystemUI/docs/device-entry/quickaffordance.md +++ b/packages/SystemUI/docs/device-entry/quickaffordance.md @@ -6,16 +6,16 @@ credit card, etc. ## 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 4ff008ffb33a..430b59cd4027 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.DismissCallbackRegistry; 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 @@ import dagger.Provides; KeyguardUserSwitcherComponent.class}, includes = { FalsingModule.class, + KeyguardQuickAffordanceModule.class, KeyguardRepositoryModule.class, KeyguardUseCaseModule.class, }) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt deleted file mode 100644 index df44957ec591..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt +++ /dev/null @@ -1,128 +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.quickaffordance - -import android.content.Context -import android.content.Intent -import androidx.annotation.DrawableRes -import com.android.systemui.animation.ActivityLaunchAnimator -import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow -import com.android.systemui.containeddrawable.ContainedDrawable -import com.android.systemui.controls.ControlsServiceInfo -import com.android.systemui.controls.controller.StructureInfo -import com.android.systemui.controls.dagger.ControlsComponent -import com.android.systemui.controls.management.ControlsListingController -import com.android.systemui.controls.ui.ControlsActivity -import com.android.systemui.controls.ui.ControlsUiController -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.util.kotlin.getOrNull -import javax.inject.Inject -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf - -/** Home controls quick affordance data source. */ -@SysUISingleton -class HomeControlsKeyguardQuickAffordanceConfig -@Inject -constructor( - @Application context: Context, - private val component: ControlsComponent, -) : KeyguardQuickAffordanceConfig { - - private val appContext = context.applicationContext - - override val state: Flow = - component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked -> - if (canShowWhileLocked) { - stateInternal(component.getControlsListingController().getOrNull()) - } else { - flowOf(KeyguardQuickAffordanceConfig.State.Hidden) - } - } - - override fun onQuickAffordanceClicked( - animationController: ActivityLaunchAnimator.Controller?, - ): KeyguardQuickAffordanceConfig.OnClickedResult { - return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity( - intent = - Intent(appContext, ControlsActivity::class.java) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) - .putExtra( - ControlsUiController.EXTRA_ANIMATE, - true, - ), - canShowWhileLocked = component.canShowWhileLockedSetting.value, - ) - } - - private fun stateInternal( - listingController: ControlsListingController?, - ): Flow { - if (listingController == null) { - return flowOf(KeyguardQuickAffordanceConfig.State.Hidden) - } - - return conflatedCallbackFlow { - val callback = - object : ControlsListingController.ControlsListingCallback { - override fun onServicesUpdated(serviceInfos: List) { - val favorites: List? = - component.getControlsController().getOrNull()?.getFavorites() - - trySendWithFailureLogging( - state( - isFeatureEnabled = component.isEnabled(), - hasFavorites = favorites?.isNotEmpty() == true, - hasServiceInfos = serviceInfos.isNotEmpty(), - iconResourceId = component.getTileImageId(), - ), - TAG, - ) - } - } - - listingController.addCallback(callback) - - awaitClose { listingController.removeCallback(callback) } - } - } - - private fun state( - isFeatureEnabled: Boolean, - hasFavorites: Boolean, - hasServiceInfos: Boolean, - @DrawableRes iconResourceId: Int?, - ): KeyguardQuickAffordanceConfig.State { - return if (isFeatureEnabled && hasFavorites && hasServiceInfos && iconResourceId != null) { - KeyguardQuickAffordanceConfig.State.Visible( - icon = ContainedDrawable.WithResource(iconResourceId), - contentDescriptionResourceId = component.getTileTitleId(), - ) - } else { - KeyguardQuickAffordanceConfig.State.Hidden - } - } - - companion object { - private const val TAG = "HomeControlsKeyguardQuickAffordanceConfig" - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt deleted file mode 100644 index 67a776eddccb..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt +++ /dev/null @@ -1,73 +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.quickaffordance - -import android.content.Intent -import androidx.annotation.StringRes -import com.android.systemui.animation.ActivityLaunchAnimator -import com.android.systemui.containeddrawable.ContainedDrawable -import kotlinx.coroutines.flow.Flow - -/** Defines interface that can act as data source for a single quick affordance model. */ -interface KeyguardQuickAffordanceConfig { - - val state: Flow - - fun onQuickAffordanceClicked( - animationController: ActivityLaunchAnimator.Controller? - ): OnClickedResult - - /** - * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a - * button on the lock-screen). - */ - sealed class State { - - /** No affordance should show up. */ - object Hidden : State() - - /** An affordance is visible. */ - data class Visible( - /** An icon for the affordance. */ - val icon: ContainedDrawable, - /** - * Resource ID for a string to use for the accessibility content description text of the - * affordance. - */ - @StringRes val contentDescriptionResourceId: Int, - ) : State() - } - - sealed class OnClickedResult { - /** - * Returning this as a result from the [onQuickAffordanceClicked] method means that the - * implementation has taken care of the click, the system will do nothing. - */ - object Handled : OnClickedResult() - - /** - * Returning this as a result from the [onQuickAffordanceClicked] method means that the - * implementation has _not_ taken care of the click and the system should start an activity - * using the given [Intent]. - */ - data class StartActivity( - val intent: Intent, - val canShowWhileLocked: Boolean, - ) : OnClickedResult() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt deleted file mode 100644 index ea6497ebcf3a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt +++ /dev/null @@ -1,90 +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.quickaffordance - -import com.android.systemui.R -import com.android.systemui.animation.ActivityLaunchAnimator -import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow -import com.android.systemui.containeddrawable.ContainedDrawable -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qrcodescanner.controller.QRCodeScannerController -import javax.inject.Inject -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow - -/** QR code scanner quick affordance data source. */ -@SysUISingleton -class QrCodeScannerKeyguardQuickAffordanceConfig -@Inject -constructor( - private val controller: QRCodeScannerController, -) : KeyguardQuickAffordanceConfig { - - override val state: Flow = conflatedCallbackFlow { - val callback = - object : QRCodeScannerController.Callback { - override fun onQRCodeScannerActivityChanged() { - trySendWithFailureLogging(state(), TAG) - } - override fun onQRCodeScannerPreferenceChanged() { - trySendWithFailureLogging(state(), TAG) - } - } - - controller.addCallback(callback) - controller.registerQRCodeScannerChangeObservers( - QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE, - QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE - ) - // Registering does not push an initial update. - trySendWithFailureLogging(state(), "initial state", TAG) - - awaitClose { - controller.unregisterQRCodeScannerChangeObservers( - QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE, - QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE - ) - controller.removeCallback(callback) - } - } - - override fun onQuickAffordanceClicked( - animationController: ActivityLaunchAnimator.Controller?, - ): KeyguardQuickAffordanceConfig.OnClickedResult { - return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity( - intent = controller.intent, - canShowWhileLocked = true, - ) - } - - private fun state(): KeyguardQuickAffordanceConfig.State { - return if (controller.isEnabledForLockScreenButton) { - KeyguardQuickAffordanceConfig.State.Visible( - icon = ContainedDrawable.WithResource(R.drawable.ic_qr_code_scanner), - contentDescriptionResourceId = R.string.accessibility_qr_code_scanner_button, - ) - } else { - KeyguardQuickAffordanceConfig.State.Hidden - } - } - - companion object { - private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig" - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt deleted file mode 100644 index cc5a9972af93..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt +++ /dev/null @@ -1,114 +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.quickaffordance - -import android.graphics.drawable.Drawable -import android.service.quickaccesswallet.GetWalletCardsError -import android.service.quickaccesswallet.GetWalletCardsResponse -import android.service.quickaccesswallet.QuickAccessWalletClient -import android.util.Log -import com.android.systemui.R -import com.android.systemui.animation.ActivityLaunchAnimator -import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow -import com.android.systemui.containeddrawable.ContainedDrawable -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.wallet.controller.QuickAccessWalletController -import javax.inject.Inject -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow - -/** Quick access wallet quick affordance data source. */ -@SysUISingleton -class QuickAccessWalletKeyguardQuickAffordanceConfig -@Inject -constructor( - private val walletController: QuickAccessWalletController, - private val activityStarter: ActivityStarter, -) : KeyguardQuickAffordanceConfig { - - override val state: Flow = conflatedCallbackFlow { - val callback = - object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback { - override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) { - trySendWithFailureLogging( - state( - isFeatureEnabled = walletController.isWalletEnabled, - hasCard = response?.walletCards?.isNotEmpty() == true, - tileIcon = walletController.walletClient.tileIcon, - ), - TAG, - ) - } - - override fun onWalletCardRetrievalError(error: GetWalletCardsError?) { - Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"") - trySendWithFailureLogging( - KeyguardQuickAffordanceConfig.State.Hidden, - TAG, - ) - } - } - - walletController.setupWalletChangeObservers( - callback, - QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE, - QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE - ) - walletController.updateWalletPreference() - walletController.queryWalletCards(callback) - - awaitClose { - walletController.unregisterWalletChangeObservers( - QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE, - QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE - ) - } - } - - override fun onQuickAffordanceClicked( - animationController: ActivityLaunchAnimator.Controller?, - ): KeyguardQuickAffordanceConfig.OnClickedResult { - walletController.startQuickAccessUiIntent( - activityStarter, - animationController, - /* hasCard= */ true, - ) - return KeyguardQuickAffordanceConfig.OnClickedResult.Handled - } - - private fun state( - isFeatureEnabled: Boolean, - hasCard: Boolean, - tileIcon: Drawable?, - ): KeyguardQuickAffordanceConfig.State { - return if (isFeatureEnabled && hasCard && tileIcon != null) { - KeyguardQuickAffordanceConfig.State.Visible( - icon = ContainedDrawable.WithDrawable(tileIcon), - contentDescriptionResourceId = R.string.accessibility_wallet_button, - ) - } else { - KeyguardQuickAffordanceConfig.State.Hidden - } - } - - companion object { - private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig" - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt deleted file mode 100644 index 7164215eb2ae..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt +++ /dev/null @@ -1,67 +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.config - -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 javax.inject.Inject -import kotlin.reflect.KClass - -/** Injectable provider of the positioning of the known quick affordance configs. */ -interface KeyguardQuickAffordanceConfigs { - fun getAll(position: KeyguardQuickAffordancePosition): List - fun get(configClass: KClass): KeyguardQuickAffordanceConfig -} - -class KeyguardQuickAffordanceConfigsImpl -@Inject -constructor( - homeControls: HomeControlsKeyguardQuickAffordanceConfig, - quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig, - qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig, -) : KeyguardQuickAffordanceConfigs { - private val configsByPosition = - mapOf( - KeyguardQuickAffordancePosition.BOTTOM_START to - listOf( - homeControls, - ), - KeyguardQuickAffordancePosition.BOTTOM_END to - listOf( - quickAccessWallet, - qrCodeScanner, - ), - ) - private val configByClass = - configsByPosition.values.flatten().associateBy { config -> config::class } - - override fun getAll( - position: KeyguardQuickAffordancePosition, - ): List { - return configsByPosition.getValue(position) - } - - override fun get( - configClass: KClass - ): KeyguardQuickAffordanceConfig { - return configByClass.getValue(configClass) - } -} 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 43c4fa06367b..000000000000 --- 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 -} - -/** 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 { - 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 1a5670c7e807..d15d7f25bbde 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/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt new file mode 100644 index 000000000000..411a2ca5ffe2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt @@ -0,0 +1,62 @@ +/* + * 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.model + +import androidx.annotation.StringRes +import com.android.systemui.containeddrawable.ContainedDrawable +import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig +import kotlin.reflect.KClass + +/** + * Models a "quick affordance" in the keyguard bottom area (for example, a button on the + * lock-screen). + */ +sealed class KeyguardQuickAffordanceModel { + /** No affordance should show up. */ + object Hidden : KeyguardQuickAffordanceModel() + + /** A affordance is visible. */ + data class Visible( + /** Identifier for the affordance this is modeling. */ + val configKey: KClass, + /** An icon for the affordance. */ + val icon: ContainedDrawable, + /** + * Resource ID for a string to use for the accessibility content description text of the + * affordance. + */ + @StringRes val contentDescriptionResourceId: Int, + ) : KeyguardQuickAffordanceModel() + + companion object { + fun from( + state: KeyguardQuickAffordanceConfig.State?, + configKey: KClass, + ): 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/domain/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt new file mode 100644 index 000000000000..581dafa33df7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt @@ -0,0 +1,23 @@ +/* + * 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.model + +/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */ +enum class KeyguardQuickAffordancePosition { + BOTTOM_START, + BOTTOM_END, +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt new file mode 100644 index 000000000000..8f32ff9db50c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt @@ -0,0 +1,128 @@ +/* + * 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 android.content.Context +import android.content.Intent +import androidx.annotation.DrawableRes +import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.containeddrawable.ContainedDrawable +import com.android.systemui.controls.ControlsServiceInfo +import com.android.systemui.controls.controller.StructureInfo +import com.android.systemui.controls.dagger.ControlsComponent +import com.android.systemui.controls.management.ControlsListingController +import com.android.systemui.controls.ui.ControlsActivity +import com.android.systemui.controls.ui.ControlsUiController +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.util.kotlin.getOrNull +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf + +/** Home controls quick affordance data source. */ +@SysUISingleton +class HomeControlsKeyguardQuickAffordanceConfig +@Inject +constructor( + @Application context: Context, + private val component: ControlsComponent, +) : KeyguardQuickAffordanceConfig { + + private val appContext = context.applicationContext + + override val state: Flow = + component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked -> + if (canShowWhileLocked) { + stateInternal(component.getControlsListingController().getOrNull()) + } else { + flowOf(KeyguardQuickAffordanceConfig.State.Hidden) + } + } + + override fun onQuickAffordanceClicked( + animationController: ActivityLaunchAnimator.Controller?, + ): KeyguardQuickAffordanceConfig.OnClickedResult { + return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity( + intent = + Intent(appContext, ControlsActivity::class.java) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra( + ControlsUiController.EXTRA_ANIMATE, + true, + ), + canShowWhileLocked = component.canShowWhileLockedSetting.value, + ) + } + + private fun stateInternal( + listingController: ControlsListingController?, + ): Flow { + if (listingController == null) { + return flowOf(KeyguardQuickAffordanceConfig.State.Hidden) + } + + return conflatedCallbackFlow { + val callback = + object : ControlsListingController.ControlsListingCallback { + override fun onServicesUpdated(serviceInfos: List) { + val favorites: List? = + component.getControlsController().getOrNull()?.getFavorites() + + trySendWithFailureLogging( + state( + isFeatureEnabled = component.isEnabled(), + hasFavorites = favorites?.isNotEmpty() == true, + hasServiceInfos = serviceInfos.isNotEmpty(), + iconResourceId = component.getTileImageId(), + ), + TAG, + ) + } + } + + listingController.addCallback(callback) + + awaitClose { listingController.removeCallback(callback) } + } + } + + private fun state( + isFeatureEnabled: Boolean, + hasFavorites: Boolean, + hasServiceInfos: Boolean, + @DrawableRes iconResourceId: Int?, + ): KeyguardQuickAffordanceConfig.State { + return if (isFeatureEnabled && hasFavorites && hasServiceInfos && iconResourceId != null) { + KeyguardQuickAffordanceConfig.State.Visible( + icon = ContainedDrawable.WithResource(iconResourceId), + contentDescriptionResourceId = component.getTileTitleId(), + ) + } else { + KeyguardQuickAffordanceConfig.State.Hidden + } + } + + companion object { + private const val TAG = "HomeControlsKeyguardQuickAffordanceConfig" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt new file mode 100644 index 000000000000..8fb952c217bd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt @@ -0,0 +1,73 @@ +/* + * 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 android.content.Intent +import androidx.annotation.StringRes +import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.containeddrawable.ContainedDrawable +import kotlinx.coroutines.flow.Flow + +/** Defines interface that can act as data source for a single quick affordance model. */ +interface KeyguardQuickAffordanceConfig { + + val state: Flow + + fun onQuickAffordanceClicked( + animationController: ActivityLaunchAnimator.Controller? + ): OnClickedResult + + /** + * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a + * button on the lock-screen). + */ + sealed class State { + + /** No affordance should show up. */ + object Hidden : State() + + /** An affordance is visible. */ + data class Visible( + /** An icon for the affordance. */ + val icon: ContainedDrawable, + /** + * Resource ID for a string to use for the accessibility content description text of the + * affordance. + */ + @StringRes val contentDescriptionResourceId: Int, + ) : State() + } + + sealed class OnClickedResult { + /** + * Returning this as a result from the [onQuickAffordanceClicked] method means that the + * implementation has taken care of the click, the system will do nothing. + */ + object Handled : OnClickedResult() + + /** + * Returning this as a result from the [onQuickAffordanceClicked] method means that the + * implementation has _not_ taken care of the click and the system should start an activity + * using the given [Intent]. + */ + data class StartActivity( + val intent: Intent, + val canShowWhileLocked: Boolean, + ) : OnClickedResult() + } +} 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 000000000000..a7b38282d0aa --- /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/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt new file mode 100644 index 000000000000..2c37f93de435 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt @@ -0,0 +1,63 @@ +/* + * 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 javax.inject.Inject +import kotlin.reflect.KClass + +/** Central registry of all known quick affordance configs. */ +interface KeyguardQuickAffordanceRegistry { + fun getAll(position: KeyguardQuickAffordancePosition): List + fun get(configClass: KClass): KeyguardQuickAffordanceConfig +} + +class KeyguardQuickAffordanceRegistryImpl +@Inject +constructor( + homeControls: HomeControlsKeyguardQuickAffordanceConfig, + quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig, + qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig, +) : KeyguardQuickAffordanceRegistry { + private val configsByPosition = + mapOf( + KeyguardQuickAffordancePosition.BOTTOM_START to + listOf( + homeControls, + ), + KeyguardQuickAffordancePosition.BOTTOM_END to + listOf( + quickAccessWallet, + qrCodeScanner, + ), + ) + private val configByClass = + configsByPosition.values.flatten().associateBy { config -> config::class } + + override fun getAll( + position: KeyguardQuickAffordancePosition, + ): List { + return configsByPosition.getValue(position) + } + + override fun get( + configClass: KClass + ): KeyguardQuickAffordanceConfig { + return configByClass.getValue(configClass) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt new file mode 100644 index 000000000000..c8e5e4aebea0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt @@ -0,0 +1,90 @@ +/* + * 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.R +import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.containeddrawable.ContainedDrawable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qrcodescanner.controller.QRCodeScannerController +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow + +/** QR code scanner quick affordance data source. */ +@SysUISingleton +class QrCodeScannerKeyguardQuickAffordanceConfig +@Inject +constructor( + private val controller: QRCodeScannerController, +) : KeyguardQuickAffordanceConfig { + + override val state: Flow = conflatedCallbackFlow { + val callback = + object : QRCodeScannerController.Callback { + override fun onQRCodeScannerActivityChanged() { + trySendWithFailureLogging(state(), TAG) + } + override fun onQRCodeScannerPreferenceChanged() { + trySendWithFailureLogging(state(), TAG) + } + } + + controller.addCallback(callback) + controller.registerQRCodeScannerChangeObservers( + QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE, + QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE + ) + // Registering does not push an initial update. + trySendWithFailureLogging(state(), "initial state", TAG) + + awaitClose { + controller.unregisterQRCodeScannerChangeObservers( + QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE, + QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE + ) + controller.removeCallback(callback) + } + } + + override fun onQuickAffordanceClicked( + animationController: ActivityLaunchAnimator.Controller?, + ): KeyguardQuickAffordanceConfig.OnClickedResult { + return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity( + intent = controller.intent, + canShowWhileLocked = true, + ) + } + + private fun state(): KeyguardQuickAffordanceConfig.State { + return if (controller.isEnabledForLockScreenButton) { + KeyguardQuickAffordanceConfig.State.Visible( + icon = ContainedDrawable.WithResource(R.drawable.ic_qr_code_scanner), + contentDescriptionResourceId = R.string.accessibility_qr_code_scanner_button, + ) + } else { + KeyguardQuickAffordanceConfig.State.Hidden + } + } + + companion object { + private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt new file mode 100644 index 000000000000..885af3343533 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt @@ -0,0 +1,114 @@ +/* + * 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 android.graphics.drawable.Drawable +import android.service.quickaccesswallet.GetWalletCardsError +import android.service.quickaccesswallet.GetWalletCardsResponse +import android.service.quickaccesswallet.QuickAccessWalletClient +import android.util.Log +import com.android.systemui.R +import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.containeddrawable.ContainedDrawable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.wallet.controller.QuickAccessWalletController +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow + +/** Quick access wallet quick affordance data source. */ +@SysUISingleton +class QuickAccessWalletKeyguardQuickAffordanceConfig +@Inject +constructor( + private val walletController: QuickAccessWalletController, + private val activityStarter: ActivityStarter, +) : KeyguardQuickAffordanceConfig { + + override val state: Flow = conflatedCallbackFlow { + val callback = + object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback { + override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) { + trySendWithFailureLogging( + state( + isFeatureEnabled = walletController.isWalletEnabled, + hasCard = response?.walletCards?.isNotEmpty() == true, + tileIcon = walletController.walletClient.tileIcon, + ), + TAG, + ) + } + + override fun onWalletCardRetrievalError(error: GetWalletCardsError?) { + Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"") + trySendWithFailureLogging( + KeyguardQuickAffordanceConfig.State.Hidden, + TAG, + ) + } + } + + walletController.setupWalletChangeObservers( + callback, + QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE, + QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE + ) + walletController.updateWalletPreference() + walletController.queryWalletCards(callback) + + awaitClose { + walletController.unregisterWalletChangeObservers( + QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE, + QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE + ) + } + } + + override fun onQuickAffordanceClicked( + animationController: ActivityLaunchAnimator.Controller?, + ): KeyguardQuickAffordanceConfig.OnClickedResult { + walletController.startQuickAccessUiIntent( + activityStarter, + animationController, + /* hasCard= */ true, + ) + return KeyguardQuickAffordanceConfig.OnClickedResult.Handled + } + + private fun state( + isFeatureEnabled: Boolean, + hasCard: Boolean, + tileIcon: Drawable?, + ): KeyguardQuickAffordanceConfig.State { + return if (isFeatureEnabled && hasCard && tileIcon != null) { + KeyguardQuickAffordanceConfig.State.Visible( + icon = ContainedDrawable.WithDrawable(tileIcon), + contentDescriptionResourceId = R.string.accessibility_wallet_button, + ) + } else { + KeyguardQuickAffordanceConfig.State.Hidden + } + } + + companion object { + private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig" + } +} 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 c44c2c9901a1..403d34352c7b 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 @@ interface KeyguardUseCaseModule { 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 eef8ec3e68f5..8dee8b38bdb8 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 +} + +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 { return combine( - repository.affordance(position), + affordance(position), isDozingUseCase(), isKeyguardShowingUseCase(), ) { affordance, isDozing, isKeyguardShowing -> @@ -46,4 +53,23 @@ constructor( } } } + + private fun affordance( + position: KeyguardQuickAffordancePosition + ): Flow { + 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 f8db90f02540..93153391ca41 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 @@ import kotlin.reflect.KClass class OnKeyguardQuickAffordanceClickedUseCase @Inject constructor( - private val configs: KeyguardQuickAffordanceConfigs, + private val registry: KeyguardQuickAffordanceRegistry, private val launchAffordanceUseCase: LaunchKeyguardQuickAffordanceUseCase, ) { operator fun invoke( @@ -35,7 +35,7 @@ constructor( animationController: ActivityLaunchAnimator.Controller?, ) { @Suppress("UNCHECKED_CAST") - val config = configs.get(configKey as KClass) + val config = registry.get(configKey as KClass) when (val result = config.onQuickAffordanceClicked(animationController)) { is OnClickedResult.StartActivity -> launchAffordanceUseCase( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt deleted file mode 100644 index 09785dfa3c03..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt +++ /dev/null @@ -1,46 +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.shared.model - -import androidx.annotation.StringRes -import com.android.systemui.containeddrawable.ContainedDrawable -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig -import kotlin.reflect.KClass - -/** - * Models a "quick affordance" in the keyguard bottom area (for example, a button on the - * lock-screen). - */ -sealed class KeyguardQuickAffordanceModel { - - /** No affordance should show up. */ - object Hidden : KeyguardQuickAffordanceModel() - - /** A affordance is visible. */ - data class Visible( - /** Identifier for the affordance this is modeling. */ - val configKey: KClass, - /** An icon for the affordance. */ - val icon: ContainedDrawable, - /** - * Resource ID for a string to use for the accessibility content description text of the - * affordance. - */ - @StringRes val contentDescriptionResourceId: Int, - ) : KeyguardQuickAffordanceModel() -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt deleted file mode 100644 index b71e15d34afe..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt +++ /dev/null @@ -1,23 +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.shared.model - -/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */ -enum class KeyguardQuickAffordancePosition { - BOTTOM_START, - BOTTOM_END, -} 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 4b69a81c8996..d296e76482ad 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.ObserveDozeAmountUseCase 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/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt deleted file mode 100644 index 810c6dc4776d..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt +++ /dev/null @@ -1,138 +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.quickaffordance - -import androidx.test.filters.SmallTest -import com.android.systemui.R -import com.android.systemui.SysuiTestCase -import com.android.systemui.controls.controller.ControlsController -import com.android.systemui.controls.dagger.ControlsComponent -import com.android.systemui.controls.management.ControlsListingController -import com.android.systemui.util.mockito.mock -import com.google.common.truth.Truth.assertThat -import java.util.Optional -import kotlinx.coroutines.flow.MutableStateFlow -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.Parameterized -import org.junit.runners.Parameterized.Parameter -import org.junit.runners.Parameterized.Parameters -import org.mockito.ArgumentCaptor -import org.mockito.Captor -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(Parameterized::class) -class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTestCase() { - - companion object { - @Parameters( - name = - "feature enabled = {0}, has favorites = {1}, has service infos = {2}, can show" + - " while locked = {3} - expected visible = {4}" - ) - @JvmStatic - fun data() = - (0 until 16) - .map { combination -> - arrayOf( - /* isFeatureEnabled= */ combination and 0b1000 != 0, - /* hasFavorites= */ combination and 0b0100 != 0, - /* hasServiceInfos= */ combination and 0b0010 != 0, - /* canShowWhileLocked= */ combination and 0b0001 != 0, - /* isVisible= */ combination == 0b1111, - ) - } - .toList() - } - - @Mock private lateinit var component: ControlsComponent - @Mock private lateinit var controlsController: ControlsController - @Mock private lateinit var controlsListingController: ControlsListingController - @Captor - private lateinit var callbackCaptor: - ArgumentCaptor - - private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig - - @JvmField @Parameter(0) var isFeatureEnabled: Boolean = false - @JvmField @Parameter(1) var hasFavorites: Boolean = false - @JvmField @Parameter(2) var hasServiceInfos: Boolean = false - @JvmField @Parameter(3) var canShowWhileLocked: Boolean = false - @JvmField @Parameter(4) var isVisible: Boolean = false - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon) - whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title) - whenever(component.getControlsController()).thenReturn(Optional.of(controlsController)) - whenever(component.getControlsListingController()) - .thenReturn(Optional.of(controlsListingController)) - whenever(component.canShowWhileLockedSetting) - .thenReturn(MutableStateFlow(canShowWhileLocked)) - - underTest = - HomeControlsKeyguardQuickAffordanceConfig( - context = context, - component = component, - ) - } - - @Test - fun state() = runBlockingTest { - whenever(component.isEnabled()).thenReturn(isFeatureEnabled) - whenever(controlsController.getFavorites()) - .thenReturn( - if (hasFavorites) { - listOf(mock()) - } else { - emptyList() - } - ) - val values = mutableListOf() - val job = underTest.state.onEach(values::add).launchIn(this) - - if (canShowWhileLocked) { - verify(controlsListingController).addCallback(callbackCaptor.capture()) - callbackCaptor.value.onServicesUpdated( - if (hasServiceInfos) { - listOf(mock()) - } else { - emptyList() - } - ) - } - - assertThat(values.last()) - .isInstanceOf( - if (isVisible) { - KeyguardQuickAffordanceConfig.State.Visible::class.java - } else { - KeyguardQuickAffordanceConfig.State.Hidden::class.java - } - ) - job.cancel() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt deleted file mode 100644 index ef588f5ce255..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt +++ /dev/null @@ -1,119 +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.quickaffordance - -import androidx.test.filters.SmallTest -import com.android.systemui.R -import com.android.systemui.SysuiTestCase -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.util.mockito.mock -import com.google.common.truth.Truth.assertThat -import java.util.Optional -import kotlinx.coroutines.flow.MutableStateFlow -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.Mock -import org.mockito.Mockito.`when` as whenever -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(JUnit4::class) -class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() { - - @Mock private lateinit var component: ControlsComponent - @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller - - private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true)) - - underTest = - HomeControlsKeyguardQuickAffordanceConfig( - context = context, - component = component, - ) - } - - @Test - fun `state - when cannot show while locked - returns Hidden`() = runBlockingTest { - whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false)) - whenever(component.isEnabled()).thenReturn(true) - whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon) - whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title) - val controlsController = mock() - whenever(component.getControlsController()).thenReturn(Optional.of(controlsController)) - whenever(component.getControlsListingController()).thenReturn(Optional.empty()) - whenever(controlsController.getFavorites()).thenReturn(listOf(mock())) - - val values = mutableListOf() - val job = underTest.state.onEach(values::add).launchIn(this) - - assertThat(values.last()) - .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java) - job.cancel() - } - - @Test - fun `state - when listing controller is missing - returns Hidden`() = runBlockingTest { - whenever(component.isEnabled()).thenReturn(true) - whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon) - whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title) - val controlsController = mock() - whenever(component.getControlsController()).thenReturn(Optional.of(controlsController)) - whenever(component.getControlsListingController()).thenReturn(Optional.empty()) - whenever(controlsController.getFavorites()).thenReturn(listOf(mock())) - - val values = mutableListOf() - val job = underTest.state.onEach(values::add).launchIn(this) - - assertThat(values.last()) - .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java) - job.cancel() - } - - @Test - fun `onQuickAffordanceClicked - canShowWhileLockedSetting is true`() = runBlockingTest { - whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true)) - - val onClickedResult = underTest.onQuickAffordanceClicked(animationController) - - assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java) - assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isTrue() - } - - @Test - fun `onQuickAffordanceClicked - canShowWhileLockedSetting is false`() = runBlockingTest { - whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false)) - - val onClickedResult = underTest.onQuickAffordanceClicked(animationController) - - assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java) - assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isFalse() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt deleted file mode 100644 index 6fd04de8c9ac..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt +++ /dev/null @@ -1,148 +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.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.qrcodescanner.controller.QRCodeScannerController -import com.android.systemui.util.mockito.argumentCaptor -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 -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(JUnit4::class) -class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() { - - @Mock private lateinit var controller: QRCodeScannerController - - private lateinit var underTest: QrCodeScannerKeyguardQuickAffordanceConfig - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - whenever(controller.intent).thenReturn(INTENT_1) - - underTest = QrCodeScannerKeyguardQuickAffordanceConfig(controller) - } - - @Test - fun `affordance - sets up registration and delivers initial model`() = runBlockingTest { - whenever(controller.isEnabledForLockScreenButton).thenReturn(true) - var latest: KeyguardQuickAffordanceConfig.State? = null - - val job = underTest.state.onEach { latest = it }.launchIn(this) - - val callbackCaptor = argumentCaptor() - verify(controller).addCallback(callbackCaptor.capture()) - verify(controller) - .registerQRCodeScannerChangeObservers( - QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE, - QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE - ) - assertVisibleState(latest) - - job.cancel() - verify(controller).removeCallback(callbackCaptor.value) - } - - @Test - fun `affordance - scanner activity changed - delivers model with updated intent`() = - runBlockingTest { - whenever(controller.isEnabledForLockScreenButton).thenReturn(true) - var latest: KeyguardQuickAffordanceConfig.State? = null - val job = underTest.state.onEach { latest = it }.launchIn(this) - val callbackCaptor = argumentCaptor() - verify(controller).addCallback(callbackCaptor.capture()) - - whenever(controller.intent).thenReturn(INTENT_2) - callbackCaptor.value.onQRCodeScannerActivityChanged() - - assertVisibleState(latest) - - job.cancel() - verify(controller).removeCallback(callbackCaptor.value) - } - - @Test - fun `affordance - scanner preference changed - delivers visible model`() = runBlockingTest { - var latest: KeyguardQuickAffordanceConfig.State? = null - val job = underTest.state.onEach { latest = it }.launchIn(this) - val callbackCaptor = argumentCaptor() - verify(controller).addCallback(callbackCaptor.capture()) - - whenever(controller.isEnabledForLockScreenButton).thenReturn(true) - callbackCaptor.value.onQRCodeScannerPreferenceChanged() - - assertVisibleState(latest) - - job.cancel() - verify(controller).removeCallback(callbackCaptor.value) - } - - @Test - fun `affordance - scanner preference changed - delivers none`() = runBlockingTest { - var latest: KeyguardQuickAffordanceConfig.State? = null - val job = underTest.state.onEach { latest = it }.launchIn(this) - val callbackCaptor = argumentCaptor() - verify(controller).addCallback(callbackCaptor.capture()) - - whenever(controller.isEnabledForLockScreenButton).thenReturn(false) - callbackCaptor.value.onQRCodeScannerPreferenceChanged() - - assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden) - - job.cancel() - verify(controller).removeCallback(callbackCaptor.value) - } - - @Test - fun onQuickAffordanceClicked() { - assertThat(underTest.onQuickAffordanceClicked(mock())) - .isEqualTo( - OnClickedResult.StartActivity( - intent = INTENT_1, - canShowWhileLocked = true, - ) - ) - } - - private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.State?) { - assertThat(latest).isInstanceOf(KeyguardQuickAffordanceConfig.State.Visible::class.java) - val visibleState = latest as KeyguardQuickAffordanceConfig.State.Visible - assertThat(visibleState.icon).isNotNull() - assertThat(visibleState.contentDescriptionResourceId).isNotNull() - } - - companion object { - private val INTENT_1 = Intent("intent1") - private val INTENT_2 = Intent("intent2") - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt deleted file mode 100644 index 345c51f6f760..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt +++ /dev/null @@ -1,179 +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.quickaffordance - -import android.graphics.drawable.Drawable -import android.service.quickaccesswallet.GetWalletCardsResponse -import android.service.quickaccesswallet.QuickAccessWalletClient -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.ActivityLaunchAnimator -import com.android.systemui.containeddrawable.ContainedDrawable -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.wallet.controller.QuickAccessWalletController -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 -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(JUnit4::class) -class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { - - @Mock private lateinit var walletController: QuickAccessWalletController - @Mock private lateinit var activityStarter: ActivityStarter - - private lateinit var underTest: QuickAccessWalletKeyguardQuickAffordanceConfig - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - underTest = - QuickAccessWalletKeyguardQuickAffordanceConfig( - walletController, - activityStarter, - ) - } - - @Test - fun `affordance - keyguard showing - has wallet card - visible model`() = runBlockingTest { - setUpState() - var latest: KeyguardQuickAffordanceConfig.State? = null - - val job = underTest.state.onEach { latest = it }.launchIn(this) - - val visibleModel = latest as KeyguardQuickAffordanceConfig.State.Visible - assertThat(visibleModel.icon).isEqualTo(ContainedDrawable.WithDrawable(ICON)) - assertThat(visibleModel.contentDescriptionResourceId).isNotNull() - job.cancel() - } - - @Test - fun `affordance - wallet not enabled - model is none`() = runBlockingTest { - setUpState(isWalletEnabled = false) - var latest: KeyguardQuickAffordanceConfig.State? = null - - val job = underTest.state.onEach { latest = it }.launchIn(this) - - assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden) - - job.cancel() - } - - @Test - fun `affordance - query not successful - model is none`() = runBlockingTest { - setUpState(isWalletQuerySuccessful = false) - var latest: KeyguardQuickAffordanceConfig.State? = null - - val job = underTest.state.onEach { latest = it }.launchIn(this) - - assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden) - - job.cancel() - } - - @Test - fun `affordance - missing icon - model is none`() = runBlockingTest { - setUpState(hasWalletIcon = false) - var latest: KeyguardQuickAffordanceConfig.State? = null - - val job = underTest.state.onEach { latest = it }.launchIn(this) - - assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden) - - job.cancel() - } - - @Test - fun `affordance - no selected card - model is none`() = runBlockingTest { - setUpState(hasWalletIcon = false) - var latest: KeyguardQuickAffordanceConfig.State? = null - - val job = underTest.state.onEach { latest = it }.launchIn(this) - - assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden) - - job.cancel() - } - - @Test - fun onQuickAffordanceClicked() { - val animationController: ActivityLaunchAnimator.Controller = mock() - - assertThat(underTest.onQuickAffordanceClicked(animationController)) - .isEqualTo(KeyguardQuickAffordanceConfig.OnClickedResult.Handled) - verify(walletController) - .startQuickAccessUiIntent( - activityStarter, - animationController, - /* hasCard= */ true, - ) - } - - private fun setUpState( - isWalletEnabled: Boolean = true, - isWalletQuerySuccessful: Boolean = true, - hasWalletIcon: Boolean = true, - hasSelectedCard: Boolean = true, - ) { - whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled) - - val walletClient: QuickAccessWalletClient = mock() - val icon: Drawable? = - if (hasWalletIcon) { - ICON - } else { - null - } - whenever(walletClient.tileIcon).thenReturn(icon) - whenever(walletController.walletClient).thenReturn(walletClient) - - whenever(walletController.queryWalletCards(any())).thenAnswer { invocation -> - with( - invocation.arguments[0] as QuickAccessWalletClient.OnWalletCardsRetrievedCallback - ) { - if (isWalletQuerySuccessful) { - onWalletCardsRetrieved( - if (hasSelectedCard) { - GetWalletCardsResponse(listOf(mock()), 0) - } else { - GetWalletCardsResponse(emptyList(), 0) - } - ) - } else { - onWalletCardRetrievalError(mock()) - } - } - } - } - - companion object { - private val ICON: Drawable = mock() - } -} 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 6fff440ec2fa..000000000000 --- 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() - val onClickedInvocations: List = _onClickedInvocations - - var onClickedResult: OnClickedResult = OnClickedResult.Handled - - private val _state = - MutableStateFlow( - KeyguardQuickAffordanceConfig.State.Hidden - ) - override val state: Flow = _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 a24fc93fedc2..000000000000 --- 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>, -) : KeyguardQuickAffordanceConfigs { - - override fun getAll( - position: KeyguardQuickAffordancePosition - ): List { - return configsByPosition.getValue(position) - } - - override fun get( - configClass: KClass - ): 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 10d2e4de631d..000000000000 --- 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>() - - init { - KeyguardQuickAffordancePosition.values().forEach { value -> - modelByPosition[value] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden) - } - } - - override fun affordance( - position: KeyguardQuickAffordancePosition - ): Flow { - 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 d40b985f64de..38a337563165 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 com.android.systemui.common.data.model.Position 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 @@ class FakeKeyguardRepository : KeyguardRepository { private val _dozeAmount = MutableStateFlow(0f) override val dozeAmount: Flow = _dozeAmount - init { - setDozeAmount(0f) - setDozing(false) - } - override fun setAnimateDozingTransitions(animate: Boolean) { _animateBottomAreaDozingTransitions.tryEmit(animate) } @@ -60,15 +56,30 @@ class FakeKeyguardRepository : KeyguardRepository { _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 dc0e6f7663ff..000000000000 --- 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, - ): 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 000000000000..6ea1daa7704f --- /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.Hidden + ) + override val state: Flow = _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 000000000000..1c9902b36517 --- /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>, +) : KeyguardQuickAffordanceRegistry { + + override fun getAll( + position: KeyguardQuickAffordancePosition + ): List { + return configsByPosition.getValue(position) + } + + override fun get( + configClass: KClass + ): KeyguardQuickAffordanceConfig { + return configsByPosition.values + .flatten() + .associateBy { config -> config::class } + .getValue(configClass) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt new file mode 100644 index 000000000000..9acd21cc6398 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt @@ -0,0 +1,139 @@ +/* + * 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 androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.controls.dagger.ControlsComponent +import com.android.systemui.controls.management.ControlsListingController +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import java.util.Optional +import kotlinx.coroutines.flow.MutableStateFlow +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.Parameterized +import org.junit.runners.Parameterized.Parameter +import org.junit.runners.Parameterized.Parameters +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(Parameterized::class) +class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTestCase() { + + companion object { + @Parameters( + name = + "feature enabled = {0}, has favorites = {1}, has service infos = {2}, can show" + + " while locked = {3} - expected visible = {4}" + ) + @JvmStatic + fun data() = + (0 until 16) + .map { combination -> + arrayOf( + /* isFeatureEnabled= */ combination and 0b1000 != 0, + /* hasFavorites= */ combination and 0b0100 != 0, + /* hasServiceInfos= */ combination and 0b0010 != 0, + /* canShowWhileLocked= */ combination and 0b0001 != 0, + /* isVisible= */ combination == 0b1111, + ) + } + .toList() + } + + @Mock private lateinit var component: ControlsComponent + @Mock private lateinit var controlsController: ControlsController + @Mock private lateinit var controlsListingController: ControlsListingController + @Captor + private lateinit var callbackCaptor: + ArgumentCaptor + + private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig + + @JvmField @Parameter(0) var isFeatureEnabled: Boolean = false + @JvmField @Parameter(1) var hasFavorites: Boolean = false + @JvmField @Parameter(2) var hasServiceInfos: Boolean = false + @JvmField @Parameter(3) var canShowWhileLocked: Boolean = false + @JvmField @Parameter(4) var isVisible: Boolean = false + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon) + whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title) + whenever(component.getControlsController()).thenReturn(Optional.of(controlsController)) + whenever(component.getControlsListingController()) + .thenReturn(Optional.of(controlsListingController)) + whenever(component.canShowWhileLockedSetting) + .thenReturn(MutableStateFlow(canShowWhileLocked)) + + underTest = + HomeControlsKeyguardQuickAffordanceConfig( + context = context, + component = component, + ) + } + + @Test + fun state() = runBlockingTest { + whenever(component.isEnabled()).thenReturn(isFeatureEnabled) + whenever(controlsController.getFavorites()) + .thenReturn( + if (hasFavorites) { + listOf(mock()) + } else { + emptyList() + } + ) + val values = mutableListOf() + val job = underTest.state.onEach(values::add).launchIn(this) + + if (canShowWhileLocked) { + verify(controlsListingController).addCallback(callbackCaptor.capture()) + callbackCaptor.value.onServicesUpdated( + if (hasServiceInfos) { + listOf(mock()) + } else { + emptyList() + } + ) + } + + assertThat(values.last()) + .isInstanceOf( + if (isVisible) { + KeyguardQuickAffordanceConfig.State.Visible::class.java + } else { + KeyguardQuickAffordanceConfig.State.Hidden::class.java + } + ) + job.cancel() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt new file mode 100644 index 000000000000..059487dfdbc8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt @@ -0,0 +1,119 @@ +/* + * 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 androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +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.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import java.util.Optional +import kotlinx.coroutines.flow.MutableStateFlow +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.Mock +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() { + + @Mock private lateinit var component: ControlsComponent + @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller + + private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true)) + + underTest = + HomeControlsKeyguardQuickAffordanceConfig( + context = context, + component = component, + ) + } + + @Test + fun `state - when cannot show while locked - returns Hidden`() = runBlockingTest { + whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false)) + whenever(component.isEnabled()).thenReturn(true) + whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon) + whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title) + val controlsController = mock() + whenever(component.getControlsController()).thenReturn(Optional.of(controlsController)) + whenever(component.getControlsListingController()).thenReturn(Optional.empty()) + whenever(controlsController.getFavorites()).thenReturn(listOf(mock())) + + val values = mutableListOf() + val job = underTest.state.onEach(values::add).launchIn(this) + + assertThat(values.last()) + .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java) + job.cancel() + } + + @Test + fun `state - when listing controller is missing - returns Hidden`() = runBlockingTest { + whenever(component.isEnabled()).thenReturn(true) + whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon) + whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title) + val controlsController = mock() + whenever(component.getControlsController()).thenReturn(Optional.of(controlsController)) + whenever(component.getControlsListingController()).thenReturn(Optional.empty()) + whenever(controlsController.getFavorites()).thenReturn(listOf(mock())) + + val values = mutableListOf() + val job = underTest.state.onEach(values::add).launchIn(this) + + assertThat(values.last()) + .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java) + job.cancel() + } + + @Test + fun `onQuickAffordanceClicked - canShowWhileLockedSetting is true`() = runBlockingTest { + whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true)) + + val onClickedResult = underTest.onQuickAffordanceClicked(animationController) + + assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java) + assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isTrue() + } + + @Test + fun `onQuickAffordanceClicked - canShowWhileLockedSetting is false`() = runBlockingTest { + whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false)) + + val onClickedResult = underTest.onQuickAffordanceClicked(animationController) + + assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java) + assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt new file mode 100644 index 000000000000..d4fba4126127 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt @@ -0,0 +1,148 @@ +/* + * 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 android.content.Intent +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +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 +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 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() { + + @Mock private lateinit var controller: QRCodeScannerController + + private lateinit var underTest: QrCodeScannerKeyguardQuickAffordanceConfig + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(controller.intent).thenReturn(INTENT_1) + + underTest = QrCodeScannerKeyguardQuickAffordanceConfig(controller) + } + + @Test + fun `affordance - sets up registration and delivers initial model`() = runBlockingTest { + whenever(controller.isEnabledForLockScreenButton).thenReturn(true) + var latest: KeyguardQuickAffordanceConfig.State? = null + + val job = underTest.state.onEach { latest = it }.launchIn(this) + + val callbackCaptor = argumentCaptor() + verify(controller).addCallback(callbackCaptor.capture()) + verify(controller) + .registerQRCodeScannerChangeObservers( + QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE, + QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE + ) + assertVisibleState(latest) + + job.cancel() + verify(controller).removeCallback(callbackCaptor.value) + } + + @Test + fun `affordance - scanner activity changed - delivers model with updated intent`() = + runBlockingTest { + whenever(controller.isEnabledForLockScreenButton).thenReturn(true) + var latest: KeyguardQuickAffordanceConfig.State? = null + val job = underTest.state.onEach { latest = it }.launchIn(this) + val callbackCaptor = argumentCaptor() + verify(controller).addCallback(callbackCaptor.capture()) + + whenever(controller.intent).thenReturn(INTENT_2) + callbackCaptor.value.onQRCodeScannerActivityChanged() + + assertVisibleState(latest) + + job.cancel() + verify(controller).removeCallback(callbackCaptor.value) + } + + @Test + fun `affordance - scanner preference changed - delivers visible model`() = runBlockingTest { + var latest: KeyguardQuickAffordanceConfig.State? = null + val job = underTest.state.onEach { latest = it }.launchIn(this) + val callbackCaptor = argumentCaptor() + verify(controller).addCallback(callbackCaptor.capture()) + + whenever(controller.isEnabledForLockScreenButton).thenReturn(true) + callbackCaptor.value.onQRCodeScannerPreferenceChanged() + + assertVisibleState(latest) + + job.cancel() + verify(controller).removeCallback(callbackCaptor.value) + } + + @Test + fun `affordance - scanner preference changed - delivers none`() = runBlockingTest { + var latest: KeyguardQuickAffordanceConfig.State? = null + val job = underTest.state.onEach { latest = it }.launchIn(this) + val callbackCaptor = argumentCaptor() + verify(controller).addCallback(callbackCaptor.capture()) + + whenever(controller.isEnabledForLockScreenButton).thenReturn(false) + callbackCaptor.value.onQRCodeScannerPreferenceChanged() + + assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden) + + job.cancel() + verify(controller).removeCallback(callbackCaptor.value) + } + + @Test + fun onQuickAffordanceClicked() { + assertThat(underTest.onQuickAffordanceClicked(mock())) + .isEqualTo( + OnClickedResult.StartActivity( + intent = INTENT_1, + canShowWhileLocked = true, + ) + ) + } + + private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.State?) { + assertThat(latest).isInstanceOf(KeyguardQuickAffordanceConfig.State.Visible::class.java) + val visibleState = latest as KeyguardQuickAffordanceConfig.State.Visible + assertThat(visibleState.icon).isNotNull() + assertThat(visibleState.contentDescriptionResourceId).isNotNull() + } + + companion object { + private val INTENT_1 = Intent("intent1") + private val INTENT_2 = Intent("intent2") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt new file mode 100644 index 000000000000..5a3a78e9cb04 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt @@ -0,0 +1,179 @@ +/* + * 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 android.graphics.drawable.Drawable +import android.service.quickaccesswallet.GetWalletCardsResponse +import android.service.quickaccesswallet.QuickAccessWalletClient +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.containeddrawable.ContainedDrawable +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.wallet.controller.QuickAccessWalletController +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 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { + + @Mock private lateinit var walletController: QuickAccessWalletController + @Mock private lateinit var activityStarter: ActivityStarter + + private lateinit var underTest: QuickAccessWalletKeyguardQuickAffordanceConfig + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + underTest = + QuickAccessWalletKeyguardQuickAffordanceConfig( + walletController, + activityStarter, + ) + } + + @Test + fun `affordance - keyguard showing - has wallet card - visible model`() = runBlockingTest { + setUpState() + var latest: KeyguardQuickAffordanceConfig.State? = null + + val job = underTest.state.onEach { latest = it }.launchIn(this) + + val visibleModel = latest as KeyguardQuickAffordanceConfig.State.Visible + assertThat(visibleModel.icon).isEqualTo(ContainedDrawable.WithDrawable(ICON)) + assertThat(visibleModel.contentDescriptionResourceId).isNotNull() + job.cancel() + } + + @Test + fun `affordance - wallet not enabled - model is none`() = runBlockingTest { + setUpState(isWalletEnabled = false) + var latest: KeyguardQuickAffordanceConfig.State? = null + + val job = underTest.state.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden) + + job.cancel() + } + + @Test + fun `affordance - query not successful - model is none`() = runBlockingTest { + setUpState(isWalletQuerySuccessful = false) + var latest: KeyguardQuickAffordanceConfig.State? = null + + val job = underTest.state.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden) + + job.cancel() + } + + @Test + fun `affordance - missing icon - model is none`() = runBlockingTest { + setUpState(hasWalletIcon = false) + var latest: KeyguardQuickAffordanceConfig.State? = null + + val job = underTest.state.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden) + + job.cancel() + } + + @Test + fun `affordance - no selected card - model is none`() = runBlockingTest { + setUpState(hasWalletIcon = false) + var latest: KeyguardQuickAffordanceConfig.State? = null + + val job = underTest.state.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden) + + job.cancel() + } + + @Test + fun onQuickAffordanceClicked() { + val animationController: ActivityLaunchAnimator.Controller = mock() + + assertThat(underTest.onQuickAffordanceClicked(animationController)) + .isEqualTo(KeyguardQuickAffordanceConfig.OnClickedResult.Handled) + verify(walletController) + .startQuickAccessUiIntent( + activityStarter, + animationController, + /* hasCard= */ true, + ) + } + + private fun setUpState( + isWalletEnabled: Boolean = true, + isWalletQuerySuccessful: Boolean = true, + hasWalletIcon: Boolean = true, + hasSelectedCard: Boolean = true, + ) { + whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled) + + val walletClient: QuickAccessWalletClient = mock() + val icon: Drawable? = + if (hasWalletIcon) { + ICON + } else { + null + } + whenever(walletClient.tileIcon).thenReturn(icon) + whenever(walletController.walletClient).thenReturn(walletClient) + + whenever(walletController.queryWalletCards(any())).thenAnswer { invocation -> + with( + invocation.arguments[0] as QuickAccessWalletClient.OnWalletCardsRetrievedCallback + ) { + if (isWalletQuerySuccessful) { + onWalletCardsRetrieved( + if (hasSelectedCard) { + GetWalletCardsResponse(listOf(mock()), 0) + } else { + GetWalletCardsResponse(emptyList(), 0) + } + ) + } else { + onWalletCardRetrievalError(mock()) + } + } + } + } + + companion object { + private val ICON: Drawable = mock() + } +} 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 000000000000..8982752c9fcc --- /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>() + + init { + KeyguardQuickAffordancePosition.values().forEach { position -> + affordanceByPosition[position] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden) + } + } + + override fun invoke( + position: KeyguardQuickAffordancePosition + ): Flow { + 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 000000000000..63eb68f423ee --- /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 b90400be16d8..000000000000 --- 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 00dd58e19142..8758ce5eade6 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.SysuiTestCase 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 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { 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 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { 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,146 +144,79 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { } @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, - ) + fun `startButton - present - visible model - starts activity on click`() = runBlockingTest { + var latest: KeyguardQuickAffordanceViewModel? = null + val job = underTest.startButton.onEach { latest = it }.launchIn(this) - assertQuickAffordanceViewModel( - viewModel = latest, - testConfig = testConfig, - configKey = configKey, + val testConfig = + TestConfig( + isVisible = true, + icon = mock(), + canShowWhileLocked = false, + intent = Intent("action"), ) - 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, + val configKey = + setUpQuickAffordanceModel( + position = KeyguardQuickAffordancePosition.BOTTOM_START, + testConfig = testConfig, ) - 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() - } + assertQuickAffordanceViewModel( + viewModel = latest, + testConfig = testConfig, + configKey = configKey, + ) + job.cancel() + } @Test - fun `startButton - present - dozing - lockscreen showing - model is none`() = runBlockingTest { + fun `endButton - present - visible model - do nothing on click`() = runBlockingTest { var latest: KeyguardQuickAffordanceViewModel? = null - val job = underTest.startButton.onEach { latest = it }.launchIn(this) + val job = underTest.endButton.onEach { latest = it }.launchIn(this) - repository.setDozing(true) - repository.setKeyguardShowing(true) val config = TestConfig( isVisible = true, icon = mock(), canShowWhileLocked = false, - intent = Intent("action"), + intent = null, // This will cause it to tell the system that the click was handled. ) val configKey = setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_START, + position = KeyguardQuickAffordancePosition.BOTTOM_END, testConfig = config, ) assertQuickAffordanceViewModel( viewModel = latest, - testConfig = TestConfig(isVisible = false), + testConfig = config, 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) - - 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, - ) + fun `startButton - not present - model is hidden`() = runBlockingTest { + var latest: KeyguardQuickAffordanceViewModel? = null + val job = underTest.startButton.onEach { latest = it }.launchIn(this) - assertQuickAffordanceViewModel( - viewModel = latest, - testConfig = TestConfig(isVisible = false), - configKey = configKey, + val config = + TestConfig( + isVisible = false, + ) + val configKey = + setUpQuickAffordanceModel( + position = KeyguardQuickAffordancePosition.BOTTOM_START, + testConfig = config, ) - job.cancel() - } + + assertQuickAffordanceViewModel( + viewModel = latest, + testConfig = config, + configKey = configKey, + ) + job.cancel() + } @Test fun animateButtonReveal() = runBlockingTest { @@ -413,7 +349,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { 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 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { 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( -- cgit v1.2.3-59-g8ed1b