1 /* 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package com.android.server.policy; 17 18 import android.content.ComponentName; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.res.Resources; 22 import android.content.res.XmlResourceParser; 23 import android.os.UserHandle; 24 import android.util.Log; 25 import android.util.SparseArray; 26 import android.view.KeyEvent; 27 28 import com.android.internal.util.XmlUtils; 29 30 import org.xmlpull.v1.XmlPullParserException; 31 32 import java.io.IOException; 33 import java.io.PrintWriter; 34 35 /** 36 * Stores a mapping of global keys. 37 * <p> 38 * A global key will NOT go to the foreground application and instead only ever be sent via targeted 39 * broadcast to the specified component. The action of the intent will be 40 * {@link Intent#ACTION_GLOBAL_BUTTON} and the KeyEvent will be included in the intent with 41 * {@link Intent#EXTRA_KEY_EVENT}. 42 * 43 * Use {@link GlobalKeyIntent} to get detail information from received {@link Intent}, includes 44 * {@link KeyEvent} and the information about if the key is dispatched from non-interactive mode. 45 */ 46 final class GlobalKeyManager { 47 48 private static final String TAG = "GlobalKeyManager"; 49 50 private static final String TAG_GLOBAL_KEYS = "global_keys"; 51 private static final String ATTR_VERSION = "version"; 52 private static final String TAG_KEY = "key"; 53 private static final String ATTR_KEY_CODE = "keyCode"; 54 private static final String ATTR_COMPONENT = "component"; 55 private static final String ATTR_DISPATCH_WHEN_NON_INTERACTIVE = "dispatchWhenNonInteractive"; 56 57 private static final int GLOBAL_KEY_FILE_VERSION = 1; 58 59 private final SparseArray<GlobalKeyAction> mKeyMapping = new SparseArray<>(); 60 private boolean mBeganFromNonInteractive = false; 61 GlobalKeyManager(Context context)62 public GlobalKeyManager(Context context) { 63 loadGlobalKeys(context); 64 } 65 66 /** 67 * Broadcasts an intent if the keycode is part of the global key mapping. 68 * 69 * @param context context used to broadcast the event 70 * @param keyCode keyCode which triggered this function 71 * @param event keyEvent which triggered this function 72 * @return {@code true} if this was handled 73 */ handleGlobalKey(Context context, int keyCode, KeyEvent event)74 boolean handleGlobalKey(Context context, int keyCode, KeyEvent event) { 75 if (mKeyMapping.size() > 0) { 76 GlobalKeyAction action = mKeyMapping.get(keyCode); 77 if (action != null) { 78 final Intent intent = new GlobalKeyIntent(action.mComponentName, event, 79 mBeganFromNonInteractive).getIntent(); 80 context.sendBroadcastAsUser(intent, UserHandle.CURRENT, null); 81 82 if (event.getAction() == KeyEvent.ACTION_UP) { 83 mBeganFromNonInteractive = false; 84 } 85 return true; 86 } 87 } 88 return false; 89 } 90 91 /** 92 * Returns {@code true} if the key will be handled globally. 93 */ shouldHandleGlobalKey(int keyCode)94 boolean shouldHandleGlobalKey(int keyCode) { 95 return mKeyMapping.get(keyCode) != null; 96 } 97 98 /** 99 * Returns {@code true} if the key will be handled globally. 100 */ shouldDispatchFromNonInteractive(int keyCode)101 boolean shouldDispatchFromNonInteractive(int keyCode) { 102 final GlobalKeyAction action = mKeyMapping.get(keyCode); 103 if (action == null) { 104 return false; 105 } 106 107 return action.mDispatchWhenNonInteractive; 108 } 109 setBeganFromNonInteractive()110 void setBeganFromNonInteractive() { 111 mBeganFromNonInteractive = true; 112 } 113 114 class GlobalKeyAction { 115 private final ComponentName mComponentName; 116 private final boolean mDispatchWhenNonInteractive; GlobalKeyAction(String componentName, String dispatchWhenNonInteractive)117 GlobalKeyAction(String componentName, String dispatchWhenNonInteractive) { 118 mComponentName = ComponentName.unflattenFromString(componentName); 119 mDispatchWhenNonInteractive = Boolean.parseBoolean(dispatchWhenNonInteractive); 120 } 121 } 122 loadGlobalKeys(Context context)123 private void loadGlobalKeys(Context context) { 124 try (XmlResourceParser parser = context.getResources().getXml( 125 com.android.internal.R.xml.global_keys)) { 126 XmlUtils.beginDocument(parser, TAG_GLOBAL_KEYS); 127 int version = parser.getAttributeIntValue(null, ATTR_VERSION, 0); 128 if (GLOBAL_KEY_FILE_VERSION == version) { 129 while (true) { 130 XmlUtils.nextElement(parser); 131 String element = parser.getName(); 132 if (element == null) { 133 break; 134 } 135 if (TAG_KEY.equals(element)) { 136 String keyCodeName = parser.getAttributeValue(null, ATTR_KEY_CODE); 137 String componentName = parser.getAttributeValue(null, ATTR_COMPONENT); 138 String dispatchWhenNonInteractive = 139 parser.getAttributeValue(null, ATTR_DISPATCH_WHEN_NON_INTERACTIVE); 140 if (keyCodeName == null || componentName == null) { 141 Log.wtf(TAG, "Failed to parse global keys entry: " + parser.getText()); 142 continue; 143 } 144 int keyCode = KeyEvent.keyCodeFromString(keyCodeName); 145 if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { 146 mKeyMapping.put(keyCode, new GlobalKeyAction( 147 componentName, dispatchWhenNonInteractive)); 148 } else { 149 Log.wtf(TAG, "Global keys entry does not map to a valid key code: " 150 + keyCodeName); 151 } 152 } 153 } 154 } 155 } catch (Resources.NotFoundException e) { 156 Log.wtf(TAG, "global keys file not found", e); 157 } catch (XmlPullParserException e) { 158 Log.wtf(TAG, "XML parser exception reading global keys file", e); 159 } catch (IOException e) { 160 Log.e(TAG, "I/O exception reading global keys file", e); 161 } 162 } 163 dump(String prefix, PrintWriter pw)164 public void dump(String prefix, PrintWriter pw) { 165 final int numKeys = mKeyMapping.size(); 166 if (numKeys == 0) { 167 pw.print(prefix); pw.println("mKeyMapping.size=0"); 168 return; 169 } 170 pw.print(prefix); pw.println("mKeyMapping={"); 171 for (int i = 0; i < numKeys; ++i) { 172 pw.print(" "); 173 pw.print(prefix); 174 pw.print(KeyEvent.keyCodeToString(mKeyMapping.keyAt(i))); 175 pw.print("="); 176 pw.print(mKeyMapping.valueAt(i).mComponentName.flattenToString()); 177 pw.print(",dispatchWhenNonInteractive="); 178 pw.println(mKeyMapping.valueAt(i).mDispatchWhenNonInteractive); 179 } 180 pw.print(prefix); pw.println("}"); 181 } 182 } 183