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.verify
61 import org.mockito.MockitoSession
62 import org.mockito.quality.Strictness
63 
64 /** atest SystemUITests:NoteTaskQuickAffordanceConfigTest */
65 @SmallTest
66 @RunWith(AndroidJUnit4::class)
67 internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() {
68 
69     @Mock lateinit var controller: NoteTaskController
70     @Mock lateinit var stylusManager: StylusManager
71     @Mock lateinit var repository: KeyguardQuickAffordanceRepository
72     @Mock lateinit var userManager: UserManager
73     @Mock lateinit var roleManager: RoleManager
74     @Mock lateinit var packageManager: PackageManager
75 
76     private lateinit var mockitoSession: MockitoSession
77 
78     @Before
setUpnull79     fun setUp() {
80         mockitoSession =
81             ExtendedMockito.mockitoSession()
82                 .initMocks(this)
83                 .mockStatic(InputSettings::class.java)
84                 .strictness(Strictness.LENIENT)
85                 .startMocking()
86 
87         whenever(
88                 packageManager.getApplicationInfoAsUser(
89                     anyString(),
90                     any(ApplicationInfoFlags::class.java),
91                     any(UserHandle::class.java)
92                 )
93             )
94             .thenReturn(ApplicationInfo())
95         whenever(controller.getUserForHandlingNotesTaking(any())).thenReturn(UserHandle.SYSTEM)
96         whenever(
97                 roleManager.getRoleHoldersAsUser(
98                     eq(RoleManager.ROLE_NOTES),
99                     any(UserHandle::class.java)
100                 )
101             )
102             .thenReturn(listOf("com.google.test.notes"))
103     }
104 
105     @After
tearDownnull106     fun tearDown() {
107         mockitoSession.finishMocking()
108     }
109 
createUnderTestnull110     private fun createUnderTest(isEnabled: Boolean = true): KeyguardQuickAffordanceConfig =
111         NoteTaskQuickAffordanceConfig(
112             context = context,
113             controller = controller,
114             stylusManager = stylusManager,
115             userManager = userManager,
116             keyguardMonitor = mock(),
117             lazyRepository = { repository },
118             isEnabled = isEnabled,
119             backgroundExecutor = FakeExecutor(FakeSystemClock()),
120             roleManager = roleManager,
121             noteTaskInfoResolver = NoteTaskInfoResolver(roleManager, packageManager)
122         )
123 
createLockScreenStateVisiblenull124     private fun createLockScreenStateVisible(): LockScreenState =
125         LockScreenState.Visible(
126             icon =
127                 Icon.Resource(
128                     res = R.drawable.ic_note_task_shortcut_keyguard,
129                     contentDescription =
130                         ContentDescription.Resource(R.string.note_task_button_label),
131                 )
132         )
133 
134     // region lockScreenState
135     @Test
136     fun lockScreenState_stylusUsed_userUnlocked_isSelected_shouldEmitVisible() = runTest {
137         val underTest = createUnderTest()
138         TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections(underTest)
139 
140         val actual by collectLastValue(underTest.lockScreenState)
141 
142         assertThat(actual).isEqualTo(createLockScreenStateVisible())
143     }
144 
145     @Test
lockScreenState_stylusUsed_userUnlocked_isSelected_noDefaultNotesAppSet_shouldEmitHiddennull146     fun lockScreenState_stylusUsed_userUnlocked_isSelected_noDefaultNotesAppSet_shouldEmitHidden() =
147         runTest {
148             val underTest = createUnderTest()
149             TestConfig()
150                 .setStylusEverUsed(true)
151                 .setUserUnlocked(true)
152                 .setConfigSelections(underTest)
153             whenever(
154                     roleManager.getRoleHoldersAsUser(
155                         eq(RoleManager.ROLE_NOTES),
156                         any(UserHandle::class.java)
157                     )
158                 )
159                 .thenReturn(emptyList())
160 
161             val actual by collectLastValue(underTest.lockScreenState)
162 
163             assertThat(actual).isEqualTo(LockScreenState.Hidden)
164         }
165 
166     @Test
<lambda>null167     fun lockScreenState_stylusUnused_userUnlocked_isSelected_shouldEmitHidden() = runTest {
168         val underTest = createUnderTest()
169         TestConfig().setStylusEverUsed(false).setUserUnlocked(true).setConfigSelections(underTest)
170 
171         val actual by collectLastValue(underTest.lockScreenState)
172 
173         assertThat(actual).isEqualTo(LockScreenState.Hidden)
174     }
175 
176     @Test
<lambda>null177     fun lockScreenState_stylusUsed_userLocked_isSelected_shouldEmitHidden() = runTest {
178         val underTest = createUnderTest()
179         TestConfig().setStylusEverUsed(true).setUserUnlocked(false).setConfigSelections(underTest)
180 
181         val actual by collectLastValue(underTest.lockScreenState)
182 
183         assertThat(actual).isEqualTo(LockScreenState.Hidden)
184     }
185 
186     @Test
<lambda>null187     fun lockScreenState_stylusUsed_userUnlocked_noSelected_shouldEmitHidden() = runTest {
188         TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections()
189 
190         val underTest = createUnderTest()
191         val actual by collectLastValue(underTest.lockScreenState)
192 
193         assertThat(actual).isEqualTo(LockScreenState.Hidden)
194     }
195 
196     @Test
<lambda>null197     fun lockScreenState_stylusUnused_userUnlocked_noSelected_shouldEmitHidden() = runTest {
198         TestConfig().setStylusEverUsed(false).setUserUnlocked(true).setConfigSelections()
199 
200         val underTest = createUnderTest()
201         val actual by collectLastValue(underTest.lockScreenState)
202 
203         assertThat(actual).isEqualTo(LockScreenState.Hidden)
204     }
205 
206     @Test
<lambda>null207     fun lockScreenState_stylusUsed_userLocked_noSelected_shouldEmitHidden() = runTest {
208         TestConfig().setStylusEverUsed(true).setUserUnlocked(false).setConfigSelections()
209 
210         val underTest = createUnderTest()
211         val actual by collectLastValue(underTest.lockScreenState)
212 
213         assertThat(actual).isEqualTo(LockScreenState.Hidden)
214     }
215 
216     @Test
<lambda>null217     fun lockScreenState_stylusUsed_userUnlocked_customSelections_shouldEmitHidden() = runTest {
218         TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections(mock())
219 
220         val underTest = createUnderTest()
221         val actual by collectLastValue(underTest.lockScreenState)
222 
223         assertThat(actual).isEqualTo(LockScreenState.Hidden)
224     }
225 
226     @Test
<lambda>null227     fun lockScreenState_stylusUnused_userUnlocked_customSelections_shouldEmitHidden() = runTest {
228         TestConfig().setStylusEverUsed(false).setUserUnlocked(true).setConfigSelections(mock())
229 
230         val underTest = createUnderTest()
231         val actual by collectLastValue(underTest.lockScreenState)
232 
233         assertThat(actual).isEqualTo(LockScreenState.Hidden)
234     }
235 
236     @Test
<lambda>null237     fun lockScreenState_stylusUsed_userLocked_customSelections_shouldEmitHidden() = runTest {
238         TestConfig().setStylusEverUsed(true).setUserUnlocked(false).setConfigSelections(mock())
239 
240         val underTest = createUnderTest()
241         val actual by collectLastValue(underTest.lockScreenState)
242 
243         assertThat(actual).isEqualTo(LockScreenState.Hidden)
244     }
245 
246     @Test
<lambda>null247     fun lockScreenState_isNotEnabled_shouldEmitHidden() = runTest {
248         TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections()
249 
250         val underTest = createUnderTest(isEnabled = false)
251         val actual by collectLastValue(underTest.lockScreenState)
252 
253         assertThat(actual).isEqualTo(LockScreenState.Hidden)
254     }
255     // endregion
256 
257     @Test
onTriggered_shouldLaunchNoteTasknull258     fun onTriggered_shouldLaunchNoteTask() {
259         val underTest = createUnderTest(isEnabled = false)
260 
261         underTest.onTriggered(expandable = null)
262 
263         verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
264     }
265 
266     // region getPickerScreenState
267     @Test
<lambda>null268     fun getPickerScreenState_defaultNoteAppSet_shouldReturnDefault() = runTest {
269         val underTest = createUnderTest(isEnabled = true)
270 
271         assertThat(underTest.getPickerScreenState())
272             .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default())
273     }
274 
275     @Test
<lambda>null276     fun getPickerScreenState_noDefaultNoteAppSet_shouldReturnDisabled() = runTest {
277         val underTest = createUnderTest(isEnabled = true)
278         whenever(
279                 roleManager.getRoleHoldersAsUser(
280                     eq(RoleManager.ROLE_NOTES),
281                     any(UserHandle::class.java)
282                 )
283             )
284             .thenReturn(emptyList())
285 
286         val pickerScreenState = underTest.getPickerScreenState()
287         assertThat(pickerScreenState is KeyguardQuickAffordanceConfig.PickerScreenState.Disabled)
288             .isTrue()
289         val disabled = pickerScreenState as KeyguardQuickAffordanceConfig.PickerScreenState.Disabled
290         assertThat(disabled.explanation)
291             .isEqualTo("Select a default notes app to use the notetaking shortcut")
292         assertThat(disabled.actionText).isEqualTo("Select app")
293         assertThat(disabled.actionIntent?.action)
294             .isEqualTo(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE)
295         assertThat(disabled.actionIntent?.`package`).isEqualTo(context.packageName)
296     }
297     // endregion
298 
299     private inner class TestConfig {
300 
<lambda>null301         fun setStylusEverUsed(value: Boolean) = also {
302             whenever(InputSettings.isStylusEverUsed(mContext)).thenReturn(value)
303         }
304 
<lambda>null305         fun setUserUnlocked(value: Boolean) = also {
306             whenever(userManager.isUserUnlocked).thenReturn(value)
307         }
308 
<lambda>null309         fun setConfigSelections(vararg values: KeyguardQuickAffordanceConfig) = also {
310             val slotKey = "bottom-right"
311             val configSnapshots = values.toList()
312             val map = mapOf(slotKey to configSnapshots)
313             whenever(repository.selections).thenReturn(MutableStateFlow(map))
314         }
315     }
316 }
317