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