1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.dialer.ui.contact; 18 19 import android.content.Context; 20 import android.net.Uri; 21 import android.provider.ContactsContract; 22 23 import androidx.annotation.NonNull; 24 import androidx.annotation.Nullable; 25 import androidx.lifecycle.LiveData; 26 import androidx.lifecycle.MediatorLiveData; 27 import androidx.lifecycle.ViewModel; 28 29 import com.android.car.arch.common.FutureData; 30 import com.android.car.arch.common.LiveDataFunctions; 31 import com.android.car.dialer.storage.FavoriteNumberRepository; 32 import com.android.car.telephony.common.Contact; 33 import com.android.car.telephony.common.InMemoryPhoneBook; 34 import com.android.car.telephony.common.PhoneNumber; 35 import com.android.car.telephony.common.WorkerExecutor; 36 37 import java.util.List; 38 import java.util.concurrent.Future; 39 40 import javax.inject.Inject; 41 42 import dagger.hilt.android.lifecycle.HiltViewModel; 43 import dagger.hilt.android.qualifiers.ApplicationContext; 44 45 /** 46 * View model for the contact details page. 47 */ 48 @HiltViewModel 49 public class ContactDetailsViewModel extends ViewModel { 50 private final Context mContext; 51 private final FavoriteNumberRepository mFavoriteNumberRepository; 52 53 @Inject ContactDetailsViewModel(@pplicationContext Context context, FavoriteNumberRepository favoriteNumberRepository)54 public ContactDetailsViewModel(@ApplicationContext Context context, 55 FavoriteNumberRepository favoriteNumberRepository) { 56 mContext = context; 57 mFavoriteNumberRepository = favoriteNumberRepository; 58 } 59 60 /** 61 * Builds the {@link LiveData} for the given contact which will update upon contact change and 62 * favorite repository change. 63 * 64 * @param contact The contact entry. It might be out of date and should update when the {@link 65 * InMemoryPhoneBook} changes. It always uses the in memory instance to get the 66 * favorite state for phone numbers. 67 */ getContactDetails(@ullable Contact contact)68 public LiveData<FutureData<Contact>> getContactDetails(@Nullable Contact contact) { 69 if (contact == null) { 70 return LiveDataFunctions.dataOf(new FutureData<>(false, null)); 71 } 72 73 LiveData<Contact> contactLiveData = new ContactDetailsLiveData(mContext, contact); 74 return LiveDataFunctions.loadingSwitchMap(contactLiveData, 75 input -> LiveDataFunctions.dataOf(input)); 76 } 77 78 /** 79 * Adds the phone number to favorite. 80 * 81 * @param contact The contact the phone number belongs to. 82 * @param phoneNumber The phone number to add to favorite. 83 */ addToFavorite(Contact contact, PhoneNumber phoneNumber)84 public void addToFavorite(Contact contact, PhoneNumber phoneNumber) { 85 mFavoriteNumberRepository.addToFavorite(contact, phoneNumber); 86 } 87 88 /** 89 * Removes the phone number from favorite. 90 * 91 * @param contact The contact the phone number belongs to. 92 * @param phoneNumber The phone number to remove from favorite. 93 */ removeFromFavorite(Contact contact, PhoneNumber phoneNumber)94 public void removeFromFavorite(Contact contact, PhoneNumber phoneNumber) { 95 mFavoriteNumberRepository.removeFromFavorite(contact, phoneNumber); 96 } 97 98 private class ContactDetailsLiveData extends MediatorLiveData<Contact> { 99 private final Uri mContactLookupUri; 100 private final String mAccountName; 101 private final WorkerExecutor mWorkerExecutor; 102 private final Context mContext; 103 private Contact mContact; 104 private Future<?> mRunnableFuture; 105 ContactDetailsLiveData(Context context, @NonNull Contact contact)106 private ContactDetailsLiveData(Context context, @NonNull Contact contact) { 107 mContext = context; 108 mWorkerExecutor = WorkerExecutor.getInstance(); 109 mContact = contact; 110 mContactLookupUri = mContact.getLookupUri(); 111 mAccountName = mContact.getAccountName(); 112 addSource(InMemoryPhoneBook.get().getContactsLiveData(), this::onContactListChanged); 113 addSource(mFavoriteNumberRepository.getFavoriteContacts(), 114 this::onFavoriteContactsChanged); 115 } 116 onContactListChanged(List<Contact> contacts)117 private void onContactListChanged(List<Contact> contacts) { 118 if (mContact != null) { 119 Contact inMemoryContact = InMemoryPhoneBook.get().lookupContactByKey( 120 mContact.getLookupKey(), mContact.getAccountName()); 121 if (inMemoryContact != null) { 122 setValue(inMemoryContact); 123 return; 124 } 125 } 126 127 if (mRunnableFuture != null) { 128 mRunnableFuture.cancel(false); 129 } 130 mRunnableFuture = mWorkerExecutor.getSingleThreadExecutor().submit( 131 () -> { 132 Uri refreshedContactLookupUri = ContactsContract.Contacts.getLookupUri( 133 mContext.getContentResolver(), 134 mContact == null ? mContactLookupUri : mContact.getLookupUri()); 135 if (refreshedContactLookupUri == null) { 136 postValue(null); 137 return; 138 } 139 140 List<String> pathSegments = refreshedContactLookupUri.getPathSegments(); 141 String lookupKey = pathSegments.get(pathSegments.size() - 2); 142 Contact contact = InMemoryPhoneBook.get().lookupContactByKey(lookupKey, 143 mContact == null ? mAccountName : mContact.getAccountName()); 144 postValue(contact); 145 } 146 ); 147 } 148 onFavoriteContactsChanged(List<Contact> favoriteContacts)149 private void onFavoriteContactsChanged(List<Contact> favoriteContacts) { 150 if (mContact == null) { 151 return; 152 } 153 Contact inMemoryContact = InMemoryPhoneBook.get().lookupContactByKey( 154 mContact.getLookupKey(), mContact.getAccountName()); 155 setValue(inMemoryContact); 156 } 157 158 @Override setValue(Contact contact)159 public void setValue(Contact contact) { 160 mContact = contact; 161 super.setValue(contact); 162 } 163 164 @Override onInactive()165 protected void onInactive() { 166 super.onInactive(); 167 if (mRunnableFuture != null) { 168 mRunnableFuture.cancel(true); 169 } 170 } 171 } 172 } 173