1 /*
2  * Copyright (C) 2023 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 @file:OptIn(ExperimentalCoroutinesApi::class)
18 
19 package com.android.systemui.notetask.quickaffordance
20 
21 import android.app.role.RoleManager
22 import android.content.pm.ApplicationInfo
23 import android.content.pm.PackageManager
24 import android.content.pm.PackageManager.ApplicationInfoFlags
25 import android.hardware.input.InputSettings
26 import android.os.UserHandle
27 import android.os.UserManager
28 import androidx.test.ext.junit.runners.AndroidJUnit4
29 import androidx.test.filters.SmallTest
30 import com.android.dx.mockito.inline.extended.ExtendedMockito
31 import com.android.systemui.SysuiTestCase
32 import com.android.systemui.common.shared.model.ContentDescription
33 import com.android.systemui.common.shared.model.Icon
34 import com.android.systemui.coroutines.collectLastValue
35 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
36 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
37 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
38 import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
39 import com.android.systemui.notetask.NoteTaskController
40 import com.android.systemui.notetask.NoteTaskEntryPoint
41 import com.android.systemui.notetask.NoteTaskInfoResolver
42 import com.android.systemui.res.R
43 import com.android.systemui.stylus.StylusManager
44 import com.android.systemui.util.concurrency.FakeExecutor
45 import com.android.systemui.util.mockito.any
46 import com.android.systemui.util.mockito.eq
47 import com.android.systemui.util.mockito.mock
48 import com.android.systemui.util.mockito.whenever
49 import com.android.systemui.util.time.FakeSystemClock
50 import com.google.common.truth.Truth.assertThat
51 import kotlinx.coroutines.ExperimentalCoroutinesApi
52 import kotlinx.coroutines.flow.MutableStateFlow
53 import kotlinx.coroutines.test.runTest
54 import org.junit.After
55 import org.junit.Before
56 import org.junit.Test
57 import org.junit.runner.RunWith
58 import org.mockito.Mock
59 import org.mockito.Mockito.anyString
60 import org.mockito.Mockito.spy
61 import org.mockito.Mockito.verify
62 import org.mockito.Mockito.`when`
63 import org.mockito.MockitoSession
64 import org.mockito.quality.Strictness
65 
66 /** atest SystemUITests:NoteTaskQuickAffordanceConfigTest */
67 @SmallTest
68 @RunWith(AndroidJUnit4::class)
69 internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() {
70 
71     @Mock lateinit var controller: NoteTaskController
72     @Mock lateinit var stylusManager: StylusManager
73     @Mock lateinit var repository: KeyguardQuickAffordanceRepository
74     @Mock lateinit var userManager: UserManager
75     @Mock lateinit var roleManager: RoleManager
76     @Mock lateinit var packageManager: PackageManager
77 
78     private lateinit var mockitoSession: MockitoSession
79 
80     private val spiedContext = spy(context)
81     private val spiedResources = spy(spiedContext.resources)
82 
83     @Before
setUpnull84     fun setUp() {
85         mockitoSession =
86             ExtendedMockito.mockitoSession()
87                 .initMocks(this)
88                 .mockStatic(InputSettings::class.java)
89                 .strictness(Strictness.LENIENT)
90                 .startMocking()
91 
92         whenever(
93                 packageManager.getApplicationInfoAsUser(
94                     anyString(),
95                     any(ApplicationInfoFlags::class.java),
96                     any(UserHandle::class.java)
97                 )
98             )
99             .thenReturn(ApplicationInfo())
100         whenever(controller.getUserForHandlingNotesTaking(any())).thenReturn(UserHandle.SYSTEM)
101         whenever(
102                 roleManager.getRoleHoldersAsUser(
103                     eq(RoleManager.ROLE_NOTES),
104                     any(UserHandle::class.java)
105                 )
106             )
107             .thenReturn(listOf("com.google.test.notes"))
108 
109         `when`(spiedContext.resources).thenReturn(spiedResources)
110     }
111 
112     @After
tearDownnull113     fun tearDown() {
114         mockitoSession.finishMocking()
115     }
116 
createUnderTestnull117     private fun createUnderTest(isEnabled: Boolean = true): KeyguardQuickAffordanceConfig =
118         NoteTaskQuickAffordanceConfig(
119             context = spiedContext,
120             controller = controller,
121             stylusManager = stylusManager,
122             userManager = userManager,
123             keyguardMonitor = mock(),
124             lazyRepository = { repository },
125             isEnabled = isEnabled,
126             backgroundExecutor = FakeExecutor(FakeSystemClock()),
127             roleManager = roleManager,
128             noteTaskInfoResolver = NoteTaskInfoResolver(roleManager, packageManager)
129         )
130 
createLockScreenStateVisiblenull131     private fun createLockScreenStateVisible(): LockScreenState =
132         LockScreenState.Visible(
133             icon =
134                 Icon.Resource(
135                     res = R.drawable.ic_note_task_shortcut_keyguard,
136                     contentDescription =
137                         ContentDescription.Resource(R.string.note_task_button_label),
138                 )
139         )
140 
141     // region lockScreenState
142     @Suppress("ktlint:standard:max-line-length")
143     @Test
144     fun lockScreenState_stylusUnused_userLocked_customizationDisabled_notesLockScreenShortcutNotSelected_shouldEmitHidden() =
145         runTest {
146             val underTest = createUnderTest()
147             TestConfig()
148                 .setStylusEverUsed(false)
149                 .setUserUnlocked(false)
150                 .setLockScreenCustomizationEnabled(false)
151                 .setConfigSelections()
152 
153             val actual by collectLastValue(underTest.lockScreenState)
154 
155             assertThat(actual).isEqualTo(LockScreenState.Hidden)
156         }
157 
158     @Suppress("ktlint:standard:max-line-length")
159     @Test
lockScreenState_stylusUnused_userLocked_customizationDisabled_notesLockScreenShortcutSelected_shouldEmitHiddennull160     fun lockScreenState_stylusUnused_userLocked_customizationDisabled_notesLockScreenShortcutSelected_shouldEmitHidden() =
161         runTest {
162             val underTest = createUnderTest()
163             TestConfig()
164                 .setStylusEverUsed(false)
165                 .setUserUnlocked(false)
166                 .setLockScreenCustomizationEnabled(false)
167                 .setConfigSelections(underTest)
168 
169             val actual by collectLastValue(underTest.lockScreenState)
170 
171             assertThat(actual).isEqualTo(LockScreenState.Hidden)
172         }
173 
174     @Suppress("ktlint:standard:max-line-length")
175     @Test
lockScreenState_stylusUnused_userLocked_customizationEnabled_notesLockScreenShortcutNotSelected_shouldEmitHiddennull176     fun lockScreenState_stylusUnused_userLocked_customizationEnabled_notesLockScreenShortcutNotSelected_shouldEmitHidden() =
177         runTest {
178             val underTest = createUnderTest()
179             TestConfig()
180                 .setStylusEverUsed(false)
181                 .setUserUnlocked(false)
182                 .setLockScreenCustomizationEnabled(true)
183                 .setConfigSelections()
184 
185             val actual by collectLastValue(underTest.lockScreenState)
186 
187             assertThat(actual).isEqualTo(LockScreenState.Hidden)
188         }
189 
190     @Suppress("ktlint:standard:max-line-length")
191     @Test
lockScreenState_stylusUnused_userLocked_customizationEnabled_notesLockScreenShortcutSelected_shouldEmitHiddennull192     fun lockScreenState_stylusUnused_userLocked_customizationEnabled_notesLockScreenShortcutSelected_shouldEmitHidden() =
193         runTest {
194             val underTest = createUnderTest()
195             TestConfig()
196                 .setStylusEverUsed(false)
197                 .setUserUnlocked(false)
198                 .setLockScreenCustomizationEnabled(true)
199                 .setConfigSelections(underTest)
200 
201             val actual by collectLastValue(underTest.lockScreenState)
202 
203             assertThat(actual).isEqualTo(LockScreenState.Hidden)
204         }
205 
206     @Suppress("ktlint:standard:max-line-length")
207     @Test
lockScreenState_stylusUnused_userUnlocked_customizationDisabled_notesLockScreenShortcutNotSelected_shouldEmitHiddennull208     fun lockScreenState_stylusUnused_userUnlocked_customizationDisabled_notesLockScreenShortcutNotSelected_shouldEmitHidden() =
209         runTest {
210             val underTest = createUnderTest()
211             TestConfig()
212                 .setStylusEverUsed(false)
213                 .setUserUnlocked(true)
214                 .setLockScreenCustomizationEnabled(false)
215                 .setConfigSelections()
216 
217             val actual by collectLastValue(underTest.lockScreenState)
218 
219             assertThat(actual).isEqualTo(LockScreenState.Hidden)
220         }
221 
222     @Suppress("ktlint:standard:max-line-length")
223     @Test
lockScreenState_stylusUnused_userUnlocked_customizationDisabled_notesLockScreenShortcutSelected_shouldEmitHiddennull224     fun lockScreenState_stylusUnused_userUnlocked_customizationDisabled_notesLockScreenShortcutSelected_shouldEmitHidden() =
225         runTest {
226             val underTest = createUnderTest()
227             TestConfig()
228                 .setStylusEverUsed(false)
229                 .setUserUnlocked(true)
230                 .setLockScreenCustomizationEnabled(false)
231                 .setConfigSelections(underTest)
232 
233             val actual by collectLastValue(underTest.lockScreenState)
234 
235             assertThat(actual).isEqualTo(LockScreenState.Hidden)
236         }
237 
238     @Suppress("ktlint:standard:max-line-length")
239     @Test
lockScreenState_stylusUnused_userUnlocked_customizationEnabled_notesLockScreenShortcutNotSelected_shouldEmitHiddennull240     fun lockScreenState_stylusUnused_userUnlocked_customizationEnabled_notesLockScreenShortcutNotSelected_shouldEmitHidden() =
241         runTest {
242             val underTest = createUnderTest()
243             TestConfig()
244                 .setStylusEverUsed(false)
245                 .setUserUnlocked(true)
246                 .setLockScreenCustomizationEnabled(true)
247                 .setConfigSelections()
248 
249             val actual by collectLastValue(underTest.lockScreenState)
250 
251             assertThat(actual).isEqualTo(LockScreenState.Hidden)
252         }
253 
254     @Suppress("ktlint:standard:max-line-length")
255     @Test
lockScreenState_stylusUnused_userUnlocked_customizationEnabled_notesLockScreenShortcutSelected_shouldEmitVisiblenull256     fun lockScreenState_stylusUnused_userUnlocked_customizationEnabled_notesLockScreenShortcutSelected_shouldEmitVisible() =
257         runTest {
258             val underTest = createUnderTest()
259             TestConfig()
260                 .setStylusEverUsed(false)
261                 .setUserUnlocked(true)
262                 .setLockScreenCustomizationEnabled(true)
263                 .setConfigSelections(underTest)
264 
265             val actual by collectLastValue(underTest.lockScreenState)
266 
267             assertThat(actual).isEqualTo(createLockScreenStateVisible())
268         }
269 
270     @Suppress("ktlint:standard:max-line-length")
271     @Test
lockScreenState_stylusUsed_userLocked_customizationDisabled_notesLockScreenShortcutNotSelected_shouldEmitHiddennull272     fun lockScreenState_stylusUsed_userLocked_customizationDisabled_notesLockScreenShortcutNotSelected_shouldEmitHidden() =
273         runTest {
274             val underTest = createUnderTest()
275             TestConfig()
276                 .setStylusEverUsed(true)
277                 .setUserUnlocked(false)
278                 .setLockScreenCustomizationEnabled(false)
279                 .setConfigSelections()
280 
281             val actual by collectLastValue(underTest.lockScreenState)
282 
283             assertThat(actual).isEqualTo(LockScreenState.Hidden)
284         }
285 
286     @Suppress("ktlint:standard:max-line-length")
287     @Test
lockScreenState_stylusUsed_userLocked_customizationDisabled_notesLockScreenShortcutSelected_shouldEmitHiddennull288     fun lockScreenState_stylusUsed_userLocked_customizationDisabled_notesLockScreenShortcutSelected_shouldEmitHidden() =
289         runTest {
290             val underTest = createUnderTest()
291             TestConfig()
292                 .setStylusEverUsed(true)
293                 .setUserUnlocked(false)
294                 .setLockScreenCustomizationEnabled(false)
295                 .setConfigSelections(underTest)
296 
297             val actual by collectLastValue(underTest.lockScreenState)
298 
299             assertThat(actual).isEqualTo(LockScreenState.Hidden)
300         }
301 
302     @Suppress("ktlint:standard:max-line-length")
303     @Test
lockScreenState_stylusUsed_userLocked_customizationEnabled_notesLockScreenShortcutNotSelected_shouldEmitHiddennull304     fun lockScreenState_stylusUsed_userLocked_customizationEnabled_notesLockScreenShortcutNotSelected_shouldEmitHidden() =
305         runTest {
306             val underTest = createUnderTest()
307             TestConfig()
308                 .setStylusEverUsed(true)
309                 .setUserUnlocked(false)
310                 .setLockScreenCustomizationEnabled(true)
311                 .setConfigSelections()
312 
313             val actual by collectLastValue(underTest.lockScreenState)
314 
315             assertThat(actual).isEqualTo(LockScreenState.Hidden)
316         }
317 
318     @Suppress("ktlint:standard:max-line-length")
319     @Test
lockScreenState_stylusUsed_userLocked_customizationEnabled_notesLockScreenShortcutSelected_shouldEmitHiddennull320     fun lockScreenState_stylusUsed_userLocked_customizationEnabled_notesLockScreenShortcutSelected_shouldEmitHidden() =
321         runTest {
322             val underTest = createUnderTest()
323             TestConfig()
324                 .setStylusEverUsed(true)
325                 .setUserUnlocked(false)
326                 .setLockScreenCustomizationEnabled(true)
327                 .setConfigSelections(underTest)
328 
329             val actual by collectLastValue(underTest.lockScreenState)
330 
331             assertThat(actual).isEqualTo(LockScreenState.Hidden)
332         }
333 
334     @Suppress("ktlint:standard:max-line-length")
335     @Test
lockScreenState_stylusUsed_userUnlocked_customizationDisabled_notesLockScreenShortcutNotSelected_shouldEmitVisiblenull336     fun lockScreenState_stylusUsed_userUnlocked_customizationDisabled_notesLockScreenShortcutNotSelected_shouldEmitVisible() =
337         runTest {
338             val underTest = createUnderTest()
339             TestConfig()
340                 .setStylusEverUsed(true)
341                 .setUserUnlocked(true)
342                 .setLockScreenCustomizationEnabled(false)
343                 .setConfigSelections()
344 
345             val actual by collectLastValue(underTest.lockScreenState)
346 
347             assertThat(actual).isEqualTo(createLockScreenStateVisible())
348         }
349 
350     @Suppress("ktlint:standard:max-line-length")
351     @Test
lockScreenState_stylusUsed_userUnlocked_customizationDisabled_notesLockScreenShortcutSelected_shouldEmitVisiblenull352     fun lockScreenState_stylusUsed_userUnlocked_customizationDisabled_notesLockScreenShortcutSelected_shouldEmitVisible() =
353         runTest {
354             val underTest = createUnderTest()
355             TestConfig()
356                 .setStylusEverUsed(true)
357                 .setUserUnlocked(true)
358                 .setLockScreenCustomizationEnabled(false)
359                 .setConfigSelections(underTest)
360 
361             val actual by collectLastValue(underTest.lockScreenState)
362 
363             assertThat(actual).isEqualTo(createLockScreenStateVisible())
364         }
365 
366     @Suppress("ktlint:standard:max-line-length")
367     @Test
lockScreenState_stylusUsed_userUnlocked_customizationEnabled_notesLockScreenShortcutNotSelected_shouldEmitHiddennull368     fun lockScreenState_stylusUsed_userUnlocked_customizationEnabled_notesLockScreenShortcutNotSelected_shouldEmitHidden() =
369         runTest {
370             val underTest = createUnderTest()
371             TestConfig()
372                 .setStylusEverUsed(true)
373                 .setUserUnlocked(true)
374                 .setLockScreenCustomizationEnabled(true)
375                 .setConfigSelections()
376 
377             val actual by collectLastValue(underTest.lockScreenState)
378 
379             assertThat(actual).isEqualTo(LockScreenState.Hidden)
380         }
381 
382     @Suppress("ktlint:standard:max-line-length")
383     @Test
lockScreenState_stylusUsed_userUnlocked_customizationEnabled_notesLockScreenShortcutSelected_shouldEmitVisiblenull384     fun lockScreenState_stylusUsed_userUnlocked_customizationEnabled_notesLockScreenShortcutSelected_shouldEmitVisible() =
385         runTest {
386             val underTest = createUnderTest()
387             TestConfig()
388                 .setStylusEverUsed(true)
389                 .setUserUnlocked(true)
390                 .setLockScreenCustomizationEnabled(true)
391                 .setConfigSelections(underTest)
392 
393             val actual by collectLastValue(underTest.lockScreenState)
394 
395             assertThat(actual).isEqualTo(createLockScreenStateVisible())
396         }
397 
398     // endregion
399 
400     @Test
onTriggered_shouldLaunchNoteTasknull401     fun onTriggered_shouldLaunchNoteTask() {
402         val underTest = createUnderTest(isEnabled = false)
403 
404         underTest.onTriggered(expandable = null)
405 
406         verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
407     }
408 
409     // region getPickerScreenState
410     @Test
<lambda>null411     fun getPickerScreenState_defaultNoteAppSet_shouldReturnDefault() = runTest {
412         val underTest = createUnderTest(isEnabled = true)
413 
414         assertThat(underTest.getPickerScreenState())
415             .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default())
416     }
417 
418     @Test
<lambda>null419     fun getPickerScreenState_noDefaultNoteAppSet_shouldReturnDisabled() = runTest {
420         val underTest = createUnderTest(isEnabled = true)
421         whenever(
422                 roleManager.getRoleHoldersAsUser(
423                     eq(RoleManager.ROLE_NOTES),
424                     any(UserHandle::class.java)
425                 )
426             )
427             .thenReturn(emptyList())
428 
429         val pickerScreenState = underTest.getPickerScreenState()
430         assertThat(pickerScreenState is KeyguardQuickAffordanceConfig.PickerScreenState.Disabled)
431             .isTrue()
432         val disabled = pickerScreenState as KeyguardQuickAffordanceConfig.PickerScreenState.Disabled
433         assertThat(disabled.explanation)
434             .isEqualTo("Select a default notes app to use the notetaking shortcut")
435         assertThat(disabled.actionText).isEqualTo("Select app")
436         assertThat(disabled.actionIntent?.action)
437             .isEqualTo(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE)
438         assertThat(disabled.actionIntent?.`package`).isEqualTo(context.packageName)
439     }
440 
441     // endregion
442 
443     private inner class TestConfig {
444 
<lambda>null445         fun setStylusEverUsed(value: Boolean) = also {
446             whenever(InputSettings.isStylusEverUsed(spiedContext)).thenReturn(value)
447         }
448 
<lambda>null449         fun setUserUnlocked(value: Boolean) = also {
450             whenever(userManager.isUserUnlocked).thenReturn(value)
451         }
452 
<lambda>null453         fun setLockScreenCustomizationEnabled(value: Boolean) = also {
454             `when`(spiedResources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled))
455                 .thenReturn(value)
456         }
457 
<lambda>null458         fun setConfigSelections(vararg values: KeyguardQuickAffordanceConfig) = also {
459             val slotKey = "bottom-right"
460             val configSnapshots = values.toList()
461             val map = mapOf(slotKey to configSnapshots)
462             whenever(repository.selections).thenReturn(MutableStateFlow(map))
463         }
464     }
465 }
466