summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/view/IWindowManager.aidl8
-rw-r--r--core/java/android/view/KeyboardShortcutGroup.aidl3
-rw-r--r--core/java/android/view/KeyboardShortcutInfo.java19
-rw-r--r--core/java/android/view/WindowManager.java9
-rw-r--r--core/java/android/view/WindowManagerImpl.java10
-rw-r--r--core/res/res/values/strings.xml19
-rw-r--r--core/res/res/values/symbols.xml11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java165
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java135
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java3
-rw-r--r--services/core/java/com/android/server/policy/ModifierShortcutManager.java149
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java6
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java10
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java13
-rw-r--r--services/tests/wmtests/AndroidManifest.xml1
-rw-r--r--services/tests/wmtests/res/xml/bookmarks.xml41
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java167
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java7
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());
+ }
}