1 /*
2  * Copyright (C) 2024 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.domain.interactor
19 
20 import android.app.admin.DevicePolicyManager
21 import android.content.Intent
22 import android.os.UserHandle
23 import androidx.test.filters.FlakyTest
24 import androidx.test.filters.SmallTest
25 import com.android.internal.widget.LockPatternUtils
26 import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
27 import com.android.systemui.SysuiTestCase
28 import com.android.systemui.animation.ActivityTransitionAnimator
29 import com.android.systemui.animation.DialogTransitionAnimator
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.dock.DockManagerFake
34 import com.android.systemui.flags.EnableSceneContainer
35 import com.android.systemui.flags.FakeFeatureFlags
36 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
37 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
38 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
39 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
40 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
41 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
42 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
43 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
44 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
45 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
46 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
47 import com.android.systemui.plugins.ActivityStarter
48 import com.android.systemui.scene.domain.interactor.sceneInteractor
49 import com.android.systemui.settings.FakeUserTracker
50 import com.android.systemui.settings.UserFileManager
51 import com.android.systemui.settings.UserTracker
52 import com.android.systemui.shade.domain.interactor.shadeInteractor
53 import com.android.systemui.statusbar.policy.KeyguardStateController
54 import com.android.systemui.testKosmos
55 import com.android.systemui.util.FakeSharedPreferences
56 import com.android.systemui.util.mockito.any
57 import com.android.systemui.util.mockito.mock
58 import com.android.systemui.util.mockito.whenever
59 import com.android.systemui.util.settings.FakeSettings
60 import kotlinx.coroutines.CoroutineScope
61 import kotlinx.coroutines.Dispatchers
62 import kotlinx.coroutines.ExperimentalCoroutinesApi
63 import kotlinx.coroutines.test.StandardTestDispatcher
64 import kotlinx.coroutines.test.TestScope
65 import kotlinx.coroutines.test.runTest
66 import org.junit.Before
67 import org.junit.Test
68 import org.junit.runner.RunWith
69 import org.mockito.ArgumentMatchers.anyInt
70 import org.mockito.ArgumentMatchers.anyString
71 import org.mockito.ArgumentMatchers.eq
72 import org.mockito.ArgumentMatchers.same
73 import org.mockito.Mock
74 import org.mockito.Mockito.verify
75 import org.mockito.Mockito.verifyNoMoreInteractions
76 import org.mockito.MockitoAnnotations
77 import platform.test.runner.parameterized.Parameter
78 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
79 import platform.test.runner.parameterized.Parameters
80 
81 @OptIn(ExperimentalCoroutinesApi::class)
82 @FlakyTest(
83     bugId = 292574995,
84     detail = "on certain architectures all permutations with startActivity=true is causing failures"
85 )
86 @SmallTest
87 @RunWith(ParameterizedAndroidJunit4::class)
88 @EnableSceneContainer
89 class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() {
90 
91     companion object {
92         private val INTENT = Intent("some.intent.action")
93         private val DRAWABLE =
<lambda>null94             mock<Icon> {
95                 whenever(this.contentDescription)
96                     .thenReturn(
97                         ContentDescription.Resource(
98                             res = CONTENT_DESCRIPTION_RESOURCE_ID,
99                         )
100                     )
101             }
102         private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
103 
104         @Parameters(
105             name =
106                 "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
107                     " keyguardIsUnlocked={2}, needsToUnlockFirst={3}, startActivity={4}"
108         )
109         @JvmStatic
datanull110         fun data() =
111             listOf(
112                 arrayOf(
113                     /* needStrongAuthAfterBoot= */ false,
114                     /* canShowWhileLocked= */ false,
115                     /* keyguardIsUnlocked= */ false,
116                     /* needsToUnlockFirst= */ true,
117                     /* startActivity= */ false,
118                 ),
119                 arrayOf(
120                     /* needStrongAuthAfterBoot= */ false,
121                     /* canShowWhileLocked= */ false,
122                     /* keyguardIsUnlocked= */ true,
123                     /* needsToUnlockFirst= */ false,
124                     /* startActivity= */ false,
125                 ),
126                 arrayOf(
127                     /* needStrongAuthAfterBoot= */ false,
128                     /* canShowWhileLocked= */ true,
129                     /* keyguardIsUnlocked= */ false,
130                     /* needsToUnlockFirst= */ false,
131                     /* startActivity= */ false,
132                 ),
133                 arrayOf(
134                     /* needStrongAuthAfterBoot= */ false,
135                     /* canShowWhileLocked= */ true,
136                     /* keyguardIsUnlocked= */ true,
137                     /* needsToUnlockFirst= */ false,
138                     /* startActivity= */ false,
139                 ),
140                 arrayOf(
141                     /* needStrongAuthAfterBoot= */ true,
142                     /* canShowWhileLocked= */ false,
143                     /* keyguardIsUnlocked= */ false,
144                     /* needsToUnlockFirst= */ true,
145                     /* startActivity= */ false,
146                 ),
147                 arrayOf(
148                     /* needStrongAuthAfterBoot= */ true,
149                     /* canShowWhileLocked= */ false,
150                     /* keyguardIsUnlocked= */ true,
151                     /* needsToUnlockFirst= */ true,
152                     /* startActivity= */ false,
153                 ),
154                 arrayOf(
155                     /* needStrongAuthAfterBoot= */ true,
156                     /* canShowWhileLocked= */ true,
157                     /* keyguardIsUnlocked= */ false,
158                     /* needsToUnlockFirst= */ true,
159                     /* startActivity= */ false,
160                 ),
161                 arrayOf(
162                     /* needStrongAuthAfterBoot= */ true,
163                     /* canShowWhileLocked= */ true,
164                     /* keyguardIsUnlocked= */ true,
165                     /* needsToUnlockFirst= */ true,
166                     /* startActivity= */ false,
167                 ),
168                 arrayOf(
169                     /* needStrongAuthAfterBoot= */ false,
170                     /* canShowWhileLocked= */ false,
171                     /* keyguardIsUnlocked= */ false,
172                     /* needsToUnlockFirst= */ true,
173                     /* startActivity= */ true,
174                 ),
175                 arrayOf(
176                     /* needStrongAuthAfterBoot= */ false,
177                     /* canShowWhileLocked= */ false,
178                     /* keyguardIsUnlocked= */ true,
179                     /* needsToUnlockFirst= */ false,
180                     /* startActivity= */ true,
181                 ),
182                 arrayOf(
183                     /* needStrongAuthAfterBoot= */ false,
184                     /* canShowWhileLocked= */ true,
185                     /* keyguardIsUnlocked= */ false,
186                     /* needsToUnlockFirst= */ false,
187                     /* startActivity= */ true,
188                 ),
189                 arrayOf(
190                     /* needStrongAuthAfterBoot= */ false,
191                     /* canShowWhileLocked= */ true,
192                     /* keyguardIsUnlocked= */ true,
193                     /* needsToUnlockFirst= */ false,
194                     /* startActivity= */ true,
195                 ),
196                 arrayOf(
197                     /* needStrongAuthAfterBoot= */ true,
198                     /* canShowWhileLocked= */ false,
199                     /* keyguardIsUnlocked= */ false,
200                     /* needsToUnlockFirst= */ true,
201                     /* startActivity= */ true,
202                 ),
203                 arrayOf(
204                     /* needStrongAuthAfterBoot= */ true,
205                     /* canShowWhileLocked= */ false,
206                     /* keyguardIsUnlocked= */ true,
207                     /* needsToUnlockFirst= */ true,
208                     /* startActivity= */ true,
209                 ),
210                 arrayOf(
211                     /* needStrongAuthAfterBoot= */ true,
212                     /* canShowWhileLocked= */ true,
213                     /* keyguardIsUnlocked= */ false,
214                     /* needsToUnlockFirst= */ true,
215                     /* startActivity= */ true,
216                 ),
217                 arrayOf(
218                     /* needStrongAuthAfterBoot= */ true,
219                     /* canShowWhileLocked= */ true,
220                     /* keyguardIsUnlocked= */ true,
221                     /* needsToUnlockFirst= */ true,
222                     /* startActivity= */ true,
223                 ),
224             )
225 
226         private val IMMEDIATE = Dispatchers.Main.immediate
227     }
228 
229     @Mock private lateinit var lockPatternUtils: LockPatternUtils
230     @Mock private lateinit var keyguardStateController: KeyguardStateController
231     @Mock private lateinit var activityStarter: ActivityStarter
232     @Mock private lateinit var animationController: ActivityTransitionAnimator.Controller
233     @Mock private lateinit var expandable: Expandable
234     @Mock private lateinit var launchAnimator: DialogTransitionAnimator
235     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
236     @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
237     @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
238 
239     private lateinit var underTest: KeyguardQuickAffordanceInteractor
240     private lateinit var testScope: TestScope
241 
242     @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
243     @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
244     @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
245     @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
246     @JvmField @Parameter(4) var startActivity: Boolean = false
247     private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
248     private lateinit var dockManager: DockManagerFake
249     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
250     private lateinit var userTracker: UserTracker
251 
252     private val kosmos = testKosmos()
253 
254     @Before
255     fun setUp() {
256         MockitoAnnotations.initMocks(this)
257         whenever(expandable.activityTransitionController()).thenReturn(animationController)
258 
259         userTracker = FakeUserTracker()
260         homeControls =
261             FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
262         dockManager = DockManagerFake()
263         biometricSettingsRepository = FakeBiometricSettingsRepository()
264         val quickAccessWallet =
265             FakeKeyguardQuickAffordanceConfig(
266                 BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
267             )
268         val qrCodeScanner =
269             FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
270         val scope = CoroutineScope(IMMEDIATE)
271         val localUserSelectionManager =
272             KeyguardQuickAffordanceLocalUserSelectionManager(
273                 context = context,
274                 userFileManager =
275                     mock<UserFileManager>().apply {
276                         whenever(
277                                 getSharedPreferences(
278                                     anyString(),
279                                     anyInt(),
280                                     anyInt(),
281                                 )
282                             )
283                             .thenReturn(FakeSharedPreferences())
284                     },
285                 userTracker = userTracker,
286                 broadcastDispatcher = fakeBroadcastDispatcher,
287             )
288         val remoteUserSelectionManager =
289             KeyguardQuickAffordanceRemoteUserSelectionManager(
290                 scope = scope,
291                 userTracker = userTracker,
292                 clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
293                 userHandle = UserHandle.SYSTEM,
294             )
295         val quickAffordanceRepository =
296             KeyguardQuickAffordanceRepository(
297                 appContext = context,
298                 scope = scope,
299                 localUserSelectionManager = localUserSelectionManager,
300                 remoteUserSelectionManager = remoteUserSelectionManager,
301                 userTracker = userTracker,
302                 legacySettingSyncer =
303                     KeyguardQuickAffordanceLegacySettingSyncer(
304                         scope = scope,
305                         backgroundDispatcher = IMMEDIATE,
306                         secureSettings = FakeSettings(),
307                         selectionsManager = localUserSelectionManager,
308                     ),
309                 configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
310                 dumpManager = mock(),
311                 userHandle = UserHandle.SYSTEM,
312             )
313         val featureFlags = FakeFeatureFlags()
314         val testDispatcher = StandardTestDispatcher()
315         testScope = TestScope(testDispatcher)
316         underTest =
317             KeyguardQuickAffordanceInteractor(
318                 keyguardInteractor =
319                     KeyguardInteractorFactory.create(
320                             featureFlags = featureFlags,
321                         )
322                         .keyguardInteractor,
323                 shadeInteractor = kosmos.shadeInteractor,
324                 lockPatternUtils = lockPatternUtils,
325                 keyguardStateController = keyguardStateController,
326                 userTracker = userTracker,
327                 activityStarter = activityStarter,
328                 featureFlags = featureFlags,
329                 repository = { quickAffordanceRepository },
330                 launchAnimator = launchAnimator,
331                 logger = logger,
332                 metricsLogger = metricsLogger,
333                 devicePolicyManager = devicePolicyManager,
334                 dockManager = dockManager,
335                 biometricSettingsRepository = biometricSettingsRepository,
336                 backgroundDispatcher = testDispatcher,
337                 appContext = mContext,
338                 sceneInteractor = { kosmos.sceneInteractor },
339             )
340     }
341 
342     @Test
onQuickAffordanceTriggerednull343     fun onQuickAffordanceTriggered() =
344         testScope.runTest {
345             val key = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
346             setUpMocks(
347                 needStrongAuthAfterBoot = needStrongAuthAfterBoot,
348                 keyguardIsUnlocked = keyguardIsUnlocked,
349             )
350 
351             homeControls.setState(
352                 lockScreenState =
353                     KeyguardQuickAffordanceConfig.LockScreenState.Visible(
354                         icon = DRAWABLE,
355                     )
356             )
357             homeControls.onTriggeredResult =
358                 if (startActivity) {
359                     KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
360                         intent = INTENT,
361                         canShowWhileLocked = canShowWhileLocked,
362                     )
363                 } else {
364                     KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
365                 }
366 
367             underTest.onQuickAffordanceTriggered(
368                 configKey = "${KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()}::$key",
369                 expandable = expandable,
370                 slotId = "",
371             )
372 
373             if (startActivity) {
374                 if (needsToUnlockFirst) {
375                     verify(activityStarter)
376                         .postStartActivityDismissingKeyguard(
377                             any(),
378                             /* delay= */ eq(0),
379                             same(animationController),
380                         )
381                 } else {
382                     verify(activityStarter)
383                         .startActivity(
384                             any(),
385                             /* dismissShade= */ eq(true),
386                             same(animationController),
387                             /* showOverLockscreenWhenLocked= */ eq(true),
388                         )
389                 }
390             } else {
391                 verifyNoMoreInteractions(activityStarter)
392             }
393         }
394 
setUpMocksnull395     private fun setUpMocks(
396         needStrongAuthAfterBoot: Boolean = true,
397         keyguardIsUnlocked: Boolean = false,
398     ) {
399         whenever(lockPatternUtils.getStrongAuthForUser(any()))
400             .thenReturn(
401                 if (needStrongAuthAfterBoot) {
402                     LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
403                 } else {
404                     LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
405                 }
406             )
407         whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
408     }
409 }
410