| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.policy; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ActivityInfo; |
| import android.content.pm.PackageManager; |
| import android.content.res.XmlResourceParser; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.util.SparseArray; |
| import android.view.KeyCharacterMap; |
| import android.view.KeyEvent; |
| import com.android.internal.util.XmlUtils; |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import java.io.IOException; |
| |
| /** |
| * Manages quick launch shortcuts by: |
| * <li> Keeping the local copy in sync with the database (this is an observer) |
| * <li> Returning a shortcut-matching intent to clients |
| */ |
| class ShortcutManager { |
| private static final String TAG = "ShortcutManager"; |
| |
| private static final String TAG_BOOKMARKS = "bookmarks"; |
| private static final String TAG_BOOKMARK = "bookmark"; |
| |
| private static final String ATTRIBUTE_PACKAGE = "package"; |
| private static final String ATTRIBUTE_CLASS = "class"; |
| private static final String ATTRIBUTE_SHORTCUT = "shortcut"; |
| private static final String ATTRIBUTE_CATEGORY = "category"; |
| private static final String ATTRIBUTE_SHIFT = "shift"; |
| |
| private final SparseArray<ShortcutInfo> mShortcuts = new SparseArray<>(); |
| private final SparseArray<ShortcutInfo> mShiftShortcuts = new SparseArray<>(); |
| |
| private final Context mContext; |
| |
| public ShortcutManager(Context context) { |
| mContext = context; |
| loadShortcuts(); |
| } |
| |
| /** |
| * Gets the shortcut intent for a given keycode+modifier. Make sure you |
| * strip whatever modifier is used for invoking shortcuts (for example, |
| * if 'Sym+A' should invoke a shortcut on 'A', you should strip the |
| * 'Sym' bit from the modifiers before calling this method. |
| * <p> |
| * This will first try an exact match (with modifiers), and then try a |
| * match without modifiers (primary character on a key). |
| * |
| * @param kcm The key character map of the device on which the key was pressed. |
| * @param keyCode The key code. |
| * @param metaState The meta state, omitting any modifiers that were used |
| * to invoke the shortcut. |
| * @return The intent that matches the shortcut, or null if not found. |
| */ |
| public Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) { |
| ShortcutInfo shortcut = null; |
| |
| // If the Shift key is preesed, then search for the shift shortcuts. |
| boolean isShiftOn = (metaState & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON; |
| SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mShortcuts; |
| |
| // First try the exact keycode (with modifiers). |
| int shortcutChar = kcm.get(keyCode, metaState); |
| if (shortcutChar != 0) { |
| shortcut = shortcutMap.get(shortcutChar); |
| } |
| |
| // Next try the primary character on that key. |
| if (shortcut == null) { |
| shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode)); |
| if (shortcutChar != 0) { |
| shortcut = shortcutMap.get(shortcutChar); |
| } |
| } |
| |
| return (shortcut != null) ? shortcut.intent : null; |
| } |
| |
| private void loadShortcuts() { |
| PackageManager packageManager = mContext.getPackageManager(); |
| try { |
| XmlResourceParser parser = mContext.getResources().getXml( |
| com.android.internal.R.xml.bookmarks); |
| XmlUtils.beginDocument(parser, TAG_BOOKMARKS); |
| |
| while (true) { |
| XmlUtils.nextElement(parser); |
| |
| if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { |
| break; |
| } |
| |
| if (!TAG_BOOKMARK.equals(parser.getName())) { |
| break; |
| } |
| |
| String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE); |
| String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS); |
| String shortcutName = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT); |
| String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY); |
| String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT); |
| |
| if (TextUtils.isEmpty(shortcutName)) { |
| Log.w(TAG, "Unable to get shortcut for: " + packageName + "/" + className); |
| continue; |
| } |
| |
| final int shortcutChar = shortcutName.charAt(0); |
| final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true")); |
| |
| final Intent intent; |
| final String title; |
| if (packageName != null && className != null) { |
| ActivityInfo info = null; |
| ComponentName componentName = new ComponentName(packageName, className); |
| try { |
| info = packageManager.getActivityInfo(componentName, |
| PackageManager.MATCH_DIRECT_BOOT_AWARE |
| | PackageManager.MATCH_DIRECT_BOOT_UNAWARE |
| | PackageManager.MATCH_UNINSTALLED_PACKAGES); |
| } catch (PackageManager.NameNotFoundException e) { |
| String[] packages = packageManager.canonicalToCurrentPackageNames( |
| new String[] { packageName }); |
| componentName = new ComponentName(packages[0], className); |
| try { |
| info = packageManager.getActivityInfo(componentName, |
| PackageManager.MATCH_DIRECT_BOOT_AWARE |
| | PackageManager.MATCH_DIRECT_BOOT_UNAWARE |
| | PackageManager.MATCH_UNINSTALLED_PACKAGES); |
| } catch (PackageManager.NameNotFoundException e1) { |
| Log.w(TAG, "Unable to add bookmark: " + packageName |
| + "/" + className, e); |
| continue; |
| } |
| } |
| |
| intent = new Intent(Intent.ACTION_MAIN); |
| intent.addCategory(Intent.CATEGORY_LAUNCHER); |
| intent.setComponent(componentName); |
| title = info.loadLabel(packageManager).toString(); |
| } else if (categoryName != null) { |
| intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName); |
| title = ""; |
| } else { |
| Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName |
| + ": missing package/class or category attributes"); |
| continue; |
| } |
| |
| ShortcutInfo shortcut = new ShortcutInfo(title, intent); |
| if (isShiftShortcut) { |
| mShiftShortcuts.put(shortcutChar, shortcut); |
| } else { |
| mShortcuts.put(shortcutChar, shortcut); |
| } |
| } |
| } catch (XmlPullParserException e) { |
| Log.w(TAG, "Got exception parsing bookmarks.", e); |
| } catch (IOException e) { |
| Log.w(TAG, "Got exception parsing bookmarks.", e); |
| } |
| } |
| |
| private static final class ShortcutInfo { |
| public final String title; |
| public final Intent intent; |
| |
| public ShortcutInfo(String title, Intent intent) { |
| this.title = title; |
| this.intent = intent; |
| } |
| } |
| } |