diff options
Diffstat (limited to 'src/com/android/dialer/list')
10 files changed, 1153 insertions, 182 deletions
diff --git a/src/com/android/dialer/list/NewPhoneFavoriteFragment.java b/src/com/android/dialer/list/NewPhoneFavoriteFragment.java index e694d6067..db2999ccf 100644 --- a/src/com/android/dialer/list/NewPhoneFavoriteFragment.java +++ b/src/com/android/dialer/list/NewPhoneFavoriteFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2013 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. @@ -27,14 +27,10 @@ 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; @@ -47,18 +43,16 @@ import android.widget.TextView; import com.android.contacts.common.ContactPhotoManager; import com.android.contacts.common.ContactTileLoaderFactory; -import com.android.contacts.common.dialog.ClearFrequentsDialog; -import com.android.contacts.common.list.ContactListFilter; -import com.android.contacts.common.list.ContactListFilterController; +import com.android.contacts.common.GeoUtil; import com.android.contacts.common.list.ContactListItemView; -import com.android.contacts.common.list.ContactTileAdapter; import com.android.contacts.common.list.ContactTileView; import com.android.contacts.common.list.PhoneNumberListAdapter; import com.android.contacts.common.preference.ContactsPreferences; -import com.android.contacts.common.util.AccountFilterUtil; -import com.android.contacts.common.interactions.ImportExportDialogFragment; -import com.android.dialer.DialtactsActivity; +import com.android.dialer.NewDialtactsActivity; import com.android.dialer.R; +import com.android.dialer.calllog.ContactInfoHelper; +import com.android.dialer.calllog.NewCallLogAdapter; +import com.android.dialer.calllog.CallLogQueryHandler; /** * Fragment for Phone UI's favorite screen. @@ -68,7 +62,8 @@ import com.android.dialer.R; * {@link com.android.contacts.common.list.PhoneNumberListAdapter} into one unified list using {@link PhoneFavoriteMergedAdapter}. * A contact filter header is also inserted between those adapters' results. */ -public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickListener { +public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickListener, + CallLogQueryHandler.Listener, NewCallLogAdapter.CallFetcher { private static final String TAG = NewPhoneFavoriteFragment.class.getSimpleName(); private static final boolean DEBUG = false; @@ -78,9 +73,9 @@ public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickLis 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 OnPhoneFavoriteFragmentStartedListener { + public void onPhoneFavoriteFragmentStarted(); + } public interface Listener { public void onContactSelected(Uri contactUri); @@ -113,11 +108,7 @@ public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickLis mAllContactsLoaderStarted = true; // Show the filter header with "loading" state. - updateFilterHeaderView(); mAccountFilterHeader.setVisibility(View.VISIBLE); - - // invalidate the options menu if needed - invalidateOptionsMenuIfNeeded(); } @Override @@ -139,7 +130,6 @@ public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickLis 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); } @@ -171,16 +161,6 @@ public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickLis } } - private class FilterHeaderClickListener implements OnClickListener { - @Override - public void onClick(View view) { - AccountFilterUtil.startAccountFilterActivityForResult( - NewPhoneFavoriteFragment.this, - REQUEST_CODE_ACCOUNT_FILTER, - mFilter); - } - } - private class ContactsPreferenceChangeListener implements ContactsPreferences.ChangeListener { @Override @@ -204,10 +184,13 @@ public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickLis mListView.setFastScrollAlwaysVisible(shouldShow); mShouldShowFastScroller = shouldShow; } + + } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { + mActivityScrollListener.onListFragmentScrollStateChange(scrollState); } } @@ -225,10 +208,15 @@ public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickLis }; private Listener mListener; - private PhoneFavoriteMergedAdapter mAdapter; - private ContactTileAdapter mContactTileAdapter; + + private OnListFragmentScrolledListener mActivityScrollListener; + private NewPhoneFavoriteMergedAdapter mAdapter; + private PhoneFavoritesTileAdapter mContactTileAdapter; private PhoneNumberListAdapter mAllContactsAdapter; + private NewCallLogAdapter mCallLogAdapter; + private CallLogQueryHandler mCallLogQueryHandler; + /** * true when the loader for {@link PhoneNumberListAdapter} has started already. */ @@ -241,7 +229,6 @@ public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickLis private boolean mAllContactsForceReload; private ContactsPreferences mContactsPrefs; - private ContactListFilter mFilter; private TextView mEmptyView; private ListView mListView; @@ -263,7 +250,6 @@ public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickLis 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(); @@ -281,9 +267,9 @@ public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickLis // 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 = new PhoneFavoritesTileAdapter(activity, mContactTileAdapterListener, + getResources().getInteger(R.integer.contact_tile_column_count_in_favorites_new), + 1); mContactTileAdapter.setPhotoLoader(ContactPhotoManager.getInstance(activity)); // Setup the "all" adapter manually. See also the setup logic in ContactEntryListFragment. @@ -293,7 +279,7 @@ public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickLis mAllContactsAdapter.setSearchMode(false); mAllContactsAdapter.setIncludeProfile(false); mAllContactsAdapter.setSelectionVisible(false); - mAllContactsAdapter.setDarkTheme(true); + mAllContactsAdapter.setDarkTheme(false); mAllContactsAdapter.setPhotoLoader(ContactPhotoManager.getInstance(activity)); // Disable directory header. mAllContactsAdapter.setHasHeader(0, false); @@ -318,27 +304,27 @@ public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickLis 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); - } - } + mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(), + this, 1); + final String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); + mCallLogAdapter = new NewCallLogAdapter(getActivity(), this, + new ContactInfoHelper(getActivity(), currentCountryIso)); setHasOptionsMenu(true); } @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putParcelable(KEY_FILTER, mFilter); + public void onResume() { + super.onResume(); + mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL); + mCallLogAdapter.setLoading(true); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View listLayout = inflater.inflate( - R.layout.phone_contact_tile_list, container, false); + R.layout.new_phone_favorites_fragment, container, false); mListView = (ListView) listLayout.findViewById(R.id.contact_tile_list); mListView.setItemsCanFocus(true); @@ -347,18 +333,18 @@ public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickLis mListView.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT); mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY); + // TODO krelease: Don't show this header anymore // 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(), + mAdapter = new NewPhoneFavoriteMergedAdapter(getActivity(), mContactTileAdapter, mAccountFilterHeaderContainer, mAllContactsAdapter, - mLoadingView); + mCallLogAdapter, mLoadingView); mListView.setAdapter(mAdapter); @@ -370,68 +356,48 @@ public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickLis mEmptyView.setText(getString(R.string.listTotalAllContactsZero)); mListView.setEmptyView(mEmptyView); - updateFilterHeaderView(); - return listLayout; } + // TODO krelease: update the options menu when displaying the popup menu instead. We could + // possibly get rid of this method entirely. 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); - } - + // TODO krelease: Configure the menu items properly. Since the menu items show up as a PopupMenu + // rather than a normal actionbar menu, the initialization should be done there. + /* @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, - DialtactsActivity.class); - 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(); + final Activity activity = getActivity(); + + try { + ((OnPhoneFavoriteFragmentStartedListener) activity).onPhoneFavoriteFragmentStarted(); + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + + " must implement OnPhoneFavoriteFragmentStartedListener"); + } + + try { + mActivityScrollListener = (OnListFragmentScrolledListener) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + + " must implement OnListFragmentScrolledListener"); + } mContactsPrefs.registerChangeListener(mContactsPreferenceChangeListener); // If ContactsPreferences has changed, we need to reload "all" contacts with the new @@ -478,18 +444,6 @@ public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickLis } } - @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; @@ -536,38 +490,24 @@ public class NewPhoneFavoriteFragment extends Fragment implements OnItemClickLis 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 void setListener(Listener listener) { + mListener = listener; } - public ContactListFilter getFilter() { - return mFilter; + // TODO krelease: Implement this + @Override + public void onVoicemailStatusFetched(Cursor statusCursor) { } - 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(); - } + @Override + public void onCallsFetched(Cursor cursor) { + mCallLogAdapter.setLoading(false); + mCallLogAdapter.changeCursor(cursor); + mAdapter.notifyDataSetChanged(); } - public void setListener(Listener listener) { - mListener = listener; + // TODO krelease: Implement this + @Override + public void fetchCalls() { } } diff --git a/src/com/android/dialer/list/NewPhoneFavoriteMergedAdapter.java b/src/com/android/dialer/list/NewPhoneFavoriteMergedAdapter.java index 047609f7d..49d46a851 100644 --- a/src/com/android/dialer/list/NewPhoneFavoriteMergedAdapter.java +++ b/src/com/android/dialer/list/NewPhoneFavoriteMergedAdapter.java @@ -27,13 +27,15 @@ import android.widget.SectionIndexer; import com.android.contacts.common.list.ContactEntryListAdapter; import com.android.contacts.common.list.ContactListItemView; -import com.android.contacts.common.list.ContactTileAdapter; import com.android.dialer.R; +import com.android.dialer.calllog.NewCallLogAdapter; /** * An adapter that combines items from {@link com.android.contacts.common.list.ContactTileAdapter} and - * {@link com.android.contacts.common.list.ContactEntryListAdapter} into a single list. In between those two results, - * an account filter header will be inserted. + * {@link com.android.contacts.common.list.ContactEntryListAdapter} into a single list. + * In between those two results, an account filter header will be inserted. + * + * Has one extra view at the top: The most recent call/voicemail/missed call. */ public class NewPhoneFavoriteMergedAdapter extends BaseAdapter implements SectionIndexer { @@ -44,38 +46,41 @@ public class NewPhoneFavoriteMergedAdapter extends BaseAdapter implements Sectio } } - private final ContactTileAdapter mContactTileAdapter; + private static final String TAG = NewPhoneFavoriteMergedAdapter.class.getSimpleName(); + + private final PhoneFavoritesTileAdapter mContactTileAdapter; private final ContactEntryListAdapter mContactEntryListAdapter; + private final NewCallLogAdapter mCallLogAdapter; private final View mAccountFilterHeaderContainer; private final View mLoadingView; + // TODO krelease: Add a setting to toggle mShowAllContacts, and really handle it + // properly below. + private boolean mShowAllContacts = false; + 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 NewPhoneFavoriteMergedAdapter(Context context, - ContactTileAdapter contactTileAdapter, + PhoneFavoritesTileAdapter contactTileAdapter, View accountFilterHeaderContainer, ContactEntryListAdapter contactEntryListAdapter, + NewCallLogAdapter callLogAdapter, View loadingView) { - Resources resources = context.getResources(); + final 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; + mCallLogAdapter = callLogAdapter; mAccountFilterHeaderContainer = accountFilterHeaderContainer; mObserver = new CustomDataSetObserver(); mContactTileAdapter.registerDataSetObserver(mObserver); mContactEntryListAdapter.registerDataSetObserver(mObserver); - mLoadingView = loadingView; } @@ -83,34 +88,52 @@ public class NewPhoneFavoriteMergedAdapter extends BaseAdapter implements Sectio 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; + return mCallLogAdapter.getCount() + mContactTileAdapter.getCount() + + mContactEntryListAdapter.getCount() == 0; } @Override public int getCount() { final int contactTileAdapterCount = mContactTileAdapter.getCount(); - final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount(); - if (mContactEntryListAdapter.isLoading()) { + final int contactEntryListAdapterCount = mShowAllContacts ? + mContactEntryListAdapter.getCount() : 0; + + final int callLogAdapterCount = mCallLogAdapter.getCount(); + + if (mShowAllContacts && mContactEntryListAdapter.isLoading()) { // Hide "all" contacts during its being loaded. Instead show "loading" view. // // "+2" for mAccountFilterHeaderContainer and mLoadingView - return contactTileAdapterCount + 2; + return contactTileAdapterCount + 1 + (mShowAllContacts ? 1 : 0) + + callLogAdapterCount; } else { // "+1" for mAccountFilterHeaderContainer - return contactTileAdapterCount + contactEntryListAdapterCount + 1; + return contactTileAdapterCount + contactEntryListAdapterCount + + (mShowAllContacts ? 1 : 0) + callLogAdapterCount; } } @Override public Object getItem(int position) { final int contactTileAdapterCount = mContactTileAdapter.getCount(); - final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount(); + final int contactEntryListAdapterCount = mShowAllContacts ? + mContactEntryListAdapter.getCount() : 0; + + final int callLogAdapterCount = mCallLogAdapter.getCount(); + + // TODO krelease: Calculate the position properly. + if (callLogAdapterCount > 0) { + if (position < callLogAdapterCount) { + return mCallLogAdapter.getItem(position); + } + position -= callLogAdapterCount; + } + 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. + if (mShowAllContacts && mContactEntryListAdapter.isLoading()) { + // "All" section is being loaded. return mLoadingView; } else { // "-1" for mAccountFilterHeaderContainer @@ -127,16 +150,31 @@ public class NewPhoneFavoriteMergedAdapter extends BaseAdapter implements Sectio @Override public int getViewTypeCount() { - // "+2" for mAccountFilterHeaderContainer and mLoadingView + // "+1" for mLoadingView return (mContactTileAdapter.getViewTypeCount() + mContactEntryListAdapter.getViewTypeCount() - + 2); + + 1 + + mCallLogAdapter.getViewTypeCount()); } @Override public int getItemViewType(int position) { final int contactTileAdapterCount = mContactTileAdapter.getCount(); - final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount(); + + final int contactEntryListAdapterCount = mShowAllContacts ? + mContactEntryListAdapter.getCount() : 0; + + final int callLogAdapterCount = mCallLogAdapter.getCount(); + + if (callLogAdapterCount > 0) { + if (position == 0) { + return mContactTileAdapter.getViewTypeCount() + + mContactEntryListAdapter.getViewTypeCount() + 2; + } + // Ignore the first position when calculating view types of all other items + position -= callLogAdapterCount; + } + // 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. // @@ -190,19 +228,28 @@ public class NewPhoneFavoriteMergedAdapter extends BaseAdapter implements Sectio @Override public View getView(int position, View convertView, ViewGroup parent) { final int contactTileAdapterCount = mContactTileAdapter.getCount(); - final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount(); + + final int contactEntryListAdapterCount = mShowAllContacts ? + mContactEntryListAdapter.getCount() : 0; + + final int callLogAdapterCount = mCallLogAdapter.getCount(); + + // TODO krelease: Handle the new callLogAdapterCount and position offsets properly + if (callLogAdapterCount > 0) { + if (position == 0) { + final View view = mCallLogAdapter.getView(position, convertView, parent); + return view; + } + position -= callLogAdapterCount; + } // 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 { + // TODO krelease: Get rid of frequent header position, we don't need it anymore + if (position >= frequentHeaderPosition) { // Views for "frequent" contacts use FrameLayout's margins instead of padding. final FrameLayout frameLayout = (FrameLayout) view; final View child = frameLayout.getChildAt(0); @@ -213,28 +260,16 @@ public class NewPhoneFavoriteMergedAdapter extends BaseAdapter implements Sectio child.setLayoutParams(params); } return view; - } else if (position == contactTileAdapterCount) { // For "all" section's account header - mAccountFilterHeaderContainer.setPadding(mItemPaddingLeft, - mAccountFilterHeaderContainer.getPaddingTop(), - mItemPaddingRight, - mAccountFilterHeaderContainer.getPaddingBottom()); - - // Show a single "No Contacts" label under the "all" section account header - // if no contacts are displayed. - mAccountFilterHeaderContainer.findViewById( - R.id.contact_list_all_empty).setVisibility( - contactEntryListAdapterCount == 0 ? View.VISIBLE : View.GONE); - return mAccountFilterHeaderContainer; } else { // For "all" section - if (mContactEntryListAdapter.isLoading()) { // "All" section is being loaded. + if (mShowAllContacts && 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 int localPosition = position - contactTileAdapterCount; final ContactListItemView itemView = (ContactListItemView) mContactEntryListAdapter.getView(localPosition, convertView, null); itemView.setPadding(mItemPaddingLeft, itemView.getPaddingTop(), @@ -278,9 +313,10 @@ public class NewPhoneFavoriteMergedAdapter extends BaseAdapter implements Sectio @Override public int getPositionForSection(int sectionIndex) { + // frequent header view. final int contactTileAdapterCount = mContactTileAdapter.getCount(); final int localPosition = mContactEntryListAdapter.getPositionForSection(sectionIndex); - return contactTileAdapterCount + 1 + localPosition; + return contactTileAdapterCount + localPosition; } @Override @@ -289,8 +325,7 @@ public class NewPhoneFavoriteMergedAdapter extends BaseAdapter implements Sectio if (position <= contactTileAdapterCount) { return 0; } else { - // "-1" for mAccountFilterHeaderContainer - final int localPosition = position - contactTileAdapterCount - 1; + final int localPosition = position - contactTileAdapterCount; return mContactEntryListAdapter.getSectionForPosition(localPosition); } } diff --git a/src/com/android/dialer/list/OnListFragmentScrolledListener.java b/src/com/android/dialer/list/OnListFragmentScrolledListener.java new file mode 100644 index 000000000..cc5f3cd3a --- /dev/null +++ b/src/com/android/dialer/list/OnListFragmentScrolledListener.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2013 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; + +/* + * Interface to provide callback to activity when a child fragment is scrolled + */ +public interface OnListFragmentScrolledListener { + public void onListFragmentScrollStateChange(int scrollState); +} diff --git a/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java b/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java index ba291a00f..bb758a710 100644 --- a/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java +++ b/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java @@ -130,7 +130,7 @@ public class PhoneFavoriteMergedAdapter extends BaseAdapter implements SectionIn // "+2" for mAccountFilterHeaderContainer and mLoadingView return (mContactTileAdapter.getViewTypeCount() + mContactEntryListAdapter.getViewTypeCount() - + 2); + + 1); } @Override diff --git a/src/com/android/dialer/list/PhoneFavoriteRegularRowView.java b/src/com/android/dialer/list/PhoneFavoriteRegularRowView.java new file mode 100644 index 000000000..2f5921eaf --- /dev/null +++ b/src/com/android/dialer/list/PhoneFavoriteRegularRowView.java @@ -0,0 +1,82 @@ +/* + * 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.content.Context; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; + +import com.android.contacts.common.MoreContactUtils; +import com.android.contacts.common.list.ContactEntry; +import com.android.contacts.common.list.ContactTileView; +import com.android.contacts.common.util.ViewUtil; + +/** + * A light version of the {@link com.android.contacts.common.list.ContactTileView} that is used in Dialtacts + * for frequently called contacts. Slightly different behavior from superclass... + * when you tap it, you want to call the frequently-called number for the + * contact, even if that is not the default number for that contact. + */ +public class PhoneFavoriteRegularRowView extends ContactTileView { + private String mPhoneNumberString; + + public PhoneFavoriteRegularRowView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected boolean isDarkTheme() { + return false; + } + + @Override + protected int getApproximateImageSize() { + return ViewUtil.getConstantPreLayoutWidth(getQuickContact()); + } + + @Override + public void loadFromContact(ContactEntry entry) { + super.loadFromContact(entry); + mPhoneNumberString = null; // ... in case we're reusing the view + if (entry != null) { + // Grab the phone-number to call directly... see {@link onClick()} + mPhoneNumberString = entry.phoneNumber; + } + } + + @Override + protected OnClickListener createClickListener() { + return new OnClickListener() { + @Override + public void onClick(View v) { + if (mListener == null) return; + if (TextUtils.isEmpty(mPhoneNumberString)) { + // Copy "superclass" implementation + mListener.onContactSelected(getLookupUri(), MoreContactUtils + .getTargetRectFromView( + mContext, PhoneFavoriteRegularRowView.this)); + } else { + // When you tap a frequently-called contact, you want to + // call them at the number that you usually talk to them + // at (i.e. the one displayed in the UI), regardless of + // whether that's their default number. + mListener.onCallNumberDirectly(mPhoneNumberString); + } + } + }; + } +} diff --git a/src/com/android/dialer/list/PhoneFavoriteTileView.java b/src/com/android/dialer/list/PhoneFavoriteTileView.java new file mode 100644 index 000000000..d87e2a837 --- /dev/null +++ b/src/com/android/dialer/list/PhoneFavoriteTileView.java @@ -0,0 +1,65 @@ +/* + * 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.content.Context; +import android.content.Intent; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageButton; + +import com.android.contacts.common.R; +import com.android.contacts.common.list.ContactTileView; + +/** + * Displays the contact's picture overlayed with their name + * in a perfect square. It also has an additional touch target for a secondary action. + */ +public class PhoneFavoriteTileView extends ContactTileView { + private ImageButton mSecondaryButton; + + public PhoneFavoriteTileView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mSecondaryButton = (ImageButton) findViewById(R.id.contact_tile_secondary_button); + mSecondaryButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_VIEW, getLookupUri()); + // Secondary target will be visible only from phone's favorite screen, then + // we want to launch it as a separate People task. + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + getContext().startActivity(intent); + } + }); + } + + @Override + protected boolean isDarkTheme() { + return false; + } + + @Override + protected int getApproximateImageSize() { + // The picture is the full size of the tile (minus some padding, but we can be generous) + return mListener.getApproximateTileWidth(); + } +} diff --git a/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java b/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java new file mode 100644 index 000000000..36fc34608 --- /dev/null +++ b/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java @@ -0,0 +1,515 @@ +/* + * Copyright (C) 2013 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.ContentUris; +import android.content.Context; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Contacts; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.FrameLayout; + +import com.android.contacts.common.ContactPhotoManager; +import com.android.contacts.common.ContactTileLoaderFactory; +import com.android.contacts.common.R; +import com.android.contacts.common.list.ContactEntry; +import com.android.contacts.common.list.ContactTileAdapter; +import com.android.contacts.common.list.ContactTileView; + +import java.util.ArrayList; + +/** + * Also allows for a configurable number of columns as well as a maximum row of tiled contacts. + * + * This adapter has been rewritten to only support a maximum of one row for favorites. + * + * TODO Krelease: Move to PhoneContactTileAdapter in Dialer package. + */ +public class PhoneFavoritesTileAdapter extends BaseAdapter { + private static final String TAG = ContactTileAdapter.class.getSimpleName(); + + public static final int NO_ROW_LIMIT = -1; + + private ContactTileView.Listener mListener; + private Context mContext; + private Resources mResources; + protected Cursor mContactCursor = null; + private ContactPhotoManager mPhotoManager; + protected int mNumFrequents; + + /** + * Index of the first NON starred contact in the {@link Cursor} + * Only valid when {@link DisplayType#STREQUENT} is true + */ + private int mDividerPosition; + protected int mColumnCount; + private int mMaxTiledRows = NO_ROW_LIMIT; + private int mStarredIndex; + + protected int mIdIndex; + protected int mLookupIndex; + protected int mPhotoUriIndex; + protected int mNameIndex; + protected int mPresenceIndex; + protected int mStatusIndex; + + /** + * Only valid when {@link DisplayType#STREQUENT_PHONE_ONLY} is true + */ + private int mPhoneNumberIndex; + private int mPhoneNumberTypeIndex; + private int mPhoneNumberLabelIndex; + + private boolean mIsQuickContactEnabled = false; + private final int mPaddingInPixels; + + public PhoneFavoritesTileAdapter(Context context, ContactTileView.Listener listener, int numCols) { + this(context, listener, numCols, NO_ROW_LIMIT); + } + + public PhoneFavoritesTileAdapter(Context context, ContactTileView.Listener listener, int numCols, + int maxTiledRows) { + mListener = listener; + mContext = context; + mResources = context.getResources(); + mColumnCount = numCols; + mNumFrequents = 0; + mMaxTiledRows = maxTiledRows; + + // Converting padding in dips to padding in pixels + mPaddingInPixels = mContext.getResources() + .getDimensionPixelSize(R.dimen.contact_tile_divider_padding); + bindColumnIndices(); + } + + public void setPhotoLoader(ContactPhotoManager photoLoader) { + mPhotoManager = photoLoader; + } + + public void setMaxRowCount(int maxRows) { + mMaxTiledRows = maxRows; + } + + public void setColumnCount(int columnCount) { + mColumnCount = columnCount; + } + + public void enableQuickContact(boolean enableQuickContact) { + mIsQuickContactEnabled = enableQuickContact; + } + + /** + * Sets the column indices for expected {@link Cursor} + * based on {@link DisplayType}. + */ + protected void bindColumnIndices() { + mIdIndex = ContactTileLoaderFactory.CONTACT_ID; + mLookupIndex = ContactTileLoaderFactory.LOOKUP_KEY; + mPhotoUriIndex = ContactTileLoaderFactory.PHOTO_URI; + mNameIndex = ContactTileLoaderFactory.DISPLAY_NAME; + mStarredIndex = ContactTileLoaderFactory.STARRED; + mPresenceIndex = ContactTileLoaderFactory.CONTACT_PRESENCE; + mStatusIndex = ContactTileLoaderFactory.CONTACT_STATUS; + + mPhoneNumberIndex = ContactTileLoaderFactory.PHONE_NUMBER; + mPhoneNumberTypeIndex = ContactTileLoaderFactory.PHONE_NUMBER_TYPE; + mPhoneNumberLabelIndex = ContactTileLoaderFactory.PHONE_NUMBER_LABEL; + } + + /** + * Gets the number of frequents from the passed in cursor. + * + * This methods is needed so the GroupMemberTileAdapter can override this. + * + * @param cursor The cursor to get number of frequents from. + */ + protected void saveNumFrequentsFromCursor(Cursor cursor) { + mNumFrequents = cursor.getCount() - mDividerPosition; + } + + /** + * Creates {@link ContactTileView}s for each item in {@link Cursor}. + * + * Else use {@link ContactTileLoaderFactory} + */ + public void setContactCursor(Cursor cursor) { + mContactCursor = cursor; + mDividerPosition = getDividerPosition(cursor); + + saveNumFrequentsFromCursor(cursor); + + // cause a refresh of any views that rely on this data + notifyDataSetChanged(); + } + + /** + * Iterates over the {@link Cursor} + * Returns position of the first NON Starred Contact + * Returns -1 if {@link DisplayType#STARRED_ONLY} + * Returns 0 if {@link DisplayType#FREQUENT_ONLY} + */ + protected int getDividerPosition(Cursor cursor) { + if (cursor == null || cursor.isClosed()) { + throw new IllegalStateException("Unable to access cursor"); + } + + cursor.moveToPosition(-1); + while (cursor.moveToNext()) { + if (cursor.getInt(mStarredIndex) == 0) { + return cursor.getPosition(); + } + } + + // There are not NON Starred contacts in cursor + // Set divider positon to end + return cursor.getCount(); + } + + protected ContactEntry createContactEntryFromCursor(Cursor cursor, int position) { + // If the loader was canceled we will be given a null cursor. + // In that case, show an empty list of contacts. + if (cursor == null || cursor.isClosed() || cursor.getCount() <= position) return null; + + cursor.moveToPosition(position); + long id = cursor.getLong(mIdIndex); + String photoUri = cursor.getString(mPhotoUriIndex); + String lookupKey = cursor.getString(mLookupIndex); + + ContactEntry contact = new ContactEntry(); + String name = cursor.getString(mNameIndex); + contact.name = (name != null) ? name : mResources.getString(R.string.missing_name); + contact.status = cursor.getString(mStatusIndex); + contact.photoUri = (photoUri != null ? Uri.parse(photoUri) : null); + contact.lookupKey = ContentUris.withAppendedId( + Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), id); + + // Set phone number and label + int phoneNumberType = cursor.getInt(mPhoneNumberTypeIndex); + String phoneNumberCustomLabel = cursor.getString(mPhoneNumberLabelIndex); + contact.phoneLabel = (String) Phone.getTypeLabel(mResources, phoneNumberType, + phoneNumberCustomLabel); + contact.phoneNumber = cursor.getString(mPhoneNumberIndex); + + return contact; + } + + /** + * Returns the number of frequents that will be displayed in the list. + */ + public int getNumFrequents() { + return mNumFrequents; + } + + @Override + public int getCount() { + if (mContactCursor == null || mContactCursor.isClosed()) { + return 0; + } + + // Takes numbers of rows the Starred Contacts Occupy + int starredRowCount = getRowCount(mDividerPosition) + + (mMaxTiledRows == NO_ROW_LIMIT ? 0 : Math.max(0, mDividerPosition - + mMaxTiledRows * mColumnCount)); + + // Compute the frequent row count which is 1 plus the number of frequents + // (to account for the divider) or 0 if there are no frequents. + int frequentRowCount = mNumFrequents == 0 ? 0 : mNumFrequents; + + // Return the number of starred plus frequent rows + return starredRowCount + frequentRowCount; + } + + /** + * Returns the number of rows required to show the provided number of entries + * with the current number of columns. + */ + protected int getRowCount(int entryCount) { + if (entryCount == 0) return 0; + final int nonLimitedRows = ((entryCount - 1) / mColumnCount) + 1; + return mMaxTiledRows == NO_ROW_LIMIT ? nonLimitedRows : Math.min(mMaxTiledRows, + nonLimitedRows); + } + + public int getColumnCount() { + return mColumnCount; + } + + /** + * Returns an ArrayList of the {@link ContactEntry}s that are to appear + * on the row for the given position. + */ + @Override + public ArrayList<ContactEntry> getItem(int position) { + ArrayList<ContactEntry> resultList = new ArrayList<ContactEntry>(mColumnCount); + int contactIndex = position * mColumnCount; + if (position < getRowCount(mDividerPosition)) { + for (int columnCounter = 0; columnCounter < mColumnCount && + contactIndex != mDividerPosition; columnCounter++) { + resultList.add(createContactEntryFromCursor(mContactCursor, contactIndex)); + contactIndex++; + } + } else { + /* + * Current position minus how many rows are before the divider and Minus 1 for the + * divider itself provides the relative index of the frequent contact being displayed. + * Then add the dividerPostion to give the offset into the contacts cursor to get the + * absolute index. + */ + final int rowCount = getRowCount(mDividerPosition); + contactIndex = position - rowCount + Math.min(mDividerPosition, + rowCount * mColumnCount); + resultList.add(createContactEntryFromCursor(mContactCursor, contactIndex)); + } + return resultList; + } + + @Override + public long getItemId(int position) { + // As we show several selectable items for each ListView row, + // we can not determine a stable id. But as we don't rely on ListView's selection, + // this should not be a problem. + return position; + } + + @Override + public boolean areAllItemsEnabled() { + // No dividers, so all items are enabled. + return true; + } + + @Override + public boolean isEnabled(int position) { + return position != getRowCount(mDividerPosition); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + int itemViewType = getItemViewType(position); + + ContactTileRow contactTileRowView = (ContactTileRow) convertView; + ArrayList<ContactEntry> contactList = getItem(position); + + if (contactTileRowView == null) { + // Creating new row if needed + contactTileRowView = new ContactTileRow(mContext, itemViewType); + } + + contactTileRowView.configureRow(contactList, position == getCount() - 1); + return contactTileRowView; + } + + private int getLayoutResourceId(int viewType) { + switch (viewType) { + case ViewTypes.FREQUENT: + return R.layout.phone_favorite_regular_row_view; + case ViewTypes.STARRED_PHONE: + return R.layout.phone_favorite_tile_view; + default: + throw new IllegalArgumentException("Unrecognized viewType " + viewType); + } + } + @Override + public int getViewTypeCount() { + return ViewTypes.COUNT; + } + + @Override + public int getItemViewType(int position) { + if (position < getRowCount(mDividerPosition)) { + return ViewTypes.STARRED_PHONE; + } else { + return ViewTypes.FREQUENT; + } + } + + /** + * Returns the "frequent header" position. Only available when STREQUENT or + * STREQUENT_PHONE_ONLY is used for its display type. + */ + public int getFrequentHeaderPosition() { + return getRowCount(mDividerPosition); + } + + /** + * Acts as a row item composed of {@link ContactTileView} + * + * TODO: FREQUENT doesn't really need it. Just let {@link #getView} return + */ + private class ContactTileRow extends FrameLayout { + private int mItemViewType; + private int mLayoutResId; + + public ContactTileRow(Context context, int itemViewType) { + super(context); + mItemViewType = itemViewType; + mLayoutResId = getLayoutResourceId(mItemViewType); + + // Remove row (but not children) from accessibility node tree. + setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } + + /** + * Configures the row to add {@link ContactEntry}s information to the views + */ + public void configureRow(ArrayList<ContactEntry> list, boolean isLastRow) { + int columnCount = mItemViewType == ViewTypes.FREQUENT ? 1 : mColumnCount; + + // Adding tiles to row and filling in contact information + for (int columnCounter = 0; columnCounter < columnCount; columnCounter++) { + ContactEntry entry = + columnCounter < list.size() ? list.get(columnCounter) : null; + addTileFromEntry(entry, columnCounter, isLastRow); + } + } + + private void addTileFromEntry(ContactEntry entry, int childIndex, boolean isLastRow) { + final ContactTileView contactTile; + + if (getChildCount() <= childIndex) { + + contactTile = (ContactTileView) inflate(mContext, mLayoutResId, null); + // Note: the layoutparam set here is only actually used for FREQUENT. + // We override onMeasure() for STARRED and we don't care the layout param there. + Resources resources = mContext.getResources(); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + + params.setMargins( + resources.getDimensionPixelSize(R.dimen.detail_item_side_margin), + 0, + resources.getDimensionPixelSize(R.dimen.detail_item_side_margin), + 0); + contactTile.setLayoutParams(params); + contactTile.setPhotoManager(mPhotoManager); + contactTile.setListener(mListener); + addView(contactTile); + } else { + contactTile = (ContactTileView) getChildAt(childIndex); + } + contactTile.loadFromContact(entry); + switch (mItemViewType) { + case ViewTypes.STARRED_PHONE: + // Setting divider visibilities + contactTile.setPaddingRelative(0, 0, + childIndex >= mColumnCount - 1 ? 0 : mPaddingInPixels, + isLastRow ? 0 : mPaddingInPixels); + break; + case ViewTypes.FREQUENT: + contactTile.setHorizontalDividerVisibility( + isLastRow ? View.GONE : View.VISIBLE); + break; + default: + break; + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + switch (mItemViewType) { + case ViewTypes.STARRED_PHONE: + onLayoutForTiles(); + return; + default: + super.onLayout(changed, left, top, right, bottom); + return; + } + } + + private void onLayoutForTiles() { + final int count = getChildCount(); + + // Just line up children horizontally. + int childLeft = 0; + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + + // Note MeasuredWidth includes the padding. + final int childWidth = child.getMeasuredWidth(); + child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight()); + childLeft += childWidth; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + switch (mItemViewType) { + case ViewTypes.STARRED_PHONE: + case ViewTypes.STARRED: + onMeasureForTiles(widthMeasureSpec); + return; + default: + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + } + + private void onMeasureForTiles(int widthMeasureSpec) { + final int width = MeasureSpec.getSize(widthMeasureSpec); + + final int childCount = getChildCount(); + if (childCount == 0) { + // Just in case... + setMeasuredDimension(width, 0); + return; + } + + // 1. Calculate image size. + // = ([total width] - [total padding]) / [child count] + // + // 2. Set it to width/height of each children. + // If we have a remainder, some tiles will have 1 pixel larger width than its height. + // + // 3. Set the dimensions of itself. + // Let width = given width. + // Let height = image size + bottom paddding. + + final int totalPaddingsInPixels = (mColumnCount - 1) * mPaddingInPixels; + + // Preferred width / height for images (excluding the padding). + // The actual width may be 1 pixel larger than this if we have a remainder. + final int imageSize = (width - totalPaddingsInPixels) / mColumnCount; + final int remainder = width - (imageSize * mColumnCount) - totalPaddingsInPixels; + + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final int childWidth = imageSize + child.getPaddingRight() + // Compensate for the remainder + + (i < remainder ? 1 : 0); + final int childHeight = imageSize + child.getPaddingBottom(); + child.measure( + MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY) + ); + } + setMeasuredDimension(width, imageSize + getChildAt(0).getPaddingBottom()); + } + } + + protected static class ViewTypes { + public static final int COUNT = 3; + public static final int STARRED = 0; + public static final int FREQUENT = 1; + public static final int STARRED_PHONE = 2; + } +} diff --git a/src/com/android/dialer/list/SmartDialNumberListAdapter.java b/src/com/android/dialer/list/SmartDialNumberListAdapter.java new file mode 100644 index 000000000..0413c4ee5 --- /dev/null +++ b/src/com/android/dialer/list/SmartDialNumberListAdapter.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2013 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.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract; +import android.provider.ContactsContract.CommonDataKinds.Callable; +import android.telephony.PhoneNumberUtils; +import android.util.Log; + +import com.android.contacts.common.list.ContactListItemView; +import com.android.contacts.common.list.PhoneNumberListAdapter; +import com.android.dialer.dialpad.SmartDialCursorLoader; +import com.android.dialer.dialpad.SmartDialCursorLoader.SmartDialPhoneQuery; +import com.android.dialer.dialpad.SmartDialNameMatcher; +import com.android.dialer.dialpad.SmartDialPrefix; +import com.android.dialer.dialpad.SmartDialMatchPosition; + +import java.util.ArrayList; + +/** + * List adapter to display the SmartDial search results. + */ +public class SmartDialNumberListAdapter extends PhoneNumberListAdapter{ + + private static final String TAG = SmartDialNumberListAdapter.class.getSimpleName(); + private static final boolean DEBUG = false; + + private SmartDialNameMatcher mNameMatcher; + + public SmartDialNumberListAdapter(Context context) { + super(context); + if (DEBUG) { + Log.v(TAG, "Constructing List Adapter"); + } + } + + /** + * Sets query for the SmartDialCursorLoader. + */ + public void configureLoader(SmartDialCursorLoader loader) { + if (DEBUG) { + Log.v(TAG, "Congifugure Loader with query" + getQueryString()); + } + + if (getQueryString() == null) { + mNameMatcher = new SmartDialNameMatcher("", SmartDialPrefix.getMap()); + loader.configureQuery(""); + } else { + loader.configureQuery(getQueryString()); + mNameMatcher = new SmartDialNameMatcher(PhoneNumberUtils.normalizeNumber( + getQueryString()), SmartDialPrefix.getMap()); + } + } + + /** + * Sets highlight options for a List item in the SmartDial search results. + * @param view ContactListItemView where the result will be displayed. + * @param cursor Object containing information of the associated List item. + */ + @Override + protected void setHighlight(ContactListItemView view, Cursor cursor) { + view.clearHighlightSequences(); + + if (mNameMatcher.matches(cursor.getString(SmartDialPhoneQuery.SMARTDIAL_DISPLAY_NAME))) { + final ArrayList<SmartDialMatchPosition> nameMatches = mNameMatcher.getMatchPositions(); + for (SmartDialMatchPosition match:nameMatches) { + view.addNameHighlightSequence(match.start, match.end); + if (DEBUG) { + Log.v(TAG, cursor.getString(SmartDialPhoneQuery.SMARTDIAL_DISPLAY_NAME) + " " + + mNameMatcher.getQuery() + " " + String.valueOf(match.start)); + } + } + } + + final SmartDialMatchPosition numberMatch = mNameMatcher.matchesNumber(cursor.getString( + SmartDialPhoneQuery.SMARTDIAL_NUMBER)); + if (numberMatch != null) { + view.addNumberHighlightSequence(numberMatch.start, numberMatch.end); + } + } + + /** + * Gets Uri for the list item at the given position. + * @param position Location of the data of interest. + * @return Data Uri of the entry. + */ + public Uri getDataUri(int position) { + Cursor cursor = ((Cursor)getItem(position)); + if (cursor != null) { + long id = cursor.getLong(SmartDialPhoneQuery.SMARTDIAL_ID); + return ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, id); + } else { + Log.w(TAG, "Cursor was null in getDataUri() call. Returning null instead."); + return null; + } + } +} diff --git a/src/com/android/dialer/list/SmartDialNumberPickerFragment.java b/src/com/android/dialer/list/SmartDialNumberPickerFragment.java new file mode 100644 index 000000000..0892de64a --- /dev/null +++ b/src/com/android/dialer/list/SmartDialNumberPickerFragment.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013 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.Loader; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.ContactsContract.Directory; +import android.util.Log; + +import com.android.contacts.common.list.ContactEntryListAdapter; +import com.android.contacts.common.list.PhoneNumberPickerFragment; +import com.android.dialer.dialpad.SmartDialCursorLoader; + +/** + * Implements a fragment to load and display SmartDial search results. + */ +public class SmartDialNumberPickerFragment extends PhoneNumberPickerFragment { + + private static final String TAG = SmartDialNumberPickerFragment.class.getSimpleName(); + + /** + * Creates a SmartDialListAdapter to display and operate on search results. + * @return + */ + @Override + protected ContactEntryListAdapter createListAdapter() { + SmartDialNumberListAdapter adapter = new SmartDialNumberListAdapter(getActivity()); + adapter.setDisplayPhotos(true); + adapter.setUseCallableUri(super.usesCallableUri()); + return adapter; + } + + /** + * Creates a SmartDialCursorLoader object to load query results. + */ + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + /** SmartDial does not support Directory Load, falls back to normal search instead. */ + if (id == getDirectoryLoaderId()) { + Log.v(TAG, "Directory load"); + return super.onCreateLoader(id, args); + } else { + Log.v(TAG, "Creating loader"); + final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter(); + SmartDialCursorLoader loader = new SmartDialCursorLoader(super.getContext()); + adapter.configureLoader(loader); + return loader; + } + } + + /** + * Gets the Phone Uri of an entry for calling. + * @param position Location of the data of interest. + * @return Phone Uri to establish a phone call. + */ + @Override + protected Uri getPhoneUri(int position) { + final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter(); + return adapter.getDataUri(position); + } +} diff --git a/src/com/android/dialer/list/SmartDialSearchFragment.java b/src/com/android/dialer/list/SmartDialSearchFragment.java new file mode 100644 index 000000000..3c1e51343 --- /dev/null +++ b/src/com/android/dialer/list/SmartDialSearchFragment.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2013 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.content.Loader; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.ContactsContract.Directory; +import android.util.Log; +import android.widget.AbsListView; +import android.widget.AbsListView.OnScrollListener; + +import com.android.contacts.common.list.ContactEntryListAdapter; +import com.android.contacts.common.list.ContactListItemView; +import com.android.contacts.common.list.PhoneNumberPickerFragment; +import com.android.dialer.dialpad.SmartDialCursorLoader; + +/** + * Implements a fragment to load and display SmartDial search results. + */ +public class SmartDialSearchFragment extends PhoneNumberPickerFragment { + private static final String TAG = SmartDialSearchFragment.class.getSimpleName(); + + private OnListFragmentScrolledListener mActivityScrollListener; + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + setQuickContactEnabled(true); + setDarkTheme(false); + setPhotoPosition(ContactListItemView.getDefaultPhotoPosition(true /* opposite */)); + setUseCallableUri(true); + + try { + mActivityScrollListener = (OnListFragmentScrolledListener) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + + " must implement OnListFragmentScrolledListener"); + } + } + + @Override + public void onStart() { + super.onStart(); + getListView().setOnScrollListener(new OnScrollListener() { + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + mActivityScrollListener.onListFragmentScrollStateChange(scrollState); + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, + int totalItemCount) { + } + }); + } + + /** + * Creates a SmartDialListAdapter to display and operate on search results. + */ + @Override + protected ContactEntryListAdapter createListAdapter() { + SmartDialNumberListAdapter adapter = new SmartDialNumberListAdapter(getActivity()); + adapter.setUseCallableUri(super.usesCallableUri()); + adapter.setQuickContactEnabled(true); + return adapter; + } + + /** + * Creates a SmartDialCursorLoader object to load query results. + */ + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + // Smart dialing does not support Directory Load, falls back to normal search instead. + if (id == getDirectoryLoaderId()) { + Log.v(TAG, "Directory load"); + return super.onCreateLoader(id, args); + } else { + Log.v(TAG, "Creating loader"); + final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter(); + SmartDialCursorLoader loader = new SmartDialCursorLoader(super.getContext()); + adapter.configureLoader(loader); + return loader; + } + } + + /** + * Gets the Phone Uri of an entry for calling. + * @param position Location of the data of interest. + * @return Phone Uri to establish a phone call. + */ + @Override + protected Uri getPhoneUri(int position) { + final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter(); + return adapter.getDataUri(position); + } + + @Override + protected void setSearchMode(boolean flag) { + super.setSearchMode(flag); + // This hides the "All contacts with phone numbers" header in the search fragment + getAdapter().setHasHeader(0, false); + } +} |