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; 17 18 import android.content.ComponentName; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.ServiceConnection; 22 import android.database.Cursor; 23 import android.net.Uri; 24 import android.os.IBinder; 25 import android.provider.CallLog; 26 import android.telecom.Call; 27 import android.telecom.CallAudioState; 28 import android.telecom.DisconnectCause; 29 import android.telecom.GatewayInfo; 30 import android.telecom.InCallService; 31 import android.telecom.TelecomManager; 32 import android.telephony.TelephonyManager; 33 import android.text.TextUtils; 34 import android.util.Log; 35 import android.view.KeyEvent; 36 37 import com.android.car.dialer.R; 38 39 import java.lang.ref.WeakReference; 40 import java.util.ArrayList; 41 import java.util.Calendar; 42 import java.util.Collections; 43 import java.util.Comparator; 44 import java.util.HashMap; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.concurrent.CopyOnWriteArrayList; 48 49 /** 50 * The entry point for all interactions between UI and telecom. 51 */ 52 public class UiCallManager { 53 private static String TAG = "Em.TelecomMgr"; 54 55 // Rate limit how often you can place outgoing calls. 56 private static final long MIN_TIME_BETWEEN_CALLS_MS = 3000; 57 private static final List<Integer> sCallStateRank = new ArrayList<>(); 58 59 // Used to assign id's to UiCall objects as they're created. 60 private static int nextCarPhoneCallId = 0; 61 62 static { 63 // States should be added from lowest rank to highest 64 sCallStateRank.add(Call.STATE_DISCONNECTED); 65 sCallStateRank.add(Call.STATE_DISCONNECTING); 66 sCallStateRank.add(Call.STATE_NEW); 67 sCallStateRank.add(Call.STATE_CONNECTING); 68 sCallStateRank.add(Call.STATE_SELECT_PHONE_ACCOUNT); 69 sCallStateRank.add(Call.STATE_HOLDING); 70 sCallStateRank.add(Call.STATE_ACTIVE); 71 sCallStateRank.add(Call.STATE_DIALING); 72 sCallStateRank.add(Call.STATE_RINGING); 73 } 74 75 private Context mContext; 76 private TelephonyManager mTelephonyManager; 77 private long mLastPlacedCallTimeMs; 78 79 private TelecomManager mTelecomManager; 80 private InCallServiceImpl mInCallService; 81 private final Map<UiCall, Call> mCallMapping = new HashMap<>(); 82 private final List<CallListener> mCallListeners = new CopyOnWriteArrayList<>(); 83 84 public UiCallManager(Context context) { 85 if (Log.isLoggable(TAG, Log.DEBUG)) { 86 Log.d(TAG, "SetUp"); 87 } 88 89 mContext = context; 90 mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); 91 92 mTelecomManager = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); 93 Intent intent = new Intent(context, InCallServiceImpl.class); 94 intent.setAction(InCallServiceImpl.ACTION_LOCAL_BIND); 95 context.bindService(intent, mInCallServiceConnection, Context.BIND_AUTO_CREATE); 96 } 97 98 private final ServiceConnection mInCallServiceConnection = new ServiceConnection() { 99 100 @Override 101 public void onServiceConnected(ComponentName name, IBinder binder) { 102 if (Log.isLoggable(TAG, Log.DEBUG)) { 103 Log.d(TAG, "onServiceConnected: " + name + ", service: " + binder); 104 } 105 mInCallService = ((InCallServiceImpl.LocalBinder) binder).getService(); 106 mInCallService.registerCallback(mInCallServiceCallback); 107 108 // The InCallServiceImpl could be bound when we already have some active calls, let's 109 // notify UI about these calls. 110 for (Call telecomCall : mInCallService.getCalls()) { 111 UiCall uiCall = doTelecomCallAdded(telecomCall); 112 onStateChanged(uiCall, uiCall.getState()); 113 } 114 } 115 116 @Override 117 public void onServiceDisconnected(ComponentName name) { 118 if (Log.isLoggable(TAG, Log.DEBUG)) { 119 Log.d(TAG, "onServiceDisconnected: " + name); 120 } 121 mInCallService.unregisterCallback(mInCallServiceCallback); 122 } 123 124 private InCallServiceImpl.Callback mInCallServiceCallback = 125 new InCallServiceImpl.Callback() { 126 @Override 127 public void onTelecomCallAdded(Call telecomCall) { 128 doTelecomCallAdded(telecomCall); 129 } 130 131 @Override 132 public void onTelecomCallRemoved(Call telecomCall) { 133 doTelecomCallRemoved(telecomCall); 134 } 135 136 @Override 137 public void onCallAudioStateChanged(CallAudioState audioState) { 138 doCallAudioStateChanged(audioState); 139 } 140 }; 141 }; 142 143 public void tearDown() { 144 if (mInCallService != null) { 145 mContext.unbindService(mInCallServiceConnection); 146 mInCallService = null; 147 } 148 mCallMapping.clear(); 149 } 150 151 public void addListener(CallListener listener) { 152 if (Log.isLoggable(TAG, Log.DEBUG)) { 153 Log.d(TAG, "addListener: " + listener); 154 } 155 mCallListeners.add(listener); 156 } 157 158 public void removeListener(CallListener listener) { 159 if (Log.isLoggable(TAG, Log.DEBUG)) { 160 Log.d(TAG, "removeListener: " + listener); 161 } 162 mCallListeners.remove(listener); 163 } 164 165 protected void placeCall(String number) { 166 if (Log.isLoggable(TAG, Log.DEBUG)) { 167 Log.d(TAG, "placeCall: " + number); 168 } 169 Uri uri = Uri.fromParts("tel", number, null); 170 Log.d(TAG, "android.telecom.TelecomManager#placeCall: " + uri); 171 mTelecomManager.placeCall(uri, null); 172 } 173 174 public void answerCall(UiCall uiCall) { 175 if (Log.isLoggable(TAG, Log.DEBUG)) { 176 Log.d(TAG, "answerCall: " + uiCall); 177 } 178 179 Call telecomCall = mCallMapping.get(uiCall); 180 if (telecomCall != null) { 181 telecomCall.answer(0); 182 } 183 } 184 185 public void rejectCall(UiCall uiCall, boolean rejectWithMessage, String textMessage) { 186 if (Log.isLoggable(TAG, Log.DEBUG)) { 187 Log.d(TAG, "rejectCall: " + uiCall + ", rejectWithMessage: " + rejectWithMessage 188 + "textMessage: " + textMessage); 189 } 190 191 Call telecomCall = mCallMapping.get(uiCall); 192 if (telecomCall != null) { 193 telecomCall.reject(rejectWithMessage, textMessage); 194 } 195 } 196 197 public void disconnectCall(UiCall uiCall) { 198 if (Log.isLoggable(TAG, Log.DEBUG)) { 199 Log.d(TAG, "disconnectCall: " + uiCall); 200 } 201 202 Call telecomCall = mCallMapping.get(uiCall); 203 if (telecomCall != null) { 204 telecomCall.disconnect(); 205 } 206 } 207 208 public List<UiCall> getCalls() { 209 return new ArrayList<>(mCallMapping.keySet()); 210 } 211 212 public boolean getMuted() { 213 if (Log.isLoggable(TAG, Log.DEBUG)) { 214 Log.d(TAG, "getMuted"); 215 } 216 if (mInCallService == null) { 217 return false; 218 } 219 CallAudioState audioState = mInCallService.getCallAudioState(); 220 return audioState != null && audioState.isMuted(); 221 } 222 223 public void setMuted(boolean muted) { 224 if (Log.isLoggable(TAG, Log.DEBUG)) { 225 Log.d(TAG, "setMuted: " + muted); 226 } 227 if (mInCallService == null) { 228 return; 229 } 230 mInCallService.setMuted(muted); 231 } 232 233 public int getSupportedAudioRouteMask() { 234 if (Log.isLoggable(TAG, Log.DEBUG)) { 235 Log.d(TAG, "getSupportedAudioRouteMask"); 236 } 237 238 CallAudioState audioState = getCallAudioStateOrNull(); 239 return audioState != null ? audioState.getSupportedRouteMask() : 0; 240 } 241 242 public int getAudioRoute() { 243 CallAudioState audioState = getCallAudioStateOrNull(); 244 int audioRoute = audioState != null ? audioState.getRoute() : 0; 245 if (Log.isLoggable(TAG, Log.DEBUG)) { 246 Log.d(TAG, "getAudioRoute " + audioRoute); 247 } 248 return audioRoute; 249 } 250 251 public void setAudioRoute(int audioRoute) { 252 // In case of embedded where the CarKitt is always connected to one kind of speaker we 253 // should simply ignore any setAudioRoute requests. 254 Log.w(TAG, "setAudioRoute ignoring request " + audioRoute); 255 } 256 257 public void holdCall(UiCall uiCall) { 258 if (Log.isLoggable(TAG, Log.DEBUG)) { 259 Log.d(TAG, "holdCall: " + uiCall); 260 } 261 262 Call telecomCall = mCallMapping.get(uiCall); 263 if (telecomCall != null) { 264 telecomCall.hold(); 265 } 266 } 267 268 public void unholdCall(UiCall uiCall) { 269 if (Log.isLoggable(TAG, Log.DEBUG)) { 270 Log.d(TAG, "unholdCall: " + uiCall); 271 } 272 273 Call telecomCall = mCallMapping.get(uiCall); 274 if (telecomCall != null) { 275 telecomCall.unhold(); 276 } 277 } 278 279 public void playDtmfTone(UiCall uiCall, char digit) { 280 if (Log.isLoggable(TAG, Log.DEBUG)) { 281 Log.d(TAG, "playDtmfTone: call: " + uiCall + ", digit: " + digit); 282 } 283 284 Call telecomCall = mCallMapping.get(uiCall); 285 if (telecomCall != null) { 286 telecomCall.playDtmfTone(digit); 287 } 288 } 289 290 public void stopDtmfTone(UiCall uiCall) { 291 if (Log.isLoggable(TAG, Log.DEBUG)) { 292 Log.d(TAG, "stopDtmfTone: call: " + uiCall); 293 } 294 295 Call telecomCall = mCallMapping.get(uiCall); 296 if (telecomCall != null) { 297 telecomCall.stopDtmfTone(); 298 } 299 } 300 301 public void postDialContinue(UiCall uiCall, boolean proceed) { 302 if (Log.isLoggable(TAG, Log.DEBUG)) { 303 Log.d(TAG, "postDialContinue: call: " + uiCall + ", proceed: " + proceed); 304 } 305 306 Call telecomCall = mCallMapping.get(uiCall); 307 if (telecomCall != null) { 308 telecomCall.postDialContinue(proceed); 309 } 310 } 311 312 public void conference(UiCall uiCall, UiCall otherUiCall) { 313 if (Log.isLoggable(TAG, Log.DEBUG)) { 314 Log.d(TAG, "conference: call: " + uiCall + ", otherCall: " + otherUiCall); 315 } 316 317 Call telecomCall = mCallMapping.get(uiCall); 318 Call otherTelecomCall = mCallMapping.get(otherUiCall); 319 if (telecomCall != null) { 320 telecomCall.conference(otherTelecomCall); 321 } 322 } 323 324 public void splitFromConference(UiCall uiCall) { 325 if (Log.isLoggable(TAG, Log.DEBUG)) { 326 Log.d(TAG, "splitFromConference: call: " + uiCall); 327 } 328 329 Call telecomCall = mCallMapping.get(uiCall); 330 if (telecomCall != null) { 331 telecomCall.splitFromConference(); 332 } 333 } 334 335 private UiCall doTelecomCallAdded(final Call telecomCall) { 336 Log.d(TAG, "doTelecomCallAdded: " + telecomCall); 337 338 UiCall uiCall = getOrCreateCallContainer(telecomCall); 339 telecomCall.registerCallback(new TelecomCallListener(this, uiCall)); 340 for (CallListener listener : mCallListeners) { 341 listener.onCallAdded(uiCall); 342 } 343 Log.d(TAG, "Call backs registered"); 344 345 if (telecomCall.getState() == Call.STATE_SELECT_PHONE_ACCOUNT) { 346 // TODO(b/26189994): need to show Phone Account picker to let user choose a phone 347 // account. It should be an account from TelecomManager#getCallCapablePhoneAccounts 348 // list. 349 Log.w(TAG, "Need to select phone account for the given call: " + telecomCall + ", " 350 + "but this feature is not implemented yet."); 351 telecomCall.disconnect(); 352 } 353 return uiCall; 354 } 355 356 private void doTelecomCallRemoved(Call telecomCall) { 357 UiCall uiCall = getOrCreateCallContainer(telecomCall); 358 359 mCallMapping.remove(uiCall); 360 361 for (CallListener listener : mCallListeners) { 362 listener.onCallRemoved(uiCall); 363 } 364 } 365 366 private void doCallAudioStateChanged(CallAudioState audioState) { 367 for (CallListener listener : mCallListeners) { 368 listener.onAudioStateChanged(audioState.isMuted(), audioState.getRoute(), 369 audioState.getSupportedRouteMask()); 370 } 371 } 372 373 private void onStateChanged(UiCall uiCall, int state) { 374 for (CallListener listener : mCallListeners) { 375 listener.onStateChanged(uiCall, state); 376 } 377 } 378 379 private void onCallUpdated(UiCall uiCall) { 380 for (CallListener listener : mCallListeners) { 381 listener.onCallUpdated(uiCall); 382 } 383 } 384 385 private UiCall getOrCreateCallContainer(Call telecomCall) { 386 for (Map.Entry<UiCall, Call> entry : mCallMapping.entrySet()) { 387 if (entry.getValue() == telecomCall) { 388 return entry.getKey(); 389 } 390 } 391 392 UiCall uiCall = new UiCall(nextCarPhoneCallId++); 393 updateCallContainerFromTelecom(uiCall, telecomCall); 394 mCallMapping.put(uiCall, telecomCall); 395 return uiCall; 396 } 397 398 private static void updateCallContainerFromTelecom(UiCall uiCall, Call telecomCall) { 399 if (Log.isLoggable(TAG, Log.DEBUG)) { 400 Log.d(TAG, "updateCallContainerFromTelecom: call: " + uiCall + ", telecomCall: " 401 + telecomCall); 402 } 403 404 uiCall.setState(telecomCall.getState()); 405 uiCall.setHasChildren(!telecomCall.getChildren().isEmpty()); 406 uiCall.setHasParent(telecomCall.getParent() != null); 407 408 Call.Details details = telecomCall.getDetails(); 409 if (details == null) { 410 return; 411 } 412 413 uiCall.setConnectTimeMillis(details.getConnectTimeMillis()); 414 415 DisconnectCause cause = details.getDisconnectCause(); 416 uiCall.setDisconnectCause(cause == null ? null : cause.getLabel()); 417 418 GatewayInfo gatewayInfo = details.getGatewayInfo(); 419 uiCall.setGatewayInfoOriginalAddress( 420 gatewayInfo == null ? null : gatewayInfo.getOriginalAddress()); 421 422 String number = ""; 423 if (gatewayInfo != null) { 424 number = gatewayInfo.getOriginalAddress().getSchemeSpecificPart(); 425 } else if (details.getHandle() != null) { 426 number = details.getHandle().getSchemeSpecificPart(); 427 } 428 uiCall.setNumber(number); 429 } 430 431 private CallAudioState getCallAudioStateOrNull() { 432 return mInCallService != null ? mInCallService.getCallAudioState() : null; 433 } 434 435 public static class CallListener { 436 @SuppressWarnings("unused") 437 public void dispatchPhoneKeyEvent(KeyEvent event) {} 438 @SuppressWarnings("unused") 439 public void onAudioStateChanged(boolean isMuted, int route, int supportedRouteMask) {} 440 @SuppressWarnings("unused") 441 public void onCallAdded(UiCall call) {} 442 @SuppressWarnings("unused") 443 public void onStateChanged(UiCall call, int state) {} 444 @SuppressWarnings("unused") 445 public void onCallUpdated(UiCall call) {} 446 @SuppressWarnings("unused") 447 public void onCallRemoved(UiCall call) {} 448 } 449 450 /** Returns a first call that matches at least one provided call state */ 451 public UiCall getCallWithState(int... callStates) { 452 if (Log.isLoggable(TAG, Log.DEBUG)) { 453 Log.d(TAG, "getCallWithState: " + callStates); 454 } 455 for (UiCall call : getCalls()) { 456 for (int callState : callStates) { 457 if (call.getState() == callState) { 458 return call; 459 } 460 } 461 } 462 return null; 463 } 464 465 public UiCall getPrimaryCall() { 466 if (Log.isLoggable(TAG, Log.DEBUG)) { 467 Log.d(TAG, "getPrimaryCall"); 468 } 469 List<UiCall> calls = getCalls(); 470 if (calls.isEmpty()) { 471 return null; 472 } 473 474 Collections.sort(calls, getCallComparator()); 475 UiCall uiCall = calls.get(0); 476 if (uiCall.hasParent()) { 477 return null; 478 } 479 return uiCall; 480 } 481 482 public UiCall getSecondaryCall() { 483 if (Log.isLoggable(TAG, Log.DEBUG)) { 484 Log.d(TAG, "getSecondaryCall"); 485 } 486 List<UiCall> calls = getCalls(); 487 if (calls.size() < 2) { 488 return null; 489 } 490 491 Collections.sort(calls, getCallComparator()); 492 UiCall uiCall = calls.get(1); 493 if (uiCall.hasParent()) { 494 return null; 495 } 496 return uiCall; 497 } 498 499 public static final int CAN_PLACE_CALL_RESULT_OK = 0; 500 public static final int CAN_PLACE_CALL_RESULT_NETWORK_UNAVAILABLE = 1; 501 public static final int CAN_PLACE_CALL_RESULT_HFP_UNAVAILABLE = 2; 502 public static final int CAN_PLACE_CALL_RESULT_AIRPLANE_MODE = 3; 503 504 public int getCanPlaceCallStatus(String number, boolean bluetoothRequired) { 505 // TODO(b/26191392): figure out the logic for projected and embedded modes 506 return CAN_PLACE_CALL_RESULT_OK; 507 } 508 509 public String getFailToPlaceCallMessage(int canPlaceCallResult) { 510 switch (canPlaceCallResult) { 511 case CAN_PLACE_CALL_RESULT_OK: 512 return ""; 513 case CAN_PLACE_CALL_RESULT_HFP_UNAVAILABLE: 514 return mContext.getString(R.string.error_no_hfp); 515 case CAN_PLACE_CALL_RESULT_AIRPLANE_MODE: 516 return mContext.getString(R.string.error_airplane_mode); 517 case CAN_PLACE_CALL_RESULT_NETWORK_UNAVAILABLE: 518 default: 519 return mContext.getString(R.string.error_network_not_available); 520 } 521 } 522 523 /** Places call only if there's no outgoing call right now */ 524 public void safePlaceCall(String number, boolean bluetoothRequired) { 525 if (Log.isLoggable(TAG, Log.DEBUG)) { 526 Log.d(TAG, "safePlaceCall: " + number); 527 } 528 529 int placeCallStatus = getCanPlaceCallStatus(number, bluetoothRequired); 530 if (placeCallStatus != CAN_PLACE_CALL_RESULT_OK) { 531 if (Log.isLoggable(TAG, Log.DEBUG)) { 532 Log.d(TAG, "Unable to place a call: " + placeCallStatus); 533 } 534 return; 535 } 536 537 UiCall outgoingCall = getCallWithState( 538 Call.STATE_CONNECTING, Call.STATE_NEW, Call.STATE_DIALING); 539 if (outgoingCall == null) { 540 long now = Calendar.getInstance().getTimeInMillis(); 541 if (now - mLastPlacedCallTimeMs > MIN_TIME_BETWEEN_CALLS_MS) { 542 placeCall(number); 543 mLastPlacedCallTimeMs = now; 544 } else { 545 if (Log.isLoggable(TAG, Log.INFO)) { 546 Log.i(TAG, "You have to wait " + MIN_TIME_BETWEEN_CALLS_MS 547 + "ms between making calls"); 548 } 549 } 550 } 551 } 552 553 public void callVoicemail() { 554 if (Log.isLoggable(TAG, Log.DEBUG)) { 555 Log.d(TAG, "callVoicemail"); 556 } 557 558 String voicemailNumber = TelecomUtils.getVoicemailNumber(mContext); 559 if (TextUtils.isEmpty(voicemailNumber)) { 560 Log.w(TAG, "Unable to get voicemail number."); 561 return; 562 } 563 safePlaceCall(voicemailNumber, false); 564 } 565 566 /** 567 * Returns the call types for the given number of items in the cursor. 568 * <p/> 569 * It uses the next {@code count} rows in the cursor to extract the types. 570 * <p/> 571 * Its position in the cursor is unchanged by this function. 572 */ 573 public int[] getCallTypes(Cursor cursor, int count) { 574 if (Log.isLoggable(TAG, Log.DEBUG)) { 575 Log.d(TAG, "getCallTypes: cursor: " + cursor + ", count: " + count); 576 } 577 578 int position = cursor.getPosition(); 579 int[] callTypes = new int[count]; 580 String voicemailNumber = mTelephonyManager.getVoiceMailNumber(); 581 int column; 582 for (int index = 0; index < count; ++index) { 583 column = cursor.getColumnIndex(CallLog.Calls.NUMBER); 584 String phoneNumber = cursor.getString(column); 585 if (phoneNumber != null && phoneNumber.equals(voicemailNumber)) { 586 callTypes[index] = PhoneLoader.VOICEMAIL_TYPE; 587 } else { 588 column = cursor.getColumnIndex(CallLog.Calls.TYPE); 589 callTypes[index] = cursor.getInt(column); 590 } 591 cursor.moveToNext(); 592 } 593 cursor.moveToPosition(position); 594 return callTypes; 595 } 596 597 private static Comparator<UiCall> getCallComparator() { 598 return new Comparator<UiCall>() { 599 @Override 600 public int compare(UiCall call, UiCall otherCall) { 601 if (call.hasParent() && !otherCall.hasParent()) { 602 return 1; 603 } else if (!call.hasParent() && otherCall.hasParent()) { 604 return -1; 605 } 606 int carCallRank = sCallStateRank.indexOf(call.getState()); 607 int otherCarCallRank = sCallStateRank.indexOf(otherCall.getState()); 608 609 return otherCarCallRank - carCallRank; 610 } 611 }; 612 } 613 614 private static class TelecomCallListener extends Call.Callback { 615 private final WeakReference<UiCallManager> mCarTelecomMangerRef; 616 private final WeakReference<UiCall> mCallContainerRef; 617 618 TelecomCallListener(UiCallManager carTelecomManager, UiCall uiCall) { 619 mCarTelecomMangerRef = new WeakReference<>(carTelecomManager); 620 mCallContainerRef = new WeakReference<>(uiCall); 621 } 622 623 @Override 624 public void onStateChanged(Call telecomCall, int state) { 625 if (Log.isLoggable(TAG, Log.DEBUG)) { 626 Log.d(TAG, "onStateChanged: " + state); 627 } 628 UiCallManager manager = mCarTelecomMangerRef.get(); 629 UiCall call = mCallContainerRef.get(); 630 if (manager != null && call != null) { 631 call.setState(state); 632 manager.onStateChanged(call, state); 633 } 634 } 635 636 @Override 637 public void onParentChanged(Call telecomCall, Call parent) { 638 doCallUpdated(telecomCall); 639 } 640 641 @Override 642 public void onCallDestroyed(Call telecomCall) { 643 if (Log.isLoggable(TAG, Log.DEBUG)) { 644 Log.d(TAG, "onCallDestroyed"); 645 } 646 } 647 648 @Override 649 public void onDetailsChanged(Call telecomCall, Call.Details details) { 650 doCallUpdated(telecomCall); 651 } 652 653 @Override 654 public void onVideoCallChanged(Call telecomCall, InCallService.VideoCall videoCall) { 655 doCallUpdated(telecomCall); 656 } 657 658 @Override 659 public void onCannedTextResponsesLoaded(Call telecomCall, 660 List<String> cannedTextResponses) { 661 doCallUpdated(telecomCall); 662 } 663 664 @Override 665 public void onChildrenChanged(Call telecomCall, List<Call> children) { 666 doCallUpdated(telecomCall); 667 } 668 669 private void doCallUpdated(Call telecomCall) { 670 UiCallManager manager = mCarTelecomMangerRef.get(); 671 UiCall uiCall = mCallContainerRef.get(); 672 if (manager != null && uiCall != null) { 673 updateCallContainerFromTelecom(uiCall, telecomCall); 674 manager.onCallUpdated(uiCall); 675 } 676 } 677 } 678 } 679