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