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