Show favorite from the local database.
Also implement the favorite actions in contact details page.
Test: manually
Bug: 132811088
Change-Id: I573b4c4cff65df8fcd85b423a4cf664d00f573e0
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 9526d33..eadeb53 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -20,20 +20,18 @@
and they are used as key mapping to fragments. The array can only be a subset of predefined
strings in any order. Tabs will be added in the same order as defined in the array.-->
<string-array name="tabs_config">
- <!--Add favorite tab back when we have favorite functionality-->
- <!--<item>FAVORITE</item>-->
<item>CALL_HISTORY</item>
<item>CONTACTS</item>
+ <item>FAVORITE</item>
<item>DIAL_PAD</item>
</string-array>
<string-array name="tabs_title">
<!-- This array is mapped to tabs_config. -->
<!-- And it should be consistent with the mapping in the TelecomPageTab.Factory -->
- <!--Add favorite tab back when we have favorite functionality-->
- <!--<item>@string/favorites_title</item>-->
<item>@string/call_history_title</item>
<item>@string/contacts_title</item>
+ <item>@string/favorites_title</item>
<item>@string/dialpad_title</item>
</string-array>
diff --git a/src/com/android/car/dialer/livedata/BluetoothPairListLiveData.java b/src/com/android/car/dialer/livedata/BluetoothPairListLiveData.java
index bc6d3a2..94b7c3a 100644
--- a/src/com/android/car/dialer/livedata/BluetoothPairListLiveData.java
+++ b/src/com/android/car/dialer/livedata/BluetoothPairListLiveData.java
@@ -28,7 +28,6 @@
import com.android.car.dialer.log.L;
-import java.util.Collections;
import java.util.Set;
/**
diff --git a/src/com/android/car/dialer/ui/contact/ContactDetailsAdapter.java b/src/com/android/car/dialer/ui/contact/ContactDetailsAdapter.java
index e1b0f6e..65bf7b4 100644
--- a/src/com/android/car/dialer/ui/contact/ContactDetailsAdapter.java
+++ b/src/com/android/car/dialer/ui/contact/ContactDetailsAdapter.java
@@ -17,7 +17,6 @@
package com.android.car.dialer.ui.contact;
import android.content.Context;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -32,31 +31,39 @@
import com.android.car.telephony.common.Contact;
import com.android.car.telephony.common.PhoneNumber;
-import com.google.common.annotations.VisibleForTesting;
-
import java.util.ArrayList;
class ContactDetailsAdapter extends RecyclerView.Adapter<ContactDetailsViewHolder> {
private static final String TAG = "CD.ContactDetailsAdapter";
- @VisibleForTesting
- static final String TELEPHONE_URI_PREFIX = "tel:";
private static final int ID_HEADER = 1;
private static final int ID_CONTENT = 2;
+ interface PhoneNumberPresenter {
+ void onClick(Contact contact, PhoneNumber phoneNumber);
+
+ boolean isFavorite(Contact contact, PhoneNumber phoneNumber);
+ }
+
private final Context mContext;
+ private final PhoneNumberPresenter mPhoneNumberPresenter;
+ private final ArrayList<Object> mItems = new ArrayList<>();
+ private Contact mContact;
- private final ArrayList<Object> mItems = new ArrayList<Object>();
-
- public ContactDetailsAdapter(@NonNull Context context, @Nullable Contact contact) {
+ ContactDetailsAdapter(
+ @NonNull Context context,
+ @Nullable Contact contact,
+ @NonNull PhoneNumberPresenter phoneNumberPresenter) {
super();
mContext = context;
+ mPhoneNumberPresenter = phoneNumberPresenter;
setContact(contact);
}
void setContact(Contact contact) {
L.d(TAG, "setContact %s", contact);
+ mContact = contact;
mItems.clear();
if (shouldShowHeader()) {
mItems.add(contact);
@@ -103,7 +110,7 @@
View view = LayoutInflater.from(parent.getContext()).inflate(layoutResId, parent,
false);
- return new ContactDetailsViewHolder(view);
+ return new ContactDetailsViewHolder(view, mPhoneNumberPresenter);
}
@Override
@@ -113,10 +120,10 @@
viewHolder.bind(mContext, (Contact) mItems.get(position));
break;
case ID_CONTENT:
- viewHolder.bind(mContext, (PhoneNumber) mItems.get(position));
+ viewHolder.bind(mContext, mContact, (PhoneNumber) mItems.get(position));
break;
default:
- Log.e(TAG, "Unknown view type " + viewHolder.getItemViewType());
+ L.e(TAG, "Unknown view type %d ", viewHolder.getItemViewType());
return;
}
}
diff --git a/src/com/android/car/dialer/ui/contact/ContactDetailsFragment.java b/src/com/android/car/dialer/ui/contact/ContactDetailsFragment.java
index 2d6f25a..5b10248 100644
--- a/src/com/android/car/dialer/ui/contact/ContactDetailsFragment.java
+++ b/src/com/android/car/dialer/ui/contact/ContactDetailsFragment.java
@@ -33,16 +33,21 @@
import com.android.car.dialer.R;
import com.android.car.dialer.ui.common.DialerListBaseFragment;
import com.android.car.dialer.ui.common.DialerUtils;
+import com.android.car.dialer.ui.favorite.FavoriteViewModel;
import com.android.car.dialer.ui.view.ContactAvatarOutputlineProvider;
import com.android.car.telephony.common.Contact;
+import com.android.car.telephony.common.PhoneNumber;
import com.android.car.telephony.common.TelecomUtils;
+import java.util.List;
+
/**
* A fragment that shows the name of the contact, the photo and all listed phone numbers. It is
* primarily used to respond to the results of search queries but supplyig it with the content://
* uri of a contact should work too.
*/
-public class ContactDetailsFragment extends DialerListBaseFragment {
+public class ContactDetailsFragment extends DialerListBaseFragment implements
+ ContactDetailsAdapter.PhoneNumberPresenter {
private static final String TAG = "CD.ContactDetailsFragment";
public static final String FRAGMENT_TAG = "CONTACT_DETAIL_FRAGMENT_TAG";
@@ -57,6 +62,7 @@
private LiveData<Contact> mContactDetailsLiveData;
private ImageView mAvatarView;
private TextView mNameView;
+ private FavoriteViewModel mFavoriteViewModel;
/** Creates a new ContactDetailsFragment using a URI to lookup a {@link Contact} at. */
public static ContactDetailsFragment newInstance(Uri uri) {
@@ -92,7 +98,7 @@
ContactDetailsViewModel contactDetailsViewModel = ViewModelProviders.of(this).get(
ContactDetailsViewModel.class);
mContactDetailsLiveData = contactDetailsViewModel.getContactDetails(mContactLookupUri);
- mContactDetailsLiveData.observe(this, this::onContactChanged);
+ mFavoriteViewModel = ViewModelProviders.of(getActivity()).get(FavoriteViewModel.class);
}
@Override
@@ -114,9 +120,15 @@
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
ContactDetailsAdapter contactDetailsAdapter = new ContactDetailsAdapter(getContext(),
- mContact);
+ mContact, this);
getRecyclerView().setAdapter(contactDetailsAdapter);
- mContactDetailsLiveData.observe(this, contactDetailsAdapter::setContact);
+ mContactDetailsLiveData.observe(this, contact -> {
+ mContact = contact;
+ onContactChanged(contact);
+ contactDetailsAdapter.setContact(contact);
+ });
+ mFavoriteViewModel.getFavoriteContacts().observe(this, favoriteList ->
+ contactDetailsAdapter.setContact(mContact));
}
private void onContactChanged(Contact contact) {
@@ -170,4 +182,26 @@
super.onSaveInstanceState(outState);
outState.putParcelable(KEY_CONTACT_ENTITY, mContactDetailsLiveData.getValue());
}
+
+ @Override
+ public void onClick(Contact contact, PhoneNumber phoneNumber) {
+ boolean isFavorite = isFavorite(contact, phoneNumber);
+ mFavoriteViewModel.setAsFavorite(contact, phoneNumber, !isFavorite);
+ }
+
+ @Override
+ public boolean isFavorite(Contact contact, PhoneNumber phoneNumber) {
+ List<Contact> favoriteContacts = mFavoriteViewModel.getFavoriteContacts().getValue();
+ if (favoriteContacts == null) {
+ return false;
+ }
+ for (Contact favoriteContact : favoriteContacts) {
+ if (favoriteContact.equals(contact)
+ && !favoriteContact.getNumbers().isEmpty()
+ && favoriteContact.getNumbers().get(0).equals(phoneNumber)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/src/com/android/car/dialer/ui/contact/ContactDetailsViewHolder.java b/src/com/android/car/dialer/ui/contact/ContactDetailsViewHolder.java
index 6f1eb35..d4816fa 100644
--- a/src/com/android/car/dialer/ui/contact/ContactDetailsViewHolder.java
+++ b/src/com/android/car/dialer/ui/contact/ContactDetailsViewHolder.java
@@ -20,7 +20,6 @@
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
-import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -51,7 +50,12 @@
@Nullable
private final View mFavoriteActionView;
- ContactDetailsViewHolder(View v) {
+ @NonNull
+ private final ContactDetailsAdapter.PhoneNumberPresenter mPhoneNumberPresenter;
+
+ ContactDetailsViewHolder(
+ View v,
+ @NonNull ContactDetailsAdapter.PhoneNumberPresenter phoneNumberPresenter) {
super(v);
mCallActionView = v.findViewById(R.id.call_action_id);
mFavoriteActionView = v.findViewById(R.id.contact_details_favorite_button);
@@ -61,6 +65,8 @@
if (mAvatar != null) {
mAvatar.setOutlineProvider(ContactAvatarOutputlineProvider.get());
}
+
+ mPhoneNumberPresenter = phoneNumberPresenter;
}
public void bind(Context context, Contact contact) {
@@ -74,7 +80,7 @@
mTitle.setText(contact.getDisplayName());
}
- public void bind(Context context, PhoneNumber phoneNumber) {
+ public void bind(Context context, Contact contact, PhoneNumber phoneNumber) {
mTitle.setText(phoneNumber.getRawNumber());
@@ -87,8 +93,9 @@
}
mCallActionView.setOnClickListener(v -> placeCall(phoneNumber));
+ mFavoriteActionView.setActivated(mPhoneNumberPresenter.isFavorite(contact, phoneNumber));
mFavoriteActionView.setOnClickListener(v -> {
- Toast.makeText(context, "Not yet implemented", Toast.LENGTH_LONG).show();
+ mPhoneNumberPresenter.onClick(contact, phoneNumber);
mFavoriteActionView.setActivated(!mFavoriteActionView.isActivated());
});
}
diff --git a/src/com/android/car/dialer/ui/favorite/FavoriteViewModel.java b/src/com/android/car/dialer/ui/favorite/FavoriteViewModel.java
index f58f56f..dc6055b 100644
--- a/src/com/android/car/dialer/ui/favorite/FavoriteViewModel.java
+++ b/src/com/android/car/dialer/ui/favorite/FavoriteViewModel.java
@@ -17,10 +17,18 @@
package com.android.car.dialer.ui.favorite;
import android.app.Application;
+import android.content.Context;
+
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
-import com.android.car.dialer.livedata.FavoriteContactLiveData;
+import androidx.lifecycle.MediatorLiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.android.car.dialer.storage.FavoriteNumberEntity;
+import com.android.car.dialer.storage.FavoriteNumberRepository;
import com.android.car.telephony.common.Contact;
+import com.android.car.telephony.common.InMemoryPhoneBook;
+import com.android.car.telephony.common.PhoneNumber;
import java.util.List;
@@ -28,15 +36,45 @@
* View model for {@link FavoriteFragment}.
*/
public class FavoriteViewModel extends AndroidViewModel {
- private LiveData<List<Contact>> mFavoriteContactsLiveData;
+ private FavoriteNumberRepository mFavoriteNumberRepository;
+ private LiveData<List<FavoriteNumberEntity>> mFavoriteNumbers;
+ private MutableLiveData<List<Contact>> mFavoriteContactsLiveData;
public FavoriteViewModel(Application application) {
super(application);
- mFavoriteContactsLiveData = FavoriteContactLiveData.newInstance(application);
+ mFavoriteNumberRepository = new FavoriteNumberRepository(application);
+ mFavoriteNumbers = mFavoriteNumberRepository.getFavoriteNumbers();
+ mFavoriteContactsLiveData = new FavoriteContactLiveData(application);
}
/** Returns favorite contact list live data. */
public LiveData<List<Contact>> getFavoriteContacts() {
return mFavoriteContactsLiveData;
}
+
+ /**
+ * Add to favorite or remove from favorite.
+ *
+ * @param contact The contact the phone number belongs to.
+ * @param phoneNumber The phone number to favorite or unfavorite.
+ * @param isFavorite When true, add the phone number to favorite. Otherwise remove from
+ * favorite.
+ */
+ public void setAsFavorite(Contact contact, PhoneNumber phoneNumber, boolean isFavorite) {
+ if (isFavorite) {
+ mFavoriteNumberRepository.addToFavorite(contact, phoneNumber);
+ } else {
+ mFavoriteNumberRepository.removeFromFavorite(contact, phoneNumber);
+ }
+ }
+
+ private class FavoriteContactLiveData extends MediatorLiveData<List<Contact>> {
+ private FavoriteContactLiveData(Context context) {
+ super();
+ addSource(InMemoryPhoneBook.get().getContactsLiveData(),
+ contacts -> mFavoriteNumberRepository.convertToContacts(context, this));
+ addSource(mFavoriteNumbers,
+ favorites -> mFavoriteNumberRepository.convertToContacts(context, this));
+ }
+ }
}
diff --git a/tests/robotests/src/com/android/car/dialer/ui/contact/ContactDetailsFragmentTest.java b/tests/robotests/src/com/android/car/dialer/ui/contact/ContactDetailsFragmentTest.java
index c41f2a6..0615391 100644
--- a/tests/robotests/src/com/android/car/dialer/ui/contact/ContactDetailsFragmentTest.java
+++ b/tests/robotests/src/com/android/car/dialer/ui/contact/ContactDetailsFragmentTest.java
@@ -34,6 +34,7 @@
import com.android.car.dialer.R;
import com.android.car.dialer.telecom.UiCallManager;
import com.android.car.dialer.testutils.ShadowAndroidViewModelFactory;
+import com.android.car.dialer.ui.favorite.FavoriteViewModel;
import com.android.car.telephony.common.Contact;
import com.android.car.telephony.common.PhoneNumber;
@@ -60,6 +61,8 @@
@Mock
private ContactDetailsViewModel mMockContactDetailsViewModel;
@Mock
+ private FavoriteViewModel mMockFavoriteViewModel;
+ @Mock
private Uri mMockContactLookupUri;
@Mock
private Contact mMockContact;
@@ -88,6 +91,9 @@
mMockContactDetailsViewModel);
when(mMockContactDetailsViewModel.getContactDetails(mMockContactLookupUri)).thenReturn(
contactDetails);
+
+ ShadowAndroidViewModelFactory.add(FavoriteViewModel.class, mMockFavoriteViewModel);
+ when(mMockFavoriteViewModel.getFavoriteContacts()).thenReturn(new MutableLiveData<>());
}
@Test
diff --git a/tests/robotests/src/com/android/car/dialer/ui/contact/ContactListFragmentTest.java b/tests/robotests/src/com/android/car/dialer/ui/contact/ContactListFragmentTest.java
index a622773..d73100f 100644
--- a/tests/robotests/src/com/android/car/dialer/ui/contact/ContactListFragmentTest.java
+++ b/tests/robotests/src/com/android/car/dialer/ui/contact/ContactListFragmentTest.java
@@ -36,6 +36,7 @@
import com.android.car.dialer.R;
import com.android.car.dialer.telecom.UiCallManager;
import com.android.car.dialer.testutils.ShadowAndroidViewModelFactory;
+import com.android.car.dialer.ui.favorite.FavoriteViewModel;
import com.android.car.telephony.common.Contact;
import com.android.car.telephony.common.PhoneNumber;
@@ -67,6 +68,8 @@
@Mock
private ContactDetailsViewModel mMockContactDetailsViewModel;
@Mock
+ private FavoriteViewModel mMockFavoriteViewModel;
+ @Mock
private Contact mMockContact1;
@Mock
private Contact mMockContact2;
@@ -89,6 +92,9 @@
ShadowAndroidViewModelFactory.add(ContactDetailsViewModel.class,
mMockContactDetailsViewModel);
when(mMockContactDetailsViewModel.getContactDetails(any())).thenReturn(contactDetail);
+
+ ShadowAndroidViewModelFactory.add(FavoriteViewModel.class, mMockFavoriteViewModel);
+ when(mMockFavoriteViewModel.getFavoriteContacts()).thenReturn(new MutableLiveData<>());
}
@Test