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             } finally {
    130                 if (cursor != null) {
    131                     cursor.close();
    132                 }
    133             }
    134         }
    135 
    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
    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 
    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
    177     public void onAttach(Activity activity) {
    178         super.onAttach(activity);
    179         mPrefs = PreferenceManager.getDefaultSharedPreferences(activity);
    180         restoreFilter();
    181         restoreSelectedUri(false);
    182     }
    183 
    184     @Override
    185     protected void setSearchMode(boolean flag) {
    186         if (isSearchMode() != flag) {
    187             if (!flag) {
    188                 restoreSelectedUri(true);
    189             }
    190             super.setSearchMode(flag);
    191         }
    192     }
    193 
    194     public void setFilter(ContactListFilter filter) {
    195         setFilter(filter, true);
    196     }
    197 
    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 
    219     public ContactListFilter getFilter() {
    220         return mFilter;
    221     }
    222 
    223     @Override
    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
    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 
    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 
    272     protected void onContactUriQueryFinished(Uri uri) {
    273         mRefreshingContactUri = false;
    274         mSelectedContactUri = uri;
    275         parseSelectedContactUri();
    276         checkSelection();
    277     }
    278 
    279     public Uri getSelectedContactUri() {
    280         return mSelectedContactUri;
    281     }
    282 
    283     /**
    284      * Sets the new selection for the list.
    285      */
    286     public void setSelectedContactUri(Uri uri) {
    287         setSelectedContactUri(uri, true, false /* no smooth scroll */, true, false);
    288     }
    289 
    290     @Override
    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      */
    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      */
    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 
    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
    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
    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
    415     public void onLoaderReset(Loader<Cursor> loader) {
    416     }
    417 
    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      */
    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 
    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 
    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
    571     public boolean isLoading() {
    572         return mRefreshingContactUri || super.isLoading();
    573     }
    574 
    575     @Override
    576     protected void startLoading() {
    577         mStartedLoading = true;
    578         mSelectionVerified = false;
    579         super.startLoading();
    580     }
    581 
    582     public void reloadDataAndSetSelectedUri(Uri uri) {
    583         setSelectedContactUri(uri, true, true, true, true);
    584         reloadData();
    585     }
    586 
    587     @Override
    588     public void reloadData() {
    589         if (mStartedLoading) {
    590             mSelectionVerified = false;
    591             mLastSelectedPosition = -1;
    592             super.reloadData();
    593         }
    594     }
    595 
    596     public void setOnContactListActionListener(OnContactBrowserActionListener listener) {
    597         mListener = listener;
    598     }
    599 
    600     public void viewContact(Uri contactUri) {
    601         setSelectedContactUri(contactUri, false, false, true, false);
    602         if (mListener != null) mListener.onViewContactAction(contactUri);
    603     }
    604 
    605     public void deleteContact(Uri contactUri) {
    606         if (mListener != null) mListener.onDeleteContactAction(contactUri);
    607     }
    608 
    609     private void notifyInvalidSelection() {
    610         if (mListener != null) mListener.onInvalidSelection();
    611     }
    612 
    613     @Override
    614     protected void finish() {
    615         super.finish();
    616         if (mListener != null) mListener.onFinishAction();
    617     }
    618 
    619     private void saveSelectedUri(Uri contactUri) {
    620         if (isSearchMode()) {
    621             return;
    622         }
    623 
    624         ContactListFilter.storeToPreferences(mPrefs, mFilter);
    625 
    626         Editor editor = mPrefs.edit();
    627         if (contactUri == null) {
    628             editor.remove(getPersistentSelectionKey());
    629         } else {
    630             editor.putString(getPersistentSelectionKey(), contactUri.toString());
    631         }
    632         editor.apply();
    633     }
    634 
    635     private void restoreSelectedUri(boolean willReloadData) {
    636         // The meaning of mSelectionRequired is that we need to show some
    637         // selection other than the previous selection saved in shared preferences
    638         if (mSelectionRequired) {
    639             return;
    640         }
    641 
    642         String selectedUri = mPrefs.getString(getPersistentSelectionKey(), null);
    643         if (selectedUri == null) {
    644             setSelectedContactUri(null, false, false, false, willReloadData);
    645         } else {
    646             setSelectedContactUri(Uri.parse(selectedUri), false, false, false, willReloadData);
    647         }
    648     }
    649 
    650     private void saveFilter() {
    651         ContactListFilter.storeToPreferences(mPrefs, mFilter);
    652     }
    653 
    654     private void restoreFilter() {
    655         mFilter = ContactListFilter.restoreDefaultPreferences(mPrefs);
    656     }
    657 
    658     private String getPersistentSelectionKey() {
    659         if (mFilter == null) {
    660             return mPersistentSelectionPrefix;
    661         } else {
    662             return mPersistentSelectionPrefix + "-" + mFilter.getId();
    663         }
    664     }
    665 
    666     public boolean isOptionsMenuChanged() {
    667         // This fragment does not have an option menu of its own
    668         return false;
    669     }
    670 }
    671