summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSource.kt107
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/AppCategoriesShortcuts.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutIcon.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSourceTest.kt227
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt11
12 files changed, 508 insertions, 23 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
index 6364a6fb53c9..1f0aef8ab977 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
@@ -20,10 +20,12 @@ import android.app.Activity
import com.android.systemui.CoreStartable
import com.android.systemui.Flags.keyboardShortcutHelperRewrite
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
+import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.InputShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.SystemShortcutsSource
+import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.InputShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts
@@ -56,6 +58,12 @@ interface ShortcutHelperModule {
@InputShortcuts
fun inputShortcutsSources(impl: InputShortcutsSource): KeyboardShortcutGroupsSource
+ @Binds
+ @AppCategoriesShortcuts
+ fun appCategoriesShortcutsSource(
+ impl: AppCategoriesShortcutsSource
+ ): KeyboardShortcutGroupsSource
+
companion object {
@Provides
@IntoMap
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 133dab6b4d7b..7b0c25e8379d 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
@@ -17,6 +17,7 @@
package com.android.systemui.keyboard.shortcut.data.repository
import android.content.Context
+import android.graphics.drawable.Icon
import android.hardware.input.InputManager
import android.util.Log
import android.view.KeyCharacterMap
@@ -26,17 +27,20 @@ import android.view.KeyboardShortcutInfo
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
+import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.InputShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
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.ShortcutCategoryType.APP_CATEGORIES
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.IME
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MULTI_TASKING
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.SYSTEM
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import javax.inject.Inject
@@ -52,6 +56,7 @@ constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
@SystemShortcuts private val systemShortcutsSource: KeyboardShortcutGroupsSource,
@MultitaskingShortcuts private val multitaskingShortcutsSource: KeyboardShortcutGroupsSource,
+ @AppCategoriesShortcuts private val appCategoriesShortcutsSource: KeyboardShortcutGroupsSource,
@InputShortcuts private val inputShortcutsSource: KeyboardShortcutGroupsSource,
private val inputManager: InputManager,
stateRepository: ShortcutHelperStateRepository
@@ -72,7 +77,8 @@ constructor(
toShortcutCategory(
it.keyCharacterMap,
SYSTEM,
- systemShortcutsSource.shortcutGroups(it.id)
+ systemShortcutsSource.shortcutGroups(it.id),
+ keepIcons = true,
)
} else {
null
@@ -85,7 +91,22 @@ constructor(
toShortcutCategory(
it.keyCharacterMap,
MULTI_TASKING,
- multitaskingShortcutsSource.shortcutGroups(it.id)
+ multitaskingShortcutsSource.shortcutGroups(it.id),
+ keepIcons = true,
+ )
+ } else {
+ null
+ }
+ }
+
+ val appCategoriesShortcutsCategory =
+ activeInputDevice.map {
+ if (it != null) {
+ toShortcutCategory(
+ it.keyCharacterMap,
+ APP_CATEGORIES,
+ appCategoriesShortcutsSource.shortcutGroups(it.id),
+ keepIcons = true,
)
} else {
null
@@ -98,7 +119,8 @@ constructor(
toShortcutCategory(
it.keyCharacterMap,
IME,
- inputShortcutsSource.shortcutGroups(it.id)
+ inputShortcutsSource.shortcutGroups(it.id),
+ keepIcons = false,
)
} else {
null
@@ -109,13 +131,14 @@ constructor(
keyCharacterMap: KeyCharacterMap,
type: ShortcutCategoryType,
shortcutGroups: List<KeyboardShortcutGroup>,
+ keepIcons: Boolean,
): ShortcutCategory? {
val subCategories =
shortcutGroups
.map { shortcutGroup ->
ShortcutSubCategory(
shortcutGroup.label.toString(),
- toShortcuts(keyCharacterMap, shortcutGroup.items)
+ toShortcuts(keyCharacterMap, shortcutGroup.items, keepIcons)
)
}
.filter { it.shortcuts.isNotEmpty() }
@@ -129,16 +152,37 @@ constructor(
private fun toShortcuts(
keyCharacterMap: KeyCharacterMap,
- infoList: List<KeyboardShortcutInfo>
- ) = infoList.mapNotNull { toShortcut(keyCharacterMap, it) }
+ infoList: List<KeyboardShortcutInfo>,
+ keepIcons: Boolean,
+ ) = infoList.mapNotNull { toShortcut(keyCharacterMap, it, keepIcons) }
private fun toShortcut(
keyCharacterMap: KeyCharacterMap,
- shortcutInfo: KeyboardShortcutInfo
+ shortcutInfo: KeyboardShortcutInfo,
+ keepIcon: Boolean,
): Shortcut? {
- val shortcutCommand = toShortcutCommand(keyCharacterMap, shortcutInfo)
- return if (shortcutCommand == null) null
- else Shortcut(label = shortcutInfo.label!!.toString(), commands = listOf(shortcutCommand))
+ val shortcutCommand = toShortcutCommand(keyCharacterMap, shortcutInfo) ?: return null
+ return Shortcut(
+ label = shortcutInfo.label!!.toString(),
+ icon = toShortcutIcon(keepIcon, shortcutInfo),
+ commands = listOf(shortcutCommand)
+ )
+ }
+
+ private fun toShortcutIcon(
+ keepIcon: Boolean,
+ shortcutInfo: KeyboardShortcutInfo
+ ): ShortcutIcon? {
+ if (!keepIcon) {
+ return null
+ }
+ val icon = shortcutInfo.icon ?: return null
+ // For now only keep icons of type resource, which is what the "default apps" shortcuts
+ // provide.
+ if (icon.type != Icon.TYPE_RESOURCE || icon.resPackage.isNullOrEmpty() || icon.resId <= 0) {
+ return null
+ }
+ return ShortcutIcon(packageName = icon.resPackage, resourceId = icon.resId)
}
private fun toShortcutCommand(
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
new file mode 100644
index 000000000000..d7cb7db0f15b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSource.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.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 javax.inject.Inject
+
+class AppCategoriesShortcutsSource
+@Inject
+constructor(
+ private val appCategoryIconProvider: AppCategoryIconProvider,
+ @Main private val resources: Resources,
+) : 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,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
index ead10e5be4e1..d41d21a3b4fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
@@ -24,7 +24,6 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
@SysUISingleton
class ShortcutHelperCategoriesInteractor
@@ -33,13 +32,13 @@ constructor(
categoriesRepository: ShortcutHelperCategoriesRepository,
) {
- private val systemsShortcutCategory = categoriesRepository.systemShortcutsCategory
- private val multitaskingShortcutsCategory = categoriesRepository.multitaskingShortcutsCategory
- private val imeShortcutsCategory = categoriesRepository.imeShortcutsCategory
-
val shortcutCategories: Flow<List<ShortcutCategory>> =
- combine(systemsShortcutCategory, multitaskingShortcutsCategory, imeShortcutsCategory) {
- shortcutCategories ->
+ combine(
+ categoriesRepository.systemShortcutsCategory,
+ categoriesRepository.multitaskingShortcutsCategory,
+ categoriesRepository.imeShortcutsCategory,
+ categoriesRepository.appCategoriesShortcutsCategory,
+ ) { shortcutCategories ->
shortcutCategories.filterNotNull().map { groupSubCategoriesInCategory(it) }
}
@@ -62,6 +61,10 @@ constructor(
.groupBy { it.label }
.entries
.map { (commonLabel, groupedShortcuts) ->
- Shortcut(label = commonLabel, commands = groupedShortcuts.flatMap { it.commands })
+ Shortcut(
+ label = commonLabel,
+ icon = groupedShortcuts.firstOrNull()?.icon,
+ commands = groupedShortcuts.flatMap { it.commands }
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/AppCategoriesShortcuts.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/AppCategoriesShortcuts.kt
new file mode 100644
index 000000000000..b105ff67beaa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/AppCategoriesShortcuts.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.qualifiers
+
+import javax.inject.Qualifier
+
+@Qualifier annotation class AppCategoriesShortcuts
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
index adc6d952f2c3..5f8570cba7a6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
@@ -16,7 +16,11 @@
package com.android.systemui.keyboard.shortcut.shared.model
-data class Shortcut(val label: String, val commands: List<ShortcutCommand>)
+data class Shortcut(
+ val label: String,
+ val commands: List<ShortcutCommand>,
+ val icon: ShortcutIcon? = null,
+)
class ShortcutBuilder(private val label: String) {
val commands = mutableListOf<ShortcutCommand>()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
index 5d0535905540..63e167a376d1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
@@ -19,7 +19,8 @@ package com.android.systemui.keyboard.shortcut.shared.model
enum class ShortcutCategoryType {
SYSTEM,
MULTI_TASKING,
- IME
+ IME,
+ APP_CATEGORIES,
}
data class ShortcutCategory(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutIcon.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutIcon.kt
new file mode 100644
index 000000000000..8b720a5a44eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutIcon.kt
@@ -0,0 +1,19 @@
+/*
+ * 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.shared.model
+
+data class ShortcutIcon(val packageName: String, val resourceId: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index b703892c2fa7..3b037bc17564 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -16,8 +16,10 @@
package com.android.systemui.keyboard.shortcut.ui.composable
+import android.graphics.drawable.Icon
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -43,6 +45,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.OpenInNew
+import androidx.compose.material.icons.filled.Apps
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.Keyboard
import androidx.compose.material.icons.filled.Search
@@ -75,6 +78,7 @@ import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -86,11 +90,13 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
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.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
@@ -221,6 +227,7 @@ private val ShortcutCategory.icon: ImageVector
ShortcutCategoryType.SYSTEM -> Icons.Default.Tv
ShortcutCategoryType.MULTI_TASKING -> Icons.Default.VerticalSplit
ShortcutCategoryType.IME -> Icons.Default.Keyboard
+ ShortcutCategoryType.APP_CATEGORIES -> Icons.Default.Apps
}
private val ShortcutCategory.labelResId: Int
@@ -229,6 +236,7 @@ private val ShortcutCategory.labelResId: Int
ShortcutCategoryType.SYSTEM -> R.string.shortcut_helper_category_system
ShortcutCategoryType.MULTI_TASKING -> R.string.shortcut_helper_category_multitasking
ShortcutCategoryType.IME -> R.string.shortcut_helper_category_input
+ ShortcutCategoryType.APP_CATEGORIES -> R.string.shortcut_helper_category_app_shortcuts
}
@Composable
@@ -359,10 +367,21 @@ private fun SubCategoryTitle(title: String) {
@Composable
private fun ShortcutViewDualPane(shortcut: Shortcut) {
Row(Modifier.padding(vertical = 16.dp)) {
- ShortcutDescriptionText(
+ Row(
modifier = Modifier.width(160.dp).align(Alignment.CenterVertically),
- shortcut = shortcut,
- )
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ if (shortcut.icon != null) {
+ ShortcutIcon(
+ shortcut.icon,
+ modifier = Modifier.size(36.dp),
+ )
+ }
+ ShortcutDescriptionText(
+ shortcut = shortcut,
+ )
+ }
Spacer(modifier = Modifier.width(16.dp))
ShortcutKeyCombinations(
modifier = Modifier.weight(1f),
@@ -371,6 +390,24 @@ private fun ShortcutViewDualPane(shortcut: Shortcut) {
}
}
+@Composable
+fun ShortcutIcon(
+ icon: ShortcutIcon,
+ modifier: Modifier = Modifier,
+ contentDescription: String? = null,
+) {
+ val context = LocalContext.current
+ val drawable =
+ remember(icon.packageName, icon.resourceId) {
+ Icon.createWithResource(icon.packageName, icon.resourceId).loadDrawable(context)
+ } ?: return
+ Image(
+ painter = rememberDrawablePainter(drawable),
+ contentDescription = contentDescription,
+ modifier = modifier,
+ )
+}
+
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun ShortcutKeyCombinations(
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
new file mode 100644
index 000000000000..e49e2b4990b4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSourceTest.kt
@@ -0,0 +1,227 @@
+/*
+ * 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.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 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.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.icons.fakeAppCategoryIconProvider
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AppCategoriesShortcutsSourceTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val defaultAppIconsProvider = kosmos.fakeAppCategoryIconProvider
+ private val source = kosmos.shortcutHelperAppCategoriesShortcutsSource
+
+ @Before
+ fun setUp() {
+ categoryApps.forEach { categoryAppIcon ->
+ defaultAppIconsProvider.installCategoryApp(
+ categoryAppIcon.category,
+ categoryAppIcon.packageName,
+ categoryAppIcon.iconResId
+ )
+ }
+ }
+
+ @Test
+ fun shortcutGroups_returnsSingleGroup() =
+ testScope.runTest { assertThat(source.shortcutGroups(TEST_DEVICE_ID)).hasSize(1) }
+
+ @Test
+ fun shortcutGroups_hasAssistantIcon() =
+ testScope.runTest {
+ defaultAppIconsProvider.installAssistantApp(ASSISTANT_PACKAGE, ASSISTANT_ICON_RES_ID)
+
+ 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)
+ }
+
+ @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),
+ )
+
+ 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/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
index a5f1f8c0896a..4c1e8696cdc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.IME
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MULTI_TASKING
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.SYSTEM
+import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
@@ -47,12 +48,14 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() {
private val systemShortcutsSource = FakeKeyboardShortcutGroupsSource()
private val multitaskingShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ private val defaultAppsShortcutsSource = FakeKeyboardShortcutGroupsSource()
@OptIn(ExperimentalCoroutinesApi::class)
private val kosmos =
testKosmos().also {
it.testDispatcher = UnconfinedTestDispatcher()
it.shortcutHelperSystemShortcutsSource = systemShortcutsSource
it.shortcutHelperMultiTaskingShortcutsSource = multitaskingShortcutsSource
+ it.shortcutHelperAppCategoriesShortcutsSource = defaultAppsShortcutsSource
}
private val testScope = kosmos.testScope
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 a1021f65bd5a..f436a68aa5be 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
@@ -24,6 +24,7 @@ import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperTestHelper
+import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.InputShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource
@@ -39,6 +40,15 @@ import com.android.systemui.kosmos.testDispatcher
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,
+ )
+ }
var Kosmos.shortcutHelperSystemShortcutsSource: KeyboardShortcutGroupsSource by
Kosmos.Fixture { SystemShortcutsSource(mainResources) }
@@ -67,6 +77,7 @@ val Kosmos.shortcutHelperCategoriesRepository by
testDispatcher,
shortcutHelperSystemShortcutsSource,
shortcutHelperMultiTaskingShortcutsSource,
+ shortcutHelperAppCategoriesShortcutsSource,
shortcutHelperInputShortcutsSource,
fakeInputManager.inputManager,
shortcutHelperStateRepository,