11f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project/*
21f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project * Copyright (C) 2007 The Android Open Source Project
31f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project *
41f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
51f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project * you may not use this file except in compliance with the License.
61f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project * You may obtain a copy of the License at
71f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project *
81f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
91f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project *
101f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
111f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
121f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project * See the License for the specific language governing permissions and
141f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project * limitations under the License.
151f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project */
161f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project
17b10e33ff804a831c71be9303146cea892b9aeb5dJorim Jaggipackage com.android.server.policy;
181f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project
19683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslavimport android.content.ComponentName;
201f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Projectimport android.content.Context;
211f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Projectimport android.content.Intent;
22683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslavimport android.content.pm.ActivityInfo;
23683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslavimport android.content.pm.PackageManager;
24683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslavimport android.content.res.XmlResourceParser;
25683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslavimport android.text.TextUtils;
261f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Projectimport android.util.Log;
271f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Projectimport android.util.SparseArray;
281f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Projectimport android.view.KeyCharacterMap;
29683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslavimport com.android.internal.util.XmlUtils;
30683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslavimport org.xmlpull.v1.XmlPullParser;
31683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslavimport org.xmlpull.v1.XmlPullParserException;
321f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project
33683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslavimport java.io.IOException;
341f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project
351f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project/**
361f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project * Manages quick launch shortcuts by:
371f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project * <li> Keeping the local copy in sync with the database (this is an observer)
381f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project * <li> Returning a shortcut-matching intent to clients
391f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project */
40683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslavclass ShortcutManager {
411f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project    private static final String TAG = "ShortcutManager";
421f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project
43683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav    private static final String TAG_BOOKMARKS = "bookmarks";
44683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav    private static final String TAG_BOOKMARK = "bookmark";
451f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project
46683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav    private static final String ATTRIBUTE_PACKAGE = "package";
47683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav    private static final String ATTRIBUTE_CLASS = "class";
48683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav    private static final String ATTRIBUTE_SHORTCUT = "shortcut";
49683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav    private static final String ATTRIBUTE_CATEGORY = "category";
501f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project
51683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav    private final SparseArray<ShortcutInfo> mShortcuts = new SparseArray<>();
52683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav
53683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav    private final Context mContext;
541f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project
55683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav    public ShortcutManager(Context context) {
56683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        mContext = context;
57683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        loadShortcuts();
58683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav    }
59683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav
601f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project    /**
611f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project     * Gets the shortcut intent for a given keycode+modifier. Make sure you
621f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project     * strip whatever modifier is used for invoking shortcuts (for example,
631f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project     * if 'Sym+A' should invoke a shortcut on 'A', you should strip the
641f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project     * 'Sym' bit from the modifiers before calling this method.
651f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project     * <p>
661f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project     * This will first try an exact match (with modifiers), and then try a
671f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project     * match without modifiers (primary character on a key).
681f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project     *
69c1fb48d77ce2db2ca3af1dd2eeb91dc1b0f81b46Jeff Brown     * @param kcm The key character map of the device on which the key was pressed.
70c1fb48d77ce2db2ca3af1dd2eeb91dc1b0f81b46Jeff Brown     * @param keyCode The key code.
71c1fb48d77ce2db2ca3af1dd2eeb91dc1b0f81b46Jeff Brown     * @param metaState The meta state, omitting any modifiers that were used
72c1fb48d77ce2db2ca3af1dd2eeb91dc1b0f81b46Jeff Brown     * to invoke the shortcut.
731f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project     * @return The intent that matches the shortcut, or null if not found.
741f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project     */
75c1fb48d77ce2db2ca3af1dd2eeb91dc1b0f81b46Jeff Brown    public Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) {
76683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        ShortcutInfo shortcut = null;
771f2451007c660091b7b090c1ea332f9044515d2dJeff Brown
78c1fb48d77ce2db2ca3af1dd2eeb91dc1b0f81b46Jeff Brown        // First try the exact keycode (with modifiers).
79683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        int shortcutChar = kcm.get(keyCode, metaState);
80683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        if (shortcutChar != 0) {
81683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav            shortcut = mShortcuts.get(shortcutChar);
82c1fb48d77ce2db2ca3af1dd2eeb91dc1b0f81b46Jeff Brown        }
83c1fb48d77ce2db2ca3af1dd2eeb91dc1b0f81b46Jeff Brown
84c1fb48d77ce2db2ca3af1dd2eeb91dc1b0f81b46Jeff Brown        // Next try the primary character on that key.
85683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        if (shortcut == null) {
86683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav            shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
87683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav            if (shortcutChar != 0) {
88683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                shortcut = mShortcuts.get(shortcutChar);
89c1fb48d77ce2db2ca3af1dd2eeb91dc1b0f81b46Jeff Brown            }
90c1fb48d77ce2db2ca3af1dd2eeb91dc1b0f81b46Jeff Brown        }
91c1fb48d77ce2db2ca3af1dd2eeb91dc1b0f81b46Jeff Brown
92683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        return (shortcut != null) ? shortcut.intent : null;
931f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project    }
941f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project
95683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav    private void loadShortcuts() {
96683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        PackageManager packageManager = mContext.getPackageManager();
97683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        try {
98683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav            XmlResourceParser parser = mContext.getResources().getXml(
99683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    com.android.internal.R.xml.bookmarks);
100683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav            XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
101683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav
102683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav            while (true) {
103683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                XmlUtils.nextElement(parser);
104683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav
105683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
106683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    break;
107683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                }
108683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav
109683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                if (!TAG_BOOKMARK.equals(parser.getName())) {
110683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    break;
111683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                }
112683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav
113683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE);
114683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS);
115683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                String shortcutName = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
116683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
117683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav
118683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                if (TextUtils.isEmpty(shortcutName)) {
119683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    Log.w(TAG, "Unable to get shortcut for: " + packageName + "/" + className);
120683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    continue;
121683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                }
122683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav
123683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                final int shortcutChar = shortcutName.charAt(0);
124683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav
125683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                final Intent intent;
126683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                final String title;
127683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                if (packageName != null && className != null) {
128683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    ActivityInfo info = null;
129683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    ComponentName componentName = new ComponentName(packageName, className);
130683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    try {
131683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                        info = packageManager.getActivityInfo(componentName, 0);
132683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    } catch (PackageManager.NameNotFoundException e) {
133683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                        String[] packages = packageManager.canonicalToCurrentPackageNames(
134683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                                new String[] { packageName });
135683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                        componentName = new ComponentName(packages[0], className);
136683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                        try {
137683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                            info = packageManager.getActivityInfo(componentName, 0);
138683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                        } catch (PackageManager.NameNotFoundException e1) {
139683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                            Log.w(TAG, "Unable to add bookmark: " + packageName
140683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                                    + "/" + className, e);
141683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                            continue;
142683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                        }
143683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    }
144683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav
145683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    intent = new Intent(Intent.ACTION_MAIN);
146683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    intent.addCategory(Intent.CATEGORY_LAUNCHER);
147683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    intent.setComponent(componentName);
148683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    title = info.loadLabel(packageManager).toString();
149683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                } else if (categoryName != null) {
150683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
151683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    title = "";
152683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                } else {
153683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
154683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                            + ": missing package/class or category attributes");
155683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                    continue;
156683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                }
157683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav
158683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                ShortcutInfo shortcut = new ShortcutInfo(title, intent);
159683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav                mShortcuts.put(shortcutChar, shortcut);
160683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav            }
161683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        } catch (XmlPullParserException e) {
162683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav            Log.w(TAG, "Got exception parsing bookmarks.", e);
163683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        } catch (IOException e) {
164683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav            Log.w(TAG, "Got exception parsing bookmarks.", e);
165683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        }
166683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav    }
167683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav
168683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav    private static final class ShortcutInfo {
169683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        public final String title;
170683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        public final Intent intent;
171683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav
172683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        public ShortcutInfo(String title, Intent intent) {
173683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav            this.title = title;
174683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav            this.intent = intent;
175683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav        }
176683914bfb13908bf380a25258cd45bcf43f13dc9Svetoslav    }
1771f838aaece3cd61d07ab9e41c5c6af761d643324The Android Open Source Project}
178