diff options
Diffstat (limited to 'java/com/android/contacts/common/list/ContactEntryListFragment.java')
-rw-r--r-- | java/com/android/contacts/common/list/ContactEntryListFragment.java | 862 |
1 files changed, 862 insertions, 0 deletions
diff --git a/java/com/android/contacts/common/list/ContactEntryListFragment.java b/java/com/android/contacts/common/list/ContactEntryListFragment.java new file mode 100644 index 000000000..a8d9b55ba --- /dev/null +++ b/java/com/android/contacts/common/list/ContactEntryListFragment.java @@ -0,0 +1,862 @@ +/* + * 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.Activity; +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.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.AdapterView.OnItemLongClickListener; +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 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, + OnItemLongClickListener, + LoaderCallbacks<Cursor> { + private static final String TAG = "ContactEntryListFragment"; + 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 = + new Handler() { + @Override + public void handleMessage(Message msg) { + if (msg.what == DIRECTORY_SEARCH_MESSAGE) { + loadDirectoryPartition(msg.arg1, (DirectoryPartition) msg.obj); + } + } + }; + private ContactsPreferences.ChangeListener mPreferencesChangeListener = + new ContactsPreferences.ChangeListener() { + @Override + public void onChange() { + loadPreferences(); + reloadData(); + } + }; + + 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); + + /** + * @param position Please note that the position is already adjusted for header views, so "0" + * means the first list item below header views. + */ + protected boolean onItemLongClick(int position, long id) { + return false; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + setContext(activity); + 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. + Log.w(TAG, "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() { + 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.setFastScrollAlwaysVisible(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); + } + } + } + + 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.setOnItemLongClickListener(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); + } + } + + @Override + public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { + int adjPosition = position - mListView.getHeaderViewsCount(); + + if (adjPosition >= 0) { + return onItemLongClick(adjPosition, id); + } + return false; + } + + 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; + } + } +} |