• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.telecom;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.os.IBinder;
24 import android.telecom.Call;
25 import android.telecom.CallAudioState;
26 
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 import androidx.lifecycle.LiveData;
30 import androidx.lifecycle.MutableLiveData;
31 
32 import com.android.car.dialer.log.L;
33 
34 import com.google.common.base.Predicate;
35 
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.List;
39 
40 import javax.inject.Inject;
41 
42 import dagger.hilt.android.qualifiers.ApplicationContext;
43 import dagger.hilt.android.scopes.ViewModelScoped;
44 
45 /**
46  * Binds to the {@link InCallServiceImpl}, and upon establishing a connection, handles call list
47  * change and call audio state change.
48  */
49 @ViewModelScoped
50 public class LocalCallHandler {
51     private static final String TAG = "CD.CallHandler";
52     private final Context mContext;
53     private final Call.Callback mRingingCallStateChangeCallback;
54 
55     private final MutableLiveData<CallAudioState> mCallAudioStateLiveData = new MutableLiveData<>();
56     private final InCallServiceImpl.CallAudioStateCallback mCallAudioStateCallback =
57             mCallAudioStateLiveData::setValue;
58 
59     private final MutableLiveData<List<Call>> mCallListLiveData;
60     private final MutableLiveData<Call> mIncomingCallLiveData;
61     private final MutableLiveData<List<Call>> mOngoingCallListLiveData;
62     private final InCallServiceImpl.ActiveCallListChangedCallback mActiveCallListChangedCallback =
63             new InCallServiceImpl.ActiveCallListChangedCallback() {
64                 @Override
65                 public boolean onTelecomCallAdded(Call telecomCall) {
66                     notifyCallAdded(telecomCall);
67                     notifyCallListChanged();
68                     return false;
69                 }
70 
71                 @Override
72                 public boolean onTelecomCallRemoved(Call telecomCall) {
73                     notifyCallRemoved(telecomCall);
74                     notifyCallListChanged();
75                     return false;
76                 }
77             };
78 
79     private InCallServiceImpl mInCallService;
80     private final ServiceConnection mInCallServiceConnection = new ServiceConnection() {
81         @Override
82         public void onServiceConnected(ComponentName name, IBinder binder) {
83             L.d(TAG, "onServiceConnected: %s, service: %s", name, binder);
84             mInCallService = ((InCallServiceImpl.LocalBinder) binder).getService();
85             for (Call call : mInCallService.getCallList()) {
86                 notifyCallAdded(call);
87             }
88             mInCallService.addActiveCallListChangedCallback(mActiveCallListChangedCallback);
89             mInCallService.addCallAudioStateChangedCallback(mCallAudioStateCallback);
90             notifyCallListChanged();
91         }
92 
93         @Override
94         public void onServiceDisconnected(ComponentName name) {
95             L.d(TAG, "onServiceDisconnected: %s", name);
96             mInCallService = null;
97         }
98     };
99 
100     /**
101      * Initiate a LocalCallHandler.
102      *
103      * @param context Application context.
104      */
105     @Inject
LocalCallHandler(@pplicationContext Context context)106     public LocalCallHandler(@ApplicationContext Context context) {
107         mContext = context;
108 
109         mCallListLiveData = new MutableLiveData<>();
110         mIncomingCallLiveData = new MutableLiveData<>();
111         mOngoingCallListLiveData = new MutableLiveData<>();
112 
113         mRingingCallStateChangeCallback = new Call.Callback() {
114             @Override
115             public void onStateChanged(Call call, int state) {
116                 // Don't show in call activity by declining a ringing call to avoid UI flashing.
117                 if (state != Call.STATE_DISCONNECTED) {
118                     notifyCallListChanged();
119                 }
120                 call.unregisterCallback(this);
121             }
122         };
123 
124         Intent intent = new Intent(mContext, InCallServiceImpl.class);
125         intent.setAction(InCallServiceImpl.ACTION_LOCAL_BIND);
126         mContext.bindService(intent, mInCallServiceConnection, Context.BIND_AUTO_CREATE);
127     }
128 
129     /** Returns the {@link LiveData} which monitors the call audio state change. */
getCallAudioStateLiveData()130     public LiveData<CallAudioState> getCallAudioStateLiveData() {
131         return mCallAudioStateLiveData;
132     }
133 
134     /** Returns the {@link LiveData} which monitors the call list. */
135     @NonNull
getCallListLiveData()136     public LiveData<List<Call>> getCallListLiveData() {
137         return mCallListLiveData;
138     }
139 
140     /** Returns the {@link LiveData} which monitors the active call list. */
141     @NonNull
getOngoingCallListLiveData()142     public LiveData<List<Call>> getOngoingCallListLiveData() {
143         return mOngoingCallListLiveData;
144     }
145 
146     /** Returns the {@link LiveData} which monitors the ringing call. */
147     @NonNull
getIncomingCallLiveData()148     public LiveData<Call> getIncomingCallLiveData() {
149         return mIncomingCallLiveData;
150     }
151 
152     /** Disconnects the {@link InCallServiceImpl} and cleanup. */
tearDown()153     public void tearDown() {
154         mContext.unbindService(mInCallServiceConnection);
155         if (mInCallService != null) {
156             for (Call call : mInCallService.getCallList()) {
157                 notifyCallRemoved(call);
158             }
159             mInCallService.removeActiveCallListChangedCallback(mActiveCallListChangedCallback);
160             mInCallService.removeCallAudioStateChangedCallback(mCallAudioStateCallback);
161         }
162         mInCallService = null;
163     }
164 
165     /**
166      * The call list has updated, notify change. It will update when:
167      * <ul>
168      *     <li> {@link InCallServiceImpl} has been bind, init the call list.
169      *     <li> A call has been added to the {@link InCallServiceImpl}.
170      *     <li> A call has been removed from the {@link InCallServiceImpl}.
171      *     <li> A call has changed.
172      * </ul>
173      */
notifyCallListChanged()174     private void notifyCallListChanged() {
175         List<Call> callList = new ArrayList<>(mInCallService.getCallList());
176 
177         List<Call> activeCallList = filter(callList,
178                 call -> call != null && call.getState() != Call.STATE_RINGING);
179         mOngoingCallListLiveData.setValue(activeCallList);
180 
181         Call ringingCall = firstMatch(callList,
182                 call -> call != null && call.getState() == Call.STATE_RINGING);
183         mIncomingCallLiveData.setValue(ringingCall);
184 
185         mCallListLiveData.setValue(callList);
186     }
187 
notifyCallAdded(Call call)188     private void notifyCallAdded(Call call) {
189         if (call.getState() == Call.STATE_RINGING) {
190             call.registerCallback(mRingingCallStateChangeCallback);
191         }
192     }
193 
notifyCallRemoved(Call call)194     private void notifyCallRemoved(Call call) {
195         call.unregisterCallback(mRingingCallStateChangeCallback);
196     }
197 
198     @Nullable
firstMatch(List<Call> callList, Predicate<Call> predicate)199     private static Call firstMatch(List<Call> callList, Predicate<Call> predicate) {
200         List<Call> filteredResults = filter(callList, predicate);
201         return filteredResults.isEmpty() ? null : filteredResults.get(0);
202     }
203 
204     @NonNull
filter(List<Call> callList, Predicate<Call> predicate)205     private static List<Call> filter(List<Call> callList, Predicate<Call> predicate) {
206         if (callList == null || predicate == null) {
207             return Collections.emptyList();
208         }
209 
210         List<Call> filteredResults = new ArrayList<>();
211         for (Call call : callList) {
212             if (predicate.apply(call)) {
213                 filteredResults.add(call);
214             }
215         }
216         return filteredResults;
217     }
218 }
219