blob: 278175c0bd371b3df7b97fd85218caa97b4e3b57 [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.common.list;
import android.app.Fragment;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Context;
import android.content.CursorLoader;
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.provider.ContactsContract.Directory;
import android.support.annotation.Nullable;
import android.text.TextUtils;
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 com.android.common.widget.CompositeCursorAdapter.Partition;
import com.android.contacts.common.ContactPhotoManager;
import com.android.contacts.common.preference.ContactsPreferences;
import com.android.contacts.common.util.ContactListViewUtils;
import com.android.dialer.common.LogUtil;
import java.lang.ref.WeakReference;
import java.util.Locale;
/** 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 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_ADJUST_SELECTION_BOUNDS_ENABLED = "adjustSelectionBoundsEnabled";
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_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 static final int STATUS_NOT_LOADED = 0;
private static final int STATUS_LOADING = 1;
private static final int STATUS_LOADED = 2;
protected boolean mUserProfileExists;
private boolean mSectionHeaderDisplayEnabled;
private boolean mPhotoLoaderEnabled;
private boolean mQuickContactEnabled = true;
private boolean mAdjustSelectionBoundsEnabled = true;
private boolean mIncludeProfile;
private boolean mSearchMode;
private boolean mVisibleScrollbarEnabled;
private boolean mShowEmptyListForEmptyQuery;
private int mVerticalScrollbarPosition = getDefaultVerticalScrollbarPosition();
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 to save the scrolling state of the list when the fragment is not recreated. */
private int mListViewTopIndex;
private int mListViewTopOffset;
/** 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 ContactPhotoManager mPhotoManager;
private ContactsPreferences mContactsPrefs;
private boolean mForceLoad;
private boolean mDarkTheme;
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;
private static class DelayedDirectorySearchHandler extends Handler {
private final WeakReference<ContactEntryListFragment<?>> contactEntryListFragmentRef;
private DelayedDirectorySearchHandler(ContactEntryListFragment<?> contactEntryListFragment) {
this.contactEntryListFragmentRef = new WeakReference<>(contactEntryListFragment);
}
@Override
public void handleMessage(Message msg) {
ContactEntryListFragment<?> contactEntryListFragment = contactEntryListFragmentRef.get();
if (contactEntryListFragment == null) {
return;
}
if (msg.what == DIRECTORY_SEARCH_MESSAGE) {
contactEntryListFragment.loadDirectoryPartition(msg.arg1, (DirectoryPartition) msg.obj);
}
}
}
private ContactsPreferences.ChangeListener mPreferencesChangeListener =
new ContactsPreferences.ChangeListener() {
@Override
public void onChange() {
loadPreferences();
reloadData();
}
};
protected ContactEntryListFragment() {
mDelayedDirectorySearchHandler = new DelayedDirectorySearchHandler(this);
}
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(Context context) {
super.onAttach(context);
setContext(context);
setLoaderManager(super.getLoaderManager());
}
@Override
public Context getContext() {
return mContext;
}
/** Sets a context for the fragment in the unit test environment. */
public void setContext(Context context) {
mContext = context;
configurePhotoLoader();
}
public void setEnabled(boolean enabled) {
if (mEnabled != enabled) {
mEnabled = enabled;
if (mAdapter != null) {
if (mEnabled) {
reloadData();
} else {
mAdapter.clearPartitions();
}
}
}
}
@Override
public LoaderManager getLoaderManager() {
return mLoaderManager;
}
/** Overrides a loader manager for use in unit tests. */
public void setLoaderManager(LoaderManager loaderManager) {
mLoaderManager = loaderManager;
}
public T getAdapter() {
return mAdapter;
}
@Override
public View getView() {
return mView;
}
public ListView getListView() {
return mListView;
}
@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_ADJUST_SELECTION_BOUNDS_ENABLED, mAdjustSelectionBoundsEnabled);
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);
restoreSavedState(savedState);
mAdapter = createListAdapter();
mContactsPrefs = new ContactsPreferences(mContext);
}
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);
mAdjustSelectionBoundsEnabled = savedState.getBoolean(KEY_ADJUST_SELECTION_BOUNDS_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);
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);
loader.setDirectorySearchMode(mAdapter.getDirectorySearchMode());
loader.setLocalInvisibleDirectoryEnabled(
ContactEntryListAdapter.LOCAL_INVISIBLE_DIRECTORY_ENABLED);
return loader;
} else {
CursorLoader loader = createCursorLoader(mContext);
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(Context context) {
return new CursorLoader(context, null, null, null, null, null) {
@Override
protected Cursor onLoadInBackground() {
try {
return super.onLoadInBackground();
} catch (RuntimeException e) {
// We don't even know what the projection should be, so no point trying to
// return an empty MatrixCursor with the correct projection here.
LogUtil.w(
"ContactEntryListFragment.onLoadInBackground",
"RuntimeException while trying to query ContactsProvider.");
return 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);
}
}
}
@Override
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();
if (!isLoading()) {
completeRestoreInstanceState();
}
}
public boolean isLoading() {
//noinspection SimplifiableIfStatement
if (mAdapter != null && mAdapter.isLoading()) {
return true;
}
return isLoadingDirectoryList();
}
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();
}
/**
* 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 boolean isSectionHeaderDisplayEnabled() {
return mSectionHeaderDisplayEnabled;
}
public void setSectionHeaderDisplayEnabled(boolean flag) {
if (mSectionHeaderDisplayEnabled != flag) {
mSectionHeaderDisplayEnabled = flag;
if (mAdapter != null) {
mAdapter.setSectionHeaderDisplayEnabled(flag);
}
configureVerticalScrollbar();
}
}
public boolean isVisibleScrollbarEnabled() {
return mVisibleScrollbarEnabled;
}
public void setVisibleScrollbarEnabled(boolean flag) {
if (mVisibleScrollbarEnabled != flag) {
mVisibleScrollbarEnabled = flag;
configureVerticalScrollbar();
}
}
private void configureVerticalScrollbar() {
boolean hasScrollbar = isVisibleScrollbarEnabled() && isSectionHeaderDisplayEnabled();
if (mListView != null) {
mListView.setFastScrollEnabled(hasScrollbar);
mListView.setVerticalScrollbarPosition(mVerticalScrollbarPosition);
mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
}
}
public boolean isPhotoLoaderEnabled() {
return mPhotoLoaderEnabled;
}
public void setPhotoLoaderEnabled(boolean flag) {
mPhotoLoaderEnabled = flag;
configurePhotoLoader();
}
public void setQuickContactEnabled(boolean flag) {
this.mQuickContactEnabled = flag;
}
public void setAdjustSelectionBoundsEnabled(boolean flag) {
mAdjustSelectionBoundsEnabled = flag;
}
public final boolean isSearchMode() {
return mSearchMode;
}
/**
* Enter/exit search mode. This is method is tightly related to the current query, and should only
* be called by {@link #setQueryString}.
*
* <p>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.setSearchMode(flag);
mAdapter.clearPartitions();
if (!flag) {
// If we are switching from search to regular display, remove all directory
// partitions after default one, assuming they are remote directories which
// should be cleaned up on exiting the search mode.
mAdapter.removeDirectoriesAfterDefault();
}
mAdapter.configureDefaultPartition(false, flag);
}
if (mListView != null) {
mListView.setFastScrollEnabled(!flag);
}
}
}
@Nullable
public final String getQueryString() {
return mQueryString;
}
public void setQueryString(String queryString) {
if (!TextUtils.equals(mQueryString, queryString)) {
if (mShowEmptyListForEmptyQuery && mAdapter != null && mListView != null) {
if (TextUtils.isEmpty(mQueryString)) {
// Restore the adapter if the query used to be empty.
mListView.setAdapter(mAdapter);
} else if (TextUtils.isEmpty(queryString)) {
// Instantly clear the list view if the new query is empty.
mListView.setAdapter(null);
}
}
mQueryString = queryString;
setSearchMode(!TextUtils.isEmpty(mQueryString) || mShowEmptyListForEmptyQuery);
if (mAdapter != null) {
mAdapter.setQueryString(queryString);
reloadData();
}
}
}
public void setShowEmptyListForNullQuery(boolean show) {
mShowEmptyListForEmptyQuery = show;
}
public boolean getShowEmptyListForNullQuery() {
return mShowEmptyListForEmptyQuery;
}
public int getDirectoryLoaderId() {
return DIRECTORY_LOADER_ID;
}
public int getDirectorySearchMode() {
return mDirectorySearchMode;
}
public void setDirectorySearchMode(int mode) {
mDirectorySearchMode = mode;
}
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;
}
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);
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(android.R.id.empty);
if (emptyView != null) {
mListView.setEmptyView(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);
configureVerticalScrollbar();
configurePhotoLoader();
getAdapter().setFragmentRootView(getView());
ContactListViewUtils.applyCardPaddingToView(getResources(), mListView, mView);
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (getActivity() != null && getView() != null && !hidden) {
// If the padding was last applied when in a hidden state, it may have been applied
// incorrectly. Therefore we need to reapply it.
ContactListViewUtils.applyCardPaddingToView(getResources(), mListView, getView());
}
}
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.setAdjustSelectionBoundsEnabled(mAdjustSelectionBoundsEnabled);
mAdapter.setQueryString(mQueryString);
mAdapter.setDirectorySearchMode(mDirectorySearchMode);
mAdapter.setPinnedPartitionHeadersEnabled(false);
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() {
// Save the scrolling state of the list view
mListViewTopIndex = mListView.getFirstVisiblePosition();
View v = mListView.getChildAt(0);
mListViewTopOffset = (v == null) ? 0 : (v.getTop() - mListView.getPaddingTop());
super.onPause();
removePendingDirectorySearchRequests();
}
@Override
public void onResume() {
super.onResume();
// Restore the selection of the list view. See b/19982820.
// This has to be done manually because if the list view has its emptyView set,
// the scrolling state will be reset when clearPartitions() is called on the adapter.
mListView.setSelectionFromTop(mListViewTopIndex, mListViewTopOffset);
}
/** Restore the list state after the adapter is populated. */
protected void completeRestoreInstanceState() {
if (mListState != null) {
mListView.onRestoreInstanceState(mListState);
mListState = null;
}
}
public void setDarkTheme(boolean value) {
mDarkTheme = value;
if (mAdapter != null) {
mAdapter.setDarkTheme(value);
}
}
private int getDefaultVerticalScrollbarPosition() {
final Locale locale = Locale.getDefault();
final int layoutDirection = TextUtils.getLayoutDirectionFromLocale(locale);
switch (layoutDirection) {
case View.LAYOUT_DIRECTION_RTL:
return View.SCROLLBAR_POSITION_LEFT;
case View.LAYOUT_DIRECTION_LTR:
default:
return View.SCROLLBAR_POSITION_RIGHT;
}
}
}