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