diff options
author | Chiao Cheng <chiaocheng@google.com> | 2012-08-24 14:19:37 -0700 |
---|---|---|
committer | Chiao Cheng <chiaocheng@google.com> | 2012-08-30 16:06:08 -0700 |
commit | 91197049c458f07092b31501d2ed512180b13d58 (patch) | |
tree | ba868289b2bf82e504b1d8ffd09cfa2a0a765f27 /src/com/android/dialer/list | |
parent | 8a372c2c67c3cf6bd5a6d4c999d68094938e365c (diff) |
Moving more classes from contacts into dialer.
- These classes are only used by dialer code.
- Fixed import order.
Bug: 6993891
Change-Id: I7941a029989c4793b766fdc77a4666f9f99b750a
Diffstat (limited to 'src/com/android/dialer/list')
-rw-r--r-- | src/com/android/dialer/list/PhoneFavoriteFragment.java | 569 | ||||
-rw-r--r-- | src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java | 301 |
2 files changed, 870 insertions, 0 deletions
diff --git a/src/com/android/dialer/list/PhoneFavoriteFragment.java b/src/com/android/dialer/list/PhoneFavoriteFragment.java new file mode 100644 index 000000000..157e82fb1 --- /dev/null +++ b/src/com/android/dialer/list/PhoneFavoriteFragment.java @@ -0,0 +1,569 @@ +/* + * Copyright (C) 2011 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.dialer.list; + +import android.app.Activity; +import android.app.Fragment; +import android.app.LoaderManager; +import android.content.CursorLoader; +import android.content.Intent; +import android.content.Loader; +import android.database.Cursor; +import android.graphics.Rect; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.provider.ContactsContract; +import android.provider.ContactsContract.Directory; +import android.provider.Settings; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.FrameLayout; +import android.widget.ListView; +import android.widget.TextView; + +import com.android.contacts.ContactPhotoManager; +import com.android.contacts.ContactTileLoaderFactory; +import com.android.contacts.R; +import com.android.contacts.dialog.ClearFrequentsDialog; +import com.android.contacts.interactions.ImportExportDialogFragment; +import com.android.contacts.list.ContactListFilter; +import com.android.contacts.list.ContactListFilterController; +import com.android.contacts.list.ContactListItemView; +import com.android.contacts.list.ContactTileAdapter; +import com.android.contacts.list.ContactTileView; +import com.android.contacts.list.PhoneNumberListAdapter; +import com.android.contacts.preference.ContactsPreferences; +import com.android.contacts.util.AccountFilterUtil; + +/** + * Fragment for Phone UI's favorite screen. + * + * This fragment contains three kinds of contacts in one screen: "starred", "frequent", and "all" + * contacts. To show them at once, this merges results from {@link com.android.contacts.list.ContactTileAdapter} and + * {@link com.android.contacts.list.PhoneNumberListAdapter} into one unified list using {@link PhoneFavoriteMergedAdapter}. + * A contact filter header is also inserted between those adapters' results. + */ +public class PhoneFavoriteFragment extends Fragment implements OnItemClickListener { + private static final String TAG = PhoneFavoriteFragment.class.getSimpleName(); + private static final boolean DEBUG = false; + + /** + * Used with LoaderManager. + */ + private static int LOADER_ID_CONTACT_TILE = 1; + private static int LOADER_ID_ALL_CONTACTS = 2; + + private static final String KEY_FILTER = "filter"; + + private static final int REQUEST_CODE_ACCOUNT_FILTER = 1; + + public interface Listener { + public void onContactSelected(Uri contactUri); + public void onCallNumberDirectly(String phoneNumber); + } + + private class ContactTileLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> { + @Override + public CursorLoader onCreateLoader(int id, Bundle args) { + if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onCreateLoader."); + return ContactTileLoaderFactory.createStrequentPhoneOnlyLoader(getActivity()); + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoadFinished"); + mContactTileAdapter.setContactCursor(data); + + if (mAllContactsForceReload) { + mAllContactsAdapter.onDataReload(); + // Use restartLoader() to make LoaderManager to load the section again. + getLoaderManager().restartLoader( + LOADER_ID_ALL_CONTACTS, null, mAllContactsLoaderListener); + } else if (!mAllContactsLoaderStarted) { + // Load "all" contacts if not loaded yet. + getLoaderManager().initLoader( + LOADER_ID_ALL_CONTACTS, null, mAllContactsLoaderListener); + } + mAllContactsForceReload = false; + mAllContactsLoaderStarted = true; + + // Show the filter header with "loading" state. + updateFilterHeaderView(); + mAccountFilterHeader.setVisibility(View.VISIBLE); + + // invalidate the options menu if needed + invalidateOptionsMenuIfNeeded(); + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoaderReset. "); + } + } + + private class AllContactsLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> { + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + if (DEBUG) Log.d(TAG, "AllContactsLoaderListener#onCreateLoader"); + CursorLoader loader = new CursorLoader(getActivity(), null, null, null, null, null); + mAllContactsAdapter.configureLoader(loader, Directory.DEFAULT); + return loader; + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + if (DEBUG) Log.d(TAG, "AllContactsLoaderListener#onLoadFinished"); + mAllContactsAdapter.changeCursor(0, data); + updateFilterHeaderView(); + mHandler.removeMessages(MESSAGE_SHOW_LOADING_EFFECT); + mLoadingView.setVisibility(View.VISIBLE); + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + if (DEBUG) Log.d(TAG, "AllContactsLoaderListener#onLoaderReset. "); + } + } + + private class ContactTileAdapterListener implements ContactTileView.Listener { + @Override + public void onContactSelected(Uri contactUri, Rect targetRect) { + if (mListener != null) { + mListener.onContactSelected(contactUri); + } + } + + @Override + public void onCallNumberDirectly(String phoneNumber) { + if (mListener != null) { + mListener.onCallNumberDirectly(phoneNumber); + } + } + + @Override + public int getApproximateTileWidth() { + return getView().getWidth() / mContactTileAdapter.getColumnCount(); + } + } + + private class FilterHeaderClickListener implements OnClickListener { + @Override + public void onClick(View view) { + AccountFilterUtil.startAccountFilterActivityForResult( + PhoneFavoriteFragment.this, + REQUEST_CODE_ACCOUNT_FILTER, + mFilter); + } + } + + private class ContactsPreferenceChangeListener + implements ContactsPreferences.ChangeListener { + @Override + public void onChange() { + if (loadContactsPreferences()) { + requestReloadAllContacts(); + } + } + } + + private class ScrollListener implements ListView.OnScrollListener { + private boolean mShouldShowFastScroller; + @Override + public void onScroll(AbsListView view, + int firstVisibleItem, int visibleItemCount, int totalItemCount) { + // FastScroller should be visible only when the user is seeing "all" contacts section. + final boolean shouldShow = mAdapter.shouldShowFirstScroller(firstVisibleItem); + if (shouldShow != mShouldShowFastScroller) { + mListView.setVerticalScrollBarEnabled(shouldShow); + mListView.setFastScrollEnabled(shouldShow); + mListView.setFastScrollAlwaysVisible(shouldShow); + mShouldShowFastScroller = shouldShow; + } + } + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + } + } + + private static final int MESSAGE_SHOW_LOADING_EFFECT = 1; + private static final int LOADING_EFFECT_DELAY = 500; // ms + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_SHOW_LOADING_EFFECT: + mLoadingView.setVisibility(View.VISIBLE); + break; + } + } + }; + + private Listener mListener; + private PhoneFavoriteMergedAdapter mAdapter; + private ContactTileAdapter mContactTileAdapter; + private PhoneNumberListAdapter mAllContactsAdapter; + + /** + * true when the loader for {@link PhoneNumberListAdapter} has started already. + */ + private boolean mAllContactsLoaderStarted; + /** + * true when the loader for {@link PhoneNumberListAdapter} must reload "all" contacts again. + * It typically happens when {@link ContactsPreferences} has changed its settings + * (display order and sort order) + */ + private boolean mAllContactsForceReload; + + private ContactsPreferences mContactsPrefs; + private ContactListFilter mFilter; + + private TextView mEmptyView; + private ListView mListView; + /** + * Layout containing {@link #mAccountFilterHeader}. Used to limit area being "pressed". + */ + private FrameLayout mAccountFilterHeaderContainer; + private View mAccountFilterHeader; + + /** + * Layout used when contacts load is slower than expected and thus "loading" view should be + * shown. + */ + private View mLoadingView; + + private final ContactTileView.Listener mContactTileAdapterListener = + new ContactTileAdapterListener(); + private final LoaderManager.LoaderCallbacks<Cursor> mContactTileLoaderListener = + new ContactTileLoaderListener(); + private final LoaderManager.LoaderCallbacks<Cursor> mAllContactsLoaderListener = + new AllContactsLoaderListener(); + private final OnClickListener mFilterHeaderClickListener = new FilterHeaderClickListener(); + private final ContactsPreferenceChangeListener mContactsPreferenceChangeListener = + new ContactsPreferenceChangeListener(); + private final ScrollListener mScrollListener = new ScrollListener(); + + private boolean mOptionsMenuHasFrequents; + + @Override + public void onAttach(Activity activity) { + if (DEBUG) Log.d(TAG, "onAttach()"); + super.onAttach(activity); + + mContactsPrefs = new ContactsPreferences(activity); + + // Construct two base adapters which will become part of PhoneFavoriteMergedAdapter. + // We don't construct the resultant adapter at this moment since it requires LayoutInflater + // that will be available on onCreateView(). + + mContactTileAdapter = new ContactTileAdapter(activity, mContactTileAdapterListener, + getResources().getInteger(R.integer.contact_tile_column_count_in_favorites), + ContactTileAdapter.DisplayType.STREQUENT_PHONE_ONLY); + mContactTileAdapter.setPhotoLoader(ContactPhotoManager.getInstance(activity)); + + // Setup the "all" adapter manually. See also the setup logic in ContactEntryListFragment. + mAllContactsAdapter = new PhoneNumberListAdapter(activity); + mAllContactsAdapter.setDisplayPhotos(true); + mAllContactsAdapter.setQuickContactEnabled(true); + mAllContactsAdapter.setSearchMode(false); + mAllContactsAdapter.setIncludeProfile(false); + mAllContactsAdapter.setSelectionVisible(false); + mAllContactsAdapter.setDarkTheme(true); + mAllContactsAdapter.setPhotoLoader(ContactPhotoManager.getInstance(activity)); + // Disable directory header. + mAllContactsAdapter.setHasHeader(0, false); + // Show A-Z section index. + mAllContactsAdapter.setSectionHeaderDisplayEnabled(true); + // Disable pinned header. It doesn't work with this fragment. + mAllContactsAdapter.setPinnedPartitionHeadersEnabled(false); + // Put photos on left for consistency with "frequent" contacts section. + mAllContactsAdapter.setPhotoPosition(ContactListItemView.PhotoPosition.LEFT); + + // Use Callable.CONTENT_URI which will include not only phone numbers but also SIP + // addresses. + mAllContactsAdapter.setUseCallableUri(true); + + mAllContactsAdapter.setContactNameDisplayOrder(mContactsPrefs.getDisplayOrder()); + mAllContactsAdapter.setSortOrder(mContactsPrefs.getSortOrder()); + } + + @Override + public void onCreate(Bundle savedState) { + if (DEBUG) Log.d(TAG, "onCreate()"); + super.onCreate(savedState); + if (savedState != null) { + mFilter = savedState.getParcelable(KEY_FILTER); + + if (mFilter != null) { + mAllContactsAdapter.setFilter(mFilter); + } + } + setHasOptionsMenu(true); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(KEY_FILTER, mFilter); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + final View listLayout = inflater.inflate( + R.layout.phone_contact_tile_list, container, false); + + mListView = (ListView) listLayout.findViewById(R.id.contact_tile_list); + mListView.setItemsCanFocus(true); + mListView.setOnItemClickListener(this); + mListView.setVerticalScrollBarEnabled(false); + mListView.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT); + mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY); + + // Create the account filter header but keep it hidden until "all" contacts are loaded. + mAccountFilterHeaderContainer = new FrameLayout(getActivity(), null); + mAccountFilterHeader = inflater.inflate(R.layout.account_filter_header_for_phone_favorite, + mListView, false); + mAccountFilterHeader.setOnClickListener(mFilterHeaderClickListener); + mAccountFilterHeaderContainer.addView(mAccountFilterHeader); + + mLoadingView = inflater.inflate(R.layout.phone_loading_contacts, mListView, false); + + mAdapter = new PhoneFavoriteMergedAdapter(getActivity(), + mContactTileAdapter, mAccountFilterHeaderContainer, mAllContactsAdapter, + mLoadingView); + + mListView.setAdapter(mAdapter); + + mListView.setOnScrollListener(mScrollListener); + mListView.setFastScrollEnabled(false); + mListView.setFastScrollAlwaysVisible(false); + + mEmptyView = (TextView) listLayout.findViewById(R.id.contact_tile_list_empty); + mEmptyView.setText(getString(R.string.listTotalAllContactsZero)); + mListView.setEmptyView(mEmptyView); + + updateFilterHeaderView(); + + return listLayout; + } + + private boolean isOptionsMenuChanged() { + return mOptionsMenuHasFrequents != hasFrequents(); + } + + private void invalidateOptionsMenuIfNeeded() { + if (isOptionsMenuChanged()) { + getActivity().invalidateOptionsMenu(); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.phone_favorite_options, menu); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents); + mOptionsMenuHasFrequents = hasFrequents(); + clearFrequents.setVisible(mOptionsMenuHasFrequents); + } + + private boolean hasFrequents() { + return mContactTileAdapter.getNumFrequents() > 0; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_import_export: + // We hard-code the "contactsAreAvailable" argument because doing it properly would + // involve querying a {@link ProviderStatusLoader}, which we don't want to do right + // now in Dialtacts for (potential) performance reasons. Compare with how it is + // done in {@link PeopleActivity}. + ImportExportDialogFragment.show(getFragmentManager(), true); + return true; + case R.id.menu_accounts: + final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS); + intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] { + ContactsContract.AUTHORITY + }); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + startActivity(intent); + return true; + case R.id.menu_clear_frequents: + ClearFrequentsDialog.show(getFragmentManager()); + return true; + } + return false; + } + + @Override + public void onStart() { + super.onStart(); + + mContactsPrefs.registerChangeListener(mContactsPreferenceChangeListener); + + // If ContactsPreferences has changed, we need to reload "all" contacts with the new + // settings. If mAllContactsFoarceReload is already true, it should be kept. + if (loadContactsPreferences()) { + mAllContactsForceReload = true; + } + + // Use initLoader() instead of restartLoader() to refraining unnecessary reload. + // This method call implicitly assures ContactTileLoaderListener's onLoadFinished() will + // be called, on which we'll check if "all" contacts should be reloaded again or not. + getLoaderManager().initLoader(LOADER_ID_CONTACT_TILE, null, mContactTileLoaderListener); + + // Delay showing "loading" view until certain amount of time so that users won't see + // instant flash of the view when the contacts load is fast enough. + // This will be kept shown until both tile and all sections are loaded. + mLoadingView.setVisibility(View.INVISIBLE); + mHandler.sendEmptyMessageDelayed(MESSAGE_SHOW_LOADING_EFFECT, LOADING_EFFECT_DELAY); + } + + @Override + public void onStop() { + super.onStop(); + mContactsPrefs.unregisterChangeListener(); + } + + /** + * {@inheritDoc} + * + * This is only effective for elements provided by {@link #mContactTileAdapter}. + * {@link #mContactTileAdapter} has its own logic for click events. + */ + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + final int contactTileAdapterCount = mContactTileAdapter.getCount(); + if (position <= contactTileAdapterCount) { + Log.e(TAG, "onItemClick() event for unexpected position. " + + "The position " + position + " is before \"all\" section. Ignored."); + } else { + final int localPosition = position - mContactTileAdapter.getCount() - 1; + if (mListener != null) { + mListener.onContactSelected(mAllContactsAdapter.getDataUri(localPosition)); + } + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CODE_ACCOUNT_FILTER) { + if (getActivity() != null) { + AccountFilterUtil.handleAccountFilterResult( + ContactListFilterController.getInstance(getActivity()), resultCode, data); + } else { + Log.e(TAG, "getActivity() returns null during Fragment#onActivityResult()"); + } + } + } + + private boolean loadContactsPreferences() { + if (mContactsPrefs == null || mAllContactsAdapter == null) { + return false; + } + + boolean changed = false; + final int currentDisplayOrder = mContactsPrefs.getDisplayOrder(); + if (mAllContactsAdapter.getContactNameDisplayOrder() != currentDisplayOrder) { + mAllContactsAdapter.setContactNameDisplayOrder(currentDisplayOrder); + changed = true; + } + + final int currentSortOrder = mContactsPrefs.getSortOrder(); + if (mAllContactsAdapter.getSortOrder() != currentSortOrder) { + mAllContactsAdapter.setSortOrder(currentSortOrder); + changed = true; + } + + return changed; + } + + /** + * Requests to reload "all" contacts. If the section is already loaded, this method will + * force reloading it now. If the section isn't loaded yet, the actual load may be done later + * (on {@link #onStart()}. + */ + private void requestReloadAllContacts() { + if (DEBUG) { + Log.d(TAG, "requestReloadAllContacts()" + + " mAllContactsAdapter: " + mAllContactsAdapter + + ", mAllContactsLoaderStarted: " + mAllContactsLoaderStarted); + } + + if (mAllContactsAdapter == null || !mAllContactsLoaderStarted) { + // Remember this request until next load on onStart(). + mAllContactsForceReload = true; + return; + } + + if (DEBUG) Log.d(TAG, "Reload \"all\" contacts now."); + + mAllContactsAdapter.onDataReload(); + // Use restartLoader() to make LoaderManager to load the section again. + getLoaderManager().restartLoader(LOADER_ID_ALL_CONTACTS, null, mAllContactsLoaderListener); + } + + private void updateFilterHeaderView() { + final ContactListFilter filter = getFilter(); + if (mAccountFilterHeader == null || mAllContactsAdapter == null || filter == null) { + return; + } + AccountFilterUtil.updateAccountFilterTitleForPhone(mAccountFilterHeader, filter, true); + } + + public ContactListFilter getFilter() { + return mFilter; + } + + public void setFilter(ContactListFilter filter) { + if ((mFilter == null && filter == null) || (mFilter != null && mFilter.equals(filter))) { + return; + } + + if (DEBUG) { + Log.d(TAG, "setFilter(). old filter (" + mFilter + + ") will be replaced with new filter (" + filter + ")"); + } + + mFilter = filter; + + if (mAllContactsAdapter != null) { + mAllContactsAdapter.setFilter(mFilter); + requestReloadAllContacts(); + updateFilterHeaderView(); + } + } + + public void setListener(Listener listener) { + mListener = listener; + } +} diff --git a/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java b/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java new file mode 100644 index 000000000..8e2339961 --- /dev/null +++ b/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2011 Google Inc. + * Licensed to 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.dialer.list; + +import android.content.Context; +import android.content.res.Resources; +import android.database.DataSetObserver; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.FrameLayout; +import android.widget.SectionIndexer; + +import com.android.contacts.R; +import com.android.contacts.list.ContactEntryListAdapter; +import com.android.contacts.list.ContactListItemView; +import com.android.contacts.list.ContactTileAdapter; + +/** + * An adapter that combines items from {@link com.android.contacts.list.ContactTileAdapter} and + * {@link com.android.contacts.list.ContactEntryListAdapter} into a single list. In between those two results, + * an account filter header will be inserted. + */ +public class PhoneFavoriteMergedAdapter extends BaseAdapter implements SectionIndexer { + + private class CustomDataSetObserver extends DataSetObserver { + @Override + public void onChanged() { + notifyDataSetChanged(); + } + } + + private final ContactTileAdapter mContactTileAdapter; + private final ContactEntryListAdapter mContactEntryListAdapter; + private final View mAccountFilterHeaderContainer; + private final View mLoadingView; + + private final int mItemPaddingLeft; + private final int mItemPaddingRight; + + // Make frequent header consistent with account filter header. + private final int mFrequentHeaderPaddingTop; + + private final DataSetObserver mObserver; + + public PhoneFavoriteMergedAdapter(Context context, + ContactTileAdapter contactTileAdapter, + View accountFilterHeaderContainer, + ContactEntryListAdapter contactEntryListAdapter, + View loadingView) { + Resources resources = context.getResources(); + mItemPaddingLeft = resources.getDimensionPixelSize(R.dimen.detail_item_side_margin); + mItemPaddingRight = resources.getDimensionPixelSize(R.dimen.list_visible_scrollbar_padding); + mFrequentHeaderPaddingTop = resources.getDimensionPixelSize( + R.dimen.contact_browser_list_top_margin); + mContactTileAdapter = contactTileAdapter; + mContactEntryListAdapter = contactEntryListAdapter; + + mAccountFilterHeaderContainer = accountFilterHeaderContainer; + + mObserver = new CustomDataSetObserver(); + mContactTileAdapter.registerDataSetObserver(mObserver); + mContactEntryListAdapter.registerDataSetObserver(mObserver); + + mLoadingView = loadingView; + } + + @Override + public boolean isEmpty() { + // Cannot use the super's method here because we add extra rows in getCount() to account + // for headers + return mContactTileAdapter.getCount() + mContactEntryListAdapter.getCount() == 0; + } + + @Override + public int getCount() { + final int contactTileAdapterCount = mContactTileAdapter.getCount(); + final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount(); + if (mContactEntryListAdapter.isLoading()) { + // Hide "all" contacts during its being loaded. Instead show "loading" view. + // + // "+2" for mAccountFilterHeaderContainer and mLoadingView + return contactTileAdapterCount + 2; + } else { + // "+1" for mAccountFilterHeaderContainer + return contactTileAdapterCount + contactEntryListAdapterCount + 1; + } + } + + @Override + public Object getItem(int position) { + final int contactTileAdapterCount = mContactTileAdapter.getCount(); + final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount(); + if (position < contactTileAdapterCount) { // For "tile" and "frequent" sections + return mContactTileAdapter.getItem(position); + } else if (position == contactTileAdapterCount) { // For "all" section's account header + return mAccountFilterHeaderContainer; + } else { // For "all" section + if (mContactEntryListAdapter.isLoading()) { // "All" section is being loaded. + return mLoadingView; + } else { + // "-1" for mAccountFilterHeaderContainer + final int localPosition = position - contactTileAdapterCount - 1; + return mContactTileAdapter.getItem(localPosition); + } + } + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public int getViewTypeCount() { + // "+2" for mAccountFilterHeaderContainer and mLoadingView + return (mContactTileAdapter.getViewTypeCount() + + mContactEntryListAdapter.getViewTypeCount() + + 2); + } + + @Override + public int getItemViewType(int position) { + final int contactTileAdapterCount = mContactTileAdapter.getCount(); + final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount(); + // There should be four kinds of types that are usually used, and one more exceptional + // type (IGNORE_ITEM_VIEW_TYPE), which sometimes comes from mContactTileAdapter. + // + // The four ordinary view types have the index equal to or more than 0, and less than + // mContactTileAdapter.getViewTypeCount()+ mContactEntryListAdapter.getViewTypeCount() + 2. + // (See also this class's getViewTypeCount()) + // + // We have those values for: + // - The view types mContactTileAdapter originally has + // - The view types mContactEntryListAdapter originally has + // - mAccountFilterHeaderContainer ("all" section's account header), and + // - mLoadingView + // + // Those types should not be mixed, so we have a different range for each kinds of types: + // - Types for mContactTileAdapter ("tile" and "frequent" sections) + // They should have the index, >=0 and <mContactTileAdapter.getViewTypeCount() + // + // - Types for mContactEntryListAdapter ("all" sections) + // They should have the index, >=mContactTileAdapter.getViewTypeCount() and + // <(mContactTileAdapter.getViewTypeCount() + mContactEntryListAdapter.getViewTypeCount()) + // + // - Type for "all" section's account header + // It should have the exact index + // mContactTileAdapter.getViewTypeCount()+ mContactEntryListAdapter.getViewTypeCount() + // + // - Type for "loading" view used during "all" section is being loaded. + // It should have the exact index + // mContactTileAdapter.getViewTypeCount()+ mContactEntryListAdapter.getViewTypeCount() + 1 + // + // As an exception, IGNORE_ITEM_VIEW_TYPE (-1) will be remained as is, which will be used + // by framework's Adapter implementation and thus should be left as is. + if (position < contactTileAdapterCount) { // For "tile" and "frequent" sections + return mContactTileAdapter.getItemViewType(position); + } else if (position == contactTileAdapterCount) { // For "all" section's account header + return mContactTileAdapter.getViewTypeCount() + + mContactEntryListAdapter.getViewTypeCount(); + } else { // For "all" section + if (mContactEntryListAdapter.isLoading()) { // "All" section is being loaded. + return mContactTileAdapter.getViewTypeCount() + + mContactEntryListAdapter.getViewTypeCount() + 1; + } else { + // "-1" for mAccountFilterHeaderContainer + final int localPosition = position - contactTileAdapterCount - 1; + final int type = mContactEntryListAdapter.getItemViewType(localPosition); + // IGNORE_ITEM_VIEW_TYPE must be handled differently. + return (type < 0) ? type : type + mContactTileAdapter.getViewTypeCount(); + } + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final int contactTileAdapterCount = mContactTileAdapter.getCount(); + final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount(); + + // Obtain a View relevant for that position, and adjust its horizontal padding. Each + // View has different implementation, so we use different way to control those padding. + if (position < contactTileAdapterCount) { // For "tile" and "frequent" sections + final View view = mContactTileAdapter.getView(position, convertView, parent); + final int frequentHeaderPosition = mContactTileAdapter.getFrequentHeaderPosition(); + if (position < frequentHeaderPosition) { // "starred" contacts + // No padding adjustment. + } else if (position == frequentHeaderPosition) { + view.setPadding(mItemPaddingLeft, mFrequentHeaderPaddingTop, + mItemPaddingRight, view.getPaddingBottom()); + } else { + // Views for "frequent" contacts use FrameLayout's margins instead of padding. + final FrameLayout frameLayout = (FrameLayout) view; + final View child = frameLayout.getChildAt(0); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT); + params.setMargins(mItemPaddingLeft, 0, mItemPaddingRight, 0); + child.setLayoutParams(params); + } + return view; + } else if (position == contactTileAdapterCount) { // For "all" section's account header + mAccountFilterHeaderContainer.setPadding(mItemPaddingLeft, + mAccountFilterHeaderContainer.getPaddingTop(), + mItemPaddingRight, + mAccountFilterHeaderContainer.getPaddingBottom()); + return mAccountFilterHeaderContainer; + } else { // For "all" section + if (mContactEntryListAdapter.isLoading()) { // "All" section is being loaded. + mLoadingView.setPadding(mItemPaddingLeft, + mLoadingView.getPaddingTop(), + mItemPaddingRight, + mLoadingView.getPaddingBottom()); + return mLoadingView; + } else { + // "-1" for mAccountFilterHeaderContainer + final int localPosition = position - contactTileAdapterCount - 1; + final ContactListItemView itemView = (ContactListItemView) + mContactEntryListAdapter.getView(localPosition, convertView, null); + itemView.setPadding(mItemPaddingLeft, itemView.getPaddingTop(), + mItemPaddingRight, itemView.getPaddingBottom()); + itemView.setSelectionBoundsHorizontalMargin(mItemPaddingLeft, mItemPaddingRight); + return itemView; + } + } + } + + @Override + public boolean areAllItemsEnabled() { + // If "all" section is being loaded we'll show mLoadingView, which is not enabled. + // Otherwise check the all the other components in the ListView and return appropriate + // result. + return !mContactEntryListAdapter.isLoading() + && (mContactTileAdapter.areAllItemsEnabled() + && mAccountFilterHeaderContainer.isEnabled() + && mContactEntryListAdapter.areAllItemsEnabled()); + } + + @Override + public boolean isEnabled(int position) { + final int contactTileAdapterCount = mContactTileAdapter.getCount(); + final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount(); + if (position < contactTileAdapterCount) { // For "tile" and "frequent" sections + return mContactTileAdapter.isEnabled(position); + } else if (position == contactTileAdapterCount) { // For "all" section's account header + // This will be handled by View's onClick event instead of ListView's onItemClick event. + return false; + } else { // For "all" section + if (mContactEntryListAdapter.isLoading()) { // "All" section is being loaded. + return false; + } else { + // "-1" for mAccountFilterHeaderContainer + final int localPosition = position - contactTileAdapterCount - 1; + return mContactEntryListAdapter.isEnabled(localPosition); + } + } + } + + @Override + public int getPositionForSection(int sectionIndex) { + final int contactTileAdapterCount = mContactTileAdapter.getCount(); + final int localPosition = mContactEntryListAdapter.getPositionForSection(sectionIndex); + return contactTileAdapterCount + 1 + localPosition; + } + + @Override + public int getSectionForPosition(int position) { + final int contactTileAdapterCount = mContactTileAdapter.getCount(); + if (position <= contactTileAdapterCount) { + return 0; + } else { + // "-1" for mAccountFilterHeaderContainer + final int localPosition = position - contactTileAdapterCount - 1; + return mContactEntryListAdapter.getSectionForPosition(localPosition); + } + } + + @Override + public Object[] getSections() { + return mContactEntryListAdapter.getSections(); + } + + public boolean shouldShowFirstScroller(int firstVisibleItem) { + final int contactTileAdapterCount = mContactTileAdapter.getCount(); + return firstVisibleItem > contactTileAdapterCount; + } +} |