/*
 * 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_ENCRYPTION_AWARE_AND_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_ENCRYPTION_AWARE_AND_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;
        }
    }
}
