diff options
20 files changed, 641 insertions, 145 deletions
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index cb5a885bd0a0..e5be53191e9e 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -53,6 +53,7 @@ import android.view.IWallpaperVisibilityListener; import android.view.IWindow; import android.view.IWindowSession; import android.view.IWindowSessionCallback; +import android.view.KeyboardShortcutGroup; import android.view.KeyEvent; import android.view.InputEvent; import android.view.InsetsState; @@ -1095,4 +1096,11 @@ interface IWindowManager boolean transferTouchGesture(in InputTransferToken transferFromToken, in InputTransferToken transferToToken); + + /** + * Request the application launch keyboard shortcuts the system has defined. + * + * @param deviceId The id of the {@link InputDevice} that will handle the shortcut. + */ + KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId); } diff --git a/core/java/android/view/KeyboardShortcutGroup.aidl b/core/java/android/view/KeyboardShortcutGroup.aidl new file mode 100644 index 000000000000..6f219dbda7ef --- /dev/null +++ b/core/java/android/view/KeyboardShortcutGroup.aidl @@ -0,0 +1,3 @@ +package android.view; + +@JavaOnlyStableParcelable parcelable KeyboardShortcutGroup; diff --git a/core/java/android/view/KeyboardShortcutInfo.java b/core/java/android/view/KeyboardShortcutInfo.java index 3f6fd646994c..3f49bf3380c1 100644 --- a/core/java/android/view/KeyboardShortcutInfo.java +++ b/core/java/android/view/KeyboardShortcutInfo.java @@ -81,12 +81,29 @@ public final class KeyboardShortcutInfo implements Parcelable { * {@link KeyEvent#META_SYM_ON}. */ public KeyboardShortcutInfo(CharSequence label, char baseCharacter, int modifiers) { + this(label, null, baseCharacter, modifiers); + } + + /** + * @param label The label that identifies the action performed by this shortcut. + * @param icon An icon that identifies the action performed by this shortcut. + * @param baseCharacter The character that triggers the shortcut. + * @param modifiers The set of modifiers that, combined with the key, trigger the shortcut. + * These should be a combination of {@link KeyEvent#META_CTRL_ON}, + * {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_META_ON}, + * {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_FUNCTION_ON} and + * {@link KeyEvent#META_SYM_ON}. + * + * @hide + */ + public KeyboardShortcutInfo( + CharSequence label, @Nullable Icon icon, char baseCharacter, int modifiers) { mLabel = label; checkArgument(baseCharacter != MIN_VALUE); mBaseCharacter = baseCharacter; mKeycode = KeyEvent.KEYCODE_UNKNOWN; mModifiers = modifiers; - mIcon = null; + mIcon = icon; } private KeyboardShortcutInfo(Parcel source) { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 18006bb7866a..14978ede88cc 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1668,6 +1668,15 @@ public interface WindowManager extends ViewManager { public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId); /** + * Request the application launch keyboard shortcuts the system has defined. + * + * @param deviceId The id of the {@link InputDevice} that will handle the shortcut. + * + * @hide + */ + KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId); + + /** * Request for ime's keyboard shortcuts to be retrieved asynchronously. * * @param receiver The callback to be triggered when the result is ready. diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index b667427fd2c5..330e46af6381 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -237,6 +237,16 @@ public final class WindowManagerImpl implements WindowManager { } @Override + public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) { + try { + return WindowManagerGlobal.getWindowManagerService() + .getApplicationLaunchKeyboardShortcuts(deviceId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override public void requestImeKeyboardShortcuts( final KeyboardShortcutsReceiver receiver, int deviceId) { IResultReceiver resultReceiver = new IResultReceiver.Stub() { diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 6b71f97e3f17..46b154163224 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6534,4 +6534,23 @@ ul.</string> <string name="bg_user_sound_notification_button_mute">Mute</string> <!-- Notification text to mute the sound from the background user [CHAR LIMIT=NOTIF_BODY]--> <string name="bg_user_sound_notification_message">Tap to mute sound</string> + + <!-- User visible title for the keyboard shortcut that takes the user to the browser app. [CHAR LIMIT=70] --> + <string name="keyboard_shortcut_group_applications_browser">Browser</string> + <!-- User visible title for the keyboard shortcut that takes the user to the contacts app. [CHAR LIMIT=70] --> + <string name="keyboard_shortcut_group_applications_contacts">Contacts</string> + <!-- User visible title for the keyboard shortcut that takes the user to the email app. [CHAR LIMIT=70] --> + <string name="keyboard_shortcut_group_applications_email">Email</string> + <!-- User visible title for the keyboard shortcut that takes the user to the SMS messaging app. [CHAR LIMIT=70] --> + <string name="keyboard_shortcut_group_applications_sms">SMS</string> + <!-- User visible title for the keyboard shortcut that takes the user to the music app. [CHAR LIMIT=70] --> + <string name="keyboard_shortcut_group_applications_music">Music</string> + <!-- User visible title for the keyboard shortcut that takes the user to the calendar app. [CHAR LIMIT=70] --> + <string name="keyboard_shortcut_group_applications_calendar">Calendar</string> + <!-- User visible title for the keyboard shortcut that takes the user to the calculator app. [CHAR LIMIT=70] --> + <string name="keyboard_shortcut_group_applications_calculator">Calculator</string> + <!-- User visible title for the keyboard shortcut that takes the user to the maps app. [CHAR LIMIT=70] --> + <string name="keyboard_shortcut_group_applications_maps">Maps</string> + <!-- User visible title for the keyboard shortcut group containing system-wide application launch shortcuts. [CHAR-LIMIT=70] --> + <string name="keyboard_shortcut_group_applications">Applications</string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d25f59d7c488..c50b961f74cd 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5561,4 +5561,15 @@ <java-symbol type="string" name="bg_user_sound_notification_button_switch_user" /> <java-symbol type="string" name="bg_user_sound_notification_button_mute" /> <java-symbol type="string" name="bg_user_sound_notification_message" /> + + <!-- Keyboard Shortcut default category names. --> + <java-symbol type="string" name="keyboard_shortcut_group_applications_browser" /> + <java-symbol type="string" name="keyboard_shortcut_group_applications_calculator" /> + <java-symbol type="string" name="keyboard_shortcut_group_applications_calendar" /> + <java-symbol type="string" name="keyboard_shortcut_group_applications_contacts" /> + <java-symbol type="string" name="keyboard_shortcut_group_applications_email" /> + <java-symbol type="string" name="keyboard_shortcut_group_applications_maps" /> + <java-symbol type="string" name="keyboard_shortcut_group_applications_music" /> + <java-symbol type="string" name="keyboard_shortcut_group_applications_sms" /> + <java-symbol type="string" name="keyboard_shortcut_group_applications" /> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index 5bb2936c91d4..c997ac5ad9df 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; +import static com.android.systemui.Flags.fetchBookmarksXmlKeyboardShortcuts; import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri; import android.annotation.NonNull; @@ -149,7 +150,7 @@ public final class KeyboardShortcutListSearch { private KeyCharacterMap mBackupKeyCharacterMap; @VisibleForTesting - KeyboardShortcutListSearch(Context context, WindowManager windowManager) { + KeyboardShortcutListSearch(Context context, WindowManager windowManager, int deviceId) { this.mContext = new ContextThemeWrapper( context, R.style.KeyboardShortcutHelper); this.mPackageManager = AppGlobals.getPackageManager(); @@ -159,12 +160,12 @@ public final class KeyboardShortcutListSearch { this.mWindowManager = mContext.getSystemService(WindowManager.class); } loadResources(this.mContext); - createHardcodedShortcuts(); + createHardcodedShortcuts(deviceId); } - private static KeyboardShortcutListSearch getInstance(Context context) { + private static KeyboardShortcutListSearch getInstance(Context context, int deviceId) { if (sInstance == null) { - sInstance = new KeyboardShortcutListSearch(context, null); + sInstance = new KeyboardShortcutListSearch(context, null, deviceId); } return sInstance; } @@ -176,7 +177,7 @@ public final class KeyboardShortcutListSearch { if (sInstance != null && !sInstance.mContext.equals(context)) { dismiss(); } - getInstance(context).showKeyboardShortcuts(deviceId); + getInstance(context, deviceId).showKeyboardShortcuts(deviceId); } } @@ -367,7 +368,7 @@ public final class KeyboardShortcutListSearch { KeyEvent.META_META_ON, context.getDrawable(R.drawable.ic_ksh_key_meta)); } - private void createHardcodedShortcuts() { + private void createHardcodedShortcuts(int deviceId) { // Add system shortcuts mKeySearchResultMap.put(SHORTCUT_SYSTEM_INDEX, true); mSystemGroup.add(getMultiMappingSystemShortcuts(mContext)); @@ -377,7 +378,7 @@ public final class KeyboardShortcutListSearch { mInputGroup.add(getMultiMappingInputShortcuts(mContext)); // Add open apps shortcuts final List<KeyboardShortcutMultiMappingGroup> appShortcuts = - Arrays.asList(getDefaultMultiMappingApplicationShortcuts()); + Arrays.asList(getDefaultMultiMappingApplicationShortcuts(deviceId)); if (appShortcuts != null && !appShortcuts.isEmpty()) { mOpenAppsGroup = appShortcuts; mKeySearchResultMap.put(SHORTCUT_OPENAPPS_INDEX, true); @@ -739,35 +740,50 @@ public final class KeyboardShortcutListSearch { shortcutMultiMappingInfoList); } - private KeyboardShortcutMultiMappingGroup getDefaultMultiMappingApplicationShortcuts() { - final int userId = mContext.getUserId(); - PackageInfo assistPackageInfo = getAssistPackageInfo(mContext, mPackageManager, userId); - CharSequence categoryTitle = - mContext.getString(R.string.keyboard_shortcut_group_applications); + private KeyboardShortcutMultiMappingGroup getDefaultMultiMappingApplicationShortcuts( + int deviceId) { List<ShortcutMultiMappingInfo> shortcutMultiMappingInfos = new ArrayList<>(); - - String[] intentCategories = { - Intent.CATEGORY_APP_BROWSER, - Intent.CATEGORY_APP_CONTACTS, - Intent.CATEGORY_APP_EMAIL, - Intent.CATEGORY_APP_CALENDAR, - Intent.CATEGORY_APP_MAPS, - Intent.CATEGORY_APP_MUSIC, - Intent.CATEGORY_APP_MESSAGING, - Intent.CATEGORY_APP_CALCULATOR, - - }; - String[] shortcutLabels = { - mContext.getString(R.string.keyboard_shortcut_group_applications_browser), - mContext.getString(R.string.keyboard_shortcut_group_applications_contacts), - mContext.getString(R.string.keyboard_shortcut_group_applications_email), - mContext.getString(R.string.keyboard_shortcut_group_applications_calendar), - mContext.getString(R.string.keyboard_shortcut_group_applications_maps), - mContext.getString(R.string.keyboard_shortcut_group_applications_music), - mContext.getString(R.string.keyboard_shortcut_group_applications_sms), - mContext.getString(R.string.keyboard_shortcut_group_applications_calculator) - }; - int[] keyCodes = { + CharSequence categoryTitle; + if (fetchBookmarksXmlKeyboardShortcuts()) { + KeyboardShortcutGroup apps = + mWindowManager.getApplicationLaunchKeyboardShortcuts(deviceId); + List<KeyboardShortcutMultiMappingGroup> shortcuts = + reMapToKeyboardShortcutMultiMappingGroup(Arrays.asList(apps)); + for (KeyboardShortcutMultiMappingGroup group : shortcuts) { + for (ShortcutMultiMappingInfo keyboardShortcutInfo : group.getItems()) { + shortcutMultiMappingInfos.add(keyboardShortcutInfo); + } + } + categoryTitle = apps.getLabel(); + } else { + // Show shortcuts based on AOSP bookmarks.xml + categoryTitle = mContext.getString(R.string.keyboard_shortcut_group_applications); + final int userId = mContext.getUserId(); + PackageInfo assistPackageInfo = + getAssistPackageInfo(mContext, mPackageManager, userId); + + String[] intentCategories = { + Intent.CATEGORY_APP_BROWSER, + Intent.CATEGORY_APP_CONTACTS, + Intent.CATEGORY_APP_EMAIL, + Intent.CATEGORY_APP_CALENDAR, + Intent.CATEGORY_APP_MAPS, + Intent.CATEGORY_APP_MUSIC, + Intent.CATEGORY_APP_MESSAGING, + Intent.CATEGORY_APP_CALCULATOR, + }; + String[] shortcutLabels = { + mContext.getString(R.string.keyboard_shortcut_group_applications_browser), + mContext.getString(R.string.keyboard_shortcut_group_applications_contacts), + mContext.getString(R.string.keyboard_shortcut_group_applications_email), + mContext.getString(R.string.keyboard_shortcut_group_applications_calendar), + mContext.getString(R.string.keyboard_shortcut_group_applications_maps), + mContext.getString(R.string.keyboard_shortcut_group_applications_music), + mContext.getString(R.string.keyboard_shortcut_group_applications_sms), + mContext.getString(R.string.keyboard_shortcut_group_applications_calculator) + }; + + int[] keyCodes = { KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_E, @@ -776,52 +792,44 @@ public final class KeyboardShortcutListSearch { KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_S, KeyEvent.KEYCODE_U, - }; + }; - // Assist. - if (assistPackageInfo != null) { + // Assist. if (assistPackageInfo != null) { - final Icon assistIcon = Icon.createWithResource( - assistPackageInfo.applicationInfo.packageName, - assistPackageInfo.applicationInfo.icon); - CharSequence assistLabel = - mContext.getString(R.string.keyboard_shortcut_group_applications_assist); - KeyboardShortcutInfo assistShortcutInfo = new KeyboardShortcutInfo( - assistLabel, - assistIcon, - KeyEvent.KEYCODE_A, - KeyEvent.META_META_ON); - shortcutMultiMappingInfos.add( - new ShortcutMultiMappingInfo( - assistLabel, - assistIcon, - Arrays.asList(new ShortcutKeyGroup(assistShortcutInfo, null)))); + if (assistPackageInfo != null) { + final Icon assistIcon = Icon.createWithResource( + assistPackageInfo.applicationInfo.packageName, + assistPackageInfo.applicationInfo.icon); + CharSequence assistLabel = mContext.getString( + R.string.keyboard_shortcut_group_applications_assist); + KeyboardShortcutInfo assistShortcutInfo = new KeyboardShortcutInfo( + assistLabel, + assistIcon, + KeyEvent.KEYCODE_A, + KeyEvent.META_META_ON); + shortcutMultiMappingInfos.add( + new ShortcutMultiMappingInfo( + assistLabel, + assistIcon, + Arrays.asList(new ShortcutKeyGroup(assistShortcutInfo, null)))); + } } - } - // Browser (Chrome as default): Meta + B - // Contacts: Meta + C - // Email (Gmail as default): Meta + E - // Gmail: Meta + G - // Calendar: Meta + K - // Maps: Meta + M - // Music: Meta + P - // SMS: Meta + S - // Calculator: Meta + U - for (int i = 0; i < shortcutLabels.length; i++) { - final Icon icon = getIconForIntentCategory(intentCategories[i], userId); - if (icon != null) { - CharSequence label = - shortcutLabels[i]; - KeyboardShortcutInfo keyboardShortcutInfo = new KeyboardShortcutInfo( - label, - icon, - keyCodes[i], - KeyEvent.META_META_ON); - List<ShortcutKeyGroup> shortcutKeyGroups = - Arrays.asList(new ShortcutKeyGroup(keyboardShortcutInfo, null)); - shortcutMultiMappingInfos.add( - new ShortcutMultiMappingInfo(label, icon, shortcutKeyGroups)); + for (int i = 0; i < shortcutLabels.length; i++) { + final Icon icon = getIconForIntentCategory(intentCategories[i], userId); + if (icon != null) { + CharSequence label = + shortcutLabels[i]; + KeyboardShortcutInfo keyboardShortcutInfo = new KeyboardShortcutInfo( + label, + icon, + keyCodes[i], + KeyEvent.META_META_ON); + List<ShortcutKeyGroup> shortcutKeyGroups = + Arrays.asList(new ShortcutKeyGroup(keyboardShortcutInfo, null)); + shortcutMultiMappingInfos.add( + new ShortcutMultiMappingInfo(label, icon, shortcutKeyGroups)); + } } } @@ -1221,7 +1229,8 @@ public final class KeyboardShortcutListSearch { String shortcutKeyString = null; Drawable shortcutKeyDrawable = null; if (info.getBaseCharacter() > Character.MIN_VALUE) { - shortcutKeyString = String.valueOf(info.getBaseCharacter()); + shortcutKeyString = String.valueOf(info.getBaseCharacter()) + .toUpperCase(Locale.getDefault()); } else if (mSpecialCharacterNames.get(info.getKeycode()) != null) { shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode()); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java index a49ca3889876..da89eea35f4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java @@ -20,6 +20,7 @@ import static android.content.Context.LAYOUT_INFLATER_SERVICE; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; +import static com.android.systemui.Flags.fetchBookmarksXmlKeyboardShortcuts; import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri; import android.annotation.NonNull; @@ -75,6 +76,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Locale; /** * Contains functionality for handling keyboard shortcuts. @@ -133,6 +135,7 @@ public final class KeyboardShortcuts { @Nullable private List<KeyboardShortcutGroup> mReceivedAppShortcutGroups = null; @Nullable private List<KeyboardShortcutGroup> mReceivedImeShortcutGroups = null; + @Nullable private KeyboardShortcutGroup mDefaultApplicationShortcuts = null; @VisibleForTesting KeyboardShortcuts(Context context, WindowManager windowManager) { @@ -390,6 +393,7 @@ public final class KeyboardShortcuts { mReceivedAppShortcutGroups = null; mReceivedImeShortcutGroups = null; + mDefaultApplicationShortcuts = getDefaultApplicationShortcuts(deviceId); mWindowManager.requestAppKeyboardShortcuts( result -> { mBackgroundHandler.post(() -> { @@ -443,10 +447,8 @@ public final class KeyboardShortcuts { mReceivedAppShortcutGroups = null; mReceivedImeShortcutGroups = null; - final KeyboardShortcutGroup defaultAppShortcuts = - getDefaultApplicationShortcuts(); - if (defaultAppShortcuts != null) { - shortcutGroups.add(defaultAppShortcuts); + if (mDefaultApplicationShortcuts != null) { + shortcutGroups.add(mDefaultApplicationShortcuts); } shortcutGroups.add(getSystemShortcuts()); showKeyboardShortcutsDialog(shortcutGroups); @@ -499,7 +501,7 @@ public final class KeyboardShortcuts { return systemGroup; } - private KeyboardShortcutGroup getDefaultApplicationShortcuts() { + private KeyboardShortcutGroup getDefaultApplicationShortcuts(int deviceId) { final int userId = mContext.getUserId(); List<KeyboardShortcutInfo> keyboardShortcutInfoAppItems = new ArrayList<>(); @@ -529,65 +531,77 @@ public final class KeyboardShortcuts { } } - // Browser. - final Icon browserIcon = getIconForIntentCategory(Intent.CATEGORY_APP_BROWSER, userId); - if (browserIcon != null) { - keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( - mContext.getString(R.string.keyboard_shortcut_group_applications_browser), - browserIcon, - KeyEvent.KEYCODE_B, - KeyEvent.META_META_ON)); - } + CharSequence categoryTitle; + if (fetchBookmarksXmlKeyboardShortcuts()) { + KeyboardShortcutGroup apps = + mWindowManager.getApplicationLaunchKeyboardShortcuts(deviceId); + categoryTitle = apps.getLabel(); + keyboardShortcutInfoAppItems.addAll(apps.getItems()); + } else { + categoryTitle = mContext.getString(R.string.keyboard_shortcut_group_applications); + // Browser. + final Icon browserIcon = getIconForIntentCategory(Intent.CATEGORY_APP_BROWSER, userId); + if (browserIcon != null) { + keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( + mContext.getString(R.string.keyboard_shortcut_group_applications_browser), + browserIcon, + KeyEvent.KEYCODE_B, + KeyEvent.META_META_ON)); + } - // Contacts. - final Icon contactsIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CONTACTS, userId); - if (contactsIcon != null) { - keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( - mContext.getString(R.string.keyboard_shortcut_group_applications_contacts), - contactsIcon, - KeyEvent.KEYCODE_C, - KeyEvent.META_META_ON)); - } + // Contacts. + final Icon contactsIcon = getIconForIntentCategory( + Intent.CATEGORY_APP_CONTACTS, userId); + if (contactsIcon != null) { + keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( + mContext.getString(R.string.keyboard_shortcut_group_applications_contacts), + contactsIcon, + KeyEvent.KEYCODE_C, + KeyEvent.META_META_ON)); + } - // Email. - final Icon emailIcon = getIconForIntentCategory(Intent.CATEGORY_APP_EMAIL, userId); - if (emailIcon != null) { - keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( - mContext.getString(R.string.keyboard_shortcut_group_applications_email), - emailIcon, - KeyEvent.KEYCODE_E, - KeyEvent.META_META_ON)); - } + // Email. + final Icon emailIcon = getIconForIntentCategory(Intent.CATEGORY_APP_EMAIL, userId); + if (emailIcon != null) { + keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( + mContext.getString(R.string.keyboard_shortcut_group_applications_email), + emailIcon, + KeyEvent.KEYCODE_E, + KeyEvent.META_META_ON)); + } - // Messaging. - final Icon messagingIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MESSAGING, userId); - if (messagingIcon != null) { - keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( - mContext.getString(R.string.keyboard_shortcut_group_applications_sms), - messagingIcon, - KeyEvent.KEYCODE_S, - KeyEvent.META_META_ON)); - } + // Messaging. + final Icon messagingIcon = getIconForIntentCategory( + Intent.CATEGORY_APP_MESSAGING, userId); + if (messagingIcon != null) { + keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( + mContext.getString(R.string.keyboard_shortcut_group_applications_sms), + messagingIcon, + KeyEvent.KEYCODE_S, + KeyEvent.META_META_ON)); + } - // Music. - final Icon musicIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MUSIC, userId); - if (musicIcon != null) { - keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( - mContext.getString(R.string.keyboard_shortcut_group_applications_music), - musicIcon, - KeyEvent.KEYCODE_P, - KeyEvent.META_META_ON)); - } + // Music. + final Icon musicIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MUSIC, userId); + if (musicIcon != null) { + keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( + mContext.getString(R.string.keyboard_shortcut_group_applications_music), + musicIcon, + KeyEvent.KEYCODE_P, + KeyEvent.META_META_ON)); + } - // Calendar. - final Icon calendarIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CALENDAR, userId); - if (calendarIcon != null) { - keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( - mContext.getString(R.string.keyboard_shortcut_group_applications_calendar), - calendarIcon, - KeyEvent.KEYCODE_K, - KeyEvent.META_META_ON)); + // Calendar. + final Icon calendarIcon = getIconForIntentCategory( + Intent.CATEGORY_APP_CALENDAR, userId); + if (calendarIcon != null) { + keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( + mContext.getString(R.string.keyboard_shortcut_group_applications_calendar), + calendarIcon, + KeyEvent.KEYCODE_K, + KeyEvent.META_META_ON)); + } } final int itemsSize = keyboardShortcutInfoAppItems.size(); @@ -598,7 +612,7 @@ public final class KeyboardShortcuts { // Sorts by label, case insensitive with nulls and/or empty labels last. Collections.sort(keyboardShortcutInfoAppItems, mApplicationItemsComparator); return new KeyboardShortcutGroup( - mContext.getString(R.string.keyboard_shortcut_group_applications), + categoryTitle, keyboardShortcutInfoAppItems, true); } @@ -777,7 +791,8 @@ public final class KeyboardShortcuts { String shortcutKeyString = null; Drawable shortcutKeyDrawable = null; if (info.getBaseCharacter() > Character.MIN_VALUE) { - shortcutKeyString = String.valueOf(info.getBaseCharacter()); + shortcutKeyString = String.valueOf(info.getBaseCharacter()) + .toUpperCase(Locale.getDefault()); } else if (mSpecialCharacterNames.get(info.getKeycode()) != null) { shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode()); } else { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java index b23dfdc68a87..859517839388 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java @@ -21,6 +21,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.os.IBinder; import android.view.Display; +import android.view.KeyboardShortcutGroup; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; @@ -55,6 +56,11 @@ public class TestableWindowManager implements WindowManager { } @Override + public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) { + return mWindowManager.getApplicationLaunchKeyboardShortcuts(deviceId); + } + + @Override public Region getCurrentImeTouchRegion() { return mWindowManager.getCurrentImeTouchRegion(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java index 6985a27a59c8..63e56eeb730f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java @@ -66,7 +66,9 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase { @Before public void setUp() { - mKeyboardShortcutListSearch = new KeyboardShortcutListSearch(mContext, mWindowManager); + when(mWindowManager.getApplicationLaunchKeyboardShortcuts(anyInt())).thenReturn( + new KeyboardShortcutGroup("", Collections.emptyList())); + mKeyboardShortcutListSearch = new KeyboardShortcutListSearch(mContext, mWindowManager, -1); mKeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch; mKeyboardShortcutListSearch.mKeyboardShortcutsBottomSheetDialog = mBottomSheetDialog; mKeyboardShortcutListSearch.mContext = mContext; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java index 6ad8b8bc3637..105cf168995c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java @@ -54,6 +54,7 @@ import org.mockito.junit.MockitoRule; import org.mockito.stubbing.Answer; import java.util.Arrays; +import java.util.Collections; import java.util.List; @SmallTest @@ -71,6 +72,8 @@ public class KeyboardShortcutsTest extends SysuiTestCase { @Before public void setUp() { + when(mWindowManager.getApplicationLaunchKeyboardShortcuts(anyInt())).thenReturn( + new KeyboardShortcutGroup("", Collections.emptyList())); mKeyboardShortcuts = new KeyboardShortcuts(mContext, mWindowManager); KeyboardShortcuts.sInstance = mKeyboardShortcuts; mKeyboardShortcuts.mKeyboardShortcutsDialog = mDialog; diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java index 3a79d0df4819..fde23b726572 100644 --- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java +++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java @@ -22,8 +22,10 @@ import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.XmlResourceParser; +import android.graphics.drawable.Icon; import android.hardware.input.InputManager; import android.os.Handler; import android.os.RemoteException; @@ -36,7 +38,11 @@ import android.util.SparseArray; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; +import android.view.KeyboardShortcutGroup; +import android.view.KeyboardShortcutInfo; +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.IShortcutService; import com.android.internal.util.XmlUtils; import com.android.server.input.KeyboardMetricsCollector; @@ -46,7 +52,9 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -183,8 +191,12 @@ public class ModifierShortcutManager { String rolePackage = mRoleManager.getDefaultApplication(role); if (rolePackage != null) { intent = mPackageManager.getLaunchIntentForPackage(rolePackage); - intent.putExtra(EXTRA_ROLE, role); - mRoleIntents.put(role, intent); + if (intent != null) { + intent.putExtra(EXTRA_ROLE, role); + mRoleIntents.put(role, intent); + } else { + Log.w(TAG, "No launch intent for role " + role); + } } else { Log.w(TAG, "No default application for role " + role); } @@ -198,8 +210,7 @@ public class ModifierShortcutManager { private void loadShortcuts() { try { - XmlResourceParser parser = mContext.getResources().getXml( - com.android.internal.R.xml.bookmarks); + XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks); XmlUtils.beginDocument(parser, TAG_BOOKMARKS); while (true) { @@ -270,6 +281,9 @@ public class ModifierShortcutManager { continue; } intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName); + if (intent == null) { + Log.w(TAG, "Null selector intent for " + categoryName); + } } else if (roleName != null) { // We can't resolve the role at the time of this file being parsed as the // device hasn't finished booting, so we will look it up lazily. @@ -466,4 +480,131 @@ public class ModifierShortcutManager { return false; } + + /** + * @param deviceId The input device id of the input device that will handle the shortcuts. + * + * @return a {@link KeyboardShortcutGroup} containing the application launch keyboard + * shortcuts parsed at boot time from {@code bookmarks.xml}. + */ + public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) { + List<KeyboardShortcutInfo> shortcuts = new ArrayList(); + for (int i = 0; i < mIntentShortcuts.size(); i++) { + KeyboardShortcutInfo info = shortcutInfoFromIntent( + (char) (mIntentShortcuts.keyAt(i)), mIntentShortcuts.valueAt(i), false); + if (info != null) { + shortcuts.add(info); + } + } + + for (int i = 0; i < mShiftShortcuts.size(); i++) { + KeyboardShortcutInfo info = shortcutInfoFromIntent( + (char) (mShiftShortcuts.keyAt(i)), mShiftShortcuts.valueAt(i), true); + if (info != null) { + shortcuts.add(info); + } + } + + for (int i = 0; i < mRoleShortcuts.size(); i++) { + String role = mRoleShortcuts.valueAt(i); + KeyboardShortcutInfo info = shortcutInfoFromIntent( + (char) (mRoleShortcuts.keyAt(i)), getRoleLaunchIntent(role), false); + if (info != null) { + shortcuts.add(info); + } + } + + for (int i = 0; i < mShiftRoleShortcuts.size(); i++) { + String role = mShiftRoleShortcuts.valueAt(i); + KeyboardShortcutInfo info = shortcutInfoFromIntent( + (char) (mShiftRoleShortcuts.keyAt(i)), getRoleLaunchIntent(role), true); + if (info != null) { + shortcuts.add(info); + } + } + + return new KeyboardShortcutGroup( + mContext.getString(R.string.keyboard_shortcut_group_applications), + shortcuts); + } + + /** + * Given an intent to launch an application and the character and shift state that should + * trigger it, return a suitable {@link KeyboardShortcutInfo} that contains the label and + * icon for the target application. + * + * @param baseChar the character that triggers the shortcut + * @param intent the application launch intent + * @param shift whether the shift key is required to be presed. + */ + @VisibleForTesting + KeyboardShortcutInfo shortcutInfoFromIntent(char baseChar, Intent intent, boolean shift) { + if (intent == null) { + return null; + } + + CharSequence label; + Icon icon; + ActivityInfo resolvedActivity = intent.resolveActivityInfo( + mPackageManager, PackageManager.MATCH_DEFAULT_ONLY); + if (resolvedActivity == null) { + return null; + } + boolean isResolver = com.android.internal.app.ResolverActivity.class.getName().equals( + resolvedActivity.name); + if (isResolver) { + label = getIntentCategoryLabel(mContext, + intent.getSelector().getCategories().iterator().next()); + if (label == null) { + return null; + } + icon = Icon.createWithResource(mContext, R.drawable.sym_def_app_icon); + + } else { + label = resolvedActivity.loadLabel(mPackageManager); + icon = Icon.createWithResource( + resolvedActivity.packageName, resolvedActivity.getIconResource()); + } + int modifiers = KeyEvent.META_META_ON; + if (shift) { + modifiers |= KeyEvent.META_SHIFT_ON; + } + return new KeyboardShortcutInfo(label, icon, baseChar, modifiers); + } + + @VisibleForTesting + static String getIntentCategoryLabel(Context context, CharSequence category) { + int resid; + switch (category.toString()) { + case Intent.CATEGORY_APP_BROWSER: + resid = R.string.keyboard_shortcut_group_applications_browser; + break; + case Intent.CATEGORY_APP_CONTACTS: + resid = R.string.keyboard_shortcut_group_applications_contacts; + break; + case Intent.CATEGORY_APP_EMAIL: + resid = R.string.keyboard_shortcut_group_applications_email; + break; + case Intent.CATEGORY_APP_CALENDAR: + resid = R.string.keyboard_shortcut_group_applications_calendar; + break; + case Intent.CATEGORY_APP_MAPS: + resid = R.string.keyboard_shortcut_group_applications_maps; + break; + case Intent.CATEGORY_APP_MUSIC: + resid = R.string.keyboard_shortcut_group_applications_music; + break; + case Intent.CATEGORY_APP_MESSAGING: + resid = R.string.keyboard_shortcut_group_applications_sms; + break; + case Intent.CATEGORY_APP_CALCULATOR: + resid = R.string.keyboard_shortcut_group_applications_calculator; + break; + default: + Log.e(TAG, ("No label for app category " + category)); + return null; + } + return context.getString(resid); + }; + } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 9d0c0e9b36bb..8dc97566d1d2 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -189,6 +189,7 @@ import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyCharacterMap.FallbackAction; import android.view.KeyEvent; +import android.view.KeyboardShortcutGroup; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.WindowManager; @@ -3321,6 +3322,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { eventToLog).sendToTarget(); } + @Override + public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) { + return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId); + } + // TODO(b/117479243): handle it in InputPolicy // TODO (b/283241997): Add the remaining keyboard shortcut logging after refactoring /** {@inheritDoc} */ diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 9ca4e273ac39..6c05d70f8513 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -82,6 +82,7 @@ import android.util.proto.ProtoOutputStream; import android.view.Display; import android.view.IDisplayFoldListener; import android.view.KeyEvent; +import android.view.KeyboardShortcutGroup; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicyConstants; @@ -698,6 +699,15 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags); /** + * Return the set of applicaition launch keyboard shortcuts the system supports. + * + * @param deviceId The id of the {@link InputDevice} that will trigger the shortcut. + * + * @return {@link KeyboardShortcutGroup} containing the shortcuts. + */ + KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId); + + /** * Called from the input reader thread before a motion is enqueued when the device is in a * non-interactive state. * diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 803312214fc3..700c069a51c3 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -283,6 +283,7 @@ import android.view.InsetsFrameProvider; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.KeyEvent; +import android.view.KeyboardShortcutGroup; import android.view.MagnificationSpec; import android.view.RemoteAnimationAdapter; import android.view.ScrollCaptureResponse; @@ -334,8 +335,8 @@ import com.android.internal.policy.IKeyguardLockedStateListener; import com.android.internal.policy.IShortcutService; import com.android.internal.policy.KeyInterceptionInfo; import com.android.internal.protolog.LegacyProtoLogImpl; -import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.ProtoLog; +import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FrameworkStatsLog; @@ -7440,6 +7441,16 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) { + long token = Binder.clearCallingIdentity(); + try { + return mPolicy.getApplicationLaunchKeyboardShortcuts(deviceId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) { enforceRegisterWindowManagerListenersPermission("requestAppKeyboardShortcuts"); diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 777f6189fdea..6e6b70d319ab 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -52,6 +52,7 @@ <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"/> <uses-permission android:name="android.permission.MANAGE_DEFAULT_APPLICATIONS"/> <uses-permission android:name="android.permission.DUMP"/> + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) --> <application android:debuggable="true" diff --git a/services/tests/wmtests/res/xml/bookmarks.xml b/services/tests/wmtests/res/xml/bookmarks.xml new file mode 100644 index 000000000000..88419e9c441b --- /dev/null +++ b/services/tests/wmtests/res/xml/bookmarks.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<bookmarks> + <bookmark + role="android.app.role.BROWSER" + shortcut="b" /> + <bookmark + category="android.intent.category.APP_CONTACTS" + shortcut="c" /> + <bookmark + category="android.intent.category.APP_EMAIL" + shortcut="e" /> + <bookmark + category="android.intent.category.APP_CALENDAR" + shortcut="k" /> + <bookmark + category="android.intent.category.APP_MAPS" + shortcut="m" /> + <bookmark + category="android.intent.category.APP_MUSIC" + shortcut="p" /> + <bookmark + role="android.app.role.SMS" + shortcut="s" /> + <bookmark + category="android.intent.category.APP_CALCULATOR" + shortcut="u" /> +</bookmarks> diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java new file mode 100644 index 000000000000..8c375d413950 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java @@ -0,0 +1,167 @@ +/* + * 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.server.policy; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyObject; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.os.Handler; +import android.os.Looper; +import android.view.KeyEvent; +import android.view.KeyboardShortcutGroup; +import android.view.KeyboardShortcutInfo; + +import androidx.test.filters.SmallTest; + +import com.android.internal.R; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Collections; + +/** + * Test class for {@link ModifierShortcutManager}. + * + * Build/Install/Run: + * atest ModifierShortcutManagerTests + */ + +@SmallTest +public class ModifierShortcutManagerTests { + private ModifierShortcutManager mModifierShortcutManager; + private Handler mHandler; + private Context mContext; + private Resources mResources; + + @Before + public void setUp() { + mHandler = new Handler(Looper.getMainLooper()); + mContext = spy(getInstrumentation().getTargetContext()); + mResources = spy(mContext.getResources()); + + XmlResourceParser testBookmarks = mResources.getXml( + com.android.frameworks.wmtests.R.xml.bookmarks); + + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks); + + mModifierShortcutManager = new ModifierShortcutManager(mContext, mHandler); + } + + @Test + public void test_getApplicationLaunchKeyboardShortcuts() { + KeyboardShortcutGroup group = + mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(-1); + assertEquals(8, group.getItems().size()); + } + + @Test + public void test_shortcutInfoFromIntent_appIntent() { + Intent mockIntent = mock(Intent.class); + ActivityInfo mockActivityInfo = mock(ActivityInfo.class); + when(mockActivityInfo.loadLabel(anyObject())).thenReturn("label"); + mockActivityInfo.packageName = "android"; + when(mockActivityInfo.getIconResource()).thenReturn(R.drawable.sym_def_app_icon); + when(mockIntent.resolveActivityInfo(anyObject(), anyInt())).thenReturn(mockActivityInfo); + + KeyboardShortcutInfo info = mModifierShortcutManager.shortcutInfoFromIntent( + 'a', mockIntent, true); + + assertEquals("label", info.getLabel().toString()); + assertEquals('a', info.getBaseCharacter()); + assertEquals(R.drawable.sym_def_app_icon, info.getIcon().getResId()); + assertEquals(KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON, info.getModifiers()); + + } + + @Test + public void test_shortcutInfoFromIntent_resolverIntent() { + Intent mockIntent = mock(Intent.class); + Intent mockSelector = mock(Intent.class); + ActivityInfo mockActivityInfo = mock(ActivityInfo.class); + mockActivityInfo.name = com.android.internal.app.ResolverActivity.class.getName(); + when(mockIntent.resolveActivityInfo(anyObject(), anyInt())).thenReturn(mockActivityInfo); + when(mockIntent.getSelector()).thenReturn(mockSelector); + when(mockSelector.getCategories()).thenReturn( + Collections.singleton(Intent.CATEGORY_APP_BROWSER)); + + KeyboardShortcutInfo info = mModifierShortcutManager.shortcutInfoFromIntent( + 'a', mockIntent, false); + + assertEquals(mContext.getString(R.string.keyboard_shortcut_group_applications_browser), + info.getLabel().toString()); + assertEquals('a', info.getBaseCharacter()); + assertEquals(R.drawable.sym_def_app_icon, info.getIcon().getResId()); + assertEquals(KeyEvent.META_META_ON, info.getModifiers()); + + // validate that an unknown category that we can't present a label to the user for + // returns null shortcut info. + when(mockSelector.getCategories()).thenReturn( + Collections.singleton("not_a_category")); + assertEquals(null, mModifierShortcutManager.shortcutInfoFromIntent( + 'a', mockIntent, false)); + } + + @Test + public void test_getIntentCategoryLabel() { + assertEquals( + mContext.getString(R.string.keyboard_shortcut_group_applications_browser), + ModifierShortcutManager.getIntentCategoryLabel( + mContext, Intent.CATEGORY_APP_BROWSER)); + assertEquals( + mContext.getString(R.string.keyboard_shortcut_group_applications_contacts), + ModifierShortcutManager.getIntentCategoryLabel( + mContext, Intent.CATEGORY_APP_CONTACTS)); + assertEquals( + mContext.getString(R.string.keyboard_shortcut_group_applications_email), + ModifierShortcutManager.getIntentCategoryLabel( + mContext, Intent.CATEGORY_APP_EMAIL)); + assertEquals( + mContext.getString(R.string.keyboard_shortcut_group_applications_calendar), + ModifierShortcutManager.getIntentCategoryLabel( + mContext, Intent.CATEGORY_APP_CALENDAR)); + assertEquals( + mContext.getString(R.string.keyboard_shortcut_group_applications_maps), + ModifierShortcutManager.getIntentCategoryLabel( + mContext, Intent.CATEGORY_APP_MAPS)); + assertEquals( + mContext.getString(R.string.keyboard_shortcut_group_applications_music), + ModifierShortcutManager.getIntentCategoryLabel( + mContext, Intent.CATEGORY_APP_MUSIC)); + assertEquals( + mContext.getString(R.string.keyboard_shortcut_group_applications_sms), + ModifierShortcutManager.getIntentCategoryLabel( + mContext, Intent.CATEGORY_APP_MESSAGING)); + assertEquals( + mContext.getString(R.string.keyboard_shortcut_group_applications_calculator), + ModifierShortcutManager.getIntentCategoryLabel( + mContext, Intent.CATEGORY_APP_CALCULATOR)); + assertEquals(null, ModifierShortcutManager.getIntentCategoryLabel(mContext, "foo")); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index 00a8842c358e..38ad9a7e0dca 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -27,6 +27,7 @@ import android.os.PowerManager.GoToSleepReason; import android.os.PowerManager.WakeReason; import android.util.proto.ProtoOutputStream; import android.view.KeyEvent; +import android.view.KeyboardShortcutGroup; import android.view.WindowManager; import android.view.animation.Animation; @@ -35,6 +36,7 @@ import com.android.internal.policy.IShortcutService; import com.android.server.policy.WindowManagerPolicy; import java.io.PrintWriter; +import java.util.Collections; class TestWindowManagerPolicy implements WindowManagerPolicy { @@ -362,4 +364,9 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { public boolean isGlobalKey(int keyCode) { return false; } + + @Override + public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) { + return new KeyboardShortcutGroup("", Collections.emptyList()); + } } |