• 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 
17 package com.android.contacts.list;
18 
19 import com.android.common.widget.CompositeCursorAdapter.Partition;
20 import com.android.contacts.ContactListEmptyView;
21 import com.android.contacts.ContactPhotoManager;
22 import com.android.contacts.R;
23 import com.android.contacts.preference.ContactsPreferences;
24 import com.android.contacts.widget.ContextMenuAdapter;
25 
26 import android.accounts.Account;
27 import android.accounts.AccountManager;
28 import android.app.Activity;
29 import android.app.Fragment;
30 import android.app.LoaderManager;
31 import android.app.LoaderManager.LoaderCallbacks;
32 import android.content.ContentResolver;
33 import android.content.Context;
34 import android.content.CursorLoader;
35 import android.content.IContentService;
36 import android.content.Intent;
37 import android.content.Loader;
38 import android.database.Cursor;
39 import android.os.Bundle;
40 import android.os.Handler;
41 import android.os.Message;
42 import android.os.Parcelable;
43 import android.os.RemoteException;
44 import android.provider.ContactsContract;
45 import android.provider.ContactsContract.Directory;
46 import android.telephony.TelephonyManager;
47 import android.text.TextUtils;
48 import android.util.Log;
49 import android.view.LayoutInflater;
50 import android.view.MotionEvent;
51 import android.view.View;
52 import android.view.View.OnFocusChangeListener;
53 import android.view.View.OnTouchListener;
54 import android.view.ViewGroup;
55 import android.view.inputmethod.InputMethodManager;
56 import android.widget.AbsListView;
57 import android.widget.AbsListView.OnScrollListener;
58 import android.widget.AdapterView;
59 import android.widget.AdapterView.OnItemClickListener;
60 import android.widget.ListView;
61 import android.widget.TextView;
62 
63 /**
64  * Common base class for various contact-related list fragments.
65  */
66 public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter>
67         extends Fragment
68         implements OnItemClickListener, OnScrollListener, OnFocusChangeListener, OnTouchListener,
69                 LoaderCallbacks<Cursor> {
70     private static final String TAG = "ContactEntryListFragment";
71 
72     // TODO: Make this protected. This should not be used from the PeopleActivity but
73     // instead use the new startActivityWithResultFromFragment API
74     public static final int ACTIVITY_REQUEST_CODE_PICKER = 1;
75 
76     private static final String KEY_LIST_STATE = "liststate";
77     private static final String KEY_SECTION_HEADER_DISPLAY_ENABLED = "sectionHeaderDisplayEnabled";
78     private static final String KEY_PHOTO_LOADER_ENABLED = "photoLoaderEnabled";
79     private static final String KEY_QUICK_CONTACT_ENABLED = "quickContactEnabled";
80     private static final String KEY_INCLUDE_PROFILE = "includeProfile";
81     private static final String KEY_SEARCH_MODE = "searchMode";
82     private static final String KEY_VISIBLE_SCROLLBAR_ENABLED = "visibleScrollbarEnabled";
83     private static final String KEY_SCROLLBAR_POSITION = "scrollbarPosition";
84     private static final String KEY_QUERY_STRING = "queryString";
85     private static final String KEY_DIRECTORY_SEARCH_MODE = "directorySearchMode";
86     private static final String KEY_SELECTION_VISIBLE = "selectionVisible";
87     private static final String KEY_REQUEST = "request";
88     private static final String KEY_DARK_THEME = "darkTheme";
89     private static final String KEY_LEGACY_COMPATIBILITY = "legacyCompatibility";
90     private static final String KEY_DIRECTORY_RESULT_LIMIT = "directoryResultLimit";
91 
92     private static final String DIRECTORY_ID_ARG_KEY = "directoryId";
93 
94     private static final int DIRECTORY_LOADER_ID = -1;
95 
96     private static final int DIRECTORY_SEARCH_DELAY_MILLIS = 300;
97     private static final int DIRECTORY_SEARCH_MESSAGE = 1;
98 
99     private static final int DEFAULT_DIRECTORY_RESULT_LIMIT = 20;
100 
101     private boolean mSectionHeaderDisplayEnabled;
102     private boolean mPhotoLoaderEnabled;
103     private boolean mQuickContactEnabled = true;
104     private boolean mIncludeProfile;
105     private boolean mSearchMode;
106     private boolean mVisibleScrollbarEnabled;
107     private int mVerticalScrollbarPosition = View.SCROLLBAR_POSITION_RIGHT;
108     private String mQueryString;
109     private int mDirectorySearchMode = DirectoryListLoader.SEARCH_MODE_NONE;
110     private boolean mSelectionVisible;
111     private ContactsRequest mRequest;
112     private boolean mLegacyCompatibility;
113 
114     private boolean mEnabled = true;
115 
116     private T mAdapter;
117     private View mView;
118     private ListView mListView;
119 
120     /**
121      * Used for keeping track of the scroll state of the list.
122      */
123     private Parcelable mListState;
124 
125     private int mDisplayOrder;
126     private int mSortOrder;
127     private int mDirectoryResultLimit = DEFAULT_DIRECTORY_RESULT_LIMIT;
128 
129     private ContextMenuAdapter mContextMenuAdapter;
130     private ContactPhotoManager mPhotoManager;
131     private ContactListEmptyView mEmptyView;
132     private ProviderStatusLoader mProviderStatusLoader;
133     private ContactsPreferences mContactsPrefs;
134 
135     private boolean mForceLoad;
136 
137     private boolean mDarkTheme;
138 
139     protected boolean mUserProfileExists;
140 
141     private static final int STATUS_NOT_LOADED = 0;
142     private static final int STATUS_LOADING = 1;
143     private static final int STATUS_LOADED = 2;
144 
145     private int mDirectoryListStatus = STATUS_NOT_LOADED;
146 
147     /**
148      * Indicates whether we are doing the initial complete load of data (false) or
149      * a refresh caused by a change notification (true)
150      */
151     private boolean mLoadPriorityDirectoriesOnly;
152 
153     private Context mContext;
154 
155     private LoaderManager mLoaderManager;
156 
157     private Handler mDelayedDirectorySearchHandler = new Handler() {
158         @Override
159         public void handleMessage(Message msg) {
160             if (msg.what == DIRECTORY_SEARCH_MESSAGE) {
161                 loadDirectoryPartition(msg.arg1, (DirectoryPartition) msg.obj);
162             }
163         }
164     };
165 
inflateView(LayoutInflater inflater, ViewGroup container)166     protected abstract View inflateView(LayoutInflater inflater, ViewGroup container);
createListAdapter()167     protected abstract T createListAdapter();
168 
169     /**
170      * @param position Please note that the position is already adjusted for
171      *            header views, so "0" means the first list item below header
172      *            views.
173      */
onItemClick(int position, long id)174     protected abstract void onItemClick(int position, long id);
175 
176     @Override
onAttach(Activity activity)177     public void onAttach(Activity activity) {
178         super.onAttach(activity);
179         setContext(activity);
180         setLoaderManager(super.getLoaderManager());
181     }
182 
183     /**
184      * Sets a context for the fragment in the unit test environment.
185      */
setContext(Context context)186     public void setContext(Context context) {
187         mContext = context;
188         configurePhotoLoader();
189     }
190 
getContext()191     public Context getContext() {
192         return mContext;
193     }
194 
setEnabled(boolean enabled)195     public void setEnabled(boolean enabled) {
196         if (mEnabled != enabled) {
197             mEnabled = enabled;
198             if (mAdapter != null) {
199                 if (mEnabled) {
200                     reloadData();
201                 } else {
202                     mAdapter.clearPartitions();
203                 }
204             }
205         }
206     }
207 
208     /**
209      * Overrides a loader manager for use in unit tests.
210      */
setLoaderManager(LoaderManager loaderManager)211     public void setLoaderManager(LoaderManager loaderManager) {
212         mLoaderManager = loaderManager;
213     }
214 
215     @Override
getLoaderManager()216     public LoaderManager getLoaderManager() {
217         return mLoaderManager;
218     }
219 
getAdapter()220     public T getAdapter() {
221         return mAdapter;
222     }
223 
224     @Override
getView()225     public View getView() {
226         return mView;
227     }
228 
getListView()229     public ListView getListView() {
230         return mListView;
231     }
232 
getEmptyView()233     public ContactListEmptyView getEmptyView() {
234         return mEmptyView;
235     }
236 
237     @Override
onSaveInstanceState(Bundle outState)238     public void onSaveInstanceState(Bundle outState) {
239         super.onSaveInstanceState(outState);
240         outState.putBoolean(KEY_SECTION_HEADER_DISPLAY_ENABLED, mSectionHeaderDisplayEnabled);
241         outState.putBoolean(KEY_PHOTO_LOADER_ENABLED, mPhotoLoaderEnabled);
242         outState.putBoolean(KEY_QUICK_CONTACT_ENABLED, mQuickContactEnabled);
243         outState.putBoolean(KEY_INCLUDE_PROFILE, mIncludeProfile);
244         outState.putBoolean(KEY_SEARCH_MODE, mSearchMode);
245         outState.putBoolean(KEY_VISIBLE_SCROLLBAR_ENABLED, mVisibleScrollbarEnabled);
246         outState.putInt(KEY_SCROLLBAR_POSITION, mVerticalScrollbarPosition);
247         outState.putInt(KEY_DIRECTORY_SEARCH_MODE, mDirectorySearchMode);
248         outState.putBoolean(KEY_SELECTION_VISIBLE, mSelectionVisible);
249         outState.putBoolean(KEY_LEGACY_COMPATIBILITY, mLegacyCompatibility);
250         outState.putString(KEY_QUERY_STRING, mQueryString);
251         outState.putInt(KEY_DIRECTORY_RESULT_LIMIT, mDirectoryResultLimit);
252         outState.putParcelable(KEY_REQUEST, mRequest);
253         outState.putBoolean(KEY_DARK_THEME, mDarkTheme);
254 
255         if (mListView != null) {
256             mListState = mListView.onSaveInstanceState();
257             outState.putParcelable(KEY_LIST_STATE, mListState);
258         }
259     }
260 
261     @Override
onCreate(Bundle savedState)262     public void onCreate(Bundle savedState) {
263         super.onCreate(savedState);
264         mContactsPrefs = new ContactsPreferences(mContext);
265         restoreSavedState(savedState);
266     }
267 
restoreSavedState(Bundle savedState)268     public void restoreSavedState(Bundle savedState) {
269         if (savedState == null) {
270             return;
271         }
272 
273         mSectionHeaderDisplayEnabled = savedState.getBoolean(KEY_SECTION_HEADER_DISPLAY_ENABLED);
274         mPhotoLoaderEnabled = savedState.getBoolean(KEY_PHOTO_LOADER_ENABLED);
275         mQuickContactEnabled = savedState.getBoolean(KEY_QUICK_CONTACT_ENABLED);
276         mIncludeProfile = savedState.getBoolean(KEY_INCLUDE_PROFILE);
277         mSearchMode = savedState.getBoolean(KEY_SEARCH_MODE);
278         mVisibleScrollbarEnabled = savedState.getBoolean(KEY_VISIBLE_SCROLLBAR_ENABLED);
279         mVerticalScrollbarPosition = savedState.getInt(KEY_SCROLLBAR_POSITION);
280         mDirectorySearchMode = savedState.getInt(KEY_DIRECTORY_SEARCH_MODE);
281         mSelectionVisible = savedState.getBoolean(KEY_SELECTION_VISIBLE);
282         mLegacyCompatibility = savedState.getBoolean(KEY_LEGACY_COMPATIBILITY);
283         mQueryString = savedState.getString(KEY_QUERY_STRING);
284         mDirectoryResultLimit = savedState.getInt(KEY_DIRECTORY_RESULT_LIMIT);
285         mRequest = savedState.getParcelable(KEY_REQUEST);
286         mDarkTheme = savedState.getBoolean(KEY_DARK_THEME);
287 
288         // Retrieve list state. This will be applied in onLoadFinished
289         mListState = savedState.getParcelable(KEY_LIST_STATE);
290     }
291 
292     /**
293      * Returns the parsed intent that started the activity hosting this fragment.
294      */
getContactsRequest()295     public ContactsRequest getContactsRequest() {
296         return mRequest;
297     }
298 
299     /**
300      * Sets a parsed intent that started the activity hosting this fragment.
301      */
setContactsRequest(ContactsRequest request)302     public void setContactsRequest(ContactsRequest request) {
303         mRequest = request;
304     }
305 
306     @Override
onStart()307     public void onStart() {
308         super.onStart();
309 
310         mContactsPrefs.registerChangeListener(mPreferencesChangeListener);
311 
312         if (mProviderStatusLoader == null) {
313             mProviderStatusLoader = new ProviderStatusLoader(mContext);
314         }
315 
316         mForceLoad = loadPreferences();
317 
318         mDirectoryListStatus = STATUS_NOT_LOADED;
319         mLoadPriorityDirectoriesOnly = true;
320 
321         startLoading();
322     }
323 
startLoading()324     protected void startLoading() {
325         if (mAdapter == null) {
326             // The method was called before the fragment was started
327             return;
328         }
329 
330         configureAdapter();
331         int partitionCount = mAdapter.getPartitionCount();
332         for (int i = 0; i < partitionCount; i++) {
333             Partition partition = mAdapter.getPartition(i);
334             if (partition instanceof DirectoryPartition) {
335                 DirectoryPartition directoryPartition = (DirectoryPartition)partition;
336                 if (directoryPartition.getStatus() == DirectoryPartition.STATUS_NOT_LOADED) {
337                     if (directoryPartition.isPriorityDirectory() || !mLoadPriorityDirectoriesOnly) {
338                         startLoadingDirectoryPartition(i);
339                     }
340                 }
341             } else {
342                 getLoaderManager().initLoader(i, null, this);
343             }
344         }
345 
346         // Next time this method is called, we should start loading non-priority directories
347         mLoadPriorityDirectoriesOnly = false;
348     }
349 
350     @Override
onCreateLoader(int id, Bundle args)351     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
352         if (id == DIRECTORY_LOADER_ID) {
353             DirectoryListLoader loader = new DirectoryListLoader(mContext);
354             mAdapter.configureDirectoryLoader(loader);
355             return loader;
356         } else {
357             CursorLoader loader = createCursorLoader();
358             long directoryId = args != null && args.containsKey(DIRECTORY_ID_ARG_KEY)
359                     ? args.getLong(DIRECTORY_ID_ARG_KEY)
360                     : Directory.DEFAULT;
361             mAdapter.configureLoader(loader, directoryId);
362             return loader;
363         }
364     }
365 
createCursorLoader()366     public CursorLoader createCursorLoader() {
367         return new CursorLoader(mContext, null, null, null, null, null);
368     }
369 
startLoadingDirectoryPartition(int partitionIndex)370     private void startLoadingDirectoryPartition(int partitionIndex) {
371         DirectoryPartition partition = (DirectoryPartition)mAdapter.getPartition(partitionIndex);
372         partition.setStatus(DirectoryPartition.STATUS_LOADING);
373         long directoryId = partition.getDirectoryId();
374         if (mForceLoad) {
375             if (directoryId == Directory.DEFAULT) {
376                 loadDirectoryPartition(partitionIndex, partition);
377             } else {
378                 loadDirectoryPartitionDelayed(partitionIndex, partition);
379             }
380         } else {
381             Bundle args = new Bundle();
382             args.putLong(DIRECTORY_ID_ARG_KEY, directoryId);
383             getLoaderManager().initLoader(partitionIndex, args, this);
384         }
385     }
386 
387     /**
388      * Queues up a delayed request to search the specified directory. Since
389      * directory search will likely introduce a lot of network traffic, we want
390      * to wait for a pause in the user's typing before sending a directory request.
391      */
loadDirectoryPartitionDelayed(int partitionIndex, DirectoryPartition partition)392     private void loadDirectoryPartitionDelayed(int partitionIndex, DirectoryPartition partition) {
393         mDelayedDirectorySearchHandler.removeMessages(DIRECTORY_SEARCH_MESSAGE, partition);
394         Message msg = mDelayedDirectorySearchHandler.obtainMessage(
395                 DIRECTORY_SEARCH_MESSAGE, partitionIndex, 0, partition);
396         mDelayedDirectorySearchHandler.sendMessageDelayed(msg, DIRECTORY_SEARCH_DELAY_MILLIS);
397     }
398 
399     /**
400      * Loads the directory partition.
401      */
loadDirectoryPartition(int partitionIndex, DirectoryPartition partition)402     protected void loadDirectoryPartition(int partitionIndex, DirectoryPartition partition) {
403         Bundle args = new Bundle();
404         args.putLong(DIRECTORY_ID_ARG_KEY, partition.getDirectoryId());
405         getLoaderManager().restartLoader(partitionIndex, args, this);
406     }
407 
408     /**
409      * Cancels all queued directory loading requests.
410      */
removePendingDirectorySearchRequests()411     private void removePendingDirectorySearchRequests() {
412         mDelayedDirectorySearchHandler.removeMessages(DIRECTORY_SEARCH_MESSAGE);
413     }
414 
415     @Override
onLoadFinished(Loader<Cursor> loader, Cursor data)416     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
417         if (!mEnabled) {
418             return;
419         }
420 
421         int loaderId = loader.getId();
422         if (loaderId == DIRECTORY_LOADER_ID) {
423             mDirectoryListStatus = STATUS_LOADED;
424             mAdapter.changeDirectories(data);
425             startLoading();
426         } else {
427             onPartitionLoaded(loaderId, data);
428             if (isSearchMode()) {
429                 int directorySearchMode = getDirectorySearchMode();
430                 if (directorySearchMode != DirectoryListLoader.SEARCH_MODE_NONE) {
431                     if (mDirectoryListStatus == STATUS_NOT_LOADED) {
432                         mDirectoryListStatus = STATUS_LOADING;
433                         getLoaderManager().initLoader(DIRECTORY_LOADER_ID, null, this);
434                     } else {
435                         startLoading();
436                     }
437                 }
438             } else {
439                 mDirectoryListStatus = STATUS_NOT_LOADED;
440                 getLoaderManager().destroyLoader(DIRECTORY_LOADER_ID);
441             }
442         }
443     }
444 
onLoaderReset(Loader<Cursor> loader)445     public void onLoaderReset(Loader<Cursor> loader) {
446     }
447 
onPartitionLoaded(int partitionIndex, Cursor data)448     protected void onPartitionLoaded(int partitionIndex, Cursor data) {
449         if (partitionIndex >= mAdapter.getPartitionCount()) {
450             // When we get unsolicited data, ignore it.  This could happen
451             // when we are switching from search mode to the default mode.
452             return;
453         }
454 
455         mAdapter.changeCursor(partitionIndex, data);
456         setProfileHeader();
457         showCount(partitionIndex, data);
458 
459         if (!isLoading()) {
460             completeRestoreInstanceState();
461         }
462     }
463 
isLoading()464     public boolean isLoading() {
465         if (mAdapter != null && mAdapter.isLoading()) {
466             return true;
467         }
468 
469         if (isLoadingDirectoryList()) {
470             return true;
471         }
472 
473         return false;
474     }
475 
isLoadingDirectoryList()476     public boolean isLoadingDirectoryList() {
477         return isSearchMode() && getDirectorySearchMode() != DirectoryListLoader.SEARCH_MODE_NONE
478                 && (mDirectoryListStatus == STATUS_NOT_LOADED
479                         || mDirectoryListStatus == STATUS_LOADING);
480     }
481 
482     @Override
onStop()483     public void onStop() {
484         super.onStop();
485         mContactsPrefs.unregisterChangeListener();
486         mAdapter.clearPartitions();
487     }
488 
reloadData()489     protected void reloadData() {
490         removePendingDirectorySearchRequests();
491         mAdapter.onDataReload();
492         mLoadPriorityDirectoriesOnly = true;
493         mForceLoad = true;
494         startLoading();
495     }
496 
497     /**
498      * Configures the empty view. It is called when we are about to populate
499      * the list with an empty cursor.
500      */
prepareEmptyView()501     protected void prepareEmptyView() {
502     }
503 
504     /**
505      * Shows the count of entries included in the list. The default
506      * implementation does nothing.
507      */
showCount(int partitionIndex, Cursor data)508     protected void showCount(int partitionIndex, Cursor data) {
509     }
510 
511     /**
512      * Shows a view at the top of the list with a pseudo local profile prompting the user to add
513      * a local profile. Default implementation does nothing.
514      */
setProfileHeader()515     protected void setProfileHeader() {
516         mUserProfileExists = false;
517     }
518 
519     /**
520      * Provides logic that dismisses this fragment. The default implementation
521      * does nothing.
522      */
finish()523     protected void finish() {
524     }
525 
setSectionHeaderDisplayEnabled(boolean flag)526     public void setSectionHeaderDisplayEnabled(boolean flag) {
527         if (mSectionHeaderDisplayEnabled != flag) {
528             mSectionHeaderDisplayEnabled = flag;
529             if (mAdapter != null) {
530                 mAdapter.setSectionHeaderDisplayEnabled(flag);
531             }
532             configureVerticalScrollbar();
533         }
534     }
535 
isSectionHeaderDisplayEnabled()536     public boolean isSectionHeaderDisplayEnabled() {
537         return mSectionHeaderDisplayEnabled;
538     }
539 
setVisibleScrollbarEnabled(boolean flag)540     public void setVisibleScrollbarEnabled(boolean flag) {
541         if (mVisibleScrollbarEnabled != flag) {
542             mVisibleScrollbarEnabled = flag;
543             configureVerticalScrollbar();
544         }
545     }
546 
isVisibleScrollbarEnabled()547     public boolean isVisibleScrollbarEnabled() {
548         return mVisibleScrollbarEnabled;
549     }
550 
setVerticalScrollbarPosition(int position)551     public void setVerticalScrollbarPosition(int position) {
552         if (mVerticalScrollbarPosition != position) {
553             mVerticalScrollbarPosition = position;
554             configureVerticalScrollbar();
555         }
556     }
557 
configureVerticalScrollbar()558     private void configureVerticalScrollbar() {
559         boolean hasScrollbar = isVisibleScrollbarEnabled() && isSectionHeaderDisplayEnabled();
560 
561         if (mListView != null) {
562             mListView.setFastScrollEnabled(hasScrollbar);
563             mListView.setFastScrollAlwaysVisible(hasScrollbar);
564             mListView.setVerticalScrollbarPosition(mVerticalScrollbarPosition);
565             mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
566             int leftPadding = 0;
567             int rightPadding = 0;
568             if (mVerticalScrollbarPosition == View.SCROLLBAR_POSITION_LEFT) {
569                 leftPadding = mContext.getResources().getDimensionPixelOffset(
570                         R.dimen.list_visible_scrollbar_padding);
571             } else {
572                 rightPadding = mContext.getResources().getDimensionPixelOffset(
573                         R.dimen.list_visible_scrollbar_padding);
574             }
575             mListView.setPadding(leftPadding, mListView.getPaddingTop(),
576                     rightPadding, mListView.getPaddingBottom());
577         }
578     }
579 
setPhotoLoaderEnabled(boolean flag)580     public void setPhotoLoaderEnabled(boolean flag) {
581         mPhotoLoaderEnabled = flag;
582         configurePhotoLoader();
583     }
584 
isPhotoLoaderEnabled()585     public boolean isPhotoLoaderEnabled() {
586         return mPhotoLoaderEnabled;
587     }
588 
589     /**
590      * Returns true if the list is supposed to visually highlight the selected item.
591      */
isSelectionVisible()592     public boolean isSelectionVisible() {
593         return mSelectionVisible;
594     }
595 
setSelectionVisible(boolean flag)596     public void setSelectionVisible(boolean flag) {
597         this.mSelectionVisible = flag;
598     }
599 
setQuickContactEnabled(boolean flag)600     public void setQuickContactEnabled(boolean flag) {
601         this.mQuickContactEnabled = flag;
602     }
603 
setIncludeProfile(boolean flag)604     public void setIncludeProfile(boolean flag) {
605         mIncludeProfile = flag;
606         if(mAdapter != null) {
607             mAdapter.setIncludeProfile(flag);
608         }
609     }
610 
setSearchMode(boolean flag)611     public void setSearchMode(boolean flag) {
612         if (mSearchMode != flag) {
613             mSearchMode = flag;
614             setSectionHeaderDisplayEnabled(!mSearchMode);
615 
616             if (!flag) {
617                 mDirectoryListStatus = STATUS_NOT_LOADED;
618                 getLoaderManager().destroyLoader(DIRECTORY_LOADER_ID);
619             }
620 
621             if (mAdapter != null) {
622                 mAdapter.setPinnedPartitionHeadersEnabled(flag);
623                 mAdapter.setSearchMode(flag);
624 
625                 mAdapter.clearPartitions();
626                 if (!flag) {
627                     // If we are switching from search to regular display,
628                     // remove all directory partitions (except the default one).
629                     int count = mAdapter.getPartitionCount();
630                     for (int i = count; --i >= 1;) {
631                         mAdapter.removePartition(i);
632                     }
633                 }
634                 mAdapter.configureDefaultPartition(false, flag);
635                 reloadData();
636             }
637 
638             if (mListView != null) {
639                 mListView.setFastScrollEnabled(!flag);
640             }
641         }
642     }
643 
isSearchMode()644     public boolean isSearchMode() {
645         return mSearchMode;
646     }
647 
getQueryString()648     public String getQueryString() {
649         return mQueryString;
650     }
651 
setQueryString(String queryString, boolean delaySelection)652     public void setQueryString(String queryString, boolean delaySelection) {
653         if (!TextUtils.equals(mQueryString, queryString)) {
654             mQueryString = queryString;
655             if (mAdapter != null) {
656                 mAdapter.setQueryString(queryString);
657                 reloadData();
658             }
659         }
660     }
661 
getDirectorySearchMode()662     public int getDirectorySearchMode() {
663         return mDirectorySearchMode;
664     }
665 
setDirectorySearchMode(int mode)666     public void setDirectorySearchMode(int mode) {
667         mDirectorySearchMode = mode;
668     }
669 
isLegacyCompatibilityMode()670     public boolean isLegacyCompatibilityMode() {
671         return mLegacyCompatibility;
672     }
673 
setLegacyCompatibilityMode(boolean flag)674     public void setLegacyCompatibilityMode(boolean flag) {
675         mLegacyCompatibility = flag;
676     }
677 
getContactNameDisplayOrder()678     protected int getContactNameDisplayOrder() {
679         return mDisplayOrder;
680     }
681 
setContactNameDisplayOrder(int displayOrder)682     protected void setContactNameDisplayOrder(int displayOrder) {
683         mDisplayOrder = displayOrder;
684         if (mAdapter != null) {
685             mAdapter.setContactNameDisplayOrder(displayOrder);
686         }
687     }
688 
getSortOrder()689     public int getSortOrder() {
690         return mSortOrder;
691     }
692 
setSortOrder(int sortOrder)693     public void setSortOrder(int sortOrder) {
694         mSortOrder = sortOrder;
695         if (mAdapter != null) {
696             mAdapter.setSortOrder(sortOrder);
697         }
698     }
699 
setDirectoryResultLimit(int limit)700     public void setDirectoryResultLimit(int limit) {
701         mDirectoryResultLimit = limit;
702     }
703 
setContextMenuAdapter(ContextMenuAdapter adapter)704     public void setContextMenuAdapter(ContextMenuAdapter adapter) {
705         mContextMenuAdapter = adapter;
706         if (mListView != null) {
707             mListView.setOnCreateContextMenuListener(adapter);
708         }
709     }
710 
getContextMenuAdapter()711     public ContextMenuAdapter getContextMenuAdapter() {
712         return mContextMenuAdapter;
713     }
714 
loadPreferences()715     protected boolean loadPreferences() {
716         boolean changed = false;
717         if (getContactNameDisplayOrder() != mContactsPrefs.getDisplayOrder()) {
718             setContactNameDisplayOrder(mContactsPrefs.getDisplayOrder());
719             changed = true;
720         }
721 
722         if (getSortOrder() != mContactsPrefs.getSortOrder()) {
723             setSortOrder(mContactsPrefs.getSortOrder());
724             changed = true;
725         }
726 
727         return changed;
728     }
729 
730     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)731     public View onCreateView(LayoutInflater inflater, ViewGroup container,
732             Bundle savedInstanceState) {
733         onCreateView(inflater, container);
734 
735         mAdapter = createListAdapter();
736 
737         boolean searchMode = isSearchMode();
738         mAdapter.setSearchMode(searchMode);
739         mAdapter.configureDefaultPartition(false, searchMode);
740         mAdapter.setPhotoLoader(mPhotoManager);
741         mListView.setAdapter(mAdapter);
742 
743         if (!isSearchMode()) {
744             mListView.setFocusableInTouchMode(true);
745             mListView.requestFocus();
746         }
747 
748         return mView;
749     }
750 
onCreateView(LayoutInflater inflater, ViewGroup container)751     protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
752         mView = inflateView(inflater, container);
753 
754         mListView = (ListView)mView.findViewById(android.R.id.list);
755         if (mListView == null) {
756             throw new RuntimeException(
757                     "Your content must have a ListView whose id attribute is " +
758                     "'android.R.id.list'");
759         }
760 
761         View emptyView = mView.findViewById(com.android.internal.R.id.empty);
762         if (emptyView != null) {
763             mListView.setEmptyView(emptyView);
764             if (emptyView instanceof ContactListEmptyView) {
765                 mEmptyView = (ContactListEmptyView)emptyView;
766             }
767         }
768 
769         mListView.setOnItemClickListener(this);
770         mListView.setOnFocusChangeListener(this);
771         mListView.setOnTouchListener(this);
772         mListView.setFastScrollEnabled(!isSearchMode());
773 
774         // Tell list view to not show dividers. We'll do it ourself so that we can *not* show
775         // them when an A-Z headers is visible.
776         mListView.setDividerHeight(0);
777 
778         // We manually save/restore the listview state
779         mListView.setSaveEnabled(false);
780 
781         if (mContextMenuAdapter != null) {
782             mListView.setOnCreateContextMenuListener(mContextMenuAdapter);
783         }
784 
785         configureVerticalScrollbar();
786         configurePhotoLoader();
787     }
788 
configurePhotoLoader()789     protected void configurePhotoLoader() {
790         if (isPhotoLoaderEnabled() && mContext != null) {
791             if (mPhotoManager == null) {
792                 mPhotoManager = ContactPhotoManager.getInstance(mContext);
793             }
794             if (mListView != null) {
795                 mListView.setOnScrollListener(this);
796             }
797             if (mAdapter != null) {
798                 mAdapter.setPhotoLoader(mPhotoManager);
799             }
800         }
801     }
802 
configureAdapter()803     protected void configureAdapter() {
804         if (mAdapter == null) {
805             return;
806         }
807 
808         mAdapter.setQuickContactEnabled(mQuickContactEnabled);
809         mAdapter.setIncludeProfile(mIncludeProfile);
810         mAdapter.setQueryString(mQueryString);
811         mAdapter.setDirectorySearchMode(mDirectorySearchMode);
812         mAdapter.setPinnedPartitionHeadersEnabled(mSearchMode);
813         mAdapter.setContactNameDisplayOrder(mDisplayOrder);
814         mAdapter.setSortOrder(mSortOrder);
815         mAdapter.setSectionHeaderDisplayEnabled(mSectionHeaderDisplayEnabled);
816         mAdapter.setSelectionVisible(mSelectionVisible);
817         mAdapter.setDirectoryResultLimit(mDirectoryResultLimit);
818         mAdapter.setDarkTheme(mDarkTheme);
819     }
820 
821     @Override
onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)822     public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
823             int totalItemCount) {
824     }
825 
826     @Override
onScrollStateChanged(AbsListView view, int scrollState)827     public void onScrollStateChanged(AbsListView view, int scrollState) {
828         if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
829             mPhotoManager.pause();
830         } else if (isPhotoLoaderEnabled()) {
831             mPhotoManager.resume();
832         }
833     }
834 
835     @Override
onItemClick(AdapterView<?> parent, View view, int position, long id)836     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
837         hideSoftKeyboard();
838 
839         int adjPosition = position - mListView.getHeaderViewsCount();
840         if (adjPosition >= 0) {
841             onItemClick(adjPosition, id);
842         }
843     }
844 
hideSoftKeyboard()845     private void hideSoftKeyboard() {
846         // Hide soft keyboard, if visible
847         InputMethodManager inputMethodManager = (InputMethodManager)
848                 mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
849         inputMethodManager.hideSoftInputFromWindow(mListView.getWindowToken(), 0);
850     }
851 
852     /**
853      * Dismisses the soft keyboard when the list takes focus.
854      */
855     @Override
onFocusChange(View view, boolean hasFocus)856     public void onFocusChange(View view, boolean hasFocus) {
857         if (view == mListView && hasFocus) {
858             hideSoftKeyboard();
859         }
860     }
861 
862     /**
863      * Dismisses the soft keyboard when the list is touched.
864      */
865     @Override
onTouch(View view, MotionEvent event)866     public boolean onTouch(View view, MotionEvent event) {
867         if (view == mListView) {
868             hideSoftKeyboard();
869         }
870         return false;
871     }
872 
873     @Override
onPause()874     public void onPause() {
875         super.onPause();
876         removePendingDirectorySearchRequests();
877     }
878 
879     /**
880      * Dismisses the search UI along with the keyboard if the filter text is empty.
881      */
onClose()882     public void onClose() {
883         hideSoftKeyboard();
884         finish();
885     }
886 
887     /**
888      * Restore the list state after the adapter is populated.
889      */
completeRestoreInstanceState()890     protected void completeRestoreInstanceState() {
891         if (mListState != null) {
892             mListView.onRestoreInstanceState(mListState);
893             mListState = null;
894         }
895     }
896 
setEmptyText(int resourceId)897     protected void setEmptyText(int resourceId) {
898         TextView empty = (TextView) getEmptyView().findViewById(R.id.emptyText);
899         empty.setText(mContext.getText(resourceId));
900         empty.setVisibility(View.VISIBLE);
901     }
902 
903     // TODO redesign into an async task or loader
isSyncActive()904     protected boolean isSyncActive() {
905         Account[] accounts = AccountManager.get(mContext).getAccounts();
906         if (accounts != null && accounts.length > 0) {
907             IContentService contentService = ContentResolver.getContentService();
908             for (Account account : accounts) {
909                 try {
910                     if (contentService.isSyncActive(account, ContactsContract.AUTHORITY)) {
911                         return true;
912                     }
913                 } catch (RemoteException e) {
914                     Log.e(TAG, "Could not get the sync status");
915                 }
916             }
917         }
918         return false;
919     }
920 
hasIccCard()921     protected boolean hasIccCard() {
922         TelephonyManager telephonyManager =
923                 (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
924         return telephonyManager.hasIccCard();
925     }
926 
setDarkTheme(boolean value)927     public void setDarkTheme(boolean value) {
928         mDarkTheme = value;
929         if (mAdapter != null) mAdapter.setDarkTheme(value);
930     }
931 
932     /**
933      * Processes a result returned by the contact picker.
934      */
onPickerResult(Intent data)935     public void onPickerResult(Intent data) {
936         throw new UnsupportedOperationException("Picker result handler is not implemented.");
937     }
938 
939     private ContactsPreferences.ChangeListener mPreferencesChangeListener =
940             new ContactsPreferences.ChangeListener() {
941         @Override
942         public void onChange() {
943             loadPreferences();
944             reloadData();
945         }
946     };
947 }
948