Create favorite number repository.
Bug: 132811088
Test: build and run
Change-Id: I47fa58ea5cd2b29a1475ea45b5172eb8a02686c0
diff --git a/src/com/android/car/dialer/storage/FavoriteNumberRepository.java b/src/com/android/car/dialer/storage/FavoriteNumberRepository.java
new file mode 100644
index 0000000..f83100f
--- /dev/null
+++ b/src/com/android/car/dialer/storage/FavoriteNumberRepository.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2019 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.car.dialer.storage;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.WorkerThread;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.android.car.telephony.common.Contact;
+import com.android.car.telephony.common.I18nPhoneNumberWrapper;
+import com.android.car.telephony.common.PhoneNumber;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * Repository for favorite numbers.It supports the operation to convert the favorite entities to
+ * {@link Contact}s and add or delete entry.
+ */
+public class FavoriteNumberRepository {
+ private static final String TAG = "CD.FavRepository";
+ private static ExecutorService sSerializedExecutor;
+
+ static {
+ sSerializedExecutor = Executors.newSingleThreadExecutor();
+ }
+
+ private final Context mContext;
+ private final FavoriteNumberDao mFavoriteNumberDao;
+ private final LiveData<List<FavoriteNumberEntity>> mFavoriteNumbers;
+ private Future<?> mConvertAllRunnableFuture;
+
+ public FavoriteNumberRepository(Context context) {
+ mContext = context.getApplicationContext();
+
+ FavoriteNumberDatabase db = FavoriteNumberDatabase.getDatabase(mContext);
+ mFavoriteNumberDao = db.favoriteNumberDao();
+ mFavoriteNumbers = mFavoriteNumberDao.loadAll();
+ }
+
+ /** Returns the favorite number list. */
+ public LiveData<List<FavoriteNumberEntity>> getFavoriteNumbers() {
+ return mFavoriteNumbers;
+ }
+
+ /** Add a phone number to favorite. */
+ public void addToFavorite(Contact contact, PhoneNumber phoneNumber) {
+ FavoriteNumberEntity favoriteNumber = new FavoriteNumberEntity();
+ favoriteNumber.setContactId(contact.getId());
+ favoriteNumber.setContactLookupKey(contact.getLookupKey());
+ favoriteNumber.setPhoneNumber(new CipherWrapper<>(
+ phoneNumber.getRawNumber()));
+ favoriteNumber.setAccountName(phoneNumber.getAccountName());
+ favoriteNumber.setAccountType(phoneNumber.getAccountType());
+ sSerializedExecutor.execute(() -> mFavoriteNumberDao.insert(favoriteNumber));
+ }
+
+ /** Remove a phone number from favorite. */
+ public void removeFromFavorite(Contact contact, PhoneNumber phoneNumber) {
+ List<FavoriteNumberEntity> favoriteNumbers = mFavoriteNumbers.getValue();
+ if (favoriteNumbers == null) {
+ return;
+ }
+ for (FavoriteNumberEntity favoriteNumberEntity : favoriteNumbers) {
+ if (matches(favoriteNumberEntity, contact, phoneNumber)) {
+ sSerializedExecutor.execute(() -> mFavoriteNumberDao.delete(favoriteNumberEntity));
+ }
+ }
+ }
+
+ /**
+ * Convert the {@link FavoriteNumberEntity}s to {@link Contact}s and update contact id and
+ * contact lookup key for all the entities that are out of date.
+ */
+ public void convertToContacts(Context context, final MutableLiveData<List<Contact>> results) {
+ if (mConvertAllRunnableFuture != null) {
+ mConvertAllRunnableFuture.cancel(false);
+ }
+
+ mConvertAllRunnableFuture = sSerializedExecutor.submit(() -> {
+ if (mFavoriteNumbers.getValue() == null) {
+ results.postValue(Collections.emptyList());
+ return;
+ }
+
+ ContentResolver cr = context.getContentResolver();
+ List<FavoriteNumberEntity> outOfDateList = new ArrayList<>();
+ List<Contact> favoriteContacts = new ArrayList<>();
+ List<FavoriteNumberEntity> favoriteNumbers = mFavoriteNumbers.getValue();
+ for (FavoriteNumberEntity favoriteNumber : favoriteNumbers) {
+ Contact contact = lookupContact(cr, favoriteNumber);
+ if (contact != null) {
+ favoriteContacts.add(contact);
+ if (favoriteNumber.getContactId() != contact.getId()
+ || !TextUtils.equals(favoriteNumber.getContactLookupKey(),
+ contact.getLookupKey())) {
+ favoriteNumber.setContactLookupKey(contact.getLookupKey());
+ favoriteNumber.setContactId(contact.getId());
+ outOfDateList.add(favoriteNumber);
+ }
+ }
+ }
+ results.postValue(favoriteContacts);
+ if (!outOfDateList.isEmpty()) {
+ mFavoriteNumberDao.updateAll(outOfDateList);
+ }
+ });
+ }
+
+ /** Remove favorite entries for devices that has been unpaired. */
+ public void cleanup(Set<BluetoothDevice> devices) {
+ Log.d(TAG, "remove entries for unpaired devices except: " + devices);
+ sSerializedExecutor.execute(() -> {
+ List<String> deviceAddresses = new ArrayList<>();
+ for (BluetoothDevice device : devices) {
+ deviceAddresses.add(device.getAddress());
+ }
+ mFavoriteNumberDao.cleanup(deviceAddresses);
+ });
+ }
+
+ @WorkerThread
+ private Contact lookupContact(ContentResolver cr, FavoriteNumberEntity favoriteNumber) {
+ Uri lookupUri = ContactsContract.Contacts.getLookupUri(
+ favoriteNumber.getContactId(), favoriteNumber.getContactLookupKey());
+ Uri refreshedUri = ContactsContract.Contacts.lookupContact(
+ mContext.getContentResolver(), lookupUri);
+ if (refreshedUri == null) {
+ return null;
+ }
+ long contactId = ContentUris.parseId(refreshedUri);
+
+ try (Cursor cursor = cr.query(
+ ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
+ /* projection= */null,
+ /* selection= */ ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
+ new String[]{String.valueOf(contactId)},
+ /* orderBy= */null)) {
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ Contact contact = Contact.fromCursor(mContext, cursor);
+ if (contact.getNumbers().isEmpty()) {
+ continue;
+ }
+ if (numberMatches(favoriteNumber, contact.getNumbers().get(0))) {
+ return contact;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean matches(FavoriteNumberEntity favoriteNumber, Contact contact,
+ PhoneNumber phoneNumber) {
+ if (TextUtils.equals(favoriteNumber.getContactLookupKey(), contact.getLookupKey())) {
+ return numberMatches(favoriteNumber, phoneNumber);
+ }
+
+ return false;
+ }
+
+ private boolean numberMatches(FavoriteNumberEntity favoriteNumber, PhoneNumber phoneNumber) {
+ if (favoriteNumber.getPhoneNumber() == null) {
+ return false;
+ }
+
+ if (!TextUtils.equals(favoriteNumber.getAccountName(), phoneNumber.getAccountName())
+ || !TextUtils.equals(favoriteNumber.getAccountType(),
+ phoneNumber.getAccountType())) {
+ return false;
+ }
+
+ I18nPhoneNumberWrapper i18nPhoneNumberWrapper = I18nPhoneNumberWrapper.Factory.INSTANCE.get(
+ mContext, favoriteNumber.getPhoneNumber().get());
+ return i18nPhoneNumberWrapper.equals(phoneNumber.getI18nPhoneNumberWrapper());
+ }
+}