1 /* 2 * Copyright (C) 2015 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 package com.android.car.dialer.telecom.embedded; 17 18 import com.android.car.dialer.telecom.UiCall; 19 import com.android.car.dialer.telecom.UiCallManager; 20 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.ServiceConnection; 25 import android.net.Uri; 26 import android.os.IBinder; 27 import android.telecom.Call; 28 import android.telecom.Call.Details; 29 import android.telecom.CallAudioState; 30 import android.telecom.InCallService.VideoCall; 31 import android.telecom.TelecomManager; 32 import android.util.Log; 33 34 import java.lang.ref.WeakReference; 35 import java.util.List; 36 import java.util.concurrent.CopyOnWriteArrayList; 37 38 /** 39 * An implementation of {@link UiCallManager} that uses {@code android.telecom.*} stack. 40 */ 41 public class TelecomUiCallManager extends UiCallManager { 42 43 private static final String TAG = "Em.TelecomMgrImpl"; 44 45 private TelecomManager mTelecomManager; 46 private InCallServiceImpl mInCallService; 47 private TelecomUiCallList mCallList = new TelecomUiCallList(); 48 49 private List<CallListener> mCallListeners = new CopyOnWriteArrayList<>(); 50 51 @Override setUp(Context context)52 protected void setUp(Context context) { 53 super.setUp(context); 54 mTelecomManager = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); 55 Intent intent = new Intent(context, InCallServiceImpl.class); 56 intent.setAction(InCallServiceImpl.ACTION_LOCAL_BIND); 57 context.bindService(intent, mInCallServiceConnection, Context.BIND_AUTO_CREATE); 58 } 59 tearDown()60 public void tearDown() { 61 if (mInCallService != null) { 62 mContext.unbindService(mInCallServiceConnection); 63 mInCallService = null; 64 } 65 mCallList.clearCalls(); 66 } 67 68 @Override addListener(CallListener listener)69 public void addListener(CallListener listener) { 70 if (Log.isLoggable(TAG, Log.DEBUG)) { 71 Log.d(TAG, "addListener: " + listener); 72 } 73 mCallListeners.add(listener); 74 } 75 76 @Override removeListener(CallListener listener)77 public void removeListener(CallListener listener) { 78 if (Log.isLoggable(TAG, Log.DEBUG)) { 79 Log.d(TAG, "removeListener: " + listener); 80 } 81 mCallListeners.remove(listener); 82 } 83 84 @Override placeCall(String number)85 public void placeCall(String number) { 86 if (Log.isLoggable(TAG, Log.DEBUG)) { 87 Log.d(TAG, "placeCall: " + number); 88 } 89 Uri uri = Uri.fromParts("tel", number, null); 90 Log.d(TAG, "android.telecom.TelecomManager#placeCall: " + uri); 91 mTelecomManager.placeCall(uri, null); 92 } 93 94 @Override answerCall(UiCall uiCall)95 public void answerCall(UiCall uiCall) { 96 if (Log.isLoggable(TAG, Log.DEBUG)) { 97 Log.d(TAG, "answerCall: " + uiCall); 98 } 99 100 Call telecomCall = mCallList.getTelecomCall(uiCall); 101 if (telecomCall != null) { 102 telecomCall.answer(0); 103 } 104 } 105 106 @Override rejectCall(UiCall uiCall, boolean rejectWithMessage, String textMessage)107 public void rejectCall(UiCall uiCall, boolean rejectWithMessage, String textMessage) { 108 if (Log.isLoggable(TAG, Log.DEBUG)) { 109 Log.d(TAG, "rejectCall: " + uiCall + ", rejectWithMessage: " + rejectWithMessage 110 + "textMessage: " + textMessage); 111 } 112 113 Call telecomCall = mCallList.getTelecomCall(uiCall); 114 if (telecomCall != null) { 115 telecomCall.reject(rejectWithMessage, textMessage); 116 } 117 } 118 119 @Override disconnectCall(UiCall uiCall)120 public void disconnectCall(UiCall uiCall) { 121 if (Log.isLoggable(TAG, Log.DEBUG)) { 122 Log.d(TAG, "disconnectCall: " + uiCall); 123 } 124 125 Call telecomCall = mCallList.getTelecomCall(uiCall); 126 if (telecomCall != null) { 127 telecomCall.disconnect(); 128 } 129 } 130 131 @Override getCalls()132 public List<UiCall> getCalls() { 133 return mCallList.getCalls(); 134 } 135 136 @Override getMuted()137 public boolean getMuted() { 138 if (Log.isLoggable(TAG, Log.DEBUG)) { 139 Log.d(TAG, "getMuted"); 140 } 141 if (mInCallService == null) { 142 return false; 143 } 144 CallAudioState audioState = mInCallService.getCallAudioState(); 145 return audioState != null && audioState.isMuted(); 146 } 147 148 @Override setMuted(boolean muted)149 public void setMuted(boolean muted) { 150 if (Log.isLoggable(TAG, Log.DEBUG)) { 151 Log.d(TAG, "setMuted: " + muted); 152 } 153 if (mInCallService == null) { 154 return; 155 } 156 mInCallService.setMuted(muted); 157 } 158 159 @Override getSupportedAudioRouteMask()160 public int getSupportedAudioRouteMask() { 161 if (Log.isLoggable(TAG, Log.DEBUG)) { 162 Log.d(TAG, "getSupportedAudioRouteMask"); 163 } 164 165 CallAudioState audioState = getCallAudioStateOrNull(); 166 return audioState != null ? audioState.getSupportedRouteMask() : 0; 167 } 168 169 @Override getAudioRoute()170 public int getAudioRoute() { 171 CallAudioState audioState = getCallAudioStateOrNull(); 172 int audioRoute = audioState != null ? audioState.getRoute() : 0; 173 if (Log.isLoggable(TAG, Log.DEBUG)) { 174 Log.d(TAG, "getAudioRoute " + audioRoute); 175 } 176 return audioRoute; 177 } 178 179 @Override setAudioRoute(int audioRoute)180 public void setAudioRoute(int audioRoute) { 181 // In case of embedded where the CarKitt is always connected to one kind of speaker we 182 // should simply ignore any setAudioRoute requests. 183 Log.w(TAG, "setAudioRoute ignoring request " + audioRoute); 184 } 185 186 @Override holdCall(UiCall uiCall)187 public void holdCall(UiCall uiCall) { 188 if (Log.isLoggable(TAG, Log.DEBUG)) { 189 Log.d(TAG, "holdCall: " + uiCall); 190 } 191 192 Call telecomCall = mCallList.getTelecomCall(uiCall); 193 if (telecomCall != null) { 194 telecomCall.hold(); 195 } 196 } 197 198 @Override unholdCall(UiCall uiCall)199 public void unholdCall(UiCall uiCall) { 200 if (Log.isLoggable(TAG, Log.DEBUG)) { 201 Log.d(TAG, "unholdCall: " + uiCall); 202 } 203 204 Call telecomCall = mCallList.getTelecomCall(uiCall); 205 if (telecomCall != null) { 206 telecomCall.unhold(); 207 } 208 } 209 210 @Override playDtmfTone(UiCall uiCall, char digit)211 public void playDtmfTone(UiCall uiCall, char digit) { 212 if (Log.isLoggable(TAG, Log.DEBUG)) { 213 Log.d(TAG, "playDtmfTone: call: " + uiCall + ", digit: " + digit); 214 } 215 216 Call telecomCall = mCallList.getTelecomCall(uiCall); 217 if (telecomCall != null) { 218 telecomCall.playDtmfTone(digit); 219 } 220 } 221 222 @Override stopDtmfTone(UiCall uiCall)223 public void stopDtmfTone(UiCall uiCall) { 224 if (Log.isLoggable(TAG, Log.DEBUG)) { 225 Log.d(TAG, "stopDtmfTone: call: " + uiCall); 226 } 227 228 Call telecomCall = mCallList.getTelecomCall(uiCall); 229 if (telecomCall != null) { 230 telecomCall.stopDtmfTone(); 231 } 232 } 233 234 @Override postDialContinue(UiCall uiCall, boolean proceed)235 public void postDialContinue(UiCall uiCall, boolean proceed) { 236 if (Log.isLoggable(TAG, Log.DEBUG)) { 237 Log.d(TAG, "postDialContinue: call: " + uiCall + ", proceed: " + proceed); 238 } 239 240 Call telecomCall = mCallList.getTelecomCall(uiCall); 241 if (telecomCall != null) { 242 telecomCall.postDialContinue(proceed); 243 } 244 } 245 246 @Override conference(UiCall uiCall, UiCall otherUiCall)247 public void conference(UiCall uiCall, UiCall otherUiCall) { 248 if (Log.isLoggable(TAG, Log.DEBUG)) { 249 Log.d(TAG, "conference: call: " + uiCall + ", otherCall: " + otherUiCall); 250 } 251 252 Call telecomCall = mCallList.getTelecomCall(uiCall); 253 Call otherTelecomCall = mCallList.getTelecomCall(otherUiCall); 254 if (telecomCall != null) { 255 telecomCall.conference(otherTelecomCall); 256 } 257 } 258 259 @Override splitFromConference(UiCall uiCall)260 public void splitFromConference(UiCall uiCall) { 261 if (Log.isLoggable(TAG, Log.DEBUG)) { 262 Log.d(TAG, "splitFromConference: call: " + uiCall); 263 } 264 265 Call telecomCall = mCallList.getTelecomCall(uiCall); 266 if (telecomCall != null) { 267 telecomCall.splitFromConference(); 268 } 269 } 270 doTelecomCallAdded(final Call telecomCall)271 private UiCall doTelecomCallAdded(final Call telecomCall) { 272 Log.d(TAG, "doTelecomCallAdded: " + telecomCall); 273 274 UiCall uiCall = getOrCreateCallContainer(telecomCall); 275 telecomCall.registerCallback(new TelecomCallListener(this, uiCall)); 276 for (CallListener listener : mCallListeners) { 277 listener.onCallAdded(uiCall); 278 } 279 Log.d(TAG, "Call backs registered"); 280 281 if (telecomCall.getState() == Call.STATE_SELECT_PHONE_ACCOUNT) { 282 // TODO(b/26189994): need to show Phone Account picker to let user choose a phone 283 // account. It should be an account from TelecomManager#getCallCapablePhoneAccounts 284 // list. 285 Log.w(TAG, "Need to select phone account for the given call: " + telecomCall + ", " 286 + "but this feature is not implemented yet."); 287 telecomCall.disconnect(); 288 } 289 return uiCall; 290 } 291 doTelecomCallRemoved(Call telecomCall)292 private void doTelecomCallRemoved(Call telecomCall) { 293 UiCall uiCall = getOrCreateCallContainer(telecomCall); 294 295 mCallList.remove(uiCall); 296 297 for (CallListener listener : mCallListeners) { 298 listener.onCallRemoved(uiCall); 299 } 300 } 301 doCallAudioStateChanged(CallAudioState audioState)302 private void doCallAudioStateChanged(CallAudioState audioState) { 303 for (CallListener listener : mCallListeners) { 304 listener.onAudioStateChanged(audioState.isMuted(), audioState.getRoute(), 305 audioState.getSupportedRouteMask()); 306 } 307 } 308 onStateChanged(UiCall uiCall, int state)309 private void onStateChanged(UiCall uiCall, int state) { 310 for (CallListener listener : mCallListeners) { 311 listener.onStateChanged(uiCall, state); 312 } 313 } 314 onCallUpdated(UiCall uiCall)315 private void onCallUpdated(UiCall uiCall) { 316 for (CallListener listener : mCallListeners) { 317 listener.onCallUpdated(uiCall); 318 } 319 } 320 321 private static class TelecomCallListener extends Call.Callback { 322 private final WeakReference<TelecomUiCallManager> mCarTelecomMangerRef; 323 private final WeakReference<UiCall> mCallContainerRef; 324 TelecomCallListener(TelecomUiCallManager carTelecomManager, UiCall uiCall)325 TelecomCallListener(TelecomUiCallManager carTelecomManager, UiCall uiCall) { 326 mCarTelecomMangerRef = new WeakReference<>(carTelecomManager); 327 mCallContainerRef = new WeakReference<>(uiCall); 328 } 329 330 @Override onStateChanged(Call telecomCall, int state)331 public void onStateChanged(Call telecomCall, int state) { 332 if (Log.isLoggable(TAG, Log.DEBUG)) { 333 Log.d(TAG, "onStateChanged: " + state); 334 } 335 TelecomUiCallManager manager = mCarTelecomMangerRef.get(); 336 UiCall call = mCallContainerRef.get(); 337 if (manager != null && call != null) { 338 call.setState(state); 339 manager.onStateChanged(call, state); 340 } 341 } 342 343 @Override onParentChanged(Call telecomCall, Call parent)344 public void onParentChanged(Call telecomCall, Call parent) { 345 doCallUpdated(telecomCall); 346 } 347 348 @Override onCallDestroyed(Call telecomCall)349 public void onCallDestroyed(Call telecomCall) { 350 if (Log.isLoggable(TAG, Log.DEBUG)) { 351 Log.d(TAG, "onCallDestroyed"); 352 } 353 } 354 355 @Override onDetailsChanged(Call telecomCall, Details details)356 public void onDetailsChanged(Call telecomCall, Details details) { 357 doCallUpdated(telecomCall); 358 } 359 360 @Override onVideoCallChanged(Call telecomCall, VideoCall videoCall)361 public void onVideoCallChanged(Call telecomCall, VideoCall videoCall) { 362 doCallUpdated(telecomCall); 363 } 364 365 @Override onCannedTextResponsesLoaded(Call telecomCall, List<String> cannedTextResponses)366 public void onCannedTextResponsesLoaded(Call telecomCall, 367 List<String> cannedTextResponses) { 368 doCallUpdated(telecomCall); 369 } 370 371 @Override onChildrenChanged(Call telecomCall, List<Call> children)372 public void onChildrenChanged(Call telecomCall, List<Call> children) { 373 doCallUpdated(telecomCall); 374 } 375 doCallUpdated(Call telecomCall)376 private void doCallUpdated(Call telecomCall) { 377 TelecomUiCallManager manager = mCarTelecomMangerRef.get(); 378 UiCall uiCall = mCallContainerRef.get(); 379 if (manager != null && uiCall != null) { 380 TelecomUiCallList.updateCallContainerFromTelecom(uiCall, telecomCall); 381 manager.onCallUpdated(uiCall); 382 } 383 } 384 } 385 386 private ServiceConnection mInCallServiceConnection = new ServiceConnection() { 387 388 @Override 389 public void onServiceConnected(ComponentName name, IBinder binder) { 390 if (Log.isLoggable(TAG, Log.DEBUG)) { 391 Log.d(TAG, "onServiceConnected: " + name + ", service: " + binder); 392 } 393 mInCallService = ((InCallServiceImpl.LocalBinder) binder).getService(); 394 mInCallService.registerCallback(mInCallServiceCallback); 395 396 // The InCallServiceImpl could be bound when we already have some active calls, let's 397 // notify UI about these calls. 398 for (Call telecomCall : mInCallService.getCalls()) { 399 UiCall uiCall = doTelecomCallAdded(telecomCall); 400 onStateChanged(uiCall, uiCall.getState()); 401 } 402 } 403 404 @Override 405 public void onServiceDisconnected(ComponentName name) { 406 if (Log.isLoggable(TAG, Log.DEBUG)) { 407 Log.d(TAG, "onServiceDisconnected: " + name); 408 } 409 mInCallService.unregisterCallback(mInCallServiceCallback); 410 } 411 412 private InCallServiceImpl.Callback mInCallServiceCallback = 413 new InCallServiceImpl.Callback() { 414 415 @Override 416 public void onTelecomCallAdded(Call telecomCall) { 417 doTelecomCallAdded(telecomCall); 418 } 419 420 @Override 421 public void onTelecomCallRemoved(Call telecomCall) { 422 doTelecomCallRemoved(telecomCall); 423 } 424 425 @Override 426 public void onCallAudioStateChanged(CallAudioState audioState) { 427 doCallAudioStateChanged(audioState); 428 } 429 }; 430 }; 431 getOrCreateCallContainer(Call telecomCall)432 private UiCall getOrCreateCallContainer(Call telecomCall) { 433 synchronized (mCallList) { 434 return mCallList.getOrCreate(telecomCall); 435 } 436 } 437 getCallAudioStateOrNull()438 private CallAudioState getCallAudioStateOrNull() { 439 return mInCallService != null ? mInCallService.getCallAudioState() : null; 440 } 441 } 442