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