QuickLaunchSettings.java revision e3ff4d8e92a19473c622c1d46b30c63bf2d001d0
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.Activity;
20import android.app.AlertDialog;
21import android.app.Dialog;
22import android.content.DialogInterface;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
26import android.database.ContentObserver;
27import android.database.Cursor;
28import android.os.Bundle;
29import android.os.Handler;
30import android.preference.Preference;
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;
43import com.android.settings.SettingsPreferenceFragment;
44
45import java.net.URISyntaxException;
46
47/**
48 * Settings activity for quick launch.
49 * <p>
50 * Shows a list of possible shortcuts, the current application each is bound to,
51 * and allows choosing a new bookmark for a shortcut.
52 */
53public class QuickLaunchSettings extends SettingsPreferenceFragment implements
54        AdapterView.OnItemLongClickListener, DialogInterface.OnClickListener {
55
56    private static final String TAG = "QuickLaunchSettings";
57
58    private static final String KEY_SHORTCUT_CATEGORY = "shortcut_category";
59
60    private static final int DIALOG_CLEAR_SHORTCUT = 0;
61
62    private static final int REQUEST_PICK_BOOKMARK = 1;
63
64    private static final int COLUMN_SHORTCUT = 0;
65    private static final int COLUMN_TITLE = 1;
66    private static final int COLUMN_INTENT = 2;
67    private static final String[] sProjection = new String[] {
68            Bookmarks.SHORTCUT, Bookmarks.TITLE, Bookmarks.INTENT
69    };
70    private static final String sShortcutSelection = Bookmarks.SHORTCUT + "=?";
71
72    private Handler mUiHandler = new Handler();
73
74    private static final String DEFAULT_BOOKMARK_FOLDER = "@quicklaunch";
75    /** Cursor for Bookmarks provider. */
76    private Cursor mBookmarksCursor;
77    /** Listens for changes to Bookmarks provider. */
78    private BookmarksObserver mBookmarksObserver;
79    /** Used to keep track of which shortcuts have bookmarks. */
80    private SparseBooleanArray mBookmarkedShortcuts;
81
82    /** Preference category to hold the shortcut preferences. */
83    private PreferenceGroup mShortcutGroup;
84    /** Mapping of a shortcut to its preference. */
85    private SparseArray<ShortcutPreference> mShortcutToPreference;
86
87    /** The bookmark title of the shortcut that is being cleared. */
88    private CharSequence mClearDialogBookmarkTitle;
89    private static final String CLEAR_DIALOG_BOOKMARK_TITLE = "CLEAR_DIALOG_BOOKMARK_TITLE";
90    /** The shortcut that is being cleared. */
91    private char mClearDialogShortcut;
92    private static final String CLEAR_DIALOG_SHORTCUT = "CLEAR_DIALOG_SHORTCUT";
93
94    @Override
95    public void onCreate(Bundle savedInstanceState) {
96        super.onCreate(savedInstanceState);
97
98        addPreferencesFromResource(R.xml.quick_launch_settings);
99
100        mShortcutGroup = (PreferenceGroup) findPreference(KEY_SHORTCUT_CATEGORY);
101        mShortcutToPreference = new SparseArray<ShortcutPreference>();
102        mBookmarksObserver = new BookmarksObserver(mUiHandler);
103        initShortcutPreferences();
104        mBookmarksCursor = getActivity().getContentResolver().query(Bookmarks.CONTENT_URI,
105                sProjection, null, null, null);
106    }
107
108    @Override
109    public void onResume() {
110        super.onResume();
111        mBookmarksCursor = getActivity().getContentResolver().query(Bookmarks.CONTENT_URI,
112                sProjection, null, null, null);
113        getContentResolver().registerContentObserver(Bookmarks.CONTENT_URI, true,
114                mBookmarksObserver);
115        refreshShortcuts();
116    }
117
118    @Override
119    public void onPause() {
120        super.onPause();
121        getContentResolver().unregisterContentObserver(mBookmarksObserver);
122    }
123
124    @Override
125    public void onStop() {
126        super.onStop();
127        mBookmarksCursor.close();
128    }
129
130    @Override
131    public void onActivityCreated(Bundle state) {
132        super.onActivityCreated(state);
133
134        getListView().setOnItemLongClickListener(this);
135
136        if (state != null) {
137            // Restore the clear dialog's info
138            mClearDialogBookmarkTitle = state.getString(CLEAR_DIALOG_BOOKMARK_TITLE);
139            mClearDialogShortcut = (char) state.getInt(CLEAR_DIALOG_SHORTCUT, 0);
140        }
141    }
142
143    @Override
144    public void onSaveInstanceState(Bundle outState) {
145        super.onSaveInstanceState(outState);
146
147        // Save the clear dialog's info
148        outState.putCharSequence(CLEAR_DIALOG_BOOKMARK_TITLE, mClearDialogBookmarkTitle);
149        outState.putInt(CLEAR_DIALOG_SHORTCUT, mClearDialogShortcut);
150    }
151
152    @Override
153    public Dialog onCreateDialog(int id) {
154        switch (id) {
155
156            case DIALOG_CLEAR_SHORTCUT: {
157                // Create the dialog for clearing a shortcut
158                return new AlertDialog.Builder(getActivity())
159                        .setTitle(getString(R.string.quick_launch_clear_dialog_title))
160                        .setMessage(getString(R.string.quick_launch_clear_dialog_message,
161                                mClearDialogShortcut, mClearDialogBookmarkTitle))
162                        .setPositiveButton(R.string.quick_launch_clear_ok_button, this)
163                        .setNegativeButton(R.string.quick_launch_clear_cancel_button, this)
164                        .create();
165            }
166        }
167
168        return super.onCreateDialog(id);
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.BUTTON_POSITIVE) {
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(getActivity(), 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    public void onActivityResult(int requestCode, int resultCode, Intent data) {
218        if (resultCode != Activity.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(getActivity(), 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.VIRTUAL_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(getActivity(), 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.parseUri(intentUri, 0);
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