• 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.activecall;
18 
19 import android.app.Application;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.ServiceConnection;
24 import android.os.IBinder;
25 import android.telecom.Call;
26 
27 import androidx.annotation.NonNull;
28 import androidx.core.util.Pair;
29 import androidx.lifecycle.AndroidViewModel;
30 import androidx.lifecycle.LiveData;
31 import androidx.lifecycle.MutableLiveData;
32 import androidx.lifecycle.Transformations;
33 
34 import com.android.car.arch.common.LiveDataFunctions;
35 import com.android.car.dialer.livedata.AudioRouteLiveData;
36 import com.android.car.dialer.livedata.CallDetailLiveData;
37 import com.android.car.dialer.livedata.CallStateLiveData;
38 import com.android.car.dialer.log.L;
39 import com.android.car.dialer.telecom.InCallServiceImpl;
40 import com.android.car.telephony.common.CallDetail;
41 
42 import com.google.common.base.Predicate;
43 import com.google.common.collect.Lists;
44 
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.Comparator;
48 import java.util.List;
49 
50 /**
51  * View model for {@link InCallActivity} and {@link OngoingCallFragment}. UI that doesn't belong to
52  * in call page should use a different ViewModel.
53  */
54 public class InCallViewModel extends AndroidViewModel implements
55         InCallServiceImpl.ActiveCallListChangedCallback {
56     private static final String TAG = "CD.InCallViewModel";
57 
58     private final MutableLiveData<List<Call>> mCallListLiveData;
59     private final LiveData<List<Call>> mOngoingCallListLiveData;
60     private final Comparator<Call> mCallComparator;
61 
62     private final LiveData<Call> mIncomingCallLiveData;
63 
64     private final LiveData<CallDetail> mCallDetailLiveData;
65     private final LiveData<Integer> mCallStateLiveData;
66     private final LiveData<Call> mPrimaryCallLiveData;
67     private final LiveData<Call> mSecondaryCallLiveData;
68     private final LiveData<CallDetail> mSecondaryCallDetailLiveData;
69     private final LiveData<Integer> mAudioRouteLiveData;
70     private LiveData<Long> mCallConnectTimeLiveData;
71     private LiveData<Pair<Integer, Long>> mCallStateAndConnectTimeLiveData;
72     private final Context mContext;
73 
74     private InCallServiceImpl mInCallService;
75     private final ServiceConnection mInCallServiceConnection = new ServiceConnection() {
76 
77         @Override
78         public void onServiceConnected(ComponentName name, IBinder binder) {
79             L.d(TAG, "onServiceConnected: %s, service: %s", name, binder);
80             mInCallService = ((InCallServiceImpl.LocalBinder) binder).getService();
81             updateCallList();
82             mInCallService.addActiveCallListChangedCallback(InCallViewModel.this);
83         }
84 
85         @Override
86         public void onServiceDisconnected(ComponentName name) {
87             L.d(TAG, "onServiceDisconnected: %s", name);
88             mInCallService = null;
89         }
90     };
91 
92     // Reuse the same instance so the callback won't be registered more than once.
93     private final Call.Callback mCallStateChangedCallback = new Call.Callback() {
94         @Override
95         public void onStateChanged(Call call, int state) {
96             // Sets value to trigger the live data for incoming call and active call list to update.
97             mCallListLiveData.setValue(mCallListLiveData.getValue());
98         }
99     };
100 
InCallViewModel(@onNull Application application)101     public InCallViewModel(@NonNull Application application) {
102         super(application);
103         mContext = application.getApplicationContext();
104 
105         mCallListLiveData = new MutableLiveData<>();
106         mCallComparator = new CallComparator();
107 
108         mIncomingCallLiveData = Transformations.map(mCallListLiveData,
109                 callList -> firstMatch(callList,
110                         call -> call != null && call.getState() == Call.STATE_RINGING));
111 
112         mOngoingCallListLiveData = Transformations.map(mCallListLiveData,
113                 callList -> {
114                     List<Call> activeCallList = filter(callList,
115                             call -> call != null && call.getState() != Call.STATE_RINGING);
116                     activeCallList.sort(mCallComparator);
117                     return activeCallList;
118                 });
119 
120         mPrimaryCallLiveData = Transformations.map(mOngoingCallListLiveData,
121                 input -> input.isEmpty() ? null : input.get(0));
122         mCallDetailLiveData = Transformations.switchMap(mPrimaryCallLiveData,
123                 input -> input != null ? new CallDetailLiveData(input) : null);
124         mCallStateLiveData = Transformations.switchMap(mPrimaryCallLiveData,
125                 input -> input != null ? new CallStateLiveData(input) : null);
126         mCallConnectTimeLiveData = Transformations.map(mCallDetailLiveData, (details) -> {
127             if (details == null) {
128                 return 0L;
129             }
130             return details.getConnectTimeMillis();
131         });
132         mCallStateAndConnectTimeLiveData =
133                 LiveDataFunctions.pair(mCallStateLiveData, mCallConnectTimeLiveData);
134 
135         mSecondaryCallLiveData = Transformations.map(mOngoingCallListLiveData,
136                 callList -> (callList != null && callList.size() > 1) ? callList.get(1) : null);
137 
138         mSecondaryCallDetailLiveData = Transformations.switchMap(mSecondaryCallLiveData,
139                 input -> input != null ? new CallDetailLiveData(input) : null);
140 
141         mAudioRouteLiveData = new AudioRouteLiveData(mContext);
142 
143         Intent intent = new Intent(mContext, InCallServiceImpl.class);
144         intent.setAction(InCallServiceImpl.ACTION_LOCAL_BIND);
145         mContext.bindService(intent, mInCallServiceConnection, Context.BIND_AUTO_CREATE);
146     }
147 
148     /** Returns the live data which monitors the current incoming call. */
getIncomingCall()149     public LiveData<Call> getIncomingCall() {
150         return mIncomingCallLiveData;
151     }
152 
153     /** Returns {@link LiveData} for the ongoing call list which excludes the ringing call. */
getOngoingCallList()154     public LiveData<List<Call>> getOngoingCallList() {
155         return mOngoingCallListLiveData;
156     }
157 
158     /**
159      * Returns the live data which monitors the primary call details.
160      */
getPrimaryCallDetail()161     public LiveData<CallDetail> getPrimaryCallDetail() {
162         return mCallDetailLiveData;
163     }
164 
165     /**
166      * Returns the live data which monitors the primary call state.
167      */
getPrimaryCallState()168     public LiveData<Integer> getPrimaryCallState() {
169         return mCallStateLiveData;
170     }
171 
172     /**
173      * Returns the live data which monitors the primary call state and the start time of the call.
174      */
getCallStateAndConnectTime()175     public LiveData<Pair<Integer, Long>> getCallStateAndConnectTime() {
176         return mCallStateAndConnectTimeLiveData;
177     }
178 
179     /**
180      * Returns the live data which monitor the primary call.
181      * A primary call in the first call in the ongoing call list,
182      * which is sorted based on {@link CallComparator}.
183      */
getPrimaryCall()184     public LiveData<Call> getPrimaryCall() {
185         return mPrimaryCallLiveData;
186     }
187 
188     /**
189      * Returns the live data which monitor the secondary call.
190      * A secondary call in the second call in the ongoing call list,
191      * which is sorted based on {@link CallComparator}.
192      * The value will be null if there is no second call in the call list.
193      */
getSecondaryCall()194     public LiveData<Call> getSecondaryCall() {
195         return mSecondaryCallLiveData;
196     }
197 
198     /**
199      * Returns the live data which monitors the secondary call details.
200      */
getSecondaryCallDetail()201     public LiveData<CallDetail> getSecondaryCallDetail() {
202         return mSecondaryCallDetailLiveData;
203     }
204 
205     /**
206      * Returns current audio route.
207      */
getAudioRoute()208     public LiveData<Integer> getAudioRoute() {
209         return mAudioRouteLiveData;
210     }
211 
212     @Override
onTelecomCallAdded(Call telecomCall)213     public boolean onTelecomCallAdded(Call telecomCall) {
214         L.i(TAG, "onTelecomCallAdded %s %s", telecomCall, this);
215         telecomCall.registerCallback(mCallStateChangedCallback);
216         updateCallList();
217         return false;
218     }
219 
220     @Override
onTelecomCallRemoved(Call telecomCall)221     public boolean onTelecomCallRemoved(Call telecomCall) {
222         L.i(TAG, "onTelecomCallRemoved %s %s", telecomCall, this);
223         telecomCall.unregisterCallback(mCallStateChangedCallback);
224         updateCallList();
225         return false;
226     }
227 
updateCallList()228     private void updateCallList() {
229         List<Call> callList = new ArrayList<>();
230         callList.addAll(mInCallService.getCalls());
231         mCallListLiveData.setValue(callList);
232     }
233 
234     @Override
onCleared()235     protected void onCleared() {
236         mContext.unbindService(mInCallServiceConnection);
237         if (mInCallService != null) {
238             mInCallService.removeActiveCallListChangedCallback(this);
239         }
240         mInCallService = null;
241     }
242 
243     private static class CallComparator implements Comparator<Call> {
244         /**
245          * The rank of call state. Used for sorting active calls. Rank is listed from lowest to
246          * highest.
247          */
248         private static final List<Integer> CALL_STATE_RANK = Lists.newArrayList(
249                 Call.STATE_RINGING,
250                 Call.STATE_DISCONNECTED,
251                 Call.STATE_DISCONNECTING,
252                 Call.STATE_NEW,
253                 Call.STATE_CONNECTING,
254                 Call.STATE_SELECT_PHONE_ACCOUNT,
255                 Call.STATE_HOLDING,
256                 Call.STATE_ACTIVE,
257                 Call.STATE_DIALING);
258 
259         @Override
compare(Call call, Call otherCall)260         public int compare(Call call, Call otherCall) {
261             boolean callHasParent = call.getParent() != null;
262             boolean otherCallHasParent = otherCall.getParent() != null;
263 
264             if (callHasParent && !otherCallHasParent) {
265                 return 1;
266             } else if (!callHasParent && otherCallHasParent) {
267                 return -1;
268             }
269             int carCallRank = CALL_STATE_RANK.indexOf(call.getState());
270             int otherCarCallRank = CALL_STATE_RANK.indexOf(otherCall.getState());
271 
272             return otherCarCallRank - carCallRank;
273         }
274     }
275 
firstMatch(List<Call> callList, Predicate<Call> predicate)276     private static Call firstMatch(List<Call> callList, Predicate<Call> predicate) {
277         List<Call> filteredResults = filter(callList, predicate);
278         return filteredResults.isEmpty() ? null : filteredResults.get(0);
279     }
280 
filter(List<Call> callList, Predicate<Call> predicate)281     private static List<Call> filter(List<Call> callList, Predicate<Call> predicate) {
282         if (callList == null || predicate == null) {
283             return Collections.emptyList();
284         }
285 
286         List<Call> filteredResults = new ArrayList<>();
287         for (Call call : callList) {
288             if (predicate.apply(call)) {
289                 filteredResults.add(call);
290             }
291         }
292         return filteredResults;
293     }
294 }
295