1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 
18 package com.android.systemui.keyguard.data.quickaffordance
19 
20 import android.graphics.drawable.Drawable
21 import android.service.quickaccesswallet.GetWalletCardsResponse
22 import android.service.quickaccesswallet.QuickAccessWalletClient
23 import android.service.quickaccesswallet.WalletCard
24 import androidx.test.ext.junit.runners.AndroidJUnit4
25 import androidx.test.filters.SmallTest
26 import com.android.systemui.R
27 import com.android.systemui.RoboPilotTest
28 import com.android.systemui.SysuiTestCase
29 import com.android.systemui.animation.ActivityLaunchAnimator
30 import com.android.systemui.animation.Expandable
31 import com.android.systemui.common.shared.model.ContentDescription
32 import com.android.systemui.common.shared.model.Icon
33 import com.android.systemui.plugins.ActivityStarter
34 import com.android.systemui.util.mockito.any
35 import com.android.systemui.util.mockito.mock
36 import com.android.systemui.util.mockito.whenever
37 import com.android.systemui.wallet.controller.QuickAccessWalletController
38 import com.google.common.truth.Truth.assertThat
39 import kotlinx.coroutines.ExperimentalCoroutinesApi
40 import kotlinx.coroutines.flow.launchIn
41 import kotlinx.coroutines.flow.onEach
42 import kotlinx.coroutines.test.UnconfinedTestDispatcher
43 import kotlinx.coroutines.test.runBlockingTest
44 import kotlinx.coroutines.test.runTest
45 import org.junit.Before
46 import org.junit.Test
47 import org.junit.runner.RunWith
48 import org.mockito.Mock
49 import org.mockito.Mockito.verify
50 import org.mockito.MockitoAnnotations
51 
52 @OptIn(ExperimentalCoroutinesApi::class)
53 @SmallTest
54 @RoboPilotTest
55 @RunWith(AndroidJUnit4::class)
56 class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
57 
58     @Mock private lateinit var walletController: QuickAccessWalletController
59     @Mock private lateinit var activityStarter: ActivityStarter
60 
61     private lateinit var underTest: QuickAccessWalletKeyguardQuickAffordanceConfig
62 
63     @Before
64     fun setUp() {
65         MockitoAnnotations.initMocks(this)
66 
67         underTest =
68             QuickAccessWalletKeyguardQuickAffordanceConfig(
69                 context,
70                 walletController,
71                 activityStarter,
72             )
73     }
74 
75     @Test
76     fun affordance_keyguardShowing_hasWalletCard_visibleModel() = runBlockingTest {
77         setUpState()
78         var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
79 
80         val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
81 
82         val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
83         assertThat(visibleModel.icon)
84             .isEqualTo(
85                 Icon.Loaded(
86                     drawable = ICON,
87                     contentDescription =
88                         ContentDescription.Resource(
89                             res = R.string.accessibility_wallet_button,
90                         ),
91                 )
92             )
93         job.cancel()
94     }
95 
96     @Test
97     fun affordance_keyguardShowing_hasNonPaymentCard_modelIsNone() =
98         runTest(UnconfinedTestDispatcher()) {
99             setUpState(cardType = WalletCard.CARD_TYPE_NON_PAYMENT)
100             var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
101 
102             val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
103 
104             assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
105             job.cancel()
106         }
107 
108     @Test
109     fun affordance_keyguardShowing_hasPaymentCard_visibleModel() =
110         runTest(UnconfinedTestDispatcher()) {
111             setUpState(cardType = WalletCard.CARD_TYPE_PAYMENT)
112             var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
113 
114             val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
115 
116             val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
117             assertThat(visibleModel.icon)
118                 .isEqualTo(
119                     Icon.Loaded(
120                         drawable = ICON,
121                         contentDescription =
122                             ContentDescription.Resource(
123                                 res = R.string.accessibility_wallet_button,
124                             ),
125                     )
126                 )
127             job.cancel()
128         }
129 
130     @Test
131     fun affordance_walletFeatureNotEnabled_modelIsNone() = runBlockingTest {
132         setUpState(isWalletFeatureAvailable = false)
133         var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
134 
135         val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
136 
137         assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
138 
139         job.cancel()
140     }
141 
142     @Test
143     fun affordance_queryNotSuccessful_modelIsNone() = runBlockingTest {
144         setUpState(isWalletQuerySuccessful = false)
145         var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
146 
147         val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
148 
149         assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
150 
151         job.cancel()
152     }
153 
154     @Test
155     fun affordance_noSelectedCard_modelIsNone() = runBlockingTest {
156         setUpState(hasSelectedCard = false)
157         var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
158 
159         val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
160 
161         assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
162 
163         job.cancel()
164     }
165 
166     @Test
167     fun onQuickAffordanceTriggered() {
168         val animationController: ActivityLaunchAnimator.Controller = mock()
169         val expandable: Expandable = mock {
170             whenever(this.activityLaunchController()).thenReturn(animationController)
171         }
172 
173         assertThat(underTest.onTriggered(expandable))
174             .isEqualTo(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled)
175         verify(walletController)
176             .startQuickAccessUiIntent(
177                 activityStarter,
178                 animationController,
179                 /* hasCard= */ true,
180             )
181     }
182 
183     @Test
184     fun getPickerScreenState_default() = runTest {
185         setUpState()
186 
187         assertThat(underTest.getPickerScreenState())
188             .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default())
189     }
190 
191     @Test
192     fun getPickerScreenState_unavailable() = runTest {
193         setUpState(
194             isWalletServiceAvailable = false,
195         )
196 
197         assertThat(underTest.getPickerScreenState())
198             .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
199     }
200 
201     @Test
202     fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = runTest {
203         setUpState(
204             isWalletFeatureAvailable = false,
205         )
206 
207         assertThat(underTest.getPickerScreenState())
208             .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
209     }
210 
211     @Test
212     fun getPickerScreenState_disabledWhenThereIsNoCard() = runTest {
213         setUpState(
214             hasSelectedCard = false,
215         )
216 
217         assertThat(underTest.getPickerScreenState())
218             .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
219     }
220 
221     private fun setUpState(
222         isWalletFeatureAvailable: Boolean = true,
223         isWalletServiceAvailable: Boolean = true,
224         isWalletQuerySuccessful: Boolean = true,
225         hasSelectedCard: Boolean = true,
226         cardType: Int = WalletCard.CARD_TYPE_UNKNOWN
227     ) {
228         val walletClient: QuickAccessWalletClient = mock()
229         whenever(walletClient.tileIcon).thenReturn(ICON)
230         whenever(walletClient.isWalletServiceAvailable).thenReturn(isWalletServiceAvailable)
231         whenever(walletClient.isWalletFeatureAvailable).thenReturn(isWalletFeatureAvailable)
232 
233         whenever(walletController.walletClient).thenReturn(walletClient)
234 
235         whenever(walletController.queryWalletCards(any())).thenAnswer { invocation ->
236             with(
237                 invocation.arguments[0] as QuickAccessWalletClient.OnWalletCardsRetrievedCallback
238             ) {
239                 if (isWalletQuerySuccessful) {
240                     onWalletCardsRetrieved(
241                         if (hasSelectedCard) {
242                             GetWalletCardsResponse(
243                                 listOf(
244                                     WalletCard.Builder(
245                                             /*cardId= */ CARD_ID,
246                                             /*cardType= */ cardType,
247                                             /*cardImage= */ mock(),
248                                             /*contentDescription=  */ CARD_DESCRIPTION,
249                                             /*pendingIntent= */ mock()
250                                         )
251                                         .build()
252                                 ),
253                                 0
254                             )
255                         } else {
256                             GetWalletCardsResponse(emptyList(), 0)
257                         }
258                     )
259                 } else {
260                     onWalletCardRetrievalError(mock())
261                 }
262             }
263         }
264     }
265 
266     companion object {
267         private val ICON: Drawable = mock()
268         private const val CARD_ID: String = "Id"
269         private const val CARD_DESCRIPTION: String = "Description"
270     }
271 }
272