diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 0efd6b0948442..c9b9a1a89238b 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2578,6 +2578,14 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_SHOW_BRIGHTNESS_DIALOG = "android.intent.action.SHOW_BRIGHTNESS_DIALOG"; + /** + * Broadcast Action: A global button was pressed. Includes a single + * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that + * caused the broadcast. + * @hide + */ + public static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 757bbc8f73b05..ced0851cd11ad 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1142,6 +1142,7 @@ + diff --git a/core/res/res/xml/global_keys.xml b/core/res/res/xml/global_keys.xml new file mode 100644 index 0000000000000..8fa6902b0e4c3 --- /dev/null +++ b/core/res/res/xml/global_keys.xml @@ -0,0 +1,31 @@ + + + + + + + + + diff --git a/policy/src/com/android/internal/policy/impl/GlobalKeyManager.java b/policy/src/com/android/internal/policy/impl/GlobalKeyManager.java new file mode 100644 index 0000000000000..3cf7e824b7a71 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/GlobalKeyManager.java @@ -0,0 +1,126 @@ +/* + * + * 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.internal.policy.impl; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.os.UserHandle; +import android.util.Log; +import android.util.SparseArray; +import android.view.KeyEvent; + +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * Stores a mapping of global keys. + *

+ * A global key will NOT go to the foreground application and instead only ever be sent via targeted + * broadcast to the specified component. The action of the intent will be + * {@link Intent#ACTION_GLOBAL_BUTTON} and the KeyEvent will be included in the intent with + * {@link Intent#EXTRA_KEY_EVENT}. + */ +final class GlobalKeyManager { + + private static final String TAG = "GlobalKeyManager"; + + private static final String TAG_GLOBAL_KEYS = "global_keys"; + private static final String ATTR_VERSION = "version"; + private static final String TAG_KEY = "key"; + private static final String ATTR_KEY_CODE = "keyCode"; + private static final String ATTR_COMPONENT = "component"; + + private static final int GLOBAL_KEY_FILE_VERSION = 1; + + private SparseArray mKeyMapping; + + public GlobalKeyManager(Context context) { + mKeyMapping = new SparseArray(); + loadGlobalKeys(context); + } + + /** + * Broadcasts an intent if the keycode is part of the global key mapping. + * + * @param context context used to broadcast the event + * @param keyCode keyCode which triggered this function + * @param event keyEvent which trigged this function + * @return {@code true} if this was handled + */ + boolean handleGlobalKey(Context context, int keyCode, KeyEvent event) { + if (mKeyMapping.size() > 0) { + ComponentName component = mKeyMapping.get(keyCode); + if (component != null) { + Intent intent = new Intent(Intent.ACTION_GLOBAL_BUTTON) + .setComponent(component) + .putExtra(Intent.EXTRA_KEY_EVENT, event); + context.sendBroadcastAsUser(intent, UserHandle.CURRENT, null); + return true; + } + } + return false; + } + + /** + * Returns {@code true} if the key will be handled globally. + */ + boolean shouldHandleGlobalKey(int keyCode, KeyEvent event) { + return mKeyMapping.get(keyCode) != null; + } + + private void loadGlobalKeys(Context context) { + XmlResourceParser parser = null; + try { + parser = context.getResources().getXml(com.android.internal.R.xml.global_keys); + XmlUtils.beginDocument(parser, TAG_GLOBAL_KEYS); + int version = parser.getAttributeIntValue(null, ATTR_VERSION, 0); + if (GLOBAL_KEY_FILE_VERSION == version) { + while (true) { + XmlUtils.nextElement(parser); + String element = parser.getName(); + if (element == null) { + break; + } + if (TAG_KEY.equals(element)) { + String keyCodeName = parser.getAttributeValue(null, ATTR_KEY_CODE); + String componentName = parser.getAttributeValue(null, ATTR_COMPONENT); + int keyCode = KeyEvent.keyCodeFromString(keyCodeName); + if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { + mKeyMapping.put(keyCode, ComponentName.unflattenFromString( + componentName)); + } + } + } + } + } catch (Resources.NotFoundException e) { + Log.w(TAG, "global keys file not found", e); + } catch (XmlPullParserException e) { + Log.w(TAG, "XML parser exception reading global keys file", e); + } catch (IOException e) { + Log.w(TAG, "I/O exception reading global keys file", e); + } finally { + if (parser != null) { + parser.close(); + } + } + } +} diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index bb053251bca2f..49460de385d27 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -441,6 +441,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { PowerManager.WakeLock mBroadcastWakeLock; boolean mHavePendingMediaKeyRepeatWithWakeLock; + // Maps global key codes to the components that will handle them. + private GlobalKeyManager mGlobalKeyManager; + // Fallback actions by key code. private final SparseArray mFallbackActions = new SparseArray(); @@ -898,6 +901,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mScreenshotChordEnabled = mContext.getResources().getBoolean( com.android.internal.R.bool.config_enableScreenshotChord); + mGlobalKeyManager = new GlobalKeyManager(mContext); + // Controls rotation and the like. initializeHdmiState(); @@ -2140,6 +2145,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { return -1; } + if (mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) { + return -1; + } + // Let the application handle the key. return 0; } @@ -3585,6 +3594,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + // If the key would be handled globally, just return the result, don't worry about special + // key processing. + if (mGlobalKeyManager.shouldHandleGlobalKey(keyCode, event)) { + return result; + } + // Handle special keys. switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_DOWN: