summaryrefslogtreecommitdiff
path: root/src/com/android/dialer/list/PhoneFavoriteFragment.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/dialer/list/PhoneFavoriteFragment.java')
-rw-r--r--src/com/android/dialer/list/PhoneFavoriteFragment.java569
1 files changed, 569 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;
+ }
+}