diff options
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, |