Home | History | Annotate | Download | only in policy
      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 
     17 package com.android.server.policy;
     18 
     19 import android.content.ComponentName;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.pm.ActivityInfo;
     23 import android.content.pm.PackageManager;
     24 import android.content.res.XmlResourceParser;
     25 import android.text.TextUtils;
     26 import android.util.Log;
     27 import android.util.SparseArray;
     28 import android.view.KeyCharacterMap;
     29 import android.view.KeyEvent;
     30 
     31 import com.android.internal.util.XmlUtils;
     32 
     33 import org.xmlpull.v1.XmlPullParser;
     34 import org.xmlpull.v1.XmlPullParserException;
     35 
     36 import 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  */
     43 class 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