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 package com.android.systemui.keyboard.shortcut.domain.interactor
18 
19 import android.content.Context
20 import android.content.Context.INPUT_SERVICE
21 import android.hardware.input.InputGestureData
22 import android.hardware.input.fakeInputManager
23 import android.platform.test.annotations.DisableFlags
24 import android.platform.test.annotations.EnableFlags
25 import androidx.test.ext.junit.runners.AndroidJUnit4
26 import androidx.test.filters.SmallTest
27 import com.android.systemui.Flags
28 import com.android.systemui.SysuiTestCase
29 import com.android.systemui.coroutines.collectLastValue
30 import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
31 import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
32 import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCustomizableInputGesturesWithSimpleShortcutCombinations
33 import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.customInputGestureTypeHome
34 import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.groupWithGoHomeShortcutInfo
35 import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.systemCategoryWithCustomHomeShortcut
36 import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.systemCategoryWithMergedGoHomeShortcut
37 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
38 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor
39 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
40 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
41 import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
42 import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesInteractor
43 import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
44 import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
45 import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
46 import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
47 import com.android.systemui.kosmos.testDispatcher
48 import com.android.systemui.kosmos.testScope
49 import com.android.systemui.settings.FakeUserTracker
50 import com.android.systemui.settings.userTracker
51 import com.android.systemui.testKosmos
52 import com.google.common.truth.Truth.assertThat
53 import kotlinx.coroutines.ExperimentalCoroutinesApi
54 import kotlinx.coroutines.test.UnconfinedTestDispatcher
55 import kotlinx.coroutines.test.runTest
56 import org.junit.Before
57 import org.junit.Test
58 import org.junit.runner.RunWith
59 import org.mockito.kotlin.anyOrNull
60 import org.mockito.kotlin.mock
61 import org.mockito.kotlin.whenever
62 
63 @SmallTest
64 @RunWith(AndroidJUnit4::class)
65 class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() {
66 
67     private val mockUserContext: Context = mock()
68     private val systemShortcutsSource = FakeKeyboardShortcutGroupsSource()
69     private val multitaskingShortcutsSource = FakeKeyboardShortcutGroupsSource()
70 
71     @OptIn(ExperimentalCoroutinesApi::class)
72     private val kosmos =
<lambda>null73         testKosmos().also {
74             it.testDispatcher = UnconfinedTestDispatcher()
75             it.shortcutHelperSystemShortcutsSource = systemShortcutsSource
76             it.shortcutHelperMultiTaskingShortcutsSource = multitaskingShortcutsSource
77             it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
78             it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
79             it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
80         }
81 
82     private val fakeInputManager = kosmos.fakeInputManager
83     private val testScope = kosmos.testScope
84     private lateinit var interactor: ShortcutHelperCategoriesInteractor
85     private val helper = kosmos.shortcutHelperTestHelper
<lambda>null86     private val inter by lazy { kosmos.shortcutHelperCategoriesInteractor }
87 
88     @Before
setShortcutsnull89     fun setShortcuts() {
90         interactor = kosmos.shortcutHelperCategoriesInteractor
91         helper.setImeShortcuts(TestShortcuts.imeGroups)
92         systemShortcutsSource.setGroups(TestShortcuts.systemGroups)
93         multitaskingShortcutsSource.setGroups(TestShortcuts.multitaskingGroups)
94         whenever(mockUserContext.getSystemService(INPUT_SERVICE))
95             .thenReturn(fakeInputManager.inputManager)
96     }
97 
98     @Test
categories_emptyByDefaultnull99     fun categories_emptyByDefault() =
100         testScope.runTest {
101             val categories by collectLastValue(interactor.shortcutCategories)
102 
103             assertThat(categories).isEmpty()
104         }
105 
106     @Test
categories_stateActive_emitsAllCategoriesInOrdernull107     fun categories_stateActive_emitsAllCategoriesInOrder() =
108         testScope.runTest {
109             val categories by collectLastValue(interactor.shortcutCategories)
110 
111             helper.showFromActivity()
112 
113             assertThat(categories)
114                 .containsExactly(
115                     TestShortcuts.systemCategory,
116                     TestShortcuts.multitaskingCategory,
117                     TestShortcuts.imeCategory,
118                 )
119                 .inOrder()
120         }
121 
122     @Test
categories_stateInactiveAfterActive_emitsEmptynull123     fun categories_stateInactiveAfterActive_emitsEmpty() =
124         testScope.runTest {
125             val categories by collectLastValue(interactor.shortcutCategories)
126             helper.showFromActivity()
127             helper.hideFromActivity()
128 
129             assertThat(categories).isEmpty()
130         }
131 
132     @Test
categories_stateActive_imeShortcutsWithDuplicateLabels_emitsGroupedShortcutsnull133     fun categories_stateActive_imeShortcutsWithDuplicateLabels_emitsGroupedShortcuts() =
134         testScope.runTest {
135             helper.setImeShortcuts(TestShortcuts.groupsWithDuplicateShortcutLabels)
136 
137             val categories by collectLastValue(interactor.shortcutCategories)
138 
139             helper.showFromActivity()
140 
141             assertThat(categories)
142                 .containsExactly(
143                     TestShortcuts.systemCategory,
144                     TestShortcuts.multitaskingCategory,
145                     ShortcutCategory(
146                         type = InputMethodEditor,
147                         subCategories =
148                             TestShortcuts.imeSubCategoriesWithGroupedDuplicatedShortcutLabels,
149                     ),
150                 )
151                 .inOrder()
152         }
153 
154     @Test
categories_stateActive_systemShortcutsWithDuplicateLabels_emitsGroupedShortcutsnull155     fun categories_stateActive_systemShortcutsWithDuplicateLabels_emitsGroupedShortcuts() =
156         testScope.runTest {
157             systemShortcutsSource.setGroups(TestShortcuts.groupsWithDuplicateShortcutLabels)
158 
159             val categories by collectLastValue(interactor.shortcutCategories)
160 
161             helper.showFromActivity()
162 
163             assertThat(categories)
164                 .containsExactly(
165                     ShortcutCategory(
166                         type = System,
167                         subCategories =
168                             TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels,
169                     ),
170                     TestShortcuts.multitaskingCategory,
171                     TestShortcuts.imeCategory,
172                 )
173                 .inOrder()
174         }
175 
176     @Test
categories_stateActive_multiTaskingShortcutsWithDuplicateLabels_emitsGroupedShortcutsnull177     fun categories_stateActive_multiTaskingShortcutsWithDuplicateLabels_emitsGroupedShortcuts() =
178         testScope.runTest {
179             multitaskingShortcutsSource.setGroups(TestShortcuts.groupsWithDuplicateShortcutLabels)
180 
181             val categories by collectLastValue(interactor.shortcutCategories)
182 
183             helper.showFromActivity()
184 
185             assertThat(categories)
186                 .containsExactly(
187                     TestShortcuts.systemCategory,
188                     ShortcutCategory(
189                         type = MultiTasking,
190                         subCategories =
191                             TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels,
192                     ),
193                     TestShortcuts.imeCategory,
194                 )
195                 .inOrder()
196         }
197 
198     @Test
categories_stateActive_imeShortcutsWithUnsupportedModifiers_discardUnsupportednull199     fun categories_stateActive_imeShortcutsWithUnsupportedModifiers_discardUnsupported() =
200         testScope.runTest {
201             helper.setImeShortcuts(TestShortcuts.groupsWithUnsupportedModifier)
202             val categories by collectLastValue(interactor.shortcutCategories)
203 
204             helper.showFromActivity()
205 
206             assertThat(categories)
207                 .containsExactly(
208                     TestShortcuts.systemCategory,
209                     TestShortcuts.multitaskingCategory,
210                     ShortcutCategory(
211                         type = InputMethodEditor,
212                         subCategories =
213                             TestShortcuts.imeSubCategoriesWithUnsupportedModifiersRemoved,
214                     ),
215                 )
216                 .inOrder()
217         }
218 
219     @Test
categories_stateActive_systemShortcutsWithUnsupportedModifiers_discardUnsupportednull220     fun categories_stateActive_systemShortcutsWithUnsupportedModifiers_discardUnsupported() =
221         testScope.runTest {
222             systemShortcutsSource.setGroups(TestShortcuts.groupsWithUnsupportedModifier)
223             val categories by collectLastValue(interactor.shortcutCategories)
224 
225             helper.showFromActivity()
226 
227             assertThat(categories)
228                 .containsExactly(
229                     ShortcutCategory(
230                         type = System,
231                         subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved,
232                     ),
233                     TestShortcuts.multitaskingCategory,
234                     TestShortcuts.imeCategory,
235                 )
236                 .inOrder()
237         }
238 
239     @Test
categories_stateActive_multitaskingShortcutsWithUnsupportedModifiers_discardUnsupportednull240     fun categories_stateActive_multitaskingShortcutsWithUnsupportedModifiers_discardUnsupported() =
241         testScope.runTest {
242             multitaskingShortcutsSource.setGroups(TestShortcuts.groupsWithUnsupportedModifier)
243             val categories by collectLastValue(interactor.shortcutCategories)
244 
245             helper.showFromActivity()
246 
247             assertThat(categories)
248                 .containsExactly(
249                     TestShortcuts.systemCategory,
250                     ShortcutCategory(
251                         type = MultiTasking,
252                         subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved,
253                     ),
254                     TestShortcuts.imeCategory,
255                 )
256                 .inOrder()
257         }
258 
259     @Test
categories_stateActive_systemShortcutsWithOnlyUnsupportedModifiers_discardsCategorynull260     fun categories_stateActive_systemShortcutsWithOnlyUnsupportedModifiers_discardsCategory() =
261         testScope.runTest {
262             systemShortcutsSource.setGroups(TestShortcuts.groupsWithOnlyUnsupportedModifiers)
263             val categories by collectLastValue(interactor.shortcutCategories)
264 
265             helper.showFromActivity()
266 
267             assertThat(categories)
268                 .containsExactly(TestShortcuts.multitaskingCategory, TestShortcuts.imeCategory)
269                 .inOrder()
270         }
271 
272     @Test
categories_stateActive_multitaskingShortcutsWitOnlyUnsupportedModifiers_discardsCategorynull273     fun categories_stateActive_multitaskingShortcutsWitOnlyUnsupportedModifiers_discardsCategory() =
274         testScope.runTest {
275             multitaskingShortcutsSource.setGroups(TestShortcuts.groupsWithOnlyUnsupportedModifiers)
276             val categories by collectLastValue(interactor.shortcutCategories)
277 
278             helper.showFromActivity()
279 
280             assertThat(categories)
281                 .containsExactly(TestShortcuts.systemCategory, TestShortcuts.imeCategory)
282                 .inOrder()
283         }
284 
285     @Test
286     @DisableFlags(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER)
categories_excludesCustomShortcutsWhenFlagIsOffnull287     fun categories_excludesCustomShortcutsWhenFlagIsOff() {
288         testScope.runTest {
289             setCustomInputGestures(allCustomizableInputGesturesWithSimpleShortcutCombinations)
290             helper.showFromActivity()
291             val categories by collectLastValue(interactor.shortcutCategories)
292             assertThat(categories)
293                 .containsExactly(
294                     TestShortcuts.systemCategory,
295                     TestShortcuts.multitaskingCategory,
296                     TestShortcuts.imeCategory,
297                 )
298         }
299     }
300 
301     @Test
302     @EnableFlags(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER)
categories_includesCustomShortcutsWhenFlagIsOnnull303     fun categories_includesCustomShortcutsWhenFlagIsOn() {
304         testScope.runTest {
305             setCustomInputGestures(listOf(customInputGestureTypeHome))
306             helper.showFromActivity()
307             val categories by collectLastValue(interactor.shortcutCategories)
308             assertThat(categories)
309                 .containsExactly(
310                     systemCategoryWithCustomHomeShortcut,
311                     TestShortcuts.multitaskingCategory,
312                     TestShortcuts.imeCategory,
313                 )
314         }
315     }
316 
317     @Test
318     @EnableFlags(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER)
categories_correctlyMergesDefaultAndCustomShortcutsOfSameTypenull319     fun categories_correctlyMergesDefaultAndCustomShortcutsOfSameType() {
320         testScope.runTest {
321             setCustomInputGestures(listOf(customInputGestureTypeHome))
322             systemShortcutsSource.setGroups(groupWithGoHomeShortcutInfo)
323             helper.showFromActivity()
324 
325             val categories by collectLastValue(interactor.shortcutCategories)
326 
327             assertThat(categories)
328                 .containsExactly(
329                     systemCategoryWithMergedGoHomeShortcut,
330                     TestShortcuts.multitaskingCategory,
331                     TestShortcuts.imeCategory,
332                 )
333         }
334     }
335 
336     @Test
categories_addsSameContentDescriptionForShortcutsOfSameTypenull337     fun categories_addsSameContentDescriptionForShortcutsOfSameType() {
338         testScope.runTest {
339             setCustomInputGestures(listOf(customInputGestureTypeHome))
340             systemShortcutsSource.setGroups(groupWithGoHomeShortcutInfo)
341             helper.showFromActivity()
342 
343             val categories by collectLastValue(interactor.shortcutCategories)
344             val contentDescriptions =
345                 categories?.flatMap {
346                     it.subCategories.flatMap { it.shortcuts.map { it.contentDescription } }
347                 }
348 
349             assertThat(contentDescriptions)
350                 .containsExactly(
351                     "Go to home screen, Press key Ctrl plus Alt plus B, or Ctrl plus Alt plus A",
352                     "Standard shortcut 3, Press key Ctrl plus J",
353                     "Standard shortcut 2, Press key Alt plus Shift plus Z",
354                     "Standard shortcut 1, Press key Meta plus N",
355                     "Standard shortcut 1, Press key Meta plus N",
356                     "Standard shortcut 2, Press key Alt plus Shift plus Z",
357                     "Standard shortcut 3, Press key Ctrl plus J",
358                     "Switch to next language, Press key Ctrl plus Space",
359                     "Switch to previous language, Press key Ctrl plus Shift plus Space",
360                     "Standard shortcut 1, Press key Meta plus N",
361                     "Standard shortcut 2, Press key Alt plus Shift plus Z",
362                     "Standard shortcut 3, Press key Ctrl plus J",
363                     "Standard shortcut 3, Press key Ctrl plus J",
364                     "Standard shortcut 2, Press key Alt plus Shift plus Z",
365                     "Standard shortcut 1, Press key Meta plus N",
366                     "Standard shortcut 2, Press key Alt plus Shift plus Z",
367                     "Standard shortcut 1, Press key Meta plus N",
368                 )
369         }
370     }
371 
372     @Test
categories_showShortcutsInAscendingOrderOfKeySizenull373     fun categories_showShortcutsInAscendingOrderOfKeySize() =
374         testScope.runTest {
375             systemShortcutsSource.setGroups(TestShortcuts.groupWithDifferentSizeOfShortcutKeys)
376             val categories by collectLastValue(interactor.shortcutCategories)
377 
378             helper.showFromActivity()
379 
380             val systemCategoryShortcuts = categories?.get(0)?.subCategories?.get(0)?.shortcuts
381             val shortcutKeyCount =
382                 systemCategoryShortcuts?.flatMap { it.commands }?.map { it.keys.size }
383             assertThat(shortcutKeyCount).containsExactly(1, 2, 3).inOrder()
384         }
385 
setCustomInputGesturesnull386     private fun setCustomInputGestures(customInputGestures: List<InputGestureData>) {
387         whenever(fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull()))
388             .thenReturn(customInputGestures)
389     }
390 }
391