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 SparseArray<GlobalKeyAction> mKeyMapping;
60     private boolean mBeganFromNonInteractive = false;
61 
GlobalKeyManager(Context context)62     public GlobalKeyManager(Context context) {
63         mKeyMapping = new SparseArray<>();
64         loadGlobalKeys(context);
65     }
66 
67     /**
68      * Broadcasts an intent if the keycode is part of the global key mapping.
69      *
70      * @param context context used to broadcast the event
71      * @param keyCode keyCode which triggered this function
72      * @param event keyEvent which trigged this function
73      * @return {@code true} if this was handled
74      */
handleGlobalKey(Context context, int keyCode, KeyEvent event)75     boolean handleGlobalKey(Context context, int keyCode, KeyEvent event) {
76         if (mKeyMapping.size() > 0) {
77             GlobalKeyAction action = mKeyMapping.get(keyCode);
78             if (action != null) {
79                 final Intent intent = new GlobalKeyIntent(action.mComponentName, event,
80                         mBeganFromNonInteractive).getIntent();
81                 context.sendBroadcastAsUser(intent, UserHandle.CURRENT, null);
82 
83                 if (event.getAction() == KeyEvent.ACTION_UP) {
84                     mBeganFromNonInteractive = false;
85                 }
86                 return true;
87             }
88         }
89         return false;
90     }
91 
92     /**
93      * Returns {@code true} if the key will be handled globally.
94      */
shouldHandleGlobalKey(int keyCode)95     boolean shouldHandleGlobalKey(int keyCode) {
96         return mKeyMapping.get(keyCode) != null;
97     }
98 
99     /**
100      * Returns {@code true} if the key will be handled globally.
101      */
shouldDispatchFromNonInteractive(int keyCode)102     boolean shouldDispatchFromNonInteractive(int keyCode) {
103         final GlobalKeyAction action = mKeyMapping.get(keyCode);
104         if (action == null) {
105             return false;
106         }
107 
108         return action.mDispatchWhenNonInteractive;
109     }
110 
setBeganFromNonInteractive()111     void setBeganFromNonInteractive() {
112         mBeganFromNonInteractive = true;
113     }
114 
115     class GlobalKeyAction {
116         private ComponentName mComponentName;
117         private boolean mDispatchWhenNonInteractive;
GlobalKeyAction(String componentName, String dispatchWhenNonInteractive)118         GlobalKeyAction(String componentName, String dispatchWhenNonInteractive) {
119             mComponentName = ComponentName.unflattenFromString(componentName);
120             mDispatchWhenNonInteractive = Boolean.valueOf(dispatchWhenNonInteractive);
121         }
122     }
123 
loadGlobalKeys(Context context)124     private void loadGlobalKeys(Context context) {
125         XmlResourceParser parser = null;
126         try {
127             parser = context.getResources().getXml(com.android.internal.R.xml.global_keys);
128             XmlUtils.beginDocument(parser, TAG_GLOBAL_KEYS);
129             int version = parser.getAttributeIntValue(null, ATTR_VERSION, 0);
130             if (GLOBAL_KEY_FILE_VERSION == version) {
131                 while (true) {
132                     XmlUtils.nextElement(parser);
133                     String element = parser.getName();
134                     if (element == null) {
135                         break;
136                     }
137                     if (TAG_KEY.equals(element)) {
138                         String keyCodeName = parser.getAttributeValue(null, ATTR_KEY_CODE);
139                         String componentName = parser.getAttributeValue(null, ATTR_COMPONENT);
140                         String dispatchWhenNonInteractive =
141                                 parser.getAttributeValue(null, ATTR_DISPATCH_WHEN_NON_INTERACTIVE);
142                         int keyCode = KeyEvent.keyCodeFromString(keyCodeName);
143                         if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
144                             mKeyMapping.put(keyCode, new GlobalKeyAction(
145                                     componentName, dispatchWhenNonInteractive));
146                         }
147                     }
148                 }
149             }
150         } catch (Resources.NotFoundException e) {
151             Log.w(TAG, "global keys file not found", e);
152         } catch (XmlPullParserException e) {
153             Log.w(TAG, "XML parser exception reading global keys file", e);
154         } catch (IOException e) {
155             Log.w(TAG, "I/O exception reading global keys file", e);
156         } finally {
157             if (parser != null) {
158                 parser.close();
159             }
160         }
161     }
162 
dump(String prefix, PrintWriter pw)163     public void dump(String prefix, PrintWriter pw) {
164         final int numKeys = mKeyMapping.size();
165         if (numKeys == 0) {
166             pw.print(prefix); pw.println("mKeyMapping.size=0");
167             return;
168         }
169         pw.print(prefix); pw.println("mKeyMapping={");
170         for (int i = 0; i < numKeys; ++i) {
171             pw.print("  ");
172             pw.print(prefix);
173             pw.print(KeyEvent.keyCodeToString(mKeyMapping.keyAt(i)));
174             pw.print("=");
175             pw.print(mKeyMapping.valueAt(i).mComponentName.flattenToString());
176             pw.print(",dispatchWhenNonInteractive=");
177             pw.println(mKeyMapping.valueAt(i).mDispatchWhenNonInteractive);
178         }
179         pw.print(prefix); pw.println("}");
180     }
181 }
182