Shortcut Helper - Retrieve app categories shortcuts from WindowManager
Before, we were hard coding the shortcuts. Now there is a new API from
WindowManager to retrieve these shortcuts, which are defined in a
config file.
Fixes: 341045436
Test: Manual
Test: AppCategoriesShortcutsSourceTest.kt
Flag: com.android.systemui.keyboard_shortcut_helper_rewrite
Change-Id: I2d3234129c37b777e4e18e213f7f9af0ee8d781a
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
index 49817b2..495e8f3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
@@ -233,7 +233,7 @@
Log.wtf(TAG, "Unsupported modifiers remaining: $remainingModifiers")
return null
}
- if (info.keycode != 0) {
+ if (info.keycode != 0 || info.baseCharacter > Char.MIN_VALUE) {
keys += toShortcutKey(keyCharacterMap, info.keycode, info.baseCharacter) ?: return null
}
if (keys.isEmpty()) {
@@ -253,7 +253,7 @@
return ShortcutKey.Icon(iconResId)
}
if (baseCharacter > Char.MIN_VALUE) {
- return ShortcutKey.Text(baseCharacter.toString())
+ return ShortcutKey.Text(baseCharacter.uppercase())
}
val specialKeyLabel = ShortcutHelperKeys.specialKeyLabels[keyCode]
if (specialKeyLabel != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSource.kt
index d7cb7db..d6c6d5b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSource.kt
@@ -16,92 +16,29 @@
package com.android.systemui.keyboard.shortcut.data.source
-import android.content.Intent
-import android.content.res.Resources
-import android.view.KeyEvent
import android.view.KeyboardShortcutGroup
-import android.view.KeyboardShortcutInfo
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.res.R
-import com.android.systemui.util.icons.AppCategoryIconProvider
+import android.view.WindowManager
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.shortcut.extensions.copy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
class AppCategoriesShortcutsSource
@Inject
constructor(
- private val appCategoryIconProvider: AppCategoryIconProvider,
- @Main private val resources: Resources,
+ private val windowManager: WindowManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) : KeyboardShortcutGroupsSource {
- override suspend fun shortcutGroups(deviceId: Int) =
- listOf(
- KeyboardShortcutGroup(
- /* label = */ resources.getString(R.string.keyboard_shortcut_group_applications),
- /* items = */ shortcuts()
- )
- )
-
- private suspend fun shortcuts(): List<KeyboardShortcutInfo> =
- listOfNotNull(
- assistantAppShortcutInfo(),
- appCategoryShortcutInfo(
- Intent.CATEGORY_APP_BROWSER,
- R.string.keyboard_shortcut_group_applications_browser,
- KeyEvent.KEYCODE_B
- ),
- appCategoryShortcutInfo(
- Intent.CATEGORY_APP_CONTACTS,
- R.string.keyboard_shortcut_group_applications_contacts,
- KeyEvent.KEYCODE_C
- ),
- appCategoryShortcutInfo(
- Intent.CATEGORY_APP_EMAIL,
- R.string.keyboard_shortcut_group_applications_email,
- KeyEvent.KEYCODE_E
- ),
- appCategoryShortcutInfo(
- Intent.CATEGORY_APP_CALENDAR,
- R.string.keyboard_shortcut_group_applications_calendar,
- KeyEvent.KEYCODE_K
- ),
- appCategoryShortcutInfo(
- Intent.CATEGORY_APP_MAPS,
- R.string.keyboard_shortcut_group_applications_maps,
- KeyEvent.KEYCODE_M
- ),
- appCategoryShortcutInfo(
- Intent.CATEGORY_APP_MUSIC,
- R.string.keyboard_shortcut_group_applications_music,
- KeyEvent.KEYCODE_P
- ),
- appCategoryShortcutInfo(
- Intent.CATEGORY_APP_MESSAGING,
- R.string.keyboard_shortcut_group_applications_sms,
- KeyEvent.KEYCODE_S
- ),
- appCategoryShortcutInfo(
- Intent.CATEGORY_APP_CALCULATOR,
- R.string.keyboard_shortcut_group_applications_calculator,
- KeyEvent.KEYCODE_U
- ),
- )
- .sortedBy { it.label!!.toString().lowercase() }
-
- private suspend fun assistantAppShortcutInfo(): KeyboardShortcutInfo? {
- val assistantIcon = appCategoryIconProvider.assistantAppIcon() ?: return null
- return KeyboardShortcutInfo(
- /* label = */ resources.getString(R.string.keyboard_shortcut_group_applications_assist),
- /* icon = */ assistantIcon,
- /* keycode = */ KeyEvent.KEYCODE_A,
- /* modifiers = */ KeyEvent.META_META_ON,
- )
- }
-
- private suspend fun appCategoryShortcutInfo(category: String, labelResId: Int, keycode: Int) =
- KeyboardShortcutInfo(
- /* label = */ resources.getString(labelResId),
- /* icon = */ appCategoryIconProvider.categoryAppIcon(category),
- /* keycode = */ keycode,
- /* modifiers = */ KeyEvent.META_META_ON,
- )
+ override suspend fun shortcutGroups(deviceId: Int): List<KeyboardShortcutGroup> =
+ withContext(backgroundDispatcher) {
+ val group = windowManager.getApplicationLaunchKeyboardShortcuts(deviceId)
+ return@withContext if (group == null) {
+ emptyList()
+ } else {
+ val sortedShortcutItems = group.items.sortedBy { it.label!!.toString().lowercase() }
+ listOf(group.copy(items = sortedShortcutItems))
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/extensions/KeyboardShortcutGroupExtensions.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/extensions/KeyboardShortcutGroupExtensions.kt
new file mode 100644
index 0000000..3a120bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/extensions/KeyboardShortcutGroupExtensions.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.shortcut.extensions
+
+import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
+
+fun KeyboardShortcutGroup.copy(
+ label: CharSequence = getLabel(),
+ items: List<KeyboardShortcutInfo> = getItems(),
+ isSystemGroup: Boolean = isSystemGroup(),
+ packageName: CharSequence? = getPackageName(),
+) = KeyboardShortcutGroup(label, items, isSystemGroup).also { it.packageName = packageName }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSourceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSourceTest.kt
index e49e2b49..5d59208 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSourceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSourceTest.kt
@@ -16,21 +16,17 @@
package com.android.systemui.keyboard.shortcut.data.source
-import android.content.Intent.CATEGORY_APP_BROWSER
-import android.content.Intent.CATEGORY_APP_CALCULATOR
-import android.content.Intent.CATEGORY_APP_CALENDAR
-import android.content.Intent.CATEGORY_APP_CONTACTS
-import android.content.Intent.CATEGORY_APP_EMAIL
-import android.content.Intent.CATEGORY_APP_MAPS
-import android.content.Intent.CATEGORY_APP_MESSAGING
-import android.content.Intent.CATEGORY_APP_MUSIC
+import android.view.KeyEvent
+import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
+import android.view.mockWindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
-import com.android.systemui.util.icons.fakeAppCategoryIconProvider
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -43,185 +39,43 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val defaultAppIconsProvider = kosmos.fakeAppCategoryIconProvider
- private val source = kosmos.shortcutHelperAppCategoriesShortcutsSource
+ private val mockWindowManager = kosmos.mockWindowManager
+ private val source =
+ AppCategoriesShortcutsSource(kosmos.mockWindowManager, kosmos.testDispatcher)
+
+ private var appCategoriesGroup: KeyboardShortcutGroup? = null
@Before
fun setUp() {
- categoryApps.forEach { categoryAppIcon ->
- defaultAppIconsProvider.installCategoryApp(
- categoryAppIcon.category,
- categoryAppIcon.packageName,
- categoryAppIcon.iconResId
- )
- }
+ whenever(mockWindowManager.getApplicationLaunchKeyboardShortcuts(TEST_DEVICE_ID))
+ .thenAnswer { appCategoriesGroup }
}
@Test
- fun shortcutGroups_returnsSingleGroup() =
- testScope.runTest { assertThat(source.shortcutGroups(TEST_DEVICE_ID)).hasSize(1) }
+ fun shortcutGroups_nullResult_returnsEmptyList() =
+ testScope.runTest {
+ appCategoriesGroup = null
+
+ assertThat(source.shortcutGroups(TEST_DEVICE_ID)).isEmpty()
+ }
@Test
- fun shortcutGroups_hasAssistantIcon() =
+ fun shortcutGroups_returnsSortedList() =
testScope.runTest {
- defaultAppIconsProvider.installAssistantApp(ASSISTANT_PACKAGE, ASSISTANT_ICON_RES_ID)
+ val testItems =
+ listOf(
+ KeyboardShortcutInfo("Info 2", KeyEvent.KEYCODE_E, KeyEvent.META_META_ON),
+ KeyboardShortcutInfo("Info 1", KeyEvent.KEYCODE_E, KeyEvent.META_META_ON),
+ KeyboardShortcutInfo("Info 3", KeyEvent.KEYCODE_E, KeyEvent.META_META_ON),
+ )
+ appCategoriesGroup = KeyboardShortcutGroup("Test Group", testItems)
val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
-
- val shortcutInfo = shortcuts.first { it.label == "Assistant" }
-
- assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(ASSISTANT_PACKAGE)
- assertThat(shortcutInfo.icon!!.resId).isEqualTo(ASSISTANT_ICON_RES_ID)
+ val shortcutLabels = shortcuts.map { it.label.toString() }
+ assertThat(shortcutLabels).containsExactly("Info 1", "Info 2", "Info 3").inOrder()
}
- @Test
- fun shortcutGroups_hasBrowserIcon() =
- testScope.runTest {
- val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
-
- val shortcutInfo = shortcuts.first { it.label == "Browser" }
-
- assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(BROWSER_PACKAGE)
- assertThat(shortcutInfo.icon!!.resId).isEqualTo(BROWSER_ICON_RES_ID)
- }
-
- @Test
- fun shortcutGroups_hasContactsIcon() =
- testScope.runTest {
- val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
-
- val shortcutInfo = shortcuts.first { it.label == "Contacts" }
-
- assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(CONTACTS_PACKAGE)
- assertThat(shortcutInfo.icon!!.resId).isEqualTo(CONTACTS_ICON_RES_ID)
- }
-
- @Test
- fun shortcutGroups_hasEmailIcon() =
- testScope.runTest {
- val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
-
- val shortcutInfo = shortcuts.first { it.label == "Email" }
-
- assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(EMAIL_PACKAGE)
- assertThat(shortcutInfo.icon!!.resId).isEqualTo(EMAIL_ICON_RES_ID)
- }
-
- @Test
- fun shortcutGroups_hasCalendarIcon() =
- testScope.runTest {
- val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
-
- val shortcutInfo = shortcuts.first { it.label == "Calendar" }
-
- assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(CALENDAR_PACKAGE)
- assertThat(shortcutInfo.icon!!.resId).isEqualTo(CALENDAR_ICON_RES_ID)
- }
-
- @Test
- fun shortcutGroups_hasMapsIcon() =
- testScope.runTest {
- val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
-
- val shortcutInfo = shortcuts.first { it.label == "Maps" }
-
- assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(MAPS_PACKAGE)
- assertThat(shortcutInfo.icon!!.resId).isEqualTo(MAPS_ICON_RES_ID)
- }
-
- @Test
- fun shortcutGroups_hasMessagingIcon() =
- testScope.runTest {
- val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
-
- val shortcutInfo = shortcuts.first { it.label == "SMS" }
-
- assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(MESSAGING_PACKAGE)
- assertThat(shortcutInfo.icon!!.resId).isEqualTo(MESSAGING_ICON_RES_ID)
- }
-
- @Test
- fun shortcutGroups_hasMusicIcon() =
- testScope.runTest {
- val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
-
- val shortcutInfo = shortcuts.first { it.label == "Music" }
-
- assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(MUSIC_PACKAGE)
- assertThat(shortcutInfo.icon!!.resId).isEqualTo(MUSIC_ICON_RES_ID)
- }
-
- @Test
- fun shortcutGroups_hasCalculatorIcon() =
- testScope.runTest {
- val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
-
- val shortcutInfo = shortcuts.first { it.label == "Calculator" }
-
- assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(CALCULATOR_PACKAGE)
- assertThat(shortcutInfo.icon!!.resId).isEqualTo(CALCULATOR_ICON_RES_ID)
- }
-
- @Test
- fun shortcutGroups_shortcutsSortedByLabelIgnoringCase() =
- testScope.runTest {
- val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
-
- val shortcutLabels = shortcuts.map { it.label!!.toString() }
- assertThat(shortcutLabels).isEqualTo(shortcutLabels.sortedBy { it.lowercase() })
- }
-
- @Test
- fun shortcutGroups_noAssistantApp_excludesAssistantFromShortcuts() =
- testScope.runTest {
- val shortcutLabels =
- source.shortcutGroups(TEST_DEVICE_ID).first().items.map { it.label!!.toString() }
-
- assertThat(shortcutLabels).doesNotContain("Assistant")
- }
-
- private companion object {
- private const val ASSISTANT_PACKAGE = "the.assistant.app"
- private const val ASSISTANT_ICON_RES_ID = 123
-
- private const val BROWSER_PACKAGE = "com.test.browser"
- private const val BROWSER_ICON_RES_ID = 1
-
- private const val CONTACTS_PACKAGE = "app.test.contacts"
- private const val CONTACTS_ICON_RES_ID = 234
-
- private const val EMAIL_PACKAGE = "email.app.test"
- private const val EMAIL_ICON_RES_ID = 351
-
- private const val CALENDAR_PACKAGE = "app.test.calendar"
- private const val CALENDAR_ICON_RES_ID = 411
-
- private const val MAPS_PACKAGE = "maps.app.package"
- private const val MAPS_ICON_RES_ID = 999
-
- private const val MUSIC_PACKAGE = "com.android.music"
- private const val MUSIC_ICON_RES_ID = 101
-
- private const val MESSAGING_PACKAGE = "my.sms.app"
- private const val MESSAGING_ICON_RES_ID = 9191
-
- private const val CALCULATOR_PACKAGE = "that.calculator.app"
- private const val CALCULATOR_ICON_RES_ID = 314
-
- private val categoryApps =
- listOf(
- CategoryApp(CATEGORY_APP_BROWSER, BROWSER_PACKAGE, BROWSER_ICON_RES_ID),
- CategoryApp(CATEGORY_APP_CONTACTS, CONTACTS_PACKAGE, CONTACTS_ICON_RES_ID),
- CategoryApp(CATEGORY_APP_EMAIL, EMAIL_PACKAGE, EMAIL_ICON_RES_ID),
- CategoryApp(CATEGORY_APP_CALENDAR, CALENDAR_PACKAGE, CALENDAR_ICON_RES_ID),
- CategoryApp(CATEGORY_APP_MAPS, MAPS_PACKAGE, MAPS_ICON_RES_ID),
- CategoryApp(CATEGORY_APP_MUSIC, MUSIC_PACKAGE, MUSIC_ICON_RES_ID),
- CategoryApp(CATEGORY_APP_MESSAGING, MESSAGING_PACKAGE, MESSAGING_ICON_RES_ID),
- CategoryApp(CATEGORY_APP_CALCULATOR, CALCULATOR_PACKAGE, CALCULATOR_ICON_RES_ID),
- )
-
+ companion object {
private const val TEST_DEVICE_ID = 123
}
-
- private class CategoryApp(val category: String, val packageName: String, val iconResId: Int)
}
diff --git a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
index 2a05598..d5451ee 100644
--- a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
@@ -19,4 +19,6 @@
import com.android.systemui.kosmos.Kosmos
import org.mockito.Mockito.mock
-val Kosmos.windowManager by Kosmos.Fixture<WindowManager> { mock(WindowManager::class.java) }
+val Kosmos.mockWindowManager: WindowManager by Kosmos.Fixture { mock(WindowManager::class.java) }
+
+var Kosmos.windowManager: WindowManager by Kosmos.Fixture { mockWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index 001b55b..c423b62 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -41,13 +41,12 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
import com.android.systemui.settings.displayTracker
-import com.android.systemui.util.icons.fakeAppCategoryIconProvider
var Kosmos.shortcutHelperAppCategoriesShortcutsSource: KeyboardShortcutGroupsSource by
Kosmos.Fixture {
AppCategoriesShortcutsSource(
- fakeAppCategoryIconProvider,
- mainResources,
+ windowManager,
+ testDispatcher,
)
}