1/*
2 * Copyright (C) 2008 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
17package com.android.settings.quicklaunch;
18
19import android.app.AlertDialog;
20import android.app.Dialog;
21import android.content.DialogInterface;
22import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.content.pm.ResolveInfo;
25import android.database.ContentObserver;
26import android.database.Cursor;
27import android.os.Bundle;
28import android.os.Handler;
29import android.preference.Preference;
30import android.preference.PreferenceActivity;
31import android.preference.PreferenceGroup;
32import android.preference.PreferenceScreen;
33import android.provider.Settings.Bookmarks;
34import android.util.Log;
35import android.util.SparseArray;
36import android.util.SparseBooleanArray;
37import android.view.KeyCharacterMap;
38import android.view.KeyEvent;
39import android.view.View;
40import android.widget.AdapterView;
41
42import com.android.settings.R;
43
44import java.net.URISyntaxException;
45
46/**
47 * Settings activity for quick launch.
48 * <p>
49 * Shows a list of possible shortcuts, the current application each is bound to,
50 * and allows choosing a new bookmark for a shortcut.
51 */
52public class QuickLaunchSettings extends PreferenceActivity implements
53        AdapterView.OnItemLongClickListener, DialogInterface.OnClickListener {
54
55    private static final String TAG = "QuickLaunchSettings";
56
57    private static final String KEY_SHORTCUT_CATEGORY = "shortcut_category";
58
59    private static final int DIALOG_CLEAR_SHORTCUT = 0;
60
61    private static final int REQUEST_PICK_BOOKMARK = 1;
62
63    private static final int COLUMN_SHORTCUT = 0;
64    private static final int COLUMN_TITLE = 1;
65    private static final int COLUMN_INTENT = 2;
66    private static final String[] sProjection = new String[] {
67            Bookmarks.SHORTCUT, Bookmarks.TITLE, Bookmarks.INTENT
68    };
69    private static final String sShortcutSelection = Bookmarks.SHORTCUT + "=?";
70
71    private Handler mUiHandler = new Handler();
72
73    private static final String DEFAULT_BOOKMARK_FOLDER = "@quicklaunch";
74    /** Cursor for Bookmarks provider. */
75    private Cursor mBookmarksCursor;
76    /** Listens for changes to Bookmarks provider. */
77    private BookmarksObserver mBookmarksObserver;
78    /** Used to keep track of which shortcuts have bookmarks. */
79    private SparseBooleanArray mBookmarkedShortcuts;
80
81    /** Preference category to hold the shortcut preferences. */
82    private PreferenceGroup mShortcutGroup;
83    /** Mapping of a shortcut to its preference. */
84    private SparseArray<ShortcutPreference> mShortcutToPreference;
85
86    /** The bookmark title of the shortcut that is being cleared. */
87    private CharSequence mClearDialogBookmarkTitle;
88    private static final String CLEAR_DIALOG_BOOKMARK_TITLE = "CLEAR_DIALOG_BOOKMARK_TITLE";
89    /** The shortcut that is being cleared. */
90    private char mClearDialogShortcut;
91    private static final String CLEAR_DIALOG_SHORTCUT = "CLEAR_DIALOG_SHORTCUT";
92
93    @Override
94    protected void onCreate(Bundle savedInstanceState) {
95        super.onCreate(savedInstanceState);
96
97        addPreferencesFromResource(R.xml.quick_launch_settings);
98
99        mShortcutGroup = (PreferenceGroup) findPreference(KEY_SHORTCUT_CATEGORY);
100        mShortcutToPreference = new SparseArray<ShortcutPreference>();
101        mBookmarksObserver = new BookmarksObserver(mUiHandler);
102        initShortcutPreferences();
103        mBookmarksCursor = managedQuery(Bookmarks.CONTENT_URI, sProjection, null, null);
104        getListView().setOnItemLongClickListener(this);
105    }
106
107    @Override
108    protected void onResume() {
109        super.onResume();
110        getContentResolver().registerContentObserver(Bookmarks.CONTENT_URI, true,
111                mBookmarksObserver);
112        refreshShortcuts();
113    }
114
115    @Override
116    protected void onPause() {
117        super.onPause();
118        getContentResolver().unregisterContentObserver(mBookmarksObserver);
119    }
120
121    @Override
122    protected void onRestoreInstanceState(Bundle state) {
123        super.onRestoreInstanceState(state);
124
125        // Restore the clear dialog's info
126        mClearDialogBookmarkTitle = state.getString(CLEAR_DIALOG_BOOKMARK_TITLE);
127        mClearDialogShortcut = (char) state.getInt(CLEAR_DIALOG_SHORTCUT, 0);
128    }
129
130    @Override
131    protected void onSaveInstanceState(Bundle outState) {
132        super.onSaveInstanceState(outState);
133
134        // Save the clear dialog's info
135        outState.putCharSequence(CLEAR_DIALOG_BOOKMARK_TITLE, mClearDialogBookmarkTitle);
136        outState.putInt(CLEAR_DIALOG_SHORTCUT, mClearDialogShortcut);
137    }
138
139    @Override
140    protected Dialog onCreateDialog(int id) {
141        switch (id) {
142
143            case DIALOG_CLEAR_SHORTCUT: {
144                // Create the dialog for clearing a shortcut
145                return new AlertDialog.Builder(this)
146                        .setTitle(getString(R.string.quick_launch_clear_dialog_title))
147                        .setIcon(android.R.drawable.ic_dialog_alert)
148                        .setMessage(getString(R.string.quick_launch_clear_dialog_message,
149                                mClearDialogShortcut, mClearDialogBookmarkTitle))
150                        .setPositiveButton(R.string.quick_launch_clear_ok_button, this)
151                        .setNegativeButton(R.string.quick_launch_clear_cancel_button, this)
152                        .create();
153            }
154        }
155
156        return super.onCreateDialog(id);
157    }
158
159    @Override
160    protected void onPrepareDialog(int id, Dialog dialog) {
161        switch (id) {
162
163            case DIALOG_CLEAR_SHORTCUT: {
164                AlertDialog alertDialog = (AlertDialog) dialog;
165                alertDialog.setMessage(getString(R.string.quick_launch_clear_dialog_message,
166                        mClearDialogShortcut, mClearDialogBookmarkTitle));
167            }
168        }
169    }
170
171    private void showClearDialog(ShortcutPreference pref) {
172
173        if (!pref.hasBookmark()) return;
174
175        mClearDialogBookmarkTitle = pref.getTitle();
176        mClearDialogShortcut = pref.getShortcut();
177        showDialog(DIALOG_CLEAR_SHORTCUT);
178    }
179
180    public void onClick(DialogInterface dialog, int which) {
181        if (mClearDialogShortcut > 0 && which == AlertDialog.BUTTON1) {
182            // Clear the shortcut
183            clearShortcut(mClearDialogShortcut);
184        }
185        mClearDialogBookmarkTitle = null;
186        mClearDialogShortcut = 0;
187    }
188
189    private void clearShortcut(char shortcut) {
190        getContentResolver().delete(Bookmarks.CONTENT_URI, sShortcutSelection,
191                new String[] { String.valueOf((int) shortcut) });
192    }
193
194    @Override
195    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
196        if (!(preference instanceof ShortcutPreference)) return false;
197
198        // Open the screen to pick a bookmark for this shortcut
199        ShortcutPreference pref = (ShortcutPreference) preference;
200        Intent intent = new Intent(this, BookmarkPicker.class);
201        intent.putExtra(BookmarkPicker.EXTRA_SHORTCUT, pref.getShortcut());
202        startActivityForResult(intent, REQUEST_PICK_BOOKMARK);
203
204        return true;
205    }
206
207    public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {
208
209        // Open the clear shortcut dialog
210        Preference pref = (Preference) getPreferenceScreen().getRootAdapter().getItem(position);
211        if (!(pref instanceof ShortcutPreference)) return false;
212        showClearDialog((ShortcutPreference) pref);
213        return true;
214    }
215
216    @Override
217    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
218        if (resultCode != RESULT_OK) {
219            return;
220        }
221
222        if (requestCode == REQUEST_PICK_BOOKMARK) {
223
224            // Returned from the 'pick bookmark for this shortcut' screen
225            if (data == null) {
226                Log.w(TAG, "Result from bookmark picker does not have an intent.");
227                return;
228            }
229
230            char shortcut = data.getCharExtra(BookmarkPicker.EXTRA_SHORTCUT, (char) 0);
231            updateShortcut(shortcut, data);
232
233        } else {
234            super.onActivityResult(requestCode, resultCode, data);
235        }
236    }
237
238    private void updateShortcut(char shortcut, Intent intent) {
239        // Update the bookmark for a shortcut
240        // Pass an empty title so it gets resolved each time this bookmark is
241        // displayed (since the locale could change after we insert into the provider).
242        Bookmarks.add(getContentResolver(), intent, "", DEFAULT_BOOKMARK_FOLDER, shortcut, 0);
243    }
244
245    private ShortcutPreference getOrCreatePreference(char shortcut) {
246        ShortcutPreference pref = mShortcutToPreference.get(shortcut);
247        if (pref != null) {
248            return pref;
249        } else {
250            Log.w(TAG, "Unknown shortcut '" + shortcut + "', creating preference anyway");
251            return createPreference(shortcut);
252        }
253    }
254
255    private ShortcutPreference createPreference(char shortcut) {
256        ShortcutPreference pref = new ShortcutPreference(QuickLaunchSettings.this, shortcut);
257        mShortcutGroup.addPreference(pref);
258        mShortcutToPreference.put(shortcut, pref);
259        return pref;
260    }
261
262    private void initShortcutPreferences() {
263
264        /** Whether the shortcut has been seen already.  The array index is the shortcut. */
265        SparseBooleanArray shortcutSeen = new SparseBooleanArray();
266        KeyCharacterMap keyMap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
267
268        // Go through all the key codes and create a preference for the appropriate keys
269        for (int keyCode = KeyEvent.getMaxKeyCode() - 1; keyCode >= 0; keyCode--) {
270            // Get the label for the primary char on the key that produces this key code
271            char shortcut = (char) Character.toLowerCase(keyMap.getDisplayLabel(keyCode));
272            if (shortcut == 0 || shortcutSeen.get(shortcut, false)) continue;
273            // TODO: need a to tell if the current keyboard can produce this key code, for now
274            // only allow the letter or digits
275            if (!Character.isLetterOrDigit(shortcut)) continue;
276            shortcutSeen.put(shortcut, true);
277
278            createPreference(shortcut);
279        }
280    }
281
282    private synchronized void refreshShortcuts() {
283        Cursor c = mBookmarksCursor;
284        if (c == null) {
285            // Haven't finished querying yet
286            return;
287        }
288
289        if (!c.requery()) {
290            Log.e(TAG, "Could not requery cursor when refreshing shortcuts.");
291            return;
292        }
293
294        /**
295         * We use the previous bookmarked shortcuts array to filter out those
296         * shortcuts that had bookmarks before this method call, and don't after
297         * (so we can set the preferences to be without bookmarks).
298         */
299        SparseBooleanArray noLongerBookmarkedShortcuts = mBookmarkedShortcuts;
300        SparseBooleanArray newBookmarkedShortcuts = new SparseBooleanArray();
301        while (c.moveToNext()) {
302            char shortcut = Character.toLowerCase((char) c.getInt(COLUMN_SHORTCUT));
303            if (shortcut == 0) continue;
304
305            ShortcutPreference pref = getOrCreatePreference(shortcut);
306            CharSequence title = Bookmarks.getTitle(this, c);
307
308            /*
309             * The title retrieved from Bookmarks.getTitle() will be in
310             * the original boot locale, not the current locale.
311             * Try to look up a localized title from the PackageManager.
312             */
313            int intentColumn = c.getColumnIndex(Bookmarks.INTENT);
314            String intentUri = c.getString(intentColumn);
315            PackageManager packageManager = getPackageManager();
316            try {
317                Intent intent = Intent.getIntent(intentUri);
318                ResolveInfo info = packageManager.resolveActivity(intent, 0);
319                if (info != null) {
320                    title = info.loadLabel(packageManager);
321                }
322            } catch (URISyntaxException e) {
323                // Just use the non-localized title, then.
324            }
325
326            pref.setTitle(title);
327            pref.setSummary(getString(R.string.quick_launch_shortcut,
328                    String.valueOf(shortcut)));
329            pref.setHasBookmark(true);
330
331            newBookmarkedShortcuts.put(shortcut, true);
332            if (noLongerBookmarkedShortcuts != null) {
333                // After this loop, the shortcuts with value true in this array
334                // will no longer have bookmarks
335                noLongerBookmarkedShortcuts.put(shortcut, false);
336            }
337        }
338
339        if (noLongerBookmarkedShortcuts != null) {
340            for (int i = noLongerBookmarkedShortcuts.size() - 1; i >= 0; i--) {
341                if (noLongerBookmarkedShortcuts.valueAt(i)) {
342                    // True, so there is no longer a bookmark for this shortcut
343                    char shortcut = (char) noLongerBookmarkedShortcuts.keyAt(i);
344                    ShortcutPreference pref = mShortcutToPreference.get(shortcut);
345                    if (pref != null) {
346                        pref.setHasBookmark(false);
347                    }
348                }
349            }
350        }
351
352        mBookmarkedShortcuts = newBookmarkedShortcuts;
353
354        c.deactivate();
355    }
356
357    private class BookmarksObserver extends ContentObserver {
358
359        public BookmarksObserver(Handler handler) {
360            super(handler);
361        }
362
363        @Override
364        public void onChange(boolean selfChange) {
365            super.onChange(selfChange);
366
367            refreshShortcuts();
368        }
369    }
370}
371