Merge "dynamic shortcut label/icon retrieval for app launch shortcuts" into main
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5519b51..11cb070 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -385,6 +385,9 @@
is ready -->
<uses-permission android:name="android.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW" />
+ <!-- To be able to decipher default applications for certain roles in shortcut helper -->
+ <uses-permission android:name="android.permission.MANAGE_DEFAULT_APPLICATIONS" />
+
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt
new file mode 100644
index 0000000..f78c692
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.data.repository
+
+import android.app.role.RoleManager
+import android.app.role.roleManager
+import android.content.Context
+import android.content.Intent
+import android.content.mockedContext
+import android.content.packageManager
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.hardware.input.AppLaunchData
+import android.hardware.input.AppLaunchData.RoleData
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputGestureData.createKeyTrigger
+import android.view.KeyEvent.KEYCODE_A
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_CTRL_ON
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.app.ResolverActivity
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyboard.shortcut.data.model.InternalGroupsSource
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo
+import com.android.systemui.keyboard.shortcut.inputGestureDataAdapter
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.res.R
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.userTracker
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InputGestureDataAdapterTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().also { kosmos ->
+ kosmos.userTracker = FakeUserTracker(onCreateCurrentUserContext = { kosmos.mockedContext })
+ }
+ private val adapter = kosmos.inputGestureDataAdapter
+ private val roleManager = kosmos.roleManager
+ private val packageManager: PackageManager = kosmos.packageManager
+ private val mockUserContext: Context = kosmos.mockedContext
+ private val intent: Intent = mock()
+ private val fakeResolverActivityInfo =
+ ActivityInfo().apply { name = ResolverActivity::class.qualifiedName }
+ private val fakeActivityInfo: ActivityInfo =
+ ActivityInfo().apply {
+ name = FAKE_ACTIVITY_NAME
+ icon = 0x1
+ nonLocalizedLabel = TEST_SHORTCUT_LABEL
+ }
+ private val mockSelectorIntent: Intent = mock()
+
+ @Before
+ fun setup() {
+ whenever(mockUserContext.packageManager).thenReturn(packageManager)
+ whenever(mockUserContext.getSystemService(RoleManager::class.java)).thenReturn(roleManager)
+ whenever(roleManager.isRoleAvailable(TEST_ROLE)).thenReturn(true)
+ whenever(roleManager.getDefaultApplication(TEST_ROLE)).thenReturn(TEST_ROLE_PACKAGE)
+ whenever(packageManager.getActivityInfo(any(), anyInt())).thenReturn(mock())
+ whenever(packageManager.getLaunchIntentForPackage(TEST_ROLE_PACKAGE)).thenReturn(intent)
+ whenever(intent.selector).thenReturn(mockSelectorIntent)
+ whenever(mockSelectorIntent.categories).thenReturn(setOf(TEST_ACTIVITY_CATEGORY))
+ }
+
+ @Test
+ fun shortcutLabel_whenDefaultAppForCategoryIsNotSet_loadsLabelFromFirstAppMatchingIntent() =
+ kosmos.runTest {
+ setApiToRetrieveResolverActivity()
+
+ val inputGestureData = buildInputGestureDataForAppLaunchShortcut()
+ val internalGroups = adapter.toInternalGroupSources(listOf(inputGestureData))
+ val label =
+ internalGroups.firstOrNull()?.groups?.firstOrNull()?.items?.firstOrNull()?.label
+
+ assertThat(label).isEqualTo(expectedShortcutLabelForFirstAppMatchingIntent)
+ }
+
+ @Test
+ fun shortcutLabel_whenDefaultAppForCategoryIsSet_loadsLabelOfDefaultApp() {
+ kosmos.runTest {
+ setApiToRetrieveSpecificActivity()
+
+ val inputGestureData = buildInputGestureDataForAppLaunchShortcut()
+ val internalGroups = adapter.toInternalGroupSources(listOf(inputGestureData))
+ val label =
+ internalGroups.firstOrNull()?.groups?.firstOrNull()?.items?.firstOrNull()?.label
+
+ assertThat(label).isEqualTo(TEST_SHORTCUT_LABEL)
+ }
+ }
+
+ @Test
+ fun shortcutIcon_whenDefaultAppForCategoryIsSet_loadsIconOfDefaultApp() {
+ kosmos.runTest {
+ setApiToRetrieveSpecificActivity()
+
+ val inputGestureData = buildInputGestureDataForAppLaunchShortcut()
+ val internalGroups = adapter.toInternalGroupSources(listOf(inputGestureData))
+ val icon =
+ internalGroups.firstOrNull()?.groups?.firstOrNull()?.items?.firstOrNull()?.icon
+
+ assertThat(icon).isNotNull()
+ }
+ }
+
+ @Test
+ fun internalGroupSource_isCorrectlyConvertedWithSimpleInputGestureData() =
+ kosmos.runTest {
+ setApiToRetrieveResolverActivity()
+
+ val inputGestureData = buildInputGestureDataForAppLaunchShortcut()
+ val internalGroups = adapter.toInternalGroupSources(listOf(inputGestureData))
+
+ assertThat(internalGroups).containsExactly(
+ InternalGroupsSource(
+ type = ShortcutCategoryType.AppCategories,
+ groups = listOf(
+ InternalKeyboardShortcutGroup(
+ label = APPLICATION_SHORTCUT_GROUP_LABEL,
+ items = listOf(
+ InternalKeyboardShortcutInfo(
+ label = expectedShortcutLabelForFirstAppMatchingIntent,
+ keycode = KEYCODE_A,
+ modifiers = META_CTRL_ON or META_ALT_ON,
+ isCustomShortcut = true
+ )
+ )
+ )
+ )
+ )
+ )
+ }
+
+ private fun setApiToRetrieveResolverActivity() {
+ whenever(intent.resolveActivityInfo(eq(packageManager), anyInt()))
+ .thenReturn(fakeResolverActivityInfo)
+ }
+
+ private fun setApiToRetrieveSpecificActivity() {
+ whenever(intent.resolveActivityInfo(eq(packageManager), anyInt()))
+ .thenReturn(fakeActivityInfo)
+ }
+
+
+ private fun buildInputGestureDataForAppLaunchShortcut(
+ keyCode: Int = KEYCODE_A,
+ modifiers: Int = META_CTRL_ON or META_ALT_ON,
+ appLaunchData: AppLaunchData = RoleData(TEST_ROLE)
+ ): InputGestureData {
+ return InputGestureData.Builder()
+ .setTrigger(createKeyTrigger(keyCode, modifiers))
+ .setAppLaunchData(appLaunchData)
+ .build()
+ }
+
+ private val expectedShortcutLabelForFirstAppMatchingIntent =
+ context.getString(R.string.keyboard_shortcut_group_applications_browser)
+
+ private companion object {
+ private const val TEST_ROLE = "Test Browser Role"
+ private const val TEST_ROLE_PACKAGE = "test.browser.package"
+ private const val APPLICATION_SHORTCUT_GROUP_LABEL = "Applications"
+ private const val FAKE_ACTIVITY_NAME = "Fake activity"
+ private const val TEST_SHORTCUT_LABEL = "Test shortcut label"
+ private const val TEST_ACTIVITY_CATEGORY = Intent.CATEGORY_APP_BROWSER
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index 75190e9..92c76ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -567,13 +567,6 @@
),
simpleShortcutCategory(System, "System controls", "Show shortcuts"),
simpleShortcutCategory(System, "System controls", "View recent apps"),
- simpleShortcutCategory(AppCategories, "Applications", "Calculator"),
- simpleShortcutCategory(AppCategories, "Applications", "Calendar"),
- simpleShortcutCategory(AppCategories, "Applications", "Browser"),
- simpleShortcutCategory(AppCategories, "Applications", "Contacts"),
- simpleShortcutCategory(AppCategories, "Applications", "Email"),
- simpleShortcutCategory(AppCategories, "Applications", "Maps"),
- simpleShortcutCategory(AppCategories, "Applications", "SMS"),
)
val customInputGestureTypeHome = simpleInputGestureData(keyGestureType = KEY_GESTURE_TYPE_HOME)
@@ -615,27 +608,6 @@
),
simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS),
simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING
- ),
- simpleInputGestureData(
keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER
),
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt
index 8c393e2..3020e5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt
@@ -21,6 +21,7 @@
import android.view.KeyboardShortcutGroup
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesUtils
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
/**
* Internal Keyboard Shortcut models to use with [ShortcutCategoriesUtils.fetchShortcutCategory]
@@ -55,3 +56,8 @@
val icon: Icon? = null,
val isCustomShortcut: Boolean = false,
)
+
+data class InternalGroupsSource(
+ val groups: List<InternalKeyboardShortcutGroup>,
+ val type: ShortcutCategoryType,
+)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
index 4af3786..8afec04 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
@@ -16,10 +16,8 @@
package com.android.systemui.keyboard.shortcut.data.repository
-import android.content.Context
import android.hardware.input.InputGestureData
import android.hardware.input.InputGestureData.Builder
-import android.hardware.input.InputGestureData.KeyTrigger
import android.hardware.input.InputGestureData.createKeyTrigger
import android.hardware.input.InputManager
import android.hardware.input.KeyGestureEvent.KeyGestureType
@@ -30,11 +28,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult
-import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup
-import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo
import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
@@ -57,8 +52,7 @@
@Background private val backgroundScope: CoroutineScope,
@Background private val bgCoroutineContext: CoroutineContext,
private val shortcutCategoriesUtils: ShortcutCategoriesUtils,
- private val context: Context,
- private val inputGestureMaps: InputGestureMaps,
+ private val inputGestureDataAdapter: InputGestureDataAdapter,
private val customInputGesturesRepository: CustomInputGesturesRepository,
private val inputManager: InputManager
) : ShortcutCategoriesRepository {
@@ -116,7 +110,7 @@
if (inputDevice == null) {
emptyList()
} else {
- val sources = toInternalGroupSources(inputGestures)
+ val sources = inputGestureDataAdapter.toInternalGroupSources(inputGestures)
val supportedKeyCodes =
shortcutCategoriesUtils.fetchSupportedKeyCodes(
inputDevice.id,
@@ -216,7 +210,8 @@
return null
}
- return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label]
+ return inputGestureDataAdapter
+ .getKeyGestureTypeFromShortcutLabel(shortcutBeingCustomized.label)
}
@KeyGestureType
@@ -232,7 +227,8 @@
return null
}
- return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label]
+ return inputGestureDataAdapter
+ .getKeyGestureTypeFromShortcutLabel(shortcutBeingCustomized.label)
}
private fun Builder.addTriggerFromSelectedKeyCombination(): Builder {
@@ -261,70 +257,6 @@
return _shortcutBeingCustomized.value
}
- private fun toInternalGroupSources(
- inputGestures: List<InputGestureData>
- ): List<InternalGroupsSource> {
- val ungroupedInternalGroupSources =
- inputGestures.mapNotNull { gestureData ->
- val keyTrigger = gestureData.trigger as KeyTrigger
- val keyGestureType = gestureData.action.keyGestureType()
- fetchGroupLabelByGestureType(keyGestureType)?.let { groupLabel ->
- toInternalKeyboardShortcutInfo(keyGestureType, keyTrigger)?.let {
- internalKeyboardShortcutInfo ->
- val group =
- InternalKeyboardShortcutGroup(
- label = groupLabel,
- items = listOf(internalKeyboardShortcutInfo),
- )
-
- fetchShortcutCategoryTypeByGestureType(keyGestureType)?.let {
- InternalGroupsSource(groups = listOf(group), type = it)
- }
- }
- }
- }
-
- return ungroupedInternalGroupSources
- }
-
- private fun toInternalKeyboardShortcutInfo(
- keyGestureType: Int,
- keyTrigger: KeyTrigger,
- ): InternalKeyboardShortcutInfo? {
- fetchShortcutInfoLabelByGestureType(keyGestureType)?.let {
- return InternalKeyboardShortcutInfo(
- label = it,
- keycode = keyTrigger.keycode,
- modifiers = keyTrigger.modifierState,
- isCustomShortcut = true,
- )
- }
- return null
- }
-
- private fun fetchGroupLabelByGestureType(@KeyGestureType keyGestureType: Int): String? {
- inputGestureMaps.gestureToInternalKeyboardShortcutGroupLabelResIdMap[keyGestureType]?.let {
- return context.getString(it)
- } ?: return null
- }
-
- private fun fetchShortcutInfoLabelByGestureType(@KeyGestureType keyGestureType: Int): String? {
- inputGestureMaps.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let {
- return context.getString(it)
- } ?: return null
- }
-
- private fun fetchShortcutCategoryTypeByGestureType(
- @KeyGestureType keyGestureType: Int
- ): ShortcutCategoryType? {
- return inputGestureMaps.gestureToShortcutCategoryTypeMap[keyGestureType]
- }
-
- private data class InternalGroupsSource(
- val groups: List<InternalKeyboardShortcutGroup>,
- val type: ShortcutCategoryType,
- )
-
private companion object {
private const val TAG = "CustomShortcutCategoriesRepository"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt
new file mode 100644
index 0000000..df7101e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt
@@ -0,0 +1,267 @@
+/*
+ * 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.data.repository
+
+import android.annotation.SuppressLint
+import android.app.role.RoleManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.ACTION_MAIN
+import android.content.Intent.CATEGORY_LAUNCHER
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager.MATCH_DEFAULT_ONLY
+import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE
+import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
+import android.content.pm.PackageManager.NameNotFoundException
+import android.graphics.drawable.Icon
+import android.hardware.input.AppLaunchData
+import android.hardware.input.AppLaunchData.CategoryData
+import android.hardware.input.AppLaunchData.ComponentData
+import android.hardware.input.AppLaunchData.RoleData
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputGestureData.KeyTrigger
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION
+import android.hardware.input.KeyGestureEvent.KeyGestureType
+import android.util.Log
+import com.android.internal.app.ResolverActivity
+import com.android.systemui.keyboard.shortcut.data.model.InternalGroupsSource
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.res.R
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+
+
+class InputGestureDataAdapter
+@Inject
+constructor(
+ private val userTracker: UserTracker,
+ private val inputGestureMaps: InputGestureMaps,
+ private val context: Context
+) {
+ private val userContext: Context
+ get() = userTracker.createCurrentUserContext(userTracker.userContext)
+
+ fun toInternalGroupSources(
+ inputGestures: List<InputGestureData>
+ ): List<InternalGroupsSource> {
+ val ungroupedInternalGroupSources =
+ inputGestures.mapNotNull { gestureData ->
+ val keyTrigger = gestureData.trigger as KeyTrigger
+ val keyGestureType = gestureData.action.keyGestureType()
+ val appLaunchData: AppLaunchData? = gestureData.action.appLaunchData()
+ fetchGroupLabelByGestureType(keyGestureType)?.let { groupLabel ->
+ toInternalKeyboardShortcutInfo(
+ keyGestureType,
+ keyTrigger,
+ appLaunchData
+ )?.let { internalKeyboardShortcutInfo ->
+ val group =
+ InternalKeyboardShortcutGroup(
+ label = groupLabel,
+ items = listOf(internalKeyboardShortcutInfo),
+ )
+
+ fetchShortcutCategoryTypeByGestureType(keyGestureType)?.let {
+ InternalGroupsSource(groups = listOf(group), type = it)
+ }
+ }
+ }
+ }
+
+ return ungroupedInternalGroupSources
+ }
+
+ fun getKeyGestureTypeFromShortcutLabel(label: String): Int? {
+ return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[label]
+ }
+
+ private fun toInternalKeyboardShortcutInfo(
+ keyGestureType: Int,
+ keyTrigger: KeyTrigger,
+ appLaunchData: AppLaunchData?,
+ ): InternalKeyboardShortcutInfo? {
+ fetchShortcutLabelByGestureType(keyGestureType, appLaunchData)?.let {
+ return InternalKeyboardShortcutInfo(
+ label = it,
+ keycode = keyTrigger.keycode,
+ modifiers = keyTrigger.modifierState,
+ isCustomShortcut = true,
+ icon = appLaunchData?.let { fetchShortcutIconByAppLaunchData(appLaunchData) }
+ )
+ }
+ return null
+ }
+
+ @SuppressLint("QueryPermissionsNeeded")
+ private fun fetchShortcutIconByAppLaunchData(
+ appLaunchData: AppLaunchData
+ ): Icon? {
+ val intent = fetchIntentFromAppLaunchData(appLaunchData) ?: return null
+ val resolvedActivity = resolveSingleMatchingActivityFrom(intent)
+
+ return if (resolvedActivity == null) {
+ null
+ } else {
+ Icon.createWithResource(context, resolvedActivity.iconResource)
+ }
+ }
+
+ private fun fetchGroupLabelByGestureType(@KeyGestureType keyGestureType: Int): String? {
+ inputGestureMaps.gestureToInternalKeyboardShortcutGroupLabelResIdMap[keyGestureType]?.let {
+ return context.getString(it)
+ } ?: return null
+ }
+
+ private fun fetchShortcutLabelByGestureType(
+ @KeyGestureType keyGestureType: Int,
+ appLaunchData: AppLaunchData?
+ ): String? {
+ inputGestureMaps.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let {
+ return context.getString(it)
+ }
+
+ if (keyGestureType == KEY_GESTURE_TYPE_LAUNCH_APPLICATION) {
+ return fetchShortcutLabelByAppLaunchData(appLaunchData!!)
+ }
+
+ return null
+ }
+
+ private fun fetchShortcutLabelByAppLaunchData(appLaunchData: AppLaunchData): String? {
+ val intent = fetchIntentFromAppLaunchData(appLaunchData) ?: return null
+ val resolvedActivity = resolveSingleMatchingActivityFrom(intent)
+
+ return if (resolvedActivity == null) {
+ getIntentCategoryLabel(intent.selector?.categories?.iterator()?.next())
+ } else resolvedActivity.loadLabel(userContext.packageManager).toString()
+
+ }
+
+ @SuppressLint("QueryPermissionsNeeded")
+ private fun resolveSingleMatchingActivityFrom(intent: Intent): ActivityInfo? {
+ val packageManager = userContext.packageManager
+ val resolvedActivity = intent.resolveActivityInfo(
+ packageManager,
+ /* flags= */ MATCH_DEFAULT_ONLY
+ ) ?: return null
+
+ val matchesMultipleActivities =
+ ResolverActivity::class.qualifiedName.equals(resolvedActivity.name)
+
+ return if (matchesMultipleActivities) {
+ return null
+ } else resolvedActivity
+ }
+
+ private fun getIntentCategoryLabel(category: String?): String? {
+ val categoryLabelRes = when (category.toString()) {
+ Intent.CATEGORY_APP_BROWSER -> R.string.keyboard_shortcut_group_applications_browser
+ Intent.CATEGORY_APP_CONTACTS -> R.string.keyboard_shortcut_group_applications_contacts
+ Intent.CATEGORY_APP_EMAIL -> R.string.keyboard_shortcut_group_applications_email
+ Intent.CATEGORY_APP_CALENDAR -> R.string.keyboard_shortcut_group_applications_calendar
+ Intent.CATEGORY_APP_MAPS -> R.string.keyboard_shortcut_group_applications_maps
+ Intent.CATEGORY_APP_MUSIC -> R.string.keyboard_shortcut_group_applications_music
+ Intent.CATEGORY_APP_MESSAGING -> R.string.keyboard_shortcut_group_applications_sms
+ Intent.CATEGORY_APP_CALCULATOR -> R.string.keyboard_shortcut_group_applications_calculator
+ else -> {
+ Log.w(TAG, ("No label for app category $category"))
+ null
+ }
+ }
+
+ return if (categoryLabelRes == null){
+ return null
+ } else {
+ context.getString(categoryLabelRes)
+ }
+ }
+
+ private fun fetchIntentFromAppLaunchData(appLaunchData: AppLaunchData): Intent? {
+ return when (appLaunchData) {
+ is CategoryData -> Intent.makeMainSelectorActivity(
+ /* selectorAction= */ ACTION_MAIN,
+ /* selectorCategory= */ appLaunchData.category
+ )
+
+ is RoleData -> getRoleLaunchIntent(appLaunchData.role)
+ is ComponentData -> resolveComponentNameIntent(
+ packageName = appLaunchData.packageName,
+ className = appLaunchData.className
+ )
+
+ else -> null
+ }
+ }
+
+ private fun resolveComponentNameIntent(packageName: String, className: String): Intent? {
+ buildIntentFromComponentName(ComponentName(packageName, className))?.let { return it }
+ buildIntentFromComponentName(ComponentName(
+ userContext.packageManager.canonicalToCurrentPackageNames(arrayOf(packageName))[0],
+ className
+ ))?.let { return it }
+ return null
+ }
+
+ private fun buildIntentFromComponentName(componentName: ComponentName): Intent? {
+ try{
+ val flags =
+ MATCH_DIRECT_BOOT_UNAWARE or MATCH_DIRECT_BOOT_AWARE or MATCH_UNINSTALLED_PACKAGES
+ // attempt to retrieve activity info to see if a NameNotFoundException is thrown.
+ userContext.packageManager.getActivityInfo(componentName, flags)
+ } catch (e: NameNotFoundException) {
+ Log.w(
+ TAG,
+ "Unable to find activity info for componentName: $componentName"
+ )
+ return null
+ }
+
+ return Intent(ACTION_MAIN).apply {
+ addCategory(CATEGORY_LAUNCHER)
+ component = componentName
+ }
+ }
+
+ @SuppressLint("NonInjectedService")
+ private fun getRoleLaunchIntent(role: String): Intent? {
+ val packageManager = userContext.packageManager
+ val roleManager = userContext.getSystemService(RoleManager::class.java)!!
+ if (roleManager.isRoleAvailable(role)) {
+ roleManager.getDefaultApplication(role)?.let { rolePackage ->
+ packageManager.getLaunchIntentForPackage(rolePackage)?.let { return it }
+ ?: Log.w(TAG, "No launch intent for role $role")
+ } ?: Log.w(TAG, "No default application for role $role, user= ${userContext.user}")
+ } else {
+ Log.w(TAG, "Role $role is not available.")
+ }
+ return null
+ }
+
+ private fun fetchShortcutCategoryTypeByGestureType(
+ @KeyGestureType keyGestureType: Int
+ ): ShortcutCategoryType? {
+ return inputGestureMaps.gestureToShortcutCategoryTypeMap[keyGestureType]
+ }
+
+ private companion object {
+ private const val TAG = "InputGestureDataUtils"
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
index 1c380c2..30a2f33 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
@@ -22,14 +22,8 @@
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN
@@ -74,13 +68,7 @@
KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to MultiTasking,
// App Category
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to AppCategories,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to AppCategories,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to AppCategories,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to AppCategories,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to AppCategories,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to AppCategories,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to AppCategories,
+ KEY_GESTURE_TYPE_LAUNCH_APPLICATION to AppCategories,
)
val gestureToInternalKeyboardShortcutGroupLabelResIdMap =
@@ -116,20 +104,14 @@
R.string.shortcutHelper_category_split_screen,
// App Category
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to
- R.string.keyboard_shortcut_group_applications,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to
- R.string.keyboard_shortcut_group_applications,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to
- R.string.keyboard_shortcut_group_applications,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to
- R.string.keyboard_shortcut_group_applications,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to R.string.keyboard_shortcut_group_applications,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to R.string.keyboard_shortcut_group_applications,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to
+ KEY_GESTURE_TYPE_LAUNCH_APPLICATION to
R.string.keyboard_shortcut_group_applications,
)
+ /**
+ * App Category shortcut labels are mapped dynamically based on intent
+ * see [InputGestureDataAdapter.fetchShortcutLabelByAppLaunchData]
+ */
val gestureToInternalKeyboardShortcutInfoLabelResIdMap =
mapOf(
// System Category
@@ -158,22 +140,6 @@
R.string.system_multitasking_splitscreen_focus_lhs,
KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to
R.string.system_multitasking_splitscreen_focus_rhs,
-
- // App Category
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to
- R.string.keyboard_shortcut_group_applications_calculator,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to
- R.string.keyboard_shortcut_group_applications_calendar,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to
- R.string.keyboard_shortcut_group_applications_browser,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to
- R.string.keyboard_shortcut_group_applications_contacts,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to
- R.string.keyboard_shortcut_group_applications_email,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to
- R.string.keyboard_shortcut_group_applications_maps,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to
- R.string.keyboard_shortcut_group_applications_sms,
)
val shortcutLabelToKeyGestureTypeMap: Map<String, Int>
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 552cd94..4cb8a41 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
@@ -25,6 +25,7 @@
import com.android.systemui.keyboard.shortcut.data.repository.CustomInputGesturesRepository
import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.InputGestureDataAdapter
import com.android.systemui.keyboard.shortcut.data.repository.InputGestureMaps
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesUtils
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
@@ -112,6 +113,8 @@
val Kosmos.inputGestureMaps by Kosmos.Fixture { InputGestureMaps(applicationContext) }
+val Kosmos.inputGestureDataAdapter by Kosmos.Fixture { InputGestureDataAdapter(userTracker, inputGestureMaps, applicationContext)}
+
val Kosmos.customInputGesturesRepository by
Kosmos.Fixture { CustomInputGesturesRepository(userTracker, testDispatcher) }
@@ -122,8 +125,7 @@
applicationCoroutineScope,
testDispatcher,
shortcutCategoriesUtils,
- applicationContext,
- inputGestureMaps,
+ inputGestureDataAdapter,
customInputGesturesRepository,
fakeInputManager.inputManager,
)