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