diff --git a/services/core/java/com/android/server/policy/ShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java similarity index 50% rename from services/core/java/com/android/server/policy/ShortcutManager.java rename to services/core/java/com/android/server/policy/ModifierShortcutManager.java index ab404dbad9105..a0771c0cc8d5a 100644 --- a/services/core/java/com/android/server/policy/ShortcutManager.java +++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * Copyright (C) 2021 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. @@ -16,18 +16,24 @@ package com.android.server.policy; +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.os.RemoteException; +import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import android.util.LongSparseArray; +import android.util.Slog; import android.util.SparseArray; import android.view.KeyCharacterMap; import android.view.KeyEvent; +import com.android.internal.policy.IShortcutService; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; @@ -39,8 +45,9 @@ import java.io.IOException; * Manages quick launch shortcuts by: *
  • Keeping the local copy in sync with the database (this is an observer) *
  • Returning a shortcut-matching intent to clients + *
  • Returning particular kind of application intent by special key. */ -class ShortcutManager { +class ModifierShortcutManager { private static final String TAG = "ShortcutManager"; private static final String TAG_BOOKMARKS = "bookmarks"; @@ -52,12 +59,39 @@ class ShortcutManager { private static final String ATTRIBUTE_CATEGORY = "category"; private static final String ATTRIBUTE_SHIFT = "shift"; - private final SparseArray mShortcuts = new SparseArray<>(); + private final SparseArray mIntentShortcuts = new SparseArray<>(); private final SparseArray mShiftShortcuts = new SparseArray<>(); + private LongSparseArray mShortcutKeyServices = new LongSparseArray<>(); + + /* Table of Application Launch keys. Maps from key codes to intent categories. + * + * These are special keys that are used to launch particular kinds of applications, + * such as a web browser. HID defines nearly a hundred of them in the Consumer (0x0C) + * usage page. We don't support quite that many yet... + */ + static SparseArray sApplicationLaunchKeyCategories; + static { + sApplicationLaunchKeyCategories = new SparseArray(); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR); + } + private final Context mContext; - - public ShortcutManager(Context context) { + private boolean mSearchKeyShortcutPending = false; + private boolean mConsumeSearchKeyUp = true; + + ModifierShortcutManager(Context context) { mContext = context; loadShortcuts(); } @@ -70,19 +104,19 @@ class ShortcutManager { *

    * This will first try an exact match (with modifiers), and then try a * match without modifiers (primary character on a key). - * + * * @param kcm The key character map of the device on which the key was pressed. * @param keyCode The key code. * @param metaState The meta state, omitting any modifiers that were used * to invoke the shortcut. * @return The intent that matches the shortcut, or null if not found. */ - public Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) { + private Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) { ShortcutInfo shortcut = null; // If the Shift key is pressed, then search for the shift shortcuts. boolean isShiftOn = (metaState & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON; - SparseArray shortcutMap = isShiftOn ? mShiftShortcuts : mShortcuts; + SparseArray shortcutMap = isShiftOn ? mShiftShortcuts : mIntentShortcuts; // First try the exact keycode (with modifiers). int shortcutChar = kcm.get(keyCode, metaState); @@ -176,7 +210,7 @@ class ShortcutManager { if (isShiftShortcut) { mShiftShortcuts.put(shortcutChar, shortcut); } else { - mShortcuts.put(shortcutChar, shortcut); + mIntentShortcuts.put(shortcutChar, shortcut); } } } catch (XmlPullParserException e) { @@ -186,11 +220,159 @@ class ShortcutManager { } } + void registerShortcutKey(long shortcutCode, IShortcutService shortcutService) + throws RemoteException { + IShortcutService service = mShortcutKeyServices.get(shortcutCode); + if (service != null && service.asBinder().pingBinder()) { + throw new RemoteException("Key already exists."); + } + + mShortcutKeyServices.put(shortcutCode, shortcutService); + } + + /** + * Handle the shortcut to {@link IShortcutService} + * @param keyCode The key code of the event. + * @param metaState The meta key modifier state. + * @return True if invoked the shortcut, otherwise false. + */ + private boolean handleShortcutService(int keyCode, int metaState) { + long shortcutCode = keyCode; + if ((metaState & KeyEvent.META_CTRL_ON) != 0) { + shortcutCode |= ((long) KeyEvent.META_CTRL_ON) << Integer.SIZE; + } + + if ((metaState & KeyEvent.META_ALT_ON) != 0) { + shortcutCode |= ((long) KeyEvent.META_ALT_ON) << Integer.SIZE; + } + + if ((metaState & KeyEvent.META_SHIFT_ON) != 0) { + shortcutCode |= ((long) KeyEvent.META_SHIFT_ON) << Integer.SIZE; + } + + if ((metaState & KeyEvent.META_META_ON) != 0) { + shortcutCode |= ((long) KeyEvent.META_META_ON) << Integer.SIZE; + } + + IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode); + if (shortcutService != null) { + try { + shortcutService.notifyShortcutKeyPressed(shortcutCode); + } catch (RemoteException e) { + mShortcutKeyServices.delete(shortcutCode); + } + return true; + } + return false; + } + + /** + * Handle the shortcut to {@link Intent} + * + * @param kcm the {@link KeyCharacterMap} associated with the keyboard device. + * @param keyCode The key code of the event. + * @param metaState The meta key modifier state. + * @return True if invoked the shortcut, otherwise false. + */ + private boolean handleIntentShortcut(KeyCharacterMap kcm, int keyCode, int metaState) { + // Shortcuts are invoked through Search+key, so intercept those here + // Any printing key that is chorded with Search should be consumed + // even if no shortcut was invoked. This prevents text from being + // inadvertently inserted when using a keyboard that has built-in macro + // shortcut keys (that emit Search+x) and some of them are not registered. + if (mSearchKeyShortcutPending) { + if (kcm.isPrintingKey(keyCode)) { + mConsumeSearchKeyUp = true; + mSearchKeyShortcutPending = false; + } else { + return false; + } + } else if ((metaState & KeyEvent.META_META_MASK) != 0) { + // Invoke shortcuts using Meta. + metaState &= ~KeyEvent.META_META_MASK; + } else { + // Handle application launch keys. + String category = sApplicationLaunchKeyCategories.get(keyCode); + if (category != null) { + Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + mContext.startActivityAsUser(intent, UserHandle.CURRENT); + } catch (ActivityNotFoundException ex) { + Slog.w(TAG, "Dropping application launch key because " + + "the activity to which it is registered was not found: " + + "keyCode=" + KeyEvent.keyCodeToString(keyCode) + "," + + " category=" + category, ex); + } + return true; + } else { + return false; + } + } + + final Intent shortcutIntent = getIntent(kcm, keyCode, metaState); + if (shortcutIntent != null) { + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + mContext.startActivityAsUser(shortcutIntent, UserHandle.CURRENT); + } catch (ActivityNotFoundException ex) { + Slog.w(TAG, "Dropping shortcut key combination because " + + "the activity to which it is registered was not found: " + + "META+ or SEARCH" + KeyEvent.keyCodeToString(keyCode), ex); + } + return true; + } + return false; + } + + /** + * Handle the shortcut from {@link KeyEvent} + * + * @param event Description of the key event. + * @return True if invoked the shortcut, otherwise false. + */ + boolean interceptKey(KeyEvent event) { + if (event.getRepeatCount() != 0) { + return false; + } + + final int metaState = event.getModifiers(); + final int keyCode = event.getKeyCode(); + if (keyCode == KeyEvent.KEYCODE_SEARCH) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + mSearchKeyShortcutPending = true; + mConsumeSearchKeyUp = false; + } else { + mSearchKeyShortcutPending = false; + if (mConsumeSearchKeyUp) { + mConsumeSearchKeyUp = false; + return true; + } + } + return false; + } + + if (event.getAction() != KeyEvent.ACTION_DOWN) { + return false; + } + + final KeyCharacterMap kcm = event.getKeyCharacterMap(); + if (handleIntentShortcut(kcm, keyCode, metaState)) { + return true; + } + + if (handleShortcutService(keyCode, metaState)) { + return true; + } + + return false; + } + private static final class ShortcutInfo { public final String title; public final Intent intent; - public ShortcutInfo(String title, Intent intent) { + ShortcutInfo(String title, Intent intent) { this.title = title; this.intent = intent; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index c50efc70a6bc4..bce218f8a74bc 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -163,7 +163,6 @@ import android.service.vr.IPersistentVrStateCallbacks; import android.speech.RecognizerIntent; import android.telecom.TelecomManager; import android.util.Log; -import android.util.LongSparseArray; import android.util.MutableBoolean; import android.util.PrintWriterPrinter; import android.util.Slog; @@ -318,29 +317,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { */ private boolean mKeyguardDrawnOnce; - /* Table of Application Launch keys. Maps from key codes to intent categories. - * - * These are special keys that are used to launch particular kinds of applications, - * such as a web browser. HID defines nearly a hundred of them in the Consumer (0x0C) - * usage page. We don't support quite that many yet... - */ - static SparseArray sApplicationLaunchKeyCategories; - static { - sApplicationLaunchKeyCategories = new SparseArray(); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR); - } - /** Amount of time (in milliseconds) to wait for windows drawn before powering on. */ static final int WAITING_FOR_DRAWN_TIMEOUT = 1000; @@ -419,8 +395,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mSafeMode; private WindowState mKeyguardCandidate = null; - private LongSparseArray mShortcutKeyServices = new LongSparseArray<>(); - // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key. // This is for car dock and this is updated from resource. private boolean mEnableCarDockHomeCapture = true; @@ -516,8 +490,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { Intent mCarDockIntent; Intent mDeskDockIntent; Intent mVrHeadsetHomeIntent; - boolean mSearchKeyShortcutPending; - boolean mConsumeSearchKeyUp; boolean mPendingMetaAction; boolean mPendingCapsLockToggle; @@ -578,7 +550,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int BRIGHTNESS_STEPS = 10; SettingsObserver mSettingsObserver; - ShortcutManager mShortcutManager; + ModifierShortcutManager mModifierShortcutManager; PowerManager.WakeLock mBroadcastWakeLock; PowerManager.WakeLock mPowerKeyWakeLock; boolean mHavePendingMediaKeyRepeatWithWakeLock; @@ -1772,7 +1744,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler); mSettingsObserver = new SettingsObserver(mHandler); mSettingsObserver.observe(); - mShortcutManager = new ShortcutManager(context); + mModifierShortcutManager = new ModifierShortcutManager(context); mUiMode = context.getResources().getInteger( com.android.internal.R.integer.config_defaultUiModeType); mHomeIntent = new Intent(Intent.ACTION_MAIN, null); @@ -2574,6 +2546,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPendingCapsLockToggle = false; } + if (isUserSetupComplete() && !keyguardOn) { + if (mModifierShortcutManager.interceptKey(event)) { + dismissKeyboardShortcutsMenu(); + mPendingMetaAction = false; + mPendingCapsLockToggle = false; + return key_consumed; + } + } + switch(keyCode) { case KeyEvent.KEYCODE_HOME: // First we always handle the home key here, so applications @@ -2599,20 +2580,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } break; - case KeyEvent.KEYCODE_SEARCH: - if (down) { - if (repeatCount == 0) { - mSearchKeyShortcutPending = true; - mConsumeSearchKeyUp = false; - } - } else { - mSearchKeyShortcutPending = false; - if (mConsumeSearchKeyUp) { - mConsumeSearchKeyUp = false; - return key_consumed; - } - } - return 0; case KeyEvent.KEYCODE_APP_SWITCH: if (!keyguardOn) { if (down && repeatCount == 0) { @@ -2820,114 +2787,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; } - // Shortcuts are invoked through Search+key, so intercept those here - // Any printing key that is chorded with Search should be consumed - // even if no shortcut was invoked. This prevents text from being - // inadvertently inserted when using a keyboard that has built-in macro - // shortcut keys (that emit Search+x) and some of them are not registered. - if (mSearchKeyShortcutPending) { - final KeyCharacterMap kcm = event.getKeyCharacterMap(); - if (kcm.isPrintingKey(keyCode)) { - mConsumeSearchKeyUp = true; - mSearchKeyShortcutPending = false; - if (down && repeatCount == 0 && !keyguardOn) { - Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode, metaState); - if (shortcutIntent != null) { - shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - startActivityAsUser(shortcutIntent, UserHandle.CURRENT); - dismissKeyboardShortcutsMenu(); - } catch (ActivityNotFoundException ex) { - Slog.w(TAG, "Dropping shortcut key combination because " - + "the activity to which it is registered was not found: " - + "SEARCH+" + KeyEvent.keyCodeToString(keyCode), ex); - } - } else { - Slog.i(TAG, "Dropping unregistered shortcut key combination: " - + "SEARCH+" + KeyEvent.keyCodeToString(keyCode)); - } - } - return key_consumed; - } - } - - // Invoke shortcuts using Meta. - if (down && repeatCount == 0 && !keyguardOn - && (metaState & KeyEvent.META_META_ON) != 0) { - final KeyCharacterMap kcm = event.getKeyCharacterMap(); - if (kcm.isPrintingKey(keyCode)) { - Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode, - metaState & ~(KeyEvent.META_META_ON - | KeyEvent.META_META_LEFT_ON | KeyEvent.META_META_RIGHT_ON)); - if (shortcutIntent != null) { - shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - startActivityAsUser(shortcutIntent, UserHandle.CURRENT); - dismissKeyboardShortcutsMenu(); - } catch (ActivityNotFoundException ex) { - Slog.w(TAG, "Dropping shortcut key combination because " - + "the activity to which it is registered was not found: " - + "META+" + KeyEvent.keyCodeToString(keyCode), ex); - } - return key_consumed; - } - } - } - - // Handle application launch keys. - if (down && repeatCount == 0 && !keyguardOn) { - String category = sApplicationLaunchKeyCategories.get(keyCode); - if (category != null) { - Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - startActivityAsUser(intent, UserHandle.CURRENT); - dismissKeyboardShortcutsMenu(); - } catch (ActivityNotFoundException ex) { - Slog.w(TAG, "Dropping application launch key because " - + "the activity to which it is registered was not found: " - + "keyCode=" + keyCode + ", category=" + category, ex); - } - return key_consumed; - } - } - if (isValidGlobalKey(keyCode) && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) { return key_consumed; } - if (down) { - long shortcutCode = keyCode; - if (event.isCtrlPressed()) { - shortcutCode |= ((long) KeyEvent.META_CTRL_ON) << Integer.SIZE; - } - - if (event.isAltPressed()) { - shortcutCode |= ((long) KeyEvent.META_ALT_ON) << Integer.SIZE; - } - - if (event.isShiftPressed()) { - shortcutCode |= ((long) KeyEvent.META_SHIFT_ON) << Integer.SIZE; - } - - if (event.isMetaPressed()) { - shortcutCode |= ((long) KeyEvent.META_META_ON) << Integer.SIZE; - } - - IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode); - if (shortcutService != null) { - try { - if (isUserSetupComplete()) { - shortcutService.notifyShortcutKeyPressed(shortcutCode); - } - } catch (RemoteException e) { - mShortcutKeyServices.delete(shortcutCode); - } - return key_consumed; - } - } - // Reserve all the META modifier combos for system behavior if ((metaState & KeyEvent.META_META_ON) != 0) { return key_consumed; @@ -3113,12 +2977,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService) throws RemoteException { synchronized (mLock) { - IShortcutService service = mShortcutKeyServices.get(shortcutCode); - if (service != null && service.asBinder().pingBinder()) { - throw new RemoteException("Key already exists."); - } - - mShortcutKeyServices.put(shortcutCode, shortcutService); + mModifierShortcutManager.registerShortcutKey(shortcutCode, shortcutService); } }