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