• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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