ContactBrowseListFragment.java revision 8daa1797310aca2333efcff0a0e0b0ed03187fff
1/* 2 * Copyright (C) 2010 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 */ 16package com.android.contacts.list; 17 18import com.android.contacts.R; 19import com.android.contacts.widget.AutoScrollListView; 20 21import android.app.Activity; 22import android.content.AsyncQueryHandler; 23import android.content.ContentResolver; 24import android.content.Loader; 25import android.content.SharedPreferences; 26import android.content.SharedPreferences.Editor; 27import android.database.Cursor; 28import android.net.Uri; 29import android.os.Bundle; 30import android.os.Handler; 31import android.os.Message; 32import android.preference.PreferenceManager; 33import android.provider.ContactsContract; 34import android.provider.ContactsContract.Contacts; 35import android.provider.ContactsContract.Directory; 36import android.text.TextUtils; 37import android.util.Log; 38 39import java.util.List; 40 41/** 42 * Fragment containing a contact list used for browsing (as compared to 43 * picking a contact with one of the PICK intents). 44 */ 45public abstract class ContactBrowseListFragment extends 46 ContactEntryListFragment<ContactListAdapter> { 47 48 private static final String TAG = "ContactList"; 49 50 private static final String KEY_SELECTED_URI = "selectedUri"; 51 private static final String KEY_SELECTION_VERIFIED = "selectionVerified"; 52 private static final String KEY_FILTER_ENABLED = "filterEnabled"; 53 private static final String KEY_FILTER = "filter"; 54 55 private static final String KEY_PERSISTENT_SELECTION_ENABLED = "persistentSelectionEnabled"; 56 private static final String PERSISTENT_SELECTION_PREFIX = "defaultContactBrowserSelection"; 57 58 /** 59 * The id for a delayed message that triggers automatic selection of the first 60 * found contact in search mode. 61 */ 62 private static final int MESSAGE_AUTOSELECT_FIRST_FOUND_CONTACT = 1; 63 64 /** 65 * The delay that is used for automatically selecting the first found contact. 66 */ 67 private static final int DELAY_AUTOSELECT_FIRST_FOUND_CONTACT_MILLIS = 500; 68 69 /** 70 * The minimum number of characters in the search query that is required 71 * before we automatically select the first found contact. 72 */ 73 private static final int AUTOSELECT_FIRST_FOUND_CONTACT_MIN_QUERY_LENGTH = 2; 74 75 private SharedPreferences mPrefs; 76 private Handler mHandler; 77 78 private boolean mStartedLoading; 79 private boolean mSelectionRequired; 80 private boolean mSelectionToScreenRequested; 81 private boolean mSmoothScrollRequested; 82 private boolean mSelectionPersistenceRequested; 83 private Uri mSelectedContactUri; 84 private long mSelectedContactDirectoryId; 85 private String mSelectedContactLookupKey; 86 private boolean mSelectionVerified; 87 private boolean mRefreshingContactUri; 88 private boolean mFilterEnabled; 89 private ContactListFilter mFilter; 90 private boolean mPersistentSelectionEnabled; 91 private String mPersistentSelectionPrefix = PERSISTENT_SELECTION_PREFIX; 92 93 protected OnContactBrowserActionListener mListener; 94 95 /** 96 * Refreshes a contact URI: it may have changed as a result of aggregation 97 * activity. 98 */ 99 private class ContactUriQueryHandler extends AsyncQueryHandler { 100 101 public ContactUriQueryHandler(ContentResolver cr) { 102 super(cr); 103 } 104 105 public void runQuery() { 106 startQuery(0, mSelectedContactUri, mSelectedContactUri, 107 new String[] { Contacts._ID, Contacts.LOOKUP_KEY }, null, null, null); 108 } 109 110 @Override 111 protected void onQueryComplete(int token, Object cookie, Cursor data) { 112 long contactId = 0; 113 String lookupKey = null; 114 if (data != null) { 115 if (data.moveToFirst()) { 116 contactId = data.getLong(0); 117 lookupKey = data.getString(1); 118 } 119 data.close(); 120 } 121 122 if (!cookie.equals(mSelectedContactUri)) { 123 return; 124 } 125 126 onContactUriQueryFinished(Contacts.getLookupUri(contactId, lookupKey)); 127 } 128 } 129 130 private ContactUriQueryHandler mQueryHandler; 131 132 private Handler getHandler() { 133 if (mHandler == null) { 134 mHandler = new Handler() { 135 @Override 136 public void handleMessage(Message msg) { 137 switch (msg.what) { 138 case MESSAGE_AUTOSELECT_FIRST_FOUND_CONTACT: 139 selectDefaultContact(); 140 break; 141 } 142 } 143 }; 144 } 145 return mHandler; 146 } 147 148 @Override 149 public void onAttach(Activity activity) { 150 super.onAttach(activity); 151 mQueryHandler = new ContactUriQueryHandler(activity.getContentResolver()); 152 mPrefs = PreferenceManager.getDefaultSharedPreferences(activity); 153 restoreFilter(); 154 restoreSelectedUri(false); 155 } 156 157 public void setPersistentSelectionEnabled(boolean flag) { 158 this.mPersistentSelectionEnabled = flag; 159 } 160 161 public void setFilter(ContactListFilter filter) { 162 if (mFilter == null && filter == null) { 163 return; 164 } 165 166 if (mFilter != null && mFilter.equals(filter)) { 167 return; 168 } 169 170 Log.v(TAG, "New filter: " + filter); 171 172 mFilter = filter; 173 saveFilter(); 174 mSelectedContactUri = null; 175 restoreSelectedUri(true); 176 reloadData(); 177 } 178 179 public ContactListFilter getFilter() { 180 return mFilter; 181 } 182 183 public boolean isFilterEnabled() { 184 return mFilterEnabled; 185 } 186 187 public void setFilterEnabled(boolean flag) { 188 this.mFilterEnabled = flag; 189 } 190 191 @Override 192 public void restoreSavedState(Bundle savedState) { 193 super.restoreSavedState(savedState); 194 195 if (savedState == null) { 196 return; 197 } 198 199 mPersistentSelectionEnabled = savedState.getBoolean(KEY_PERSISTENT_SELECTION_ENABLED); 200 mFilterEnabled = savedState.getBoolean(KEY_FILTER_ENABLED); 201 mFilter = savedState.getParcelable(KEY_FILTER); 202 mSelectedContactUri = savedState.getParcelable(KEY_SELECTED_URI); 203 mSelectionVerified = savedState.getBoolean(KEY_SELECTION_VERIFIED); 204 parseSelectedContactUri(); 205 } 206 207 @Override 208 public void onSaveInstanceState(Bundle outState) { 209 super.onSaveInstanceState(outState); 210 outState.putBoolean(KEY_PERSISTENT_SELECTION_ENABLED, mPersistentSelectionEnabled); 211 outState.putBoolean(KEY_FILTER_ENABLED, mFilterEnabled); 212 outState.putParcelable(KEY_FILTER, mFilter); 213 outState.putParcelable(KEY_SELECTED_URI, mSelectedContactUri); 214 outState.putBoolean(KEY_SELECTION_VERIFIED, mSelectionVerified); 215 } 216 217 protected void refreshSelectedContactUri() { 218 if (mQueryHandler == null) { 219 return; 220 } 221 222 mQueryHandler.cancelOperation(0); 223 224 if (!isSelectionVisible()) { 225 return; 226 } 227 228 mRefreshingContactUri = true; 229 230 if (mSelectedContactUri == null) { 231 onContactUriQueryFinished(null); 232 return; 233 } 234 235 if (mSelectedContactDirectoryId != Directory.DEFAULT 236 && mSelectedContactDirectoryId != Directory.LOCAL_INVISIBLE) { 237 onContactUriQueryFinished(mSelectedContactUri); 238 } else { 239 mQueryHandler.runQuery(); 240 } 241 } 242 243 protected void onContactUriQueryFinished(Uri uri) { 244 mRefreshingContactUri = false; 245 mSelectedContactUri = uri; 246 parseSelectedContactUri(); 247 checkSelection(); 248 } 249 250 @Override 251 protected void prepareEmptyView() { 252 if (isSearchMode()) { 253 return; 254 } else if (isSyncActive()) { 255 if (hasIccCard()) { 256 setEmptyText(R.string.noContactsHelpTextWithSync); 257 } else { 258 setEmptyText(R.string.noContactsNoSimHelpTextWithSync); 259 } 260 } else { 261 if (hasIccCard()) { 262 setEmptyText(R.string.noContactsHelpText); 263 } else { 264 setEmptyText(R.string.noContactsNoSimHelpText); 265 } 266 } 267 } 268 269 public Uri getSelectedContactUri() { 270 return mSelectedContactUri; 271 } 272 273 /** 274 * Sets the new selection for the list. 275 */ 276 public void setSelectedContactUri(Uri uri) { 277 setSelectedContactUri(uri, true, true, true, false); 278 } 279 280 /** 281 * Sets the new contact selection. 282 * 283 * @param uri the new selection 284 * @param required if true, we need to check if the selection is present in 285 * the list and if not notify the listener so that it can load a 286 * different list 287 * @param smoothScroll if true, the UI will roll smoothly to the new 288 * selection 289 * @param persistent if true, the selection will be stored in shared 290 * preferences. 291 * @param willReloadData if true, the selection will be remembered but not 292 * actually shown, because we are expecting that the data will be 293 * reloaded momentarily 294 */ 295 private void setSelectedContactUri(Uri uri, boolean required, boolean smoothScroll, 296 boolean persistent, boolean willReloadData) { 297 mSmoothScrollRequested = smoothScroll; 298 mSelectionToScreenRequested = true; 299 300 if ((mSelectedContactUri == null && uri != null) 301 || (mSelectedContactUri != null && !mSelectedContactUri.equals(uri))) { 302 mSelectionVerified = false; 303 mSelectionRequired = required; 304 mSelectionPersistenceRequested = persistent; 305 mSelectedContactUri = uri; 306 parseSelectedContactUri(); 307 308 if (!willReloadData) { 309 // Configure the adapter to show the selection based on the 310 // lookup key extracted from the URI 311 ContactListAdapter adapter = getAdapter(); 312 if (adapter != null) { 313 adapter.setSelectedContact( 314 mSelectedContactDirectoryId, mSelectedContactLookupKey); 315 getListView().invalidateViews(); 316 } 317 } 318 319 // Also, launch a loader to pick up a new lookup URI in case it has changed 320 refreshSelectedContactUri(); 321 } 322 } 323 324 private void parseSelectedContactUri() { 325 if (mSelectedContactUri != null) { 326 String directoryParam = 327 mSelectedContactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY); 328 mSelectedContactDirectoryId = TextUtils.isEmpty(directoryParam) ? Directory.DEFAULT 329 : Long.parseLong(directoryParam); 330 if (mSelectedContactUri.toString().startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) { 331 List<String> pathSegments = mSelectedContactUri.getPathSegments(); 332 mSelectedContactLookupKey = Uri.encode(pathSegments.get(2)); 333 } else { 334 mSelectedContactLookupKey = null; 335 } 336 337 } else { 338 mSelectedContactDirectoryId = Directory.DEFAULT; 339 mSelectedContactLookupKey = null; 340 } 341 } 342 343 @Override 344 protected void configureAdapter() { 345 super.configureAdapter(); 346 347 ContactListAdapter adapter = getAdapter(); 348 if (adapter == null) { 349 return; 350 } 351 352 if (mFilterEnabled && mFilter != null) { 353 adapter.setFilter(mFilter); 354 } 355 } 356 357 @Override 358 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 359 super.onLoadFinished(loader, data); 360 mSelectionVerified = false; 361 362 // Refresh the currently selected lookup in case it changed while we were sleeping 363 refreshSelectedContactUri(); 364 } 365 366 @Override 367 public void onLoaderReset(Loader<Cursor> loader) { 368 } 369 370 private void checkSelection() { 371 if (mSelectionVerified) { 372 return; 373 } 374 375 if (isLoading()) { 376 return; 377 } 378 379 ContactListAdapter adapter = getAdapter(); 380 if (adapter == null) { 381 return; 382 } 383 384 adapter.setSelectedContact(mSelectedContactDirectoryId, mSelectedContactLookupKey); 385 386 int selectedPosition = adapter.getSelectedContactPosition(); 387 if (selectedPosition == -1) { 388 if (mSelectionRequired) { 389 notifyInvalidSelection(); 390 return; 391 } 392 393 if (isSearchMode()) { 394 selectFirstFoundContactAfterDelay(); 395 if (mListener != null) { 396 mListener.onSelectionChange(); 397 } 398 return; 399 } 400 401 saveSelectedUri(null); 402 selectDefaultContact(); 403 } 404 405 mSelectionRequired = false; 406 mSelectionVerified = true; 407 408 if (mSelectionPersistenceRequested) { 409 saveSelectedUri(mSelectedContactUri); 410 mSelectionPersistenceRequested = false; 411 } 412 413 if (mSelectionToScreenRequested) { 414 requestSelectionToScreen(); 415 } 416 417 getListView().invalidateViews(); 418 419 if (mListener != null) { 420 mListener.onSelectionChange(); 421 } 422 } 423 424 /** 425 * Automatically selects the first found contact in search mode. The selection 426 * is updated after a delay to allow the user to type without to much UI churn 427 * and to save bandwidth on directory queries. 428 */ 429 public void selectFirstFoundContactAfterDelay() { 430 Handler handler = getHandler(); 431 handler.removeMessages(MESSAGE_AUTOSELECT_FIRST_FOUND_CONTACT); 432 433 String queryString = getQueryString(); 434 if (queryString != null 435 && queryString.length() >= AUTOSELECT_FIRST_FOUND_CONTACT_MIN_QUERY_LENGTH) { 436 handler.sendEmptyMessageDelayed(MESSAGE_AUTOSELECT_FIRST_FOUND_CONTACT, 437 DELAY_AUTOSELECT_FIRST_FOUND_CONTACT_MILLIS); 438 } else { 439 setSelectedContactUri(null, false, false, false, false); 440 } 441 } 442 443 protected void selectDefaultContact() { 444 Uri firstContactUri = getAdapter().getFirstContactUri(); 445 setSelectedContactUri(firstContactUri, false, mSmoothScrollRequested, false, false); 446 } 447 448 protected void requestSelectionToScreen() { 449 int selectedPosition = getAdapter().getSelectedContactPosition(); 450 if (selectedPosition != -1) { 451 AutoScrollListView listView = (AutoScrollListView)getListView(); 452 listView.requestPositionToScreen( 453 selectedPosition + listView.getHeaderViewsCount(), mSmoothScrollRequested); 454 mSelectionToScreenRequested = false; 455 } 456 } 457 458 @Override 459 public boolean isLoading() { 460 return mRefreshingContactUri || super.isLoading(); 461 } 462 463 @Override 464 protected void startLoading() { 465 mStartedLoading = true; 466 mSelectionVerified = false; 467 super.startLoading(); 468 } 469 470 @Override 471 public void reloadData() { 472 if (mStartedLoading) { 473 mSelectionVerified = false; 474 super.reloadData(); 475 } 476 } 477 478 public void setOnContactListActionListener(OnContactBrowserActionListener listener) { 479 mListener = listener; 480 } 481 482 public void createNewContact() { 483 if (mListener != null) mListener.onCreateNewContactAction(); 484 } 485 486 public void viewContact(Uri contactUri) { 487 setSelectedContactUri(contactUri, false, false, true, false); 488 if (mListener != null) { mListener.onViewContactAction(contactUri); } 489 } 490 491 public void editContact(Uri contactUri) { 492 if (mListener != null) mListener.onEditContactAction(contactUri); 493 } 494 495 public void deleteContact(Uri contactUri) { 496 if (mListener != null) mListener.onDeleteContactAction(contactUri); 497 } 498 499 public void addToFavorites(Uri contactUri) { 500 if (mListener != null) mListener.onAddToFavoritesAction(contactUri); 501 } 502 503 public void removeFromFavorites(Uri contactUri) { 504 if (mListener != null) mListener.onRemoveFromFavoritesAction(contactUri); 505 } 506 507 public void callContact(Uri contactUri) { 508 if (mListener != null) mListener.onCallContactAction(contactUri); 509 } 510 511 public void smsContact(Uri contactUri) { 512 if (mListener != null) mListener.onSmsContactAction(contactUri); 513 } 514 515 private void notifyInvalidSelection() { 516 if (mListener != null) mListener.onInvalidSelection(); 517 } 518 519 @Override 520 protected void finish() { 521 super.finish(); 522 if (mListener != null) mListener.onFinishAction(); 523 } 524 525 private void saveSelectedUri(Uri contactUri) { 526 if (mFilterEnabled) { 527 ContactListFilter.storeToPreferences(mPrefs, mFilter); 528 } 529 530 if (mPersistentSelectionEnabled) { 531 Editor editor = mPrefs.edit(); 532 if (contactUri == null) { 533 editor.remove(getPersistentSelectionKey()); 534 } else { 535 editor.putString(getPersistentSelectionKey(), contactUri.toString()); 536 } 537 editor.apply(); 538 } 539 } 540 541 private void restoreSelectedUri(boolean willReloadData) { 542 if (!mPersistentSelectionEnabled) { 543 return; 544 } 545 546 // The meaning of mSelectionRequired is that we need to show some 547 // selection other than the previous selection saved in shared preferences 548 if (mSelectionRequired) { 549 return; 550 } 551 552 String selectedUri = mPrefs.getString(getPersistentSelectionKey(), null); 553 if (selectedUri == null) { 554 setSelectedContactUri(null, false, false, false, willReloadData); 555 } else { 556 setSelectedContactUri(Uri.parse(selectedUri), false, false, false, willReloadData); 557 } 558 } 559 560 private void saveFilter() { 561 if (mFilterEnabled) { 562 ContactListFilter.storeToPreferences(mPrefs, mFilter); 563 } 564 } 565 566 private void restoreFilter() { 567 if (mFilterEnabled) { 568 mFilter = ContactListFilter.restoreFromPreferences(mPrefs); 569 } 570 } 571 572 private String getPersistentSelectionKey() { 573 if (mFilter == null) { 574 return mPersistentSelectionPrefix; 575 } else { 576 return mPersistentSelectionPrefix + "-" + mFilter.getId(); 577 } 578 } 579 580 public boolean isOptionsMenuChanged() { 581 // This fragment does not have an option menu of its own 582 return false; 583 } 584} 585