1/* 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 17package com.android.server.policy; 18 19import android.content.ComponentName; 20import android.content.Context; 21import android.content.Intent; 22import android.content.pm.ActivityInfo; 23import android.content.pm.PackageManager; 24import android.content.res.XmlResourceParser; 25import android.text.TextUtils; 26import android.util.Log; 27import android.util.SparseArray; 28import android.view.KeyCharacterMap; 29import android.view.KeyEvent; 30 31import com.android.internal.util.XmlUtils; 32 33import org.xmlpull.v1.XmlPullParser; 34import org.xmlpull.v1.XmlPullParserException; 35 36import java.io.IOException; 37 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 */ 43class ShortcutManager { 44 private static final String TAG = "ShortcutManager"; 45 46 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"; 53 private static final String ATTRIBUTE_SHIFT = "shift"; 54 55 private final SparseArray<ShortcutInfo> mShortcuts = new SparseArray<>(); 56 private final SparseArray<ShortcutInfo> mShiftShortcuts = new SparseArray<>(); 57 58 private final Context mContext; 59 60 public ShortcutManager(Context context) { 61 mContext = context; 62 loadShortcuts(); 63 } 64 65 /** 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 * 74 * @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. 78 * @return The intent that matches the shortcut, or null if not found. 79 */ 80 public Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) { 81 ShortcutInfo shortcut = null; 82 83 // If the Shift key is pressed, then search for the shift shortcuts. 84 boolean isShiftOn = (metaState & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON; 85 SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mShortcuts; 86 87 // First try the exact keycode (with modifiers). 88 int shortcutChar = kcm.get(keyCode, metaState); 89 if (shortcutChar != 0) { 90 shortcut = shortcutMap.get(shortcutChar); 91 } 92 93 // Next try the primary character on that key. 94 if (shortcut == null) { 95 shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode)); 96 if (shortcutChar != 0) { 97 shortcut = shortcutMap.get(shortcutChar); 98 } 99 } 100 101 return (shortcut != null) ? shortcut.intent : null; 102 } 103 104 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); 126 String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT); 127 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); 134 final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true")); 135 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 { 142 info = packageManager.getActivityInfo(componentName, 143 PackageManager.MATCH_DIRECT_BOOT_AWARE 144 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 145 | PackageManager.MATCH_UNINSTALLED_PACKAGES); 146 } catch (PackageManager.NameNotFoundException e) { 147 String[] packages = packageManager.canonicalToCurrentPackageNames( 148 new String[] { packageName }); 149 componentName = new ComponentName(packages[0], className); 150 try { 151 info = packageManager.getActivityInfo(componentName, 152 PackageManager.MATCH_DIRECT_BOOT_AWARE 153 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 154 | PackageManager.MATCH_UNINSTALLED_PACKAGES); 155 } 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); 176 if (isShiftShortcut) { 177 mShiftShortcuts.put(shortcutChar, shortcut); 178 } else { 179 mShortcuts.put(shortcutChar, shortcut); 180 } 181 } 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 } 198} 199