QuickLaunchSettings.java revision ec598cb91c02c035d19ccd8a9f17e2bf8da9da5a
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            String title = data.getStringExtra(BookmarkPicker.EXTRA_TITLE);
231            char shortcut = data.getCharExtra(BookmarkPicker.EXTRA_SHORTCUT, (char) 0);
232            updateShortcut(shortcut, data);
233
234        } else {
235            super.onActivityResult(requestCode, resultCode, data);
236        }
237    }
238
239    private void updateShortcut(char shortcut, Intent intent) {
240        // Update the bookmark for a shortcut
241        // Pass an empty title so it gets resolved each time this bookmark is
242        // displayed (since the locale could change after we insert into the provider).
243        Bookmarks.add(getContentResolver(), intent, "", DEFAULT_BOOKMARK_FOLDER, shortcut, 0);
244    }
245
246    private ShortcutPreference getOrCreatePreference(char shortcut) {
247        ShortcutPreference pref = mShortcutToPreference.get(shortcut);
248        if (pref != null) {
249            return pref;
250        } else {
251            Log.w(TAG, "Unknown shortcut '" + shortcut + "', creating preference anyway");
252            return createPreference(shortcut);
253        }
254    }
255
256    private ShortcutPreference createPreference(char shortcut) {
257        ShortcutPreference pref = new ShortcutPreference(QuickLaunchSettings.this, shortcut);
258        mShortcutGroup.addPreference(pref);
259        mShortcutToPreference.put(shortcut, pref);
260        return pref;
261    }
262
263    private void initShortcutPreferences() {
264
265        /** Whether the shortcut has been seen already.  The array index is the shortcut. */
266        SparseBooleanArray shortcutSeen = new SparseBooleanArray();
267        KeyCharacterMap keyMap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
268
269        // Go through all the key codes and create a preference for the appropriate keys
270        for (int keyCode = KeyEvent.getMaxKeyCode() - 1; keyCode >= 0; keyCode--) {
271            // Get the label for the primary char on the key that produces this key code
272            char shortcut = (char) Character.toLowerCase(keyMap.getDisplayLabel(keyCode));
273            if (shortcut == 0 || shortcutSeen.get(shortcut, false)) continue;
274            // TODO: need a to tell if the current keyboard can produce this key code, for now
275            // only allow the letter or digits
276            if (!Character.isLetterOrDigit(shortcut)) continue;
277            shortcutSeen.put(shortcut, true);
278
279            createPreference(shortcut);
280        }
281    }
282
283    private synchronized void refreshShortcuts() {
284        Cursor c = mBookmarksCursor;
285        if (c == null) {
286            // Haven't finished querying yet
287            return;
288        }
289
290        if (!c.requery()) {
291            Log.e(TAG, "Could not requery cursor when refreshing shortcuts.");
292            return;
293        }
294
295        /**
296         * We use the previous bookmarked shortcuts array to filter out those
297         * shortcuts that had bookmarks before this method call, and don't after
298         * (so we can set the preferences to be without bookmarks).
299         */
300        SparseBooleanArray noLongerBookmarkedShortcuts = mBookmarkedShortcuts;
301        SparseBooleanArray newBookmarkedShortcuts = new SparseBooleanArray();
302        while (c.moveToNext()) {
303            char shortcut = Character.toLowerCase((char) c.getInt(COLUMN_SHORTCUT));
304            if (shortcut == 0) continue;
305
306            ShortcutPreference pref = getOrCreatePreference(shortcut);
307            CharSequence title = Bookmarks.getTitle(this, c);
308
309            /*
310             * The title retrieved from Bookmarks.getTitle() will be in
311             * the original boot locale, not the current locale.
312             * Try to look up a localized title from the PackageManager.
313             */
314            int intentColumn = c.getColumnIndex(Bookmarks.INTENT);
315            String intentUri = c.getString(intentColumn);
316            PackageManager packageManager = getPackageManager();
317            try {
318                Intent intent = Intent.getIntent(intentUri);
319                ResolveInfo info = packageManager.resolveActivity(intent, 0);
320                if (info != null) {
321                    title = info.loadLabel(packageManager);
322                }
323            } catch (URISyntaxException e) {
324                // Just use the non-localized title, then.
325            }
326
327            pref.setTitle(title);
328            pref.setSummary(getString(R.string.quick_launch_shortcut,
329                    String.valueOf(shortcut)));
330            pref.setHasBookmark(true);
331
332            newBookmarkedShortcuts.put(shortcut, true);
333            if (noLongerBookmarkedShortcuts != null) {
334                // After this loop, the shortcuts with value true in this array
335                // will no longer have bookmarks
336                noLongerBookmarkedShortcuts.put(shortcut, false);
337            }
338        }
339
340        if (noLongerBookmarkedShortcuts != null) {
341            for (int i = noLongerBookmarkedShortcuts.size() - 1; i >= 0; i--) {
342                if (noLongerBookmarkedShortcuts.valueAt(i)) {
343                    // True, so there is no longer a bookmark for this shortcut
344                    char shortcut = (char) noLongerBookmarkedShortcuts.keyAt(i);
345                    ShortcutPreference pref = mShortcutToPreference.get(shortcut);
346                    if (pref != null) {
347                        pref.setHasBookmark(false);
348                    }
349                }
350            }
351        }
352
353        mBookmarkedShortcuts = newBookmarkedShortcuts;
354
355        c.deactivate();
356    }
357
358    private class BookmarksObserver extends ContentObserver {
359
360        public BookmarksObserver(Handler handler) {
361            super(handler);
362        }
363
364        @Override
365        public void onChange(boolean selfChange) {
366            super.onChange(selfChange);
367
368            refreshShortcuts();
369        }
370    }
371}
372