blob: ab404dbad91054adae091714eda0bfc5f3ad0699 [file] [log] [blame]
The Android Open Source Project1f838aa2009-03-03 19:32:13 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Jorim Jaggib10e33f2015-02-04 21:57:40 +010017package com.android.server.policy;
The Android Open Source Project1f838aa2009-03-03 19:32:13 -080018
Svetoslav683914b2015-01-15 14:22:26 -080019import android.content.ComponentName;
The Android Open Source Project1f838aa2009-03-03 19:32:13 -080020import android.content.Context;
21import android.content.Intent;
Svetoslav683914b2015-01-15 14:22:26 -080022import android.content.pm.ActivityInfo;
23import android.content.pm.PackageManager;
24import android.content.res.XmlResourceParser;
25import android.text.TextUtils;
The Android Open Source Project1f838aa2009-03-03 19:32:13 -080026import android.util.Log;
27import android.util.SparseArray;
28import android.view.KeyCharacterMap;
Jiaquan Hee7d716d2016-01-13 15:46:52 -080029import android.view.KeyEvent;
Makoto Onuki5ba0d3e2016-04-11 14:03:46 -070030
Svetoslav683914b2015-01-15 14:22:26 -080031import com.android.internal.util.XmlUtils;
Makoto Onuki5ba0d3e2016-04-11 14:03:46 -070032
Svetoslav683914b2015-01-15 14:22:26 -080033import org.xmlpull.v1.XmlPullParser;
34import org.xmlpull.v1.XmlPullParserException;
The Android Open Source Project1f838aa2009-03-03 19:32:13 -080035
Svetoslav683914b2015-01-15 14:22:26 -080036import java.io.IOException;
The Android Open Source Project1f838aa2009-03-03 19:32:13 -080037
38/**
39 * Manages quick launch shortcuts by:
40 * <li> Keeping the local copy in sync with the database (this is an observer)
41 * <li> Returning a shortcut-matching intent to clients
42 */
Svetoslav683914b2015-01-15 14:22:26 -080043class ShortcutManager {
The Android Open Source Project1f838aa2009-03-03 19:32:13 -080044 private static final String TAG = "ShortcutManager";
The Android Open Source Project1f838aa2009-03-03 19:32:13 -080045
Svetoslav683914b2015-01-15 14:22:26 -080046 private static final String TAG_BOOKMARKS = "bookmarks";
47 private static final String TAG_BOOKMARK = "bookmark";
48
49 private static final String ATTRIBUTE_PACKAGE = "package";
50 private static final String ATTRIBUTE_CLASS = "class";
51 private static final String ATTRIBUTE_SHORTCUT = "shortcut";
52 private static final String ATTRIBUTE_CATEGORY = "category";
Jiaquan Hee7d716d2016-01-13 15:46:52 -080053 private static final String ATTRIBUTE_SHIFT = "shift";
Svetoslav683914b2015-01-15 14:22:26 -080054
55 private final SparseArray<ShortcutInfo> mShortcuts = new SparseArray<>();
Jiaquan Hee7d716d2016-01-13 15:46:52 -080056 private final SparseArray<ShortcutInfo> mShiftShortcuts = new SparseArray<>();
Svetoslav683914b2015-01-15 14:22:26 -080057
58 private final Context mContext;
The Android Open Source Project1f838aa2009-03-03 19:32:13 -080059
Svetoslav683914b2015-01-15 14:22:26 -080060 public ShortcutManager(Context context) {
The Android Open Source Project1f838aa2009-03-03 19:32:13 -080061 mContext = context;
Svetoslav683914b2015-01-15 14:22:26 -080062 loadShortcuts();
The Android Open Source Project1f838aa2009-03-03 19:32:13 -080063 }
64
The Android Open Source Project1f838aa2009-03-03 19:32:13 -080065 /**
66 * Gets the shortcut intent for a given keycode+modifier. Make sure you
67 * strip whatever modifier is used for invoking shortcuts (for example,
68 * if 'Sym+A' should invoke a shortcut on 'A', you should strip the
69 * 'Sym' bit from the modifiers before calling this method.
70 * <p>
71 * This will first try an exact match (with modifiers), and then try a
72 * match without modifiers (primary character on a key).
73 *
Jeff Brownc1fb48d2010-12-08 16:52:09 -080074 * @param kcm The key character map of the device on which the key was pressed.
75 * @param keyCode The key code.
76 * @param metaState The meta state, omitting any modifiers that were used
77 * to invoke the shortcut.
The Android Open Source Project1f838aa2009-03-03 19:32:13 -080078 * @return The intent that matches the shortcut, or null if not found.
79 */
Jeff Brownc1fb48d2010-12-08 16:52:09 -080080 public Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) {
Svetoslav683914b2015-01-15 14:22:26 -080081 ShortcutInfo shortcut = null;
Jeff Brown1f245102010-11-18 20:53:46 -080082
Makoto Onukicdc78f72016-03-21 15:47:52 -070083 // If the Shift key is pressed, then search for the shift shortcuts.
Jiaquan Hee7d716d2016-01-13 15:46:52 -080084 boolean isShiftOn = (metaState & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON;
85 SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mShortcuts;
86
Jeff Brownc1fb48d2010-12-08 16:52:09 -080087 // First try the exact keycode (with modifiers).
Svetoslav683914b2015-01-15 14:22:26 -080088 int shortcutChar = kcm.get(keyCode, metaState);
89 if (shortcutChar != 0) {
Jiaquan Hee7d716d2016-01-13 15:46:52 -080090 shortcut = shortcutMap.get(shortcutChar);
Jeff Brownc1fb48d2010-12-08 16:52:09 -080091 }
92
93 // Next try the primary character on that key.
Svetoslav683914b2015-01-15 14:22:26 -080094 if (shortcut == null) {
95 shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
96 if (shortcutChar != 0) {
Jiaquan Hee7d716d2016-01-13 15:46:52 -080097 shortcut = shortcutMap.get(shortcutChar);
Jeff Brownc1fb48d2010-12-08 16:52:09 -080098 }
99 }
100
Svetoslav683914b2015-01-15 14:22:26 -0800101 return (shortcut != null) ? shortcut.intent : null;
The Android Open Source Project1f838aa2009-03-03 19:32:13 -0800102 }
103
Svetoslav683914b2015-01-15 14:22:26 -0800104 private void loadShortcuts() {
105 PackageManager packageManager = mContext.getPackageManager();
106 try {
107 XmlResourceParser parser = mContext.getResources().getXml(
108 com.android.internal.R.xml.bookmarks);
109 XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
110
111 while (true) {
112 XmlUtils.nextElement(parser);
113
114 if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
115 break;
116 }
117
118 if (!TAG_BOOKMARK.equals(parser.getName())) {
119 break;
120 }
121
122 String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE);
123 String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS);
124 String shortcutName = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
125 String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
Jiaquan Hee7d716d2016-01-13 15:46:52 -0800126 String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
Svetoslav683914b2015-01-15 14:22:26 -0800127
128 if (TextUtils.isEmpty(shortcutName)) {
129 Log.w(TAG, "Unable to get shortcut for: " + packageName + "/" + className);
130 continue;
131 }
132
133 final int shortcutChar = shortcutName.charAt(0);
Jiaquan Hee7d716d2016-01-13 15:46:52 -0800134 final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
Svetoslav683914b2015-01-15 14:22:26 -0800135
136 final Intent intent;
137 final String title;
138 if (packageName != null && className != null) {
139 ActivityInfo info = null;
140 ComponentName componentName = new ComponentName(packageName, className);
141 try {
Jeff Sharkey2a9e3f82015-12-18 10:57:58 -0700142 info = packageManager.getActivityInfo(componentName,
Jeff Sharkey8a372a02016-03-16 16:25:45 -0600143 PackageManager.MATCH_DIRECT_BOOT_AWARE
144 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
145 | PackageManager.MATCH_UNINSTALLED_PACKAGES);
Svetoslav683914b2015-01-15 14:22:26 -0800146 } catch (PackageManager.NameNotFoundException e) {
147 String[] packages = packageManager.canonicalToCurrentPackageNames(
148 new String[] { packageName });
149 componentName = new ComponentName(packages[0], className);
150 try {
Jeff Sharkey2a9e3f82015-12-18 10:57:58 -0700151 info = packageManager.getActivityInfo(componentName,
Jeff Sharkey8a372a02016-03-16 16:25:45 -0600152 PackageManager.MATCH_DIRECT_BOOT_AWARE
153 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
154 | PackageManager.MATCH_UNINSTALLED_PACKAGES);
Svetoslav683914b2015-01-15 14:22:26 -0800155 } catch (PackageManager.NameNotFoundException e1) {
156 Log.w(TAG, "Unable to add bookmark: " + packageName
157 + "/" + className, e);
158 continue;
159 }
160 }
161
162 intent = new Intent(Intent.ACTION_MAIN);
163 intent.addCategory(Intent.CATEGORY_LAUNCHER);
164 intent.setComponent(componentName);
165 title = info.loadLabel(packageManager).toString();
166 } else if (categoryName != null) {
167 intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
168 title = "";
169 } else {
170 Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
171 + ": missing package/class or category attributes");
172 continue;
173 }
174
175 ShortcutInfo shortcut = new ShortcutInfo(title, intent);
Jiaquan Hee7d716d2016-01-13 15:46:52 -0800176 if (isShiftShortcut) {
177 mShiftShortcuts.put(shortcutChar, shortcut);
178 } else {
179 mShortcuts.put(shortcutChar, shortcut);
180 }
Svetoslav683914b2015-01-15 14:22:26 -0800181 }
182 } catch (XmlPullParserException e) {
183 Log.w(TAG, "Got exception parsing bookmarks.", e);
184 } catch (IOException e) {
185 Log.w(TAG, "Got exception parsing bookmarks.", e);
186 }
187 }
188
189 private static final class ShortcutInfo {
190 public final String title;
191 public final Intent intent;
192
193 public ShortcutInfo(String title, Intent intent) {
194 this.title = title;
195 this.intent = intent;
196 }
197 }
The Android Open Source Project1f838aa2009-03-03 19:32:13 -0800198}