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,
         )