• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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                         int keyCode = KeyEvent.keyCodeFromString(keyCodeName);
141                         if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
142                             mKeyMapping.put(keyCode, new GlobalKeyAction(
143                                     componentName, dispatchWhenNonInteractive));
144                         }
145                     }
146                 }
147             }
148         } catch (Resources.NotFoundException e) {
149             Log.w(TAG, "global keys file not found", e);
150         } catch (XmlPullParserException e) {
151             Log.w(TAG, "XML parser exception reading global keys file", e);
152         } catch (IOException e) {
153             Log.w(TAG, "I/O exception reading global keys file", e);
154         }
155     }
156 
dump(String prefix, PrintWriter pw)157     public void dump(String prefix, PrintWriter pw) {
158         final int numKeys = mKeyMapping.size();
159         if (numKeys == 0) {
160             pw.print(prefix); pw.println("mKeyMapping.size=0");
161             return;
162         }
163         pw.print(prefix); pw.println("mKeyMapping={");
164         for (int i = 0; i < numKeys; ++i) {
165             pw.print("  ");
166             pw.print(prefix);
167             pw.print(KeyEvent.keyCodeToString(mKeyMapping.keyAt(i)));
168             pw.print("=");
169             pw.print(mKeyMapping.valueAt(i).mComponentName.flattenToString());
170             pw.print(",dispatchWhenNonInteractive=");
171             pw.println(mKeyMapping.valueAt(i).mDispatchWhenNonInteractive);
172         }
173         pw.print(prefix); pw.println("}");
174     }
175 }
176