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
16package com.android.server.policy;
17
18import android.content.ComponentName;
19import android.content.Context;
20import android.content.Intent;
21import android.content.res.Resources;
22import android.content.res.XmlResourceParser;
23import android.os.UserHandle;
24import android.util.Log;
25import android.util.SparseArray;
26import android.view.KeyEvent;
27
28import com.android.internal.util.XmlUtils;
29
30import org.xmlpull.v1.XmlPullParserException;
31
32import java.io.IOException;
33import 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 */
43final class GlobalKeyManager {
44
45    private static final String TAG = "GlobalKeyManager";
46
47    private static final String TAG_GLOBAL_KEYS = "global_keys";
48    private static final String ATTR_VERSION = "version";
49    private static final String TAG_KEY = "key";
50    private static final String ATTR_KEY_CODE = "keyCode";
51    private static final String ATTR_COMPONENT = "component";
52
53    private static final int GLOBAL_KEY_FILE_VERSION = 1;
54
55    private SparseArray<ComponentName> mKeyMapping;
56
57    public GlobalKeyManager(Context context) {
58        mKeyMapping = new SparseArray<ComponentName>();
59        loadGlobalKeys(context);
60    }
61
62    /**
63     * Broadcasts an intent if the keycode is part of the global key mapping.
64     *
65     * @param context context used to broadcast the event
66     * @param keyCode keyCode which triggered this function
67     * @param event keyEvent which trigged this function
68     * @return {@code true} if this was handled
69     */
70    boolean handleGlobalKey(Context context, int keyCode, KeyEvent event) {
71        if (mKeyMapping.size() > 0) {
72            ComponentName component = mKeyMapping.get(keyCode);
73            if (component != null) {
74                Intent intent = new Intent(Intent.ACTION_GLOBAL_BUTTON)
75                        .setComponent(component)
76                        .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
77                        .putExtra(Intent.EXTRA_KEY_EVENT, event);
78                context.sendBroadcastAsUser(intent, UserHandle.CURRENT, null);
79                return true;
80            }
81        }
82        return false;
83    }
84
85    /**
86     * Returns {@code true} if the key will be handled globally.
87     */
88    boolean shouldHandleGlobalKey(int keyCode, KeyEvent event) {
89        return mKeyMapping.get(keyCode) != null;
90    }
91
92    private void loadGlobalKeys(Context context) {
93        XmlResourceParser parser = null;
94        try {
95            parser = context.getResources().getXml(com.android.internal.R.xml.global_keys);
96            XmlUtils.beginDocument(parser, TAG_GLOBAL_KEYS);
97            int version = parser.getAttributeIntValue(null, ATTR_VERSION, 0);
98            if (GLOBAL_KEY_FILE_VERSION == version) {
99                while (true) {
100                    XmlUtils.nextElement(parser);
101                    String element = parser.getName();
102                    if (element == null) {
103                        break;
104                    }
105                    if (TAG_KEY.equals(element)) {
106                        String keyCodeName = parser.getAttributeValue(null, ATTR_KEY_CODE);
107                        String componentName = parser.getAttributeValue(null, ATTR_COMPONENT);
108                        int keyCode = KeyEvent.keyCodeFromString(keyCodeName);
109                        if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
110                            mKeyMapping.put(keyCode, ComponentName.unflattenFromString(
111                                    componentName));
112                        }
113                    }
114                }
115            }
116        } catch (Resources.NotFoundException e) {
117            Log.w(TAG, "global keys file not found", e);
118        } catch (XmlPullParserException e) {
119            Log.w(TAG, "XML parser exception reading global keys file", e);
120        } catch (IOException e) {
121            Log.w(TAG, "I/O exception reading global keys file", e);
122        } finally {
123            if (parser != null) {
124                parser.close();
125            }
126        }
127    }
128
129    public void dump(String prefix, PrintWriter pw) {
130        final int numKeys = mKeyMapping.size();
131        if (numKeys == 0) {
132            pw.print(prefix); pw.println("mKeyMapping.size=0");
133            return;
134        }
135        pw.print(prefix); pw.println("mKeyMapping={");
136        for (int i = 0; i < numKeys; ++i) {
137            pw.print("  ");
138            pw.print(prefix);
139            pw.print(KeyEvent.keyCodeToString(mKeyMapping.keyAt(i)));
140            pw.print("=");
141            pw.println(mKeyMapping.valueAt(i).flattenToString());
142        }
143        pw.print(prefix); pw.println("}");
144    }
145}
146