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