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 */ 16 package com.android.contacts.list; 17 18 import com.android.common.widget.CompositeCursorAdapter.Partition; 19 import com.android.contacts.R; 20 import com.android.contacts.util.ContactLoaderUtils; 21 import com.android.contacts.widget.AutoScrollListView; 22 23 import android.app.Activity; 24 import android.content.ContentResolver; 25 import android.content.ContentUris; 26 import android.content.Loader; 27 import android.content.SharedPreferences; 28 import android.content.SharedPreferences.Editor; 29 import android.database.Cursor; 30 import android.net.Uri; 31 import android.os.AsyncTask; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.Message; 35 import android.preference.PreferenceManager; 36 import android.provider.ContactsContract; 37 import android.provider.ContactsContract.Contacts; 38 import android.provider.ContactsContract.Directory; 39 import android.text.TextUtils; 40 import android.util.Log; 41 42 import java.util.List; 43 44 /** 45 * Fragment containing a contact list used for browsing (as compared to 46 * picking a contact with one of the PICK intents). 47 */ 48 public abstract class ContactBrowseListFragment extends 49 ContactEntryListFragment<ContactListAdapter> { 50 51 private static final String TAG = "ContactList"; 52 53 private static final String KEY_SELECTED_URI = "selectedUri"; 54 private static final String KEY_SELECTION_VERIFIED = "selectionVerified"; 55 private static final String KEY_FILTER = "filter"; 56 private static final String KEY_LAST_SELECTED_POSITION = "lastSelected"; 57 58 private static final String PERSISTENT_SELECTION_PREFIX = "defaultContactBrowserSelection"; 59 60 /** 61 * The id for a delayed message that triggers automatic selection of the first 62 * found contact in search mode. 63 */ 64 private static final int MESSAGE_AUTOSELECT_FIRST_FOUND_CONTACT = 1; 65 66 /** 67 * The delay that is used for automatically selecting the first found contact. 68 */ 69 private static final int DELAY_AUTOSELECT_FIRST_FOUND_CONTACT_MILLIS = 500; 70 71 /** 72 * The minimum number of characters in the search query that is required 73 * before we automatically select the first found contact. 74 */ 75 private static final int AUTOSELECT_FIRST_FOUND_CONTACT_MIN_QUERY_LENGTH = 2; 76 77 private SharedPreferences mPrefs; 78 private Handler mHandler; 79 80 private boolean mStartedLoading; 81 private boolean mSelectionRequired; 82 private boolean mSelectionToScreenRequested; 83 private boolean mSmoothScrollRequested; 84 private boolean mSelectionPersistenceRequested; 85 private Uri mSelectedContactUri; 86 private long mSelectedContactDirectoryId; 87 private String mSelectedContactLookupKey; 88 private long mSelectedContactId; 89 private boolean mSelectionVerified; 90 private int mLastSelectedPosition = -1; 91 private boolean mRefreshingContactUri; 92 private ContactListFilter mFilter; 93 private String mPersistentSelectionPrefix = PERSISTENT_SELECTION_PREFIX; 94 95 protected OnContactBrowserActionListener mListener; 96 private ContactLookupTask mContactLookupTask; 97 98 private final class ContactLookupTask extends AsyncTask<Void, Void, Uri> { 99 100 private final Uri mUri; 101 private boolean mIsCancelled; 102 ContactLookupTask(Uri uri)103 public ContactLookupTask(Uri uri) { 104 mUri = uri; 105 } 106 107 @Override doInBackground(Void... args)108 protected Uri doInBackground(Void... args) { 109 Cursor cursor = null; 110 try { 111 final ContentResolver resolver = getContext().getContentResolver(); 112 final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(resolver, mUri); 113 cursor = resolver.query(uriCurrentFormat, 114 new String[] { Contacts._ID, Contacts.LOOKUP_KEY }, null, null, null); 115 116 if (cursor != null && cursor.moveToFirst()) { 117 final long contactId = cursor.getLong(0); 118 final String lookupKey = cursor.getString(1); 119 if (contactId != 0 && !TextUtils.isEmpty(lookupKey)) { 120 return Contacts.getLookupUri(contactId, lookupKey); 121 } 122 } 123 124 Log.e(TAG, "Error: No contact ID or lookup key for contact " + mUri); 125 return null; 126 } finally { 127 if (cursor != null) { 128 cursor.close(); 129 } 130 } 131 } 132 cancel()133 public void cancel() { 134 super.cancel(true); 135 // Use a flag to keep track of whether the {@link AsyncTask} was cancelled or not in 136 // order to ensure onPostExecute() is not executed after the cancel request. The flag is 137 // necessary because {@link AsyncTask} still calls onPostExecute() if the cancel request 138 // came after the worker thread was finished. 139 mIsCancelled = true; 140 } 141 142 @Override onPostExecute(Uri uri)143 protected void onPostExecute(Uri uri) { 144 // Make sure the {@link Fragment} is at least still attached to the {@link Activity} 145 // before continuing. Null URIs should still be allowed so that the list can be 146 // refreshed and a default contact can be selected (i.e. the case of deleted 147 // contacts). 148 if (mIsCancelled || !isAdded()) { 149 return; 150 } 151 onContactUriQueryFinished(uri); 152 } 153 } 154 155 private boolean mDelaySelection; 156 getHandler()157 private Handler getHandler() { 158 if (mHandler == null) { 159 mHandler = new Handler() { 160 @Override 161 public void handleMessage(Message msg) { 162 switch (msg.what) { 163 case MESSAGE_AUTOSELECT_FIRST_FOUND_CONTACT: 164 selectDefaultContact(); 165 break; 166 } 167 } 168 }; 169 } 170 return mHandler; 171 } 172 173 @Override onAttach(Activity activity)174 public void onAttach(Activity activity) { 175 super.onAttach(activity); 176 mPrefs = PreferenceManager.getDefaultSharedPreferences(activity); 177 restoreFilter(); 178 restoreSelectedUri(false); 179 } 180 181 @Override setSearchMode(boolean flag)182 protected void setSearchMode(boolean flag) { 183 if (isSearchMode() != flag) { 184 if (!flag) { 185 restoreSelectedUri(true); 186 } 187 super.setSearchMode(flag); 188 } 189 } 190 setFilter(ContactListFilter filter)191 public void setFilter(ContactListFilter filter) { 192 setFilter(filter, true); 193 } 194 setFilter(ContactListFilter filter, boolean restoreSelectedUri)195 public void setFilter(ContactListFilter filter, boolean restoreSelectedUri) { 196 if (mFilter == null && filter == null) { 197 return; 198 } 199 200 if (mFilter != null && mFilter.equals(filter)) { 201 return; 202 } 203 204 Log.v(TAG, "New filter: " + filter); 205 206 mFilter = filter; 207 mLastSelectedPosition = -1; 208 saveFilter(); 209 if (restoreSelectedUri) { 210 mSelectedContactUri = null; 211 restoreSelectedUri(true); 212 } 213 reloadData(); 214 } 215 getFilter()216 public ContactListFilter getFilter() { 217 return mFilter; 218 } 219 220 @Override restoreSavedState(Bundle savedState)221 public void restoreSavedState(Bundle savedState) { 222 super.restoreSavedState(savedState); 223 224 if (savedState == null) { 225 return; 226 } 227 228 mFilter = savedState.getParcelable(KEY_FILTER); 229 mSelectedContactUri = savedState.getParcelable(KEY_SELECTED_URI); 230 mSelectionVerified = savedState.getBoolean(KEY_SELECTION_VERIFIED); 231 mLastSelectedPosition = savedState.getInt(KEY_LAST_SELECTED_POSITION); 232 parseSelectedContactUri(); 233 } 234 235 @Override onSaveInstanceState(Bundle outState)236 public void onSaveInstanceState(Bundle outState) { 237 super.onSaveInstanceState(outState); 238 outState.putParcelable(KEY_FILTER, mFilter); 239 outState.putParcelable(KEY_SELECTED_URI, mSelectedContactUri); 240 outState.putBoolean(KEY_SELECTION_VERIFIED, mSelectionVerified); 241 outState.putInt(KEY_LAST_SELECTED_POSITION, mLastSelectedPosition); 242 } 243 refreshSelectedContactUri()244 protected void refreshSelectedContactUri() { 245 if (mContactLookupTask != null) { 246 mContactLookupTask.cancel(); 247 } 248 249 if (!isSelectionVisible()) { 250 return; 251 } 252 253 mRefreshingContactUri = true; 254 255 if (mSelectedContactUri == null) { 256 onContactUriQueryFinished(null); 257 return; 258 } 259 260 if (mSelectedContactDirectoryId != Directory.DEFAULT 261 && mSelectedContactDirectoryId != Directory.LOCAL_INVISIBLE) { 262 onContactUriQueryFinished(mSelectedContactUri); 263 } else { 264 mContactLookupTask = new ContactLookupTask(mSelectedContactUri); 265 mContactLookupTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null); 266 } 267 } 268 onContactUriQueryFinished(Uri uri)269 protected void onContactUriQueryFinished(Uri uri) { 270 mRefreshingContactUri = false; 271 mSelectedContactUri = uri; 272 parseSelectedContactUri(); 273 checkSelection(); 274 } 275 276 @Override prepareEmptyView()277 protected void prepareEmptyView() { 278 if (isSearchMode()) { 279 return; 280 } else if (isSyncActive()) { 281 if (hasIccCard()) { 282 setEmptyText(R.string.noContactsHelpTextWithSync); 283 } else { 284 setEmptyText(R.string.noContactsNoSimHelpTextWithSync); 285 } 286 } else { 287 if (hasIccCard()) { 288 setEmptyText(R.string.noContactsHelpText); 289 } else { 290 setEmptyText(R.string.noContactsNoSimHelpText); 291 } 292 } 293 } 294 getSelectedContactUri()295 public Uri getSelectedContactUri() { 296 return mSelectedContactUri; 297 } 298 299 /** 300 * Sets the new selection for the list. 301 */ setSelectedContactUri(Uri uri)302 public void setSelectedContactUri(Uri uri) { 303 setSelectedContactUri(uri, true, false /* no smooth scroll */, true, false); 304 } 305 306 @Override setQueryString(String queryString, boolean delaySelection)307 public void setQueryString(String queryString, boolean delaySelection) { 308 mDelaySelection = delaySelection; 309 super.setQueryString(queryString, delaySelection); 310 } 311 312 /** 313 * Sets whether or not a contact selection must be made. 314 * @param required if true, we need to check if the selection is present in 315 * the list and if not notify the listener so that it can load a 316 * different list. 317 * TODO: Figure out how to reconcile this with {@link #setSelectedContactUri}, 318 * without causing unnecessary loading of the list if the selected contact URI is 319 * the same as before. 320 */ setSelectionRequired(boolean required)321 public void setSelectionRequired(boolean required) { 322 mSelectionRequired = required; 323 } 324 325 /** 326 * Sets the new contact selection. 327 * 328 * @param uri the new selection 329 * @param required if true, we need to check if the selection is present in 330 * the list and if not notify the listener so that it can load a 331 * different list 332 * @param smoothScroll if true, the UI will roll smoothly to the new 333 * selection 334 * @param persistent if true, the selection will be stored in shared 335 * preferences. 336 * @param willReloadData if true, the selection will be remembered but not 337 * actually shown, because we are expecting that the data will be 338 * reloaded momentarily 339 */ setSelectedContactUri(Uri uri, boolean required, boolean smoothScroll, boolean persistent, boolean willReloadData)340 private void setSelectedContactUri(Uri uri, boolean required, boolean smoothScroll, 341 boolean persistent, boolean willReloadData) { 342 mSmoothScrollRequested = smoothScroll; 343 mSelectionToScreenRequested = true; 344 345 if ((mSelectedContactUri == null && uri != null) 346 || (mSelectedContactUri != null && !mSelectedContactUri.equals(uri))) { 347 mSelectionVerified = false; 348 mSelectionRequired = required; 349 mSelectionPersistenceRequested = persistent; 350 mSelectedContactUri = uri; 351 parseSelectedContactUri(); 352 353 if (!willReloadData) { 354 // Configure the adapter to show the selection based on the 355 // lookup key extracted from the URI 356 ContactListAdapter adapter = getAdapter(); 357 if (adapter != null) { 358 adapter.setSelectedContact(mSelectedContactDirectoryId, 359 mSelectedContactLookupKey, mSelectedContactId); 360 getListView().invalidateViews(); 361 } 362 } 363 364 // Also, launch a loader to pick up a new lookup URI in case it has changed 365 refreshSelectedContactUri(); 366 } 367 } 368 parseSelectedContactUri()369 private void parseSelectedContactUri() { 370 if (mSelectedContactUri != null) { 371 String directoryParam = 372 mSelectedContactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY); 373 mSelectedContactDirectoryId = TextUtils.isEmpty(directoryParam) ? Directory.DEFAULT 374 : Long.parseLong(directoryParam); 375 if (mSelectedContactUri.toString().startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) { 376 List<String> pathSegments = mSelectedContactUri.getPathSegments(); 377 mSelectedContactLookupKey = Uri.encode(pathSegments.get(2)); 378 if (pathSegments.size() == 4) { 379 mSelectedContactId = ContentUris.parseId(mSelectedContactUri); 380 } 381 } else if (mSelectedContactUri.toString().startsWith(Contacts.CONTENT_URI.toString()) && 382 mSelectedContactUri.getPathSegments().size() >= 2) { 383 mSelectedContactLookupKey = null; 384 mSelectedContactId = ContentUris.parseId(mSelectedContactUri); 385 } else { 386 Log.e(TAG, "Unsupported contact URI: " + mSelectedContactUri); 387 mSelectedContactLookupKey = null; 388 mSelectedContactId = 0; 389 } 390 391 } else { 392 mSelectedContactDirectoryId = Directory.DEFAULT; 393 mSelectedContactLookupKey = null; 394 mSelectedContactId = 0; 395 } 396 } 397 398 @Override configureAdapter()399 protected void configureAdapter() { 400 super.configureAdapter(); 401 402 ContactListAdapter adapter = getAdapter(); 403 if (adapter == null) { 404 return; 405 } 406 407 boolean searchMode = isSearchMode(); 408 if (!searchMode && mFilter != null) { 409 adapter.setFilter(mFilter); 410 if (mSelectionRequired 411 || mFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) { 412 adapter.setSelectedContact( 413 mSelectedContactDirectoryId, mSelectedContactLookupKey, mSelectedContactId); 414 } 415 } 416 417 // Display the user's profile if not in search mode 418 adapter.setIncludeProfile(!searchMode); 419 } 420 421 @Override onLoadFinished(Loader<Cursor> loader, Cursor data)422 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 423 super.onLoadFinished(loader, data); 424 mSelectionVerified = false; 425 426 // Refresh the currently selected lookup in case it changed while we were sleeping 427 refreshSelectedContactUri(); 428 } 429 430 @Override onLoaderReset(Loader<Cursor> loader)431 public void onLoaderReset(Loader<Cursor> loader) { 432 } 433 checkSelection()434 private void checkSelection() { 435 if (mSelectionVerified) { 436 return; 437 } 438 439 if (mRefreshingContactUri) { 440 return; 441 } 442 443 if (isLoadingDirectoryList()) { 444 return; 445 } 446 447 ContactListAdapter adapter = getAdapter(); 448 if (adapter == null) { 449 return; 450 } 451 452 boolean directoryLoading = true; 453 int count = adapter.getPartitionCount(); 454 for (int i = 0; i < count; i++) { 455 Partition partition = adapter.getPartition(i); 456 if (partition instanceof DirectoryPartition) { 457 DirectoryPartition directory = (DirectoryPartition) partition; 458 if (directory.getDirectoryId() == mSelectedContactDirectoryId) { 459 directoryLoading = directory.isLoading(); 460 break; 461 } 462 } 463 } 464 465 if (directoryLoading) { 466 return; 467 } 468 469 adapter.setSelectedContact( 470 mSelectedContactDirectoryId, mSelectedContactLookupKey, mSelectedContactId); 471 472 final int selectedPosition = adapter.getSelectedContactPosition(); 473 if (selectedPosition != -1) { 474 mLastSelectedPosition = selectedPosition; 475 } else { 476 if (isSearchMode()) { 477 if (mDelaySelection) { 478 selectFirstFoundContactAfterDelay(); 479 if (mListener != null) { 480 mListener.onSelectionChange(); 481 } 482 return; 483 } 484 } else if (mSelectionRequired) { 485 // A specific contact was requested, but it's not in the loaded list. 486 487 // Try reconfiguring and reloading the list that will hopefully contain 488 // the requested contact. Only take one attempt to avoid an infinite loop 489 // in case the contact cannot be found at all. 490 mSelectionRequired = false; 491 492 // If we were looking at a different specific contact, just reload 493 if (mFilter != null 494 && mFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) { 495 reloadData(); 496 } else { 497 // Otherwise, call the listener, which will adjust the filter. 498 notifyInvalidSelection(); 499 } 500 return; 501 } else if (mFilter != null 502 && mFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) { 503 // If we were trying to load a specific contact, but that contact no longer 504 // exists, call the listener, which will adjust the filter. 505 notifyInvalidSelection(); 506 return; 507 } 508 509 saveSelectedUri(null); 510 selectDefaultContact(); 511 } 512 513 mSelectionRequired = false; 514 mSelectionVerified = true; 515 516 if (mSelectionPersistenceRequested) { 517 saveSelectedUri(mSelectedContactUri); 518 mSelectionPersistenceRequested = false; 519 } 520 521 if (mSelectionToScreenRequested) { 522 requestSelectionToScreen(selectedPosition); 523 } 524 525 getListView().invalidateViews(); 526 527 if (mListener != null) { 528 mListener.onSelectionChange(); 529 } 530 } 531 532 /** 533 * Automatically selects the first found contact in search mode. The selection 534 * is updated after a delay to allow the user to type without to much UI churn 535 * and to save bandwidth on directory queries. 536 */ selectFirstFoundContactAfterDelay()537 public void selectFirstFoundContactAfterDelay() { 538 Handler handler = getHandler(); 539 handler.removeMessages(MESSAGE_AUTOSELECT_FIRST_FOUND_CONTACT); 540 541 String queryString = getQueryString(); 542 if (queryString != null 543 && queryString.length() >= AUTOSELECT_FIRST_FOUND_CONTACT_MIN_QUERY_LENGTH) { 544 handler.sendEmptyMessageDelayed(MESSAGE_AUTOSELECT_FIRST_FOUND_CONTACT, 545 DELAY_AUTOSELECT_FIRST_FOUND_CONTACT_MILLIS); 546 } else { 547 setSelectedContactUri(null, false, false, false, false); 548 } 549 } 550 selectDefaultContact()551 protected void selectDefaultContact() { 552 Uri contactUri = null; 553 ContactListAdapter adapter = getAdapter(); 554 if (mLastSelectedPosition != -1) { 555 int count = adapter.getCount(); 556 int pos = mLastSelectedPosition; 557 if (pos >= count && count > 0) { 558 pos = count - 1; 559 } 560 contactUri = adapter.getContactUri(pos); 561 } 562 563 if (contactUri == null) { 564 contactUri = adapter.getFirstContactUri(); 565 } 566 567 setSelectedContactUri(contactUri, false, mSmoothScrollRequested, false, false); 568 } 569 requestSelectionToScreen(int selectedPosition)570 protected void requestSelectionToScreen(int selectedPosition) { 571 if (selectedPosition != -1) { 572 AutoScrollListView listView = (AutoScrollListView)getListView(); 573 listView.requestPositionToScreen( 574 selectedPosition + listView.getHeaderViewsCount(), mSmoothScrollRequested); 575 mSelectionToScreenRequested = false; 576 } 577 } 578 579 @Override isLoading()580 public boolean isLoading() { 581 return mRefreshingContactUri || super.isLoading(); 582 } 583 584 @Override startLoading()585 protected void startLoading() { 586 mStartedLoading = true; 587 mSelectionVerified = false; 588 super.startLoading(); 589 } 590 reloadDataAndSetSelectedUri(Uri uri)591 public void reloadDataAndSetSelectedUri(Uri uri) { 592 setSelectedContactUri(uri, true, true, true, true); 593 reloadData(); 594 } 595 596 @Override reloadData()597 public void reloadData() { 598 if (mStartedLoading) { 599 mSelectionVerified = false; 600 mLastSelectedPosition = -1; 601 super.reloadData(); 602 } 603 } 604 setOnContactListActionListener(OnContactBrowserActionListener listener)605 public void setOnContactListActionListener(OnContactBrowserActionListener listener) { 606 mListener = listener; 607 } 608 createNewContact()609 public void createNewContact() { 610 if (mListener != null) mListener.onCreateNewContactAction(); 611 } 612 viewContact(Uri contactUri)613 public void viewContact(Uri contactUri) { 614 setSelectedContactUri(contactUri, false, false, true, false); 615 if (mListener != null) mListener.onViewContactAction(contactUri); 616 } 617 editContact(Uri contactUri)618 public void editContact(Uri contactUri) { 619 if (mListener != null) mListener.onEditContactAction(contactUri); 620 } 621 deleteContact(Uri contactUri)622 public void deleteContact(Uri contactUri) { 623 if (mListener != null) mListener.onDeleteContactAction(contactUri); 624 } 625 addToFavorites(Uri contactUri)626 public void addToFavorites(Uri contactUri) { 627 if (mListener != null) mListener.onAddToFavoritesAction(contactUri); 628 } 629 removeFromFavorites(Uri contactUri)630 public void removeFromFavorites(Uri contactUri) { 631 if (mListener != null) mListener.onRemoveFromFavoritesAction(contactUri); 632 } 633 callContact(Uri contactUri)634 public void callContact(Uri contactUri) { 635 if (mListener != null) mListener.onCallContactAction(contactUri); 636 } 637 smsContact(Uri contactUri)638 public void smsContact(Uri contactUri) { 639 if (mListener != null) mListener.onSmsContactAction(contactUri); 640 } 641 notifyInvalidSelection()642 private void notifyInvalidSelection() { 643 if (mListener != null) mListener.onInvalidSelection(); 644 } 645 646 @Override finish()647 protected void finish() { 648 super.finish(); 649 if (mListener != null) mListener.onFinishAction(); 650 } 651 saveSelectedUri(Uri contactUri)652 private void saveSelectedUri(Uri contactUri) { 653 if (isSearchMode()) { 654 return; 655 } 656 657 ContactListFilter.storeToPreferences(mPrefs, mFilter); 658 659 Editor editor = mPrefs.edit(); 660 if (contactUri == null) { 661 editor.remove(getPersistentSelectionKey()); 662 } else { 663 editor.putString(getPersistentSelectionKey(), contactUri.toString()); 664 } 665 editor.apply(); 666 } 667 restoreSelectedUri(boolean willReloadData)668 private void restoreSelectedUri(boolean willReloadData) { 669 // The meaning of mSelectionRequired is that we need to show some 670 // selection other than the previous selection saved in shared preferences 671 if (mSelectionRequired) { 672 return; 673 } 674 675 String selectedUri = mPrefs.getString(getPersistentSelectionKey(), null); 676 if (selectedUri == null) { 677 setSelectedContactUri(null, false, false, false, willReloadData); 678 } else { 679 setSelectedContactUri(Uri.parse(selectedUri), false, false, false, willReloadData); 680 } 681 } 682 saveFilter()683 private void saveFilter() { 684 ContactListFilter.storeToPreferences(mPrefs, mFilter); 685 } 686 restoreFilter()687 private void restoreFilter() { 688 mFilter = ContactListFilter.restoreDefaultPreferences(mPrefs); 689 } 690 getPersistentSelectionKey()691 private String getPersistentSelectionKey() { 692 if (mFilter == null) { 693 return mPersistentSelectionPrefix; 694 } else { 695 return mPersistentSelectionPrefix + "-" + mFilter.getId(); 696 } 697 } 698 isOptionsMenuChanged()699 public boolean isOptionsMenuChanged() { 700 // This fragment does not have an option menu of its own 701 return false; 702 } 703 } 704