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.app.Application;
20 import android.content.Context;
21 import android.util.Pair;
22 
23 import androidx.annotation.NonNull;
24 import androidx.lifecycle.AndroidViewModel;
25 import androidx.lifecycle.LiveData;
26 import androidx.lifecycle.MediatorLiveData;
27 
28 import com.android.car.arch.common.FutureData;
29 import com.android.car.arch.common.LiveDataFunctions;
30 import com.android.car.dialer.R;
31 import com.android.car.dialer.livedata.SharedPreferencesLiveData;
32 import com.android.car.dialer.ui.common.entity.ContactSortingInfo;
33 import com.android.car.telephony.common.Contact;
34 import com.android.car.telephony.common.InMemoryPhoneBook;
35 
36 import java.util.Collections;
37 import java.util.Comparator;
38 import java.util.List;
39 import java.util.concurrent.ExecutorService;
40 import java.util.concurrent.Executors;
41 import java.util.concurrent.Future;
42 
43 /**
44  * View model for {@link ContactListFragment}.
45  */
46 public class ContactListViewModel extends AndroidViewModel {
47     private final Context mContext;
48     private final LiveData<Pair<Integer, List<Contact>>> mSortedContactListLiveData;
49     private final LiveData<FutureData<Pair<Integer, List<Contact>>>> mContactList;
50 
ContactListViewModel(@onNull Application application)51     public ContactListViewModel(@NonNull Application application) {
52         super(application);
53         mContext = application.getApplicationContext();
54 
55         SharedPreferencesLiveData preferencesLiveData =
56                 new SharedPreferencesLiveData(mContext, R.string.sort_order_key);
57         LiveData<List<Contact>> contactListLiveData = InMemoryPhoneBook.get().getContactsLiveData();
58         mSortedContactListLiveData = new SortedContactListLiveData(
59                 mContext, contactListLiveData, preferencesLiveData);
60         mContactList = LiveDataFunctions.loadingSwitchMap(mSortedContactListLiveData,
61                 input -> LiveDataFunctions.dataOf(input));
62     }
63 
64     /**
65      * Returns a live data which represents a list of all contacts.
66      */
getAllContacts()67     public LiveData<FutureData<Pair<Integer, List<Contact>>>> getAllContacts() {
68         return mContactList;
69     }
70 
71     private static class SortedContactListLiveData
72             extends MediatorLiveData<Pair<Integer, List<Contact>>> {
73 
74         private final LiveData<List<Contact>> mContactListLiveData;
75         private final SharedPreferencesLiveData mPreferencesLiveData;
76         private final Context mContext;
77 
78         private final ExecutorService mExecutorService;
79         private Future<?> mRunnableFuture;
80 
SortedContactListLiveData(Context context, @NonNull LiveData<List<Contact>> contactListLiveData, @NonNull SharedPreferencesLiveData sharedPreferencesLiveData)81         private SortedContactListLiveData(Context context,
82                 @NonNull LiveData<List<Contact>> contactListLiveData,
83                 @NonNull SharedPreferencesLiveData sharedPreferencesLiveData) {
84             mContext = context;
85             mContactListLiveData = contactListLiveData;
86             mPreferencesLiveData = sharedPreferencesLiveData;
87             mExecutorService = Executors.newSingleThreadExecutor();
88 
89             addSource(mPreferencesLiveData, (trigger) -> updateSortedContactList());
90             addSource(mContactListLiveData, (trigger) -> updateSortedContactList());
91         }
92 
updateSortedContactList()93         private void updateSortedContactList() {
94             // Don't set null value to trigger an update when there is no value set.
95             if (mContactListLiveData.getValue() == null && getValue() == null) {
96                 return;
97             }
98 
99             if (mContactListLiveData.getValue() == null
100                     || mContactListLiveData.getValue().isEmpty()) {
101                 setValue(null);
102                 return;
103             }
104 
105             List<Contact> contactList = mContactListLiveData.getValue();
106             Pair<Comparator<Contact>, Integer> contactSortingInfo = ContactSortingInfo
107                     .getSortingInfo(mContext, mPreferencesLiveData);
108             Comparator<Contact> comparator = contactSortingInfo.first;
109             Integer sortMethod = contactSortingInfo.second;
110 
111             // SingleThreadPoolExecutor is used here to avoid multiple threads sorting the list
112             // at the same time.
113             if (mRunnableFuture != null) {
114                 mRunnableFuture.cancel(true);
115             }
116 
117             Runnable runnable = () -> {
118                 Collections.sort(contactList, comparator);
119                 postValue(new Pair<>(sortMethod, contactList));
120             };
121             mRunnableFuture = mExecutorService.submit(runnable);
122         }
123 
124         @Override
onInactive()125         protected void onInactive() {
126             super.onInactive();
127             if (mRunnableFuture != null) {
128                 mRunnableFuture.cancel(true);
129             }
130         }
131     }
132 }
133