content: /*
   * 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 pressed, 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;
          }
      }
  }
  