blob: 8db477e25bb2bb2487de4a9dd33667963d929ac9 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.contacts.list;
import com.android.common.widget.CompositeCursorAdapter.Partition;
import com.android.contacts.ContactListEmptyView;
import com.android.contacts.ContactPhotoManager;
import com.android.contacts.R;
import com.android.contacts.preference.ContactsPreferences;
import com.android.contacts.widget.ContextMenuAdapter;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.Fragment;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.ContentResolver;
import android.content.Context;
import android.content.CursorLoader;
import android.content.IContentService;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Parcelable;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Directory;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;
/**
* Common base class for various contact-related list fragments.
*/
public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter>
extends Fragment
implements OnItemClickListener, OnScrollListener, OnFocusChangeListener, OnTouchListener,
LoaderCallbacks<Cursor> {
private static final String TAG = "ContactEntryListFragment";
// TODO: Make this protected. This should not be used from the PeopleActivity but
// instead use the new startActivityWithResultFromFragment API
public static final int ACTIVITY_REQUEST_CODE_PICKER = 1;
private static final String KEY_LIST_STATE = "liststate";
private static final String KEY_SECTION_HEADER_DISPLAY_ENABLED = "sectionHeaderDisplayEnabled";
private static final String KEY_PHOTO_LOADER_ENABLED = "photoLoaderEnabled";
private static final String KEY_QUICK_CONTACT_ENABLED = "quickContactEnabled";
private static final String KEY_INCLUDE_PROFILE = "includeProfile";
private static final String KEY_SEARCH_MODE = "searchMode";
private static final String KEY_VISIBLE_SCROLLBAR_ENABLED = "visibleScrollbarEnabled";
private static final String KEY_SCROLLBAR_POSITION = "scrollbarPosition";
private static final String KEY_QUERY_STRING = "queryString";
private static final String KEY_DIRECTORY_SEARCH_MODE = "directorySearchMode";
private static final String KEY_SELECTION_VISIBLE = "selectionVisible";
private static final String KEY_REQUEST = "request";
private static final String KEY_DARK_THEME = "darkTheme";
private static final String KEY_LEGACY_COMPATIBILITY = "legacyCompatibility";
private static final String KEY_DIRECTORY_RESULT_LIMIT = "directoryResultLimit";
private static final String DIRECTORY_ID_ARG_KEY = "directoryId";
private static final int DIRECTORY_LOADER_ID = -1;
private static final int DIRECTORY_SEARCH_DELAY_MILLIS = 300;
private static final int DIRECTORY_SEARCH_MESSAGE = 1;
private static final int DEFAULT_DIRECTORY_RESULT_LIMIT = 20;
private boolean mSectionHeaderDisplayEnabled;
private boolean mPhotoLoaderEnabled;
private boolean mQuickContactEnabled = true;
private boolean mIncludeProfile;
private boolean mSearchMode;
private boolean mVisibleScrollbarEnabled;
private int mVerticalScrollbarPosition = View.SCROLLBAR_POSITION_RIGHT;
private String mQueryString;
private int mDirectorySearchMode = DirectoryListLoader.SEARCH_MODE_NONE;
private boolean mSelectionVisible;
private boolean mLegacyCompatibility;
private boolean mEnabled = true;
private T mAdapter;
private View mView;
private ListView mListView;
/**
* Used for keeping track of the scroll state of the list.
*/
private Parcelable mListState;
private int mDisplayOrder;
private int mSortOrder;
private int mDirectoryResultLimit = DEFAULT_DIRECTORY_RESULT_LIMIT;
private ContextMenuAdapter mContextMenuAdapter;
private ContactPhotoManager mPhotoManager;
private ContactListEmptyView mEmptyView;
private ProviderStatusLoader mProviderStatusLoader;
private ContactsPreferences mContactsPrefs;
private boolean mForceLoad;
private boolean mDarkTheme;
protected boolean mUserProfileExists;
private static final int STATUS_NOT_LOADED = 0;
private static final int STATUS_LOADING = 1;
private static final int STATUS_LOADED = 2;
private int mDirectoryListStatus = STATUS_NOT_LOADED;
/**
* Indicates whether we are doing the initial complete load of data (false) or
* a refresh caused by a change notification (true)
*/
private boolean mLoadPriorityDirectoriesOnly;
private Context mContext;
private LoaderManager mLoaderManager;
private Handler mDelayedDirectorySearchHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == DIRECTORY_SEARCH_MESSAGE) {
loadDirectoryPartition(msg.arg1, (DirectoryPartition) msg.obj);
}
}
};
protected abstract View inflateView(LayoutInflater inflater, ViewGroup container);
protected abstract T createListAdapter();
/**
* @param position Please note that the position is already adjusted for
* header views, so "0" means the first list item below header
* views.
*/
protected abstract void onItemClick(int position, long id);
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
setContext(activity);
setLoaderManager(super.getLoaderManager());
}
/**
* Sets a context for the fragment in the unit test environment.
*/
public void setContext(Context context) {
mContext = context;
configurePhotoLoader();
}
public Context getContext() {
return mContext;
}
public void setEnabled(boolean enabled) {
if (mEnabled != enabled) {
mEnabled = enabled;
if (mAdapter != null) {
if (mEnabled) {
reloadData();
} else {
mAdapter.clearPartitions();
}
}
}
}
/**
* Overrides a loader manager for use in unit tests.
*/
public void setLoaderManager(LoaderManager loaderManager) {
mLoaderManager = loaderManager;
}
@Override
public LoaderManager getLoaderManager() {
return mLoaderManager;
}
public T getAdapter() {
return mAdapter;
}
@Override
public View getView() {
return mView;
}
public ListView getListView() {
return mListView;
}
public ContactListEmptyView getEmptyView() {
return mEmptyView;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(KEY_SECTION_HEADER_DISPLAY_ENABLED, mSectionHeaderDisplayEnabled);
outState.putBoolean(KEY_PHOTO_LOADER_ENABLED, mPhotoLoaderEnabled);
outState.putBoolean(KEY_QUICK_CONTACT_ENABLED, mQuickContactEnabled);
outState.putBoolean(KEY_INCLUDE_PROFILE, mIncludeProfile);
outState.putBoolean(KEY_SEARCH_MODE, mSearchMode);
outState.putBoolean(KEY_VISIBLE_SCROLLBAR_ENABLED, mVisibleScrollbarEnabled);
outState.putInt(KEY_SCROLLBAR_POSITION, mVerticalScrollbarPosition);
outState.putInt(KEY_DIRECTORY_SEARCH_MODE, mDirectorySearchMode);
outState.putBoolean(KEY_SELECTION_VISIBLE, mSelectionVisible);
outState.putBoolean(KEY_LEGACY_COMPATIBILITY, mLegacyCompatibility);
outState.putString(KEY_QUERY_STRING, mQueryString);
outState.putInt(KEY_DIRECTORY_RESULT_LIMIT, mDirectoryResultLimit);
outState.putBoolean(KEY_DARK_THEME, mDarkTheme);
if (mListView != null) {
outState.putParcelable(KEY_LIST_STATE, mListView.onSaveInstanceState());
}
}
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
mContactsPrefs = new ContactsPreferences(mContext);
restoreSavedState(savedState);
}
public void restoreSavedState(Bundle savedState) {
if (savedState == null) {
return;
}
mSectionHeaderDisplayEnabled = savedState.getBoolean(KEY_SECTION_HEADER_DISPLAY_ENABLED);
mPhotoLoaderEnabled = savedState.getBoolean(KEY_PHOTO_LOADER_ENABLED);
mQuickContactEnabled = savedState.getBoolean(KEY_QUICK_CONTACT_ENABLED);
mIncludeProfile = savedState.getBoolean(KEY_INCLUDE_PROFILE);
mSearchMode = savedState.getBoolean(KEY_SEARCH_MODE);
mVisibleScrollbarEnabled = savedState.getBoolean(KEY_VISIBLE_SCROLLBAR_ENABLED);
mVerticalScrollbarPosition = savedState.getInt(KEY_SCROLLBAR_POSITION);
mDirectorySearchMode = savedState.getInt(KEY_DIRECTORY_SEARCH_MODE);
mSelectionVisible = savedState.getBoolean(KEY_SELECTION_VISIBLE);
mLegacyCompatibility = savedState.getBoolean(KEY_LEGACY_COMPATIBILITY);
mQueryString = savedState.getString(KEY_QUERY_STRING);
mDirectoryResultLimit = savedState.getInt(KEY_DIRECTORY_RESULT_LIMIT);
mDarkTheme = savedState.getBoolean(KEY_DARK_THEME);
// Retrieve list state. This will be applied in onLoadFinished
mListState = savedState.getParcelable(KEY_LIST_STATE);
}
@Override
public void onStart() {
super.onStart();
mContactsPrefs.registerChangeListener(mPreferencesChangeListener);
if (mProviderStatusLoader == null) {
mProviderStatusLoader = new ProviderStatusLoader(mContext);
}
mForceLoad = loadPreferences();
mDirectoryListStatus = STATUS_NOT_LOADED;
mLoadPriorityDirectoriesOnly = true;
startLoading();
}
protected void startLoading() {
if (mAdapter == null) {
// The method was called before the fragment was started
return;
}
configureAdapter();
int partitionCount = mAdapter.getPartitionCount();
for (int i = 0; i < partitionCount; i++) {
Partition partition = mAdapter.getPartition(i);
if (partition instanceof DirectoryPartition) {
DirectoryPartition directoryPartition = (DirectoryPartition)partition;
if (directoryPartition.getStatus() == DirectoryPartition.STATUS_NOT_LOADED) {
if (directoryPartition.isPriorityDirectory() || !mLoadPriorityDirectoriesOnly) {
startLoadingDirectoryPartition(i);
}
}
} else {
getLoaderManager().initLoader(i, null, this);
}
}
// Next time this method is called, we should start loading non-priority directories
mLoadPriorityDirectoriesOnly = false;
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == DIRECTORY_LOADER_ID) {
DirectoryListLoader loader = new DirectoryListLoader(mContext);
mAdapter.configureDirectoryLoader(loader);
return loader;
} else {
CursorLoader loader = createCursorLoader();
long directoryId = args != null && args.containsKey(DIRECTORY_ID_ARG_KEY)
? args.getLong(DIRECTORY_ID_ARG_KEY)
: Directory.DEFAULT;
mAdapter.configureLoader(loader, directoryId);
return loader;
}
}
public CursorLoader createCursorLoader() {
return new CursorLoader(mContext, null, null, null, null, null);
}
private void startLoadingDirectoryPartition(int partitionIndex) {
DirectoryPartition partition = (DirectoryPartition)mAdapter.getPartition(partitionIndex);
partition.setStatus(DirectoryPartition.STATUS_LOADING);
long directoryId = partition.getDirectoryId();
if (mForceLoad) {
if (directoryId == Directory.DEFAULT) {
loadDirectoryPartition(partitionIndex, partition);
} else {
loadDirectoryPartitionDelayed(partitionIndex, partition);
}
} else {
Bundle args = new Bundle();
args.putLong(DIRECTORY_ID_ARG_KEY, directoryId);
getLoaderManager().initLoader(partitionIndex, args, this);
}
}
/**
* Queues up a delayed request to search the specified directory. Since
* directory search will likely introduce a lot of network traffic, we want
* to wait for a pause in the user's typing before sending a directory request.
*/
private void loadDirectoryPartitionDelayed(int partitionIndex, DirectoryPartition partition) {
mDelayedDirectorySearchHandler.removeMessages(DIRECTORY_SEARCH_MESSAGE, partition);
Message msg = mDelayedDirectorySearchHandler.obtainMessage(
DIRECTORY_SEARCH_MESSAGE, partitionIndex, 0, partition);
mDelayedDirectorySearchHandler.sendMessageDelayed(msg, DIRECTORY_SEARCH_DELAY_MILLIS);
}
/**
* Loads the directory partition.
*/
protected void loadDirectoryPartition(int partitionIndex, DirectoryPartition partition) {
Bundle args = new Bundle();
args.putLong(DIRECTORY_ID_ARG_KEY, partition.getDirectoryId());
getLoaderManager().restartLoader(partitionIndex, args, this);
}
/**
* Cancels all queued directory loading requests.
*/
private void removePendingDirectorySearchRequests() {
mDelayedDirectorySearchHandler.removeMessages(DIRECTORY_SEARCH_MESSAGE);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (!mEnabled) {
return;
}
int loaderId = loader.getId();
if (loaderId == DIRECTORY_LOADER_ID) {
mDirectoryListStatus = STATUS_LOADED;
mAdapter.changeDirectories(data);
startLoading();
} else {
onPartitionLoaded(loaderId, data);
if (isSearchMode()) {
int directorySearchMode = getDirectorySearchMode();
if (directorySearchMode != DirectoryListLoader.SEARCH_MODE_NONE) {
if (mDirectoryListStatus == STATUS_NOT_LOADED) {
mDirectoryListStatus = STATUS_LOADING;
getLoaderManager().initLoader(DIRECTORY_LOADER_ID, null, this);
} else {
startLoading();
}
}
} else {
mDirectoryListStatus = STATUS_NOT_LOADED;
getLoaderManager().destroyLoader(DIRECTORY_LOADER_ID);
}
}
}
public void onLoaderReset(Loader<Cursor> loader) {
}
protected void onPartitionLoaded(int partitionIndex, Cursor data) {
if (partitionIndex >= mAdapter.getPartitionCount()) {
// When we get unsolicited data, ignore it. This could happen
// when we are switching from search mode to the default mode.
return;
}
mAdapter.changeCursor(partitionIndex, data);
setProfileHeader();
showCount(partitionIndex, data);
if (!isLoading()) {
completeRestoreInstanceState();
}
}
public boolean isLoading() {
if (mAdapter != null && mAdapter.isLoading()) {
return true;
}
if (isLoadingDirectoryList()) {
return true;
}
return false;
}
public boolean isLoadingDirectoryList() {
return isSearchMode() && getDirectorySearchMode() != DirectoryListLoader.SEARCH_MODE_NONE
&& (mDirectoryListStatus == STATUS_NOT_LOADED
|| mDirectoryListStatus == STATUS_LOADING);
}
@Override
public void onStop() {
super.onStop();
mContactsPrefs.unregisterChangeListener();
mAdapter.clearPartitions();
}
protected void reloadData() {
removePendingDirectorySearchRequests();
mAdapter.onDataReload();
mLoadPriorityDirectoriesOnly = true;
mForceLoad = true;
startLoading();
}
/**
* Configures the empty view. It is called when we are about to populate
* the list with an empty cursor.
*/
protected void prepareEmptyView() {
}
/**
* Shows the count of entries included in the list. The default
* implementation does nothing.
*/
protected void showCount(int partitionIndex, Cursor data) {
}
/**
* Shows a view at the top of the list with a pseudo local profile prompting the user to add
* a local profile. Default implementation does nothing.
*/
protected void setProfileHeader() {
mUserProfileExists = false;
}
/**
* Provides logic that dismisses this fragment. The default implementation
* does nothing.
*/
protected void finish() {
}
public void setSectionHeaderDisplayEnabled(boolean flag) {
if (mSectionHeaderDisplayEnabled != flag) {
mSectionHeaderDisplayEnabled = flag;
if (mAdapter != null) {
mAdapter.setSectionHeaderDisplayEnabled(flag);
}
configureVerticalScrollbar();
}
}
public boolean isSectionHeaderDisplayEnabled() {
return mSectionHeaderDisplayEnabled;
}
public void setVisibleScrollbarEnabled(boolean flag) {
if (mVisibleScrollbarEnabled != flag) {
mVisibleScrollbarEnabled = flag;
configureVerticalScrollbar();
}
}
public boolean isVisibleScrollbarEnabled() {
return mVisibleScrollbarEnabled;
}
public void setVerticalScrollbarPosition(int position) {
if (mVerticalScrollbarPosition != position) {
mVerticalScrollbarPosition = position;
configureVerticalScrollbar();
}
}
private void configureVerticalScrollbar() {
boolean hasScrollbar = isVisibleScrollbarEnabled() && isSectionHeaderDisplayEnabled();
if (mListView != null) {
mListView.setFastScrollEnabled(hasScrollbar);
mListView.setFastScrollAlwaysVisible(hasScrollbar);
mListView.setVerticalScrollbarPosition(mVerticalScrollbarPosition);
mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
int leftPadding = 0;
int rightPadding = 0;
if (mVerticalScrollbarPosition == View.SCROLLBAR_POSITION_LEFT) {
leftPadding = mContext.getResources().getDimensionPixelOffset(
R.dimen.list_visible_scrollbar_padding);
} else {
rightPadding = mContext.getResources().getDimensionPixelOffset(
R.dimen.list_visible_scrollbar_padding);
}
mListView.setPadding(leftPadding, mListView.getPaddingTop(),
rightPadding, mListView.getPaddingBottom());
}
}
public void setPhotoLoaderEnabled(boolean flag) {
mPhotoLoaderEnabled = flag;
configurePhotoLoader();
}
public boolean isPhotoLoaderEnabled() {
return mPhotoLoaderEnabled;
}
/**
* Returns true if the list is supposed to visually highlight the selected item.
*/
public boolean isSelectionVisible() {
return mSelectionVisible;
}
public void setSelectionVisible(boolean flag) {
this.mSelectionVisible = flag;
}
public void setQuickContactEnabled(boolean flag) {
this.mQuickContactEnabled = flag;
}
public void setIncludeProfile(boolean flag) {
mIncludeProfile = flag;
if(mAdapter != null) {
mAdapter.setIncludeProfile(flag);
}
}
/**
* Enter/exit search mode. By design, a fragment enters search mode only when it has a
* non-empty query text, so the mode must be tightly related to the current query.
* For this reason this method must only be called by {@link #setQueryString}.
*
* Also note this method doesn't call {@link #reloadData()}; {@link #setQueryString} does it.
*/
protected void setSearchMode(boolean flag) {
if (mSearchMode != flag) {
mSearchMode = flag;
setSectionHeaderDisplayEnabled(!mSearchMode);
if (!flag) {
mDirectoryListStatus = STATUS_NOT_LOADED;
getLoaderManager().destroyLoader(DIRECTORY_LOADER_ID);
}
if (mAdapter != null) {
mAdapter.setPinnedPartitionHeadersEnabled(flag);
mAdapter.setSearchMode(flag);
mAdapter.clearPartitions();
if (!flag) {
// If we are switching from search to regular display,
// remove all directory partitions (except the default one).
int count = mAdapter.getPartitionCount();
for (int i = count; --i >= 1;) {
mAdapter.removePartition(i);
}
}
mAdapter.configureDefaultPartition(false, flag);
}
if (mListView != null) {
mListView.setFastScrollEnabled(!flag);
}
}
}
public final boolean isSearchMode() {
return mSearchMode;
}
public final String getQueryString() {
return mQueryString;
}
public void setQueryString(String queryString, boolean delaySelection) {
// Normalize the empty query.
if (TextUtils.isEmpty(queryString)) queryString = null;
if (!TextUtils.equals(mQueryString, queryString)) {
mQueryString = queryString;
setSearchMode(!TextUtils.isEmpty(mQueryString));
if (mAdapter != null) {
mAdapter.setQueryString(queryString);
reloadData();
}
}
}
public int getDirectorySearchMode() {
return mDirectorySearchMode;
}
public void setDirectorySearchMode(int mode) {
mDirectorySearchMode = mode;
}
public boolean isLegacyCompatibilityMode() {
return mLegacyCompatibility;
}
public void setLegacyCompatibilityMode(boolean flag) {
mLegacyCompatibility = flag;
}
protected int getContactNameDisplayOrder() {
return mDisplayOrder;
}
protected void setContactNameDisplayOrder(int displayOrder) {
mDisplayOrder = displayOrder;
if (mAdapter != null) {
mAdapter.setContactNameDisplayOrder(displayOrder);
}
}
public int getSortOrder() {
return mSortOrder;
}
public void setSortOrder(int sortOrder) {
mSortOrder = sortOrder;
if (mAdapter != null) {
mAdapter.setSortOrder(sortOrder);
}
}
public void setDirectoryResultLimit(int limit) {
mDirectoryResultLimit = limit;
}
public void setContextMenuAdapter(ContextMenuAdapter adapter) {
mContextMenuAdapter = adapter;
if (mListView != null) {
mListView.setOnCreateContextMenuListener(adapter);
}
}
public ContextMenuAdapter getContextMenuAdapter() {
return mContextMenuAdapter;
}
protected boolean loadPreferences() {
boolean changed = false;
if (getContactNameDisplayOrder() != mContactsPrefs.getDisplayOrder()) {
setContactNameDisplayOrder(mContactsPrefs.getDisplayOrder());
changed = true;
}
if (getSortOrder() != mContactsPrefs.getSortOrder()) {
setSortOrder(mContactsPrefs.getSortOrder());
changed = true;
}
return changed;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
onCreateView(inflater, container);
mAdapter = createListAdapter();
boolean searchMode = isSearchMode();
mAdapter.setSearchMode(searchMode);
mAdapter.configureDefaultPartition(false, searchMode);
mAdapter.setPhotoLoader(mPhotoManager);
mListView.setAdapter(mAdapter);
if (!isSearchMode()) {
mListView.setFocusableInTouchMode(true);
mListView.requestFocus();
}
return mView;
}
protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
mView = inflateView(inflater, container);
mListView = (ListView)mView.findViewById(android.R.id.list);
if (mListView == null) {
throw new RuntimeException(
"Your content must have a ListView whose id attribute is " +
"'android.R.id.list'");
}
View emptyView = mView.findViewById(com.android.internal.R.id.empty);
if (emptyView != null) {
mListView.setEmptyView(emptyView);
if (emptyView instanceof ContactListEmptyView) {
mEmptyView = (ContactListEmptyView)emptyView;
}
}
mListView.setOnItemClickListener(this);
mListView.setOnFocusChangeListener(this);
mListView.setOnTouchListener(this);
mListView.setFastScrollEnabled(!isSearchMode());
// Tell list view to not show dividers. We'll do it ourself so that we can *not* show
// them when an A-Z headers is visible.
mListView.setDividerHeight(0);
// We manually save/restore the listview state
mListView.setSaveEnabled(false);
if (mContextMenuAdapter != null) {
mListView.setOnCreateContextMenuListener(mContextMenuAdapter);
}
configureVerticalScrollbar();
configurePhotoLoader();
}
protected void configurePhotoLoader() {
if (isPhotoLoaderEnabled() && mContext != null) {
if (mPhotoManager == null) {
mPhotoManager = ContactPhotoManager.getInstance(mContext);
}
if (mListView != null) {
mListView.setOnScrollListener(this);
}
if (mAdapter != null) {
mAdapter.setPhotoLoader(mPhotoManager);
}
}
}
protected void configureAdapter() {
if (mAdapter == null) {
return;
}
mAdapter.setQuickContactEnabled(mQuickContactEnabled);
mAdapter.setIncludeProfile(mIncludeProfile);
mAdapter.setQueryString(mQueryString);
mAdapter.setDirectorySearchMode(mDirectorySearchMode);
mAdapter.setPinnedPartitionHeadersEnabled(mSearchMode);
mAdapter.setContactNameDisplayOrder(mDisplayOrder);
mAdapter.setSortOrder(mSortOrder);
mAdapter.setSectionHeaderDisplayEnabled(mSectionHeaderDisplayEnabled);
mAdapter.setSelectionVisible(mSelectionVisible);
mAdapter.setDirectoryResultLimit(mDirectoryResultLimit);
mAdapter.setDarkTheme(mDarkTheme);
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
mPhotoManager.pause();
} else if (isPhotoLoaderEnabled()) {
mPhotoManager.resume();
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
hideSoftKeyboard();
int adjPosition = position - mListView.getHeaderViewsCount();
if (adjPosition >= 0) {
onItemClick(adjPosition, id);
}
}
private void hideSoftKeyboard() {
// Hide soft keyboard, if visible
InputMethodManager inputMethodManager = (InputMethodManager)
mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(mListView.getWindowToken(), 0);
}
/**
* Dismisses the soft keyboard when the list takes focus.
*/
@Override
public void onFocusChange(View view, boolean hasFocus) {
if (view == mListView && hasFocus) {
hideSoftKeyboard();
}
}
/**
* Dismisses the soft keyboard when the list is touched.
*/
@Override
public boolean onTouch(View view, MotionEvent event) {
if (view == mListView) {
hideSoftKeyboard();
}
return false;
}
@Override
public void onPause() {
super.onPause();
removePendingDirectorySearchRequests();
}
/**
* Dismisses the search UI along with the keyboard if the filter text is empty.
*/
public void onClose() {
hideSoftKeyboard();
finish();
}
/**
* Restore the list state after the adapter is populated.
*/
protected void completeRestoreInstanceState() {
if (mListState != null) {
mListView.onRestoreInstanceState(mListState);
mListState = null;
}
}
protected void setEmptyText(int resourceId) {
TextView empty = (TextView) getEmptyView().findViewById(R.id.emptyText);
empty.setText(mContext.getText(resourceId));
empty.setVisibility(View.VISIBLE);
}
// TODO redesign into an async task or loader
protected boolean isSyncActive() {
Account[] accounts = AccountManager.get(mContext).getAccounts();
if (accounts != null && accounts.length > 0) {
IContentService contentService = ContentResolver.getContentService();
for (Account account : accounts) {
try {
if (contentService.isSyncActive(account, ContactsContract.AUTHORITY)) {
return true;
}
} catch (RemoteException e) {
Log.e(TAG, "Could not get the sync status");
}
}
}
return false;
}
protected boolean hasIccCard() {
TelephonyManager telephonyManager =
(TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
return telephonyManager.hasIccCard();
}
public void setDarkTheme(boolean value) {
mDarkTheme = value;
if (mAdapter != null) mAdapter.setDarkTheme(value);
}
/**
* Processes a result returned by the contact picker.
*/
public void onPickerResult(Intent data) {
throw new UnsupportedOperationException("Picker result handler is not implemented.");
}
private ContactsPreferences.ChangeListener mPreferencesChangeListener =
new ContactsPreferences.ChangeListener() {
@Override
public void onChange() {
loadPreferences();
reloadData();
}
};
}