Home | History | Annotate | Download | only in quicklaunch
      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 
     17 package com.android.settings.quicklaunch;
     18 
     19 import android.app.Activity;
     20 import android.app.AlertDialog;
     21 import android.app.Dialog;
     22 import android.content.DialogInterface;
     23 import android.content.Intent;
     24 import android.content.pm.PackageManager;
     25 import android.content.pm.ResolveInfo;
     26 import android.database.ContentObserver;
     27 import android.database.Cursor;
     28 import android.os.Bundle;
     29 import android.os.Handler;
     30 import android.preference.Preference;
     31 import android.preference.PreferenceGroup;
     32 import android.preference.PreferenceScreen;
     33 import android.provider.Settings.Bookmarks;
     34 import android.util.Log;
     35 import android.util.SparseArray;
     36 import android.util.SparseBooleanArray;
     37 import android.view.KeyCharacterMap;
     38 import android.view.KeyEvent;
     39 import android.view.View;
     40 import android.widget.AdapterView;
     41 
     42 import com.android.settings.R;
     43 import com.android.settings.SettingsPreferenceFragment;
     44 
     45 import 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  */
     53 public 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