diff options
author | calderwoodra <calderwoodra@google.com> | 2017-12-08 20:52:56 -0800 |
---|---|---|
committer | Copybara-Service <copybara-piper@google.com> | 2017-12-08 20:53:51 -0800 |
commit | 73b51d5771b31c932a589abc9bb0fe64c52fe102 (patch) | |
tree | 3845e7aad9e502bf5a91eb8705c8005f06990105 | |
parent | 20ebcdca46e6be68050cd44087f0f768f5dae5c6 (diff) |
Implemented adding a new favorites contact flow NUI.
This change consists of mainly 3 things:
- Update contacts fragment to meet AddFavoriteActivity requirements
- Implement AddFavoriteActivity
- Passing the contact back to SpeedDialFragment
Bug: 36841782
Test: SpeedDialIntegrationTest
PiperOrigin-RevId: 178461265
Change-Id: Ib3a13eae311acf6ce10a94df4f2c95b9af120cff
14 files changed, 352 insertions, 128 deletions
diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java index 2d82b6f33..48796dba3 100644 --- a/java/com/android/dialer/app/DialtactsActivity.java +++ b/java/com/android/dialer/app/DialtactsActivity.java @@ -32,6 +32,7 @@ import android.os.Bundle; import android.os.SystemClock; import android.os.Trace; import android.provider.CallLog.Calls; +import android.provider.ContactsContract.QuickContact; import android.speech.RecognizerIntent; import android.support.annotation.MainThread; import android.support.annotation.NonNull; @@ -59,6 +60,7 @@ import android.view.animation.AnimationUtils; import android.widget.AbsListView.OnScrollListener; import android.widget.EditText; import android.widget.ImageButton; +import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.TextView; import android.widget.Toast; @@ -101,6 +103,7 @@ import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.constants.ActivityRequestCodes; import com.android.dialer.contactsfragment.ContactsFragment; +import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener; import com.android.dialer.database.Database; import com.android.dialer.database.DialerDatabaseHelper; import com.android.dialer.dialpadview.DialpadFragment; @@ -109,6 +112,7 @@ import com.android.dialer.dialpadview.DialpadFragment.LastOutgoingCallCallback; import com.android.dialer.interactions.PhoneNumberInteraction; import com.android.dialer.interactions.PhoneNumberInteraction.InteractionErrorCode; import com.android.dialer.logging.DialerImpression; +import com.android.dialer.logging.InteractionEvent; import com.android.dialer.logging.Logger; import com.android.dialer.logging.LoggingBindings; import com.android.dialer.logging.ScreenEvent; @@ -166,7 +170,8 @@ public class DialtactsActivity extends TransactionSafeActivity PhoneNumberInteraction.DisambigDialogDismissedListener, ActivityCompat.OnRequestPermissionsResultCallback, DialpadListener, - SearchFragmentListener { + SearchFragmentListener, + OnContactSelectedListener { public static final boolean DEBUG = false; @VisibleForTesting public static final String TAG_DIALPAD_FRAGMENT = "dialpad"; @@ -1695,6 +1700,14 @@ public class DialtactsActivity extends TransactionSafeActivity return mPreviouslySelectedTabIndex; } + @Override + public void onContactSelected(ImageView photo, Uri contactUri, long contactId) { + Logger.get(this) + .logInteraction(InteractionEvent.Type.OPEN_QUICK_CONTACT_FROM_CONTACTS_FRAGMENT_ITEM); + QuickContact.showQuickContact( + this, photo, contactUri, QuickContact.MODE_LARGE, null /* excludeMimes */); + } + /** Popup menu accessible from the search bar */ protected class OptionsPopupMenu extends PopupMenu { diff --git a/java/com/android/dialer/app/list/DialtactsPagerAdapter.java b/java/com/android/dialer/app/list/DialtactsPagerAdapter.java index 484ab0b9e..d27293244 100644 --- a/java/com/android/dialer/app/list/DialtactsPagerAdapter.java +++ b/java/com/android/dialer/app/list/DialtactsPagerAdapter.java @@ -28,7 +28,6 @@ import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.contactsfragment.ContactsFragment; -import com.android.dialer.contactsfragment.ContactsFragment.ClickAction; import com.android.dialer.contactsfragment.ContactsFragment.Header; import com.android.dialer.database.CallLogQueryHandler; import com.android.dialer.speeddial.SpeedDialFragment; @@ -108,8 +107,7 @@ public class DialtactsPagerAdapter extends FragmentPagerAdapter { case TAB_INDEX_ALL_CONTACTS: if (useNewContactsTab) { if (contactsFragment == null) { - contactsFragment = - ContactsFragment.newInstance(Header.ADD_CONTACT, ClickAction.OPEN_CONTACT_CARD); + contactsFragment = ContactsFragment.newInstance(Header.ADD_CONTACT); } return contactsFragment; } else { diff --git a/java/com/android/dialer/contactsfragment/ContactViewHolder.java b/java/com/android/dialer/contactsfragment/ContactViewHolder.java index 0597c2a7e..2730c0d38 100644 --- a/java/com/android/dialer/contactsfragment/ContactViewHolder.java +++ b/java/com/android/dialer/contactsfragment/ContactViewHolder.java @@ -18,7 +18,6 @@ package com.android.dialer.contactsfragment; import android.content.Context; import android.net.Uri; -import android.provider.ContactsContract.QuickContact; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.View; @@ -26,7 +25,7 @@ import android.view.View.OnClickListener; import android.widget.QuickContactBadge; import android.widget.TextView; import com.android.dialer.common.Assert; -import com.android.dialer.contactsfragment.ContactsFragment.ClickAction; +import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener; import com.android.dialer.logging.InteractionEvent; import com.android.dialer.logging.Logger; @@ -37,20 +36,20 @@ final class ContactViewHolder extends RecyclerView.ViewHolder implements OnClick private final TextView name; private final QuickContactBadge photo; private final Context context; - private final @ClickAction int clickAction; + private final OnContactSelectedListener onContactSelectedListener; private String headerText; private Uri contactUri; + private long contactId; - ContactViewHolder(View itemView, @ClickAction int clickAction) { + ContactViewHolder(View itemView, OnContactSelectedListener onContactSelectedListener) { super(itemView); - Assert.checkArgument(clickAction != ClickAction.INVALID, "Invalid click action."); + this.onContactSelectedListener = Assert.isNotNull(onContactSelectedListener); context = itemView.getContext(); itemView.findViewById(R.id.click_target).setOnClickListener(this); header = itemView.findViewById(R.id.header); name = itemView.findViewById(R.id.contact_name); photo = itemView.findViewById(R.id.photo); - this.clickAction = clickAction; } /** @@ -61,9 +60,11 @@ final class ContactViewHolder extends RecyclerView.ViewHolder implements OnClick * @param contactUri to be shown by the contact card on photo click. * @param showHeader if header view should be shown {@code True}, {@code False} otherwise. */ - public void bind(String headerText, String displayName, Uri contactUri, boolean showHeader) { + public void bind( + String headerText, String displayName, Uri contactUri, long contactId, boolean showHeader) { Assert.checkArgument(!TextUtils.isEmpty(displayName)); this.contactUri = contactUri; + this.contactId = contactId; this.headerText = headerText; name.setText(displayName); @@ -89,20 +90,6 @@ final class ContactViewHolder extends RecyclerView.ViewHolder implements OnClick @Override public void onClick(View v) { - switch (clickAction) { - case ClickAction.OPEN_CONTACT_CARD: - Logger.get(context) - .logInteraction(InteractionEvent.Type.OPEN_QUICK_CONTACT_FROM_CONTACTS_FRAGMENT_ITEM); - QuickContact.showQuickContact( - photo.getContext(), - photo, - contactUri, - QuickContact.MODE_LARGE, - null /* excludeMimes */); - break; - case ClickAction.INVALID: - default: - throw Assert.createIllegalStateFailException("Invalid click action."); - } + onContactSelectedListener.onContactSelected(photo, contactUri, contactId); } } diff --git a/java/com/android/dialer/contactsfragment/ContactsAdapter.java b/java/com/android/dialer/contactsfragment/ContactsAdapter.java index 8f2120cd7..44abe29da 100644 --- a/java/com/android/dialer/contactsfragment/ContactsAdapter.java +++ b/java/com/android/dialer/contactsfragment/ContactsAdapter.java @@ -29,8 +29,8 @@ import android.view.ViewGroup; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.contactphoto.ContactPhotoManager; -import com.android.dialer.contactsfragment.ContactsFragment.ClickAction; import com.android.dialer.contactsfragment.ContactsFragment.Header; +import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener; import com.android.dialer.lettertile.LetterTileDrawable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -50,7 +50,7 @@ final class ContactsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder private final ArrayMap<ContactViewHolder, Integer> holderMap = new ArrayMap<>(); private final Context context; private final @Header int header; - private final @ClickAction int clickAction; + private final OnContactSelectedListener onContactSelectedListener; // List of contact sublist headers private String[] headers = new String[0]; @@ -59,10 +59,11 @@ final class ContactsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder // Cursor with list of contacts private Cursor cursor; - ContactsAdapter(Context context, @Header int header, @ClickAction int clickAction) { + ContactsAdapter( + Context context, @Header int header, OnContactSelectedListener onContactSelectedListener) { this.context = context; this.header = header; - this.clickAction = clickAction; + this.onContactSelectedListener = Assert.isNotNull(onContactSelectedListener); } void updateCursor(Cursor cursor) { @@ -92,7 +93,8 @@ final class ContactsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder LayoutInflater.from(context).inflate(R.layout.add_contact_row, parent, false)); case CONTACT_VIEW_TYPE: return new ContactViewHolder( - LayoutInflater.from(context).inflate(R.layout.contact_row, parent, false), clickAction); + LayoutInflater.from(context).inflate(R.layout.contact_row, parent, false), + onContactSelectedListener); case UNKNOWN_VIEW_TYPE: default: throw Assert.createIllegalStateFailException("Invalid view type: " + viewType); @@ -133,7 +135,7 @@ final class ContactsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder // it to the previous element and only show the anchored header if the row elements fall into // the same sublists. boolean showHeader = position == 0 || !header.equals(getHeaderString(position - 1)); - contactViewHolder.bind(header, name, contactUri, showHeader); + contactViewHolder.bind(header, name, contactUri, getContactId(cursor), showHeader); } /** @@ -190,11 +192,15 @@ final class ContactsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder } private static Uri getContactUri(Cursor cursor) { - long contactId = cursor.getLong(ContactsCursorLoader.CONTACT_ID); + long contactId = getContactId(cursor); String lookupKey = cursor.getString(ContactsCursorLoader.CONTACT_LOOKUP_KEY); return Contacts.getLookupUri(contactId, lookupKey); } + private static long getContactId(Cursor cursor) { + return cursor.getLong(ContactsCursorLoader.CONTACT_ID); + } + String getHeaderString(int position) { if (header != Header.NONE) { if (position == 0) { diff --git a/java/com/android/dialer/contactsfragment/ContactsCursorLoader.java b/java/com/android/dialer/contactsfragment/ContactsCursorLoader.java index a22f7eb39..e55f95149 100644 --- a/java/com/android/dialer/contactsfragment/ContactsCursorLoader.java +++ b/java/com/android/dialer/contactsfragment/ContactsCursorLoader.java @@ -18,7 +18,10 @@ package com.android.dialer.contactsfragment; import android.content.Context; import android.content.CursorLoader; +import android.net.Uri; import android.provider.ContactsContract.Contacts; +import android.text.TextUtils; +import com.android.contacts.common.preference.ContactsPreferences; /** Cursor Loader for {@link ContactsFragment}. */ final class ContactsCursorLoader extends CursorLoader { @@ -47,26 +50,52 @@ final class ContactsCursorLoader extends CursorLoader { Contacts.LOOKUP_KEY, // 4 }; - private ContactsCursorLoader(Context context, String[] contactProjection, String sortKey) { + ContactsCursorLoader(Context context, boolean hasPhoneNumbers) { super( context, - Contacts.CONTENT_URI - .buildUpon() - .appendQueryParameter(Contacts.EXTRA_ADDRESS_BOOK_INDEX, "true") - .build(), - contactProjection, - contactProjection[CONTACT_DISPLAY_NAME] + " IS NOT NULL", + buildUri(""), + getProjection(context), + getWhere(context, hasPhoneNumbers), null, - sortKey + " ASC"); + getSortKey(context) + " ASC"); } - public static ContactsCursorLoader createInstanceDisplayNamePrimary( - Context context, String sortKey) { - return new ContactsCursorLoader(context, CONTACTS_PROJECTION_DISPLAY_NAME_PRIMARY, sortKey); + private static String[] getProjection(Context context) { + ContactsPreferences contactsPrefs = new ContactsPreferences(context); + boolean displayOrderPrimary = + (contactsPrefs.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY); + return displayOrderPrimary + ? CONTACTS_PROJECTION_DISPLAY_NAME_PRIMARY + : CONTACTS_PROJECTION_DISPLAY_NAME_ALTERNATIVE; } - public static ContactsCursorLoader createInstanceDisplayNameAlternative( - Context context, String sortKey) { - return new ContactsCursorLoader(context, CONTACTS_PROJECTION_DISPLAY_NAME_ALTERNATIVE, sortKey); + private static String getWhere(Context context, boolean hasPhoneNumbers) { + String where = getProjection(context)[CONTACT_DISPLAY_NAME] + " IS NOT NULL"; + if (hasPhoneNumbers) { + where += " AND " + Contacts.HAS_PHONE_NUMBER + "=1"; + } + return where; + } + + private static String getSortKey(Context context) { + ContactsPreferences contactsPrefs = new ContactsPreferences(context); + boolean sortOrderPrimary = + (contactsPrefs.getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY); + return sortOrderPrimary ? Contacts.SORT_KEY_PRIMARY : Contacts.SORT_KEY_ALTERNATIVE; + } + + /** Update cursor loader to filter contacts based on the provided query. */ + public void setQuery(String query) { + setUri(buildUri(query)); + } + + private static Uri buildUri(String query) { + Uri.Builder baseUri; + if (TextUtils.isEmpty(query)) { + baseUri = Contacts.CONTENT_URI.buildUpon(); + } else { + baseUri = Contacts.CONTENT_FILTER_URI.buildUpon().appendPath(query); + } + return baseUri.appendQueryParameter(Contacts.EXTRA_ADDRESS_BOOK_INDEX, "true").build(); } } diff --git a/java/com/android/dialer/contactsfragment/ContactsFragment.java b/java/com/android/dialer/contactsfragment/ContactsFragment.java index 82b68b8ee..714739300 100644 --- a/java/com/android/dialer/contactsfragment/ContactsFragment.java +++ b/java/com/android/dialer/contactsfragment/ContactsFragment.java @@ -26,8 +26,8 @@ import android.content.Intent; import android.content.Loader; import android.content.pm.PackageManager; import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; -import android.provider.ContactsContract.Contacts; import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.support.v13.app.FragmentCompat; @@ -39,12 +39,14 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnScrollChangeListener; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.TextView; import com.android.contacts.common.preference.ContactsPreferences; import com.android.contacts.common.preference.ContactsPreferences.ChangeListener; import com.android.dialer.common.Assert; import com.android.dialer.common.FragmentUtils; import com.android.dialer.common.LogUtil; +import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener; import com.android.dialer.performancereport.PerformanceReport; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.IntentUtil; @@ -62,15 +64,6 @@ public class ContactsFragment extends Fragment OnEmptyViewActionButtonClickedListener, ChangeListener { - /** IntDef to define the OnClick action for contact rows. */ - @Retention(RetentionPolicy.SOURCE) - @IntDef({ClickAction.INVALID, ClickAction.OPEN_CONTACT_CARD}) - public @interface ClickAction { - int INVALID = 0; - /** Open contact card on click. */ - int OPEN_CONTACT_CARD = 1; - } - /** An enum for the different types of headers that be inserted at position 0 in the list. */ @Retention(RetentionPolicy.SOURCE) @IntDef({Header.NONE, Header.ADD_CONTACT}) @@ -83,7 +76,7 @@ public class ContactsFragment extends Fragment public static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1; private static final String EXTRA_HEADER = "extra_header"; - private static final String EXTRA_CLICK_ACTION = "extra_click_action"; + private static final String EXTRA_HAS_PHONE_NUMBERS = "extra_has_phone_numbers"; /** * Listen to broadcast events about permissions in order to be notified if the READ_CONTACTS @@ -104,14 +97,11 @@ public class ContactsFragment extends Fragment private ContactsAdapter adapter; private EmptyContentView emptyContentView; - private ContactsPreferences contactsPrefs; private @Header int header; - private @ClickAction int clickAction; - /** Listener for contacts list scroll state. */ - public interface OnContactsListScrolledListener { - void onContactsListScrolled(boolean isDragging); - } + private ContactsPreferences contactsPrefs; + private boolean hasPhoneNumbers; + private String query; /** * Used to get a configured instance of ContactsFragment. @@ -121,26 +111,42 @@ public class ContactsFragment extends Fragment * * <ul> * <li>{@link Header#ADD_CONTACT} to insert a header that allows users to add a contact - * <li>{@link ClickAction#OPEN_CONTACT_CARD} to open contact cards on click + * <li>Open contact cards on click * </ul> * * And for the add favorite contact screen we might use: * * <ul> * <li>{@link Header#NONE} so that all rows are contacts (i.e. no header inserted) - * <li>{@link ClickAction#SET_RESULT_AND_FINISH} to send a selected contact to the previous - * activity. + * <li>Send a selected contact to the parent activity. * </ul> * * @param header determines the type of header inserted at position 0 in the contacts list - * @param clickAction defines the on click actions on rows that represent contacts */ - public static ContactsFragment newInstance(@Header int header, @ClickAction int clickAction) { - Assert.checkArgument(clickAction != ClickAction.INVALID, "Invalid click action"); + public static ContactsFragment newInstance(@Header int header) { ContactsFragment fragment = new ContactsFragment(); Bundle args = new Bundle(); args.putInt(EXTRA_HEADER, header); - args.putInt(EXTRA_CLICK_ACTION, clickAction); + fragment.setArguments(args); + return fragment; + } + + /** + * Returns {@link ContactsFragment} with a list of contacts such that: + * + * <ul> + * <li>Each contact has a phone number + * <li>Contacts are filterable via {@link #updateQuery(String)} + * <li>There is no list header (i.e. {@link Header#NONE} + * <li>Clicking on a contact notifies the parent activity via {@link + * OnContactSelectedListener#onContactSelected(ImageView, Uri, long)}. + * </ul> + */ + public static ContactsFragment newAddFavoritesInstance() { + ContactsFragment fragment = new ContactsFragment(); + Bundle args = new Bundle(); + args.putInt(EXTRA_HEADER, Header.NONE); + args.putBoolean(EXTRA_HAS_PHONE_NUMBERS, true); fragment.setArguments(args); return fragment; } @@ -152,7 +158,21 @@ public class ContactsFragment extends Fragment contactsPrefs = new ContactsPreferences(getContext()); contactsPrefs.registerChangeListener(this); header = getArguments().getInt(EXTRA_HEADER); - clickAction = getArguments().getInt(EXTRA_CLICK_ACTION); + hasPhoneNumbers = getArguments().getBoolean(EXTRA_HAS_PHONE_NUMBERS); + } + + @Override + public void onStart() { + super.onStart(); + PermissionsUtil.registerPermissionReceiver( + getActivity(), readContactsPermissionGrantedReceiver, READ_CONTACTS); + } + + @Override + public void onStop() { + PermissionsUtil.unregisterPermissionReceiver( + getActivity(), readContactsPermissionGrantedReceiver); + super.onStop(); } @Nullable @@ -163,7 +183,9 @@ public class ContactsFragment extends Fragment fastScroller = view.findViewById(R.id.fast_scroller); anchoredHeader = view.findViewById(R.id.header); recyclerView = view.findViewById(R.id.recycler_view); - adapter = new ContactsAdapter(getContext(), header, clickAction); + adapter = + new ContactsAdapter( + getContext(), header, FragmentUtils.getParent(this, OnContactSelectedListener.class)); recyclerView.setAdapter(adapter); manager = new LinearLayoutManager(getContext()) { @@ -208,15 +230,14 @@ public class ContactsFragment extends Fragment /** @return a loader according to sort order and display order. */ @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { - boolean sortOrderPrimary = - (contactsPrefs.getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY); - boolean displayOrderPrimary = - (contactsPrefs.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY); - - String sortKey = sortOrderPrimary ? Contacts.SORT_KEY_PRIMARY : Contacts.SORT_KEY_ALTERNATIVE; - return displayOrderPrimary - ? ContactsCursorLoader.createInstanceDisplayNamePrimary(getContext(), sortKey) - : ContactsCursorLoader.createInstanceDisplayNameAlternative(getContext(), sortKey); + ContactsCursorLoader cursorLoader = new ContactsCursorLoader(getContext(), hasPhoneNumbers); + cursorLoader.setQuery(query); + return cursorLoader; + } + + public void updateQuery(String query) { + this.query = query; + getLoaderManager().restartLoader(0, null, this); } @Override @@ -266,10 +287,13 @@ public class ContactsFragment extends Fragment } String anchoredHeaderString = adapter.getHeaderString(firstCompletelyVisible); - FragmentUtils.getParentUnsafe(this, OnContactsListScrolledListener.class) - .onContactsListScrolled( - recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING - || fastScroller.isDragStarted()); + OnContactsListScrolledListener listener = + FragmentUtils.getParent(this, OnContactsListScrolledListener.class); + if (listener != null) { + listener.onContactsListScrolled( + recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING + || fastScroller.isDragStarted()); + } // If the user swipes to the top of the list very quickly, there is some strange behavior // between this method updating headers and adapter#onBindViewHolder updating headers. @@ -331,17 +355,15 @@ public class ContactsFragment extends Fragment } } - @Override - public void onStart() { - super.onStart(); - PermissionsUtil.registerPermissionReceiver( - getActivity(), readContactsPermissionGrantedReceiver, READ_CONTACTS); + /** Listener for contacts list scroll state. */ + public interface OnContactsListScrolledListener { + void onContactsListScrolled(boolean isDragging); } - @Override - public void onStop() { - PermissionsUtil.unregisterPermissionReceiver( - getActivity(), readContactsPermissionGrantedReceiver); - super.onStop(); + /** Listener to notify parents when a contact is selected. */ + public interface OnContactSelectedListener { + + /** Called when a contact is selected in {@link ContactsFragment}. */ + void onContactSelected(ImageView photo, Uri contactUri, long contactId); } } diff --git a/java/com/android/dialer/databasepopulator/ContactsPopulator.java b/java/com/android/dialer/databasepopulator/ContactsPopulator.java index 79492e9d3..f22552db7 100644 --- a/java/com/android/dialer/databasepopulator/ContactsPopulator.java +++ b/java/com/android/dialer/databasepopulator/ContactsPopulator.java @@ -148,6 +148,19 @@ public final class ContactsPopulator { } @WorkerThread + public static void populateSpeedDialTestContacts(@NonNull Context context) { + Assert.isWorkerThread(); + ArrayList<ContentProviderOperation> operations = new ArrayList<>(); + addContact(SIMPLE_CONTACTS[0], operations); + addContact(SIMPLE_CONTACTS[5], operations); + try { + context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations); + } catch (RemoteException | OperationApplicationException e) { + Assert.fail("error adding contacts: " + e); + } + } + + @WorkerThread public static void populateContacts(@NonNull Context context) { populateContacts(context, false); } diff --git a/java/com/android/dialer/speeddial/AddFavoriteActivity.java b/java/com/android/dialer/speeddial/AddFavoriteActivity.java new file mode 100644 index 000000000..eea6e840e --- /dev/null +++ b/java/com/android/dialer/speeddial/AddFavoriteActivity.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017 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.speeddial; + +import android.content.ContentValues; +import android.net.Uri; +import android.os.Bundle; +import android.provider.ContactsContract.Contacts; +import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.SearchView; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.ImageView; +import com.android.dialer.common.Assert; +import com.android.dialer.common.concurrent.DialerExecutorComponent; +import com.android.dialer.contactsfragment.ContactsFragment; +import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener; + +/** + * Activity for selecting a single contact and adding it to favorites. + * + * <p>Contacts are displayed using {@link ContactsFragment}. Contacts are searchable via search bar + * in the toolbar. When a contact is selected, it's uri is passed back in the result data. + */ +public class AddFavoriteActivity extends AppCompatActivity implements OnContactSelectedListener { + + private ContactsFragment contactsFragment; + + @Override + protected void onCreate(@Nullable Bundle bundle) { + super.onCreate(bundle); + setContentView(R.layout.add_favorite_activity); + contactsFragment = ContactsFragment.newAddFavoritesInstance(); + getFragmentManager() + .beginTransaction() + .add(R.id.add_favorite_container, contactsFragment, null) + .commit(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.add_favorite_menu, menu); + + MenuItem searchItem = menu.findItem(R.id.action_search); + SearchView searchView = (SearchView) searchItem.getActionView(); + searchView.setOnQueryTextListener( + new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + if (!searchView.isIconified()) { + searchView.setIconified(true); + } + searchItem.collapseActionView(); + return false; + } + + @Override + public boolean onQueryTextChange(String s) { + contactsFragment.updateQuery(s); + return false; + } + }); + return true; + } + + @Override + public void onContactSelected(ImageView photo, Uri contactUri, long contactId) { + DialerExecutorComponent.get(this) + .dialerExecutorFactory() + .createUiTaskBuilder( + getFragmentManager(), "mark_contact_favorite", this::markContactStarred) + .onSuccess(output -> finish()) + .onFailure(this::onContactStarredFailed) + .build() + .executeParallel(contactId); + } + + @WorkerThread + private int markContactStarred(long contactId) { + // TODO(calderwoodra): For now, we will just mark contacts as starred. This means that contacts + // will only be able to exist once in favorites until we implement multiple contact avenues. + ContentValues contentValues = new ContentValues(); + contentValues.put(Contacts.STARRED, 1); + + String where = Contacts._ID + " = ?"; + String[] selectionArgs = new String[] {Long.toString(contactId)}; + return getContentResolver().update(Contacts.CONTENT_URI, contentValues, where, selectionArgs); + } + + private void onContactStarredFailed(Throwable throwable) { + throw Assert.createAssertionFailException(throwable.getMessage()); + } +} diff --git a/java/com/android/dialer/speeddial/AndroidManifest.xml b/java/com/android/dialer/speeddial/AndroidManifest.xml index f4f0d82eb..99ab12c4b 100644 --- a/java/com/android/dialer/speeddial/AndroidManifest.xml +++ b/java/com/android/dialer/speeddial/AndroidManifest.xml @@ -13,4 +13,15 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<manifest package="com.android.dialer.speeddial"/> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.dialer.speeddial"> + + <application android:theme="@style/Theme.AppCompat"> + <activity + android:name="com.android.dialer.speeddial.AddFavoriteActivity" + android:label="@string/add_favorite_activity_title" + android:exported="false" + android:theme="@style/DialtactsTheme"/> + </application> +</manifest>
\ No newline at end of file diff --git a/java/com/android/dialer/speeddial/SpeedDialCursor.java b/java/com/android/dialer/speeddial/SpeedDialCursor.java index 552fab175..1208daefd 100644 --- a/java/com/android/dialer/speeddial/SpeedDialCursor.java +++ b/java/com/android/dialer/speeddial/SpeedDialCursor.java @@ -64,10 +64,6 @@ final class SpeedDialCursor extends MergeCursor { strequentCursor.moveToPosition(-1); while (strequentCursor.moveToNext()) { - if (strequentCursor.getInt(StrequentContactsCursorLoader.PHONE_IS_SUPER_PRIMARY) != 0) { - continue; - } - if (strequentCursor.getPosition() != 0) { long contactId = strequentCursor.getLong(StrequentContactsCursorLoader.PHONE_CONTACT_ID); int position = strequentCursor.getPosition(); diff --git a/java/com/android/dialer/speeddial/SpeedDialFragment.java b/java/com/android/dialer/speeddial/SpeedDialFragment.java index 65e542cd4..08861dae9 100644 --- a/java/com/android/dialer/speeddial/SpeedDialFragment.java +++ b/java/com/android/dialer/speeddial/SpeedDialFragment.java @@ -18,10 +18,10 @@ package com.android.dialer.speeddial; import android.app.Fragment; import android.app.LoaderManager.LoaderCallbacks; +import android.content.Intent; import android.content.Loader; import android.database.Cursor; import android.os.Bundle; -import android.provider.ContactsContract.Contacts; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; @@ -35,7 +35,16 @@ import com.android.dialer.speeddial.FavoritesViewHolder.FavoriteContactsListener import com.android.dialer.speeddial.HeaderViewHolder.SpeedDialHeaderListener; import com.android.dialer.speeddial.SuggestionViewHolder.SuggestedContactsListener; -/** Favorites fragment. Contents TBD. TODO(calderwoodra) */ +/** + * Fragment for displaying: + * + * <ul> + * <li>Favorite/Starred contacts + * <li>Suggested contacts + * </ul> + * + * <p>Suggested contacts built from {@link android.provider.ContactsContract#STREQUENT_PHONE_ONLY}. + */ public class SpeedDialFragment extends Fragment { private static final int STREQUENT_CONTACTS_LOADER_ID = 1; @@ -73,16 +82,16 @@ public class SpeedDialFragment extends Fragment { } @Override - public void onPause() { - super.onPause(); - loaderCallback.unregisterContentObserver(); + public void onResume() { + super.onResume(); + getLoaderManager().restartLoader(STREQUENT_CONTACTS_LOADER_ID, null, loaderCallback); } - private static class SpeedDialFragmentHeaderListener implements SpeedDialHeaderListener { + private class SpeedDialFragmentHeaderListener implements SpeedDialHeaderListener { @Override public void onAddFavoriteClicked() { - // TODO(calderwoodra): implement add favorite screen + startActivity(new Intent(getContext(), AddFavoriteActivity.class)); } } @@ -123,8 +132,6 @@ public class SpeedDialFragment extends Fragment { */ private class SpeedDialFragmentLoaderCallback implements LoaderCallbacks<Cursor> { - private StrequentContactsCursorLoader cursorLoader; - @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { if (id == STREQUENT_CONTACTS_LOADER_ID) { @@ -135,24 +142,9 @@ public class SpeedDialFragment extends Fragment { @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - cursorLoader = (StrequentContactsCursorLoader) loader; - // Since the original cursor we queried against was modified and closed, we need to register a - // new content observer in order to get updates on changes to our contacts. - getContext() - .getContentResolver() - .registerContentObserver( - Contacts.CONTENT_STREQUENT_URI, - true /* notifyForDescendants*/, - cursorLoader.getContentObserver()); adapter.setCursor((SpeedDialCursor) data); } - public void unregisterContentObserver() { - getContext() - .getContentResolver() - .unregisterContentObserver(cursorLoader.getContentObserver()); - } - @Override public void onLoaderReset(Loader<Cursor> loader) { adapter.setCursor(null); diff --git a/java/com/android/dialer/speeddial/res/layout/add_favorite_activity.xml b/java/com/android/dialer/speeddial/res/layout/add_favorite_activity.xml new file mode 100644 index 000000000..4df2f29b4 --- /dev/null +++ b/java/com/android/dialer/speeddial/res/layout/add_favorite_activity.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 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 + --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/add_favorite_container" + android:layout_width="match_parent" + android:layout_height="match_parent"/>
\ No newline at end of file diff --git a/java/com/android/dialer/speeddial/res/menu/add_favorite_menu.xml b/java/com/android/dialer/speeddial/res/menu/add_favorite_menu.xml new file mode 100644 index 000000000..b11c7f5d6 --- /dev/null +++ b/java/com/android/dialer/speeddial/res/menu/add_favorite_menu.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 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 + --> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item + android:id="@+id/action_search" + android:title="@android:string/search_go" + app:showAsAction="always" + app:actionViewClass="android.support.v7.widget.SearchView"/> +</menu>
\ No newline at end of file diff --git a/java/com/android/dialer/speeddial/res/values/strings.xml b/java/com/android/dialer/speeddial/res/values/strings.xml index f814ed6d4..d64d03575 100644 --- a/java/com/android/dialer/speeddial/res/values/strings.xml +++ b/java/com/android/dialer/speeddial/res/values/strings.xml @@ -15,12 +15,15 @@ ~ limitations under the License --> <resources> - <!-- header for a list of contacts that are the users favorites. --> + <!-- header for a list of contacts that are the users favorites. [CHAR LIMIT=30] --> <string name="favorites_header">Favorites</string> - <!-- header for a list of contacts that are suggestions for the user to place calls to --> + <!-- header for a list of contacts that are suggestions for the user to place calls to. [CHAR LIMIT=30] --> <string name="suggestions_header">Suggestions</string> - <!-- text for a button that prompts the user to add a contact to their favorites --> + <!-- text for a button that prompts the user to add a contact to their favorites. [CHAR LIMIT=12] --> <string name="speed_dial_add_button_text">Add</string> + + <!-- Title for screen prompting the user to select a contact to mark as a favorite. [CHAR LIMIT=NONE] --> + <string name="add_favorite_activity_title">Add Favorite</string> </resources>
\ No newline at end of file |