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