1 /* 2 * Copyright (C) 2013 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.incallui.call; 18 19 import android.Manifest.permission; 20 import android.annotation.SuppressLint; 21 import android.annotation.TargetApi; 22 import android.content.Context; 23 import android.hardware.camera2.CameraCharacteristics; 24 import android.net.Uri; 25 import android.os.Build; 26 import android.os.Build.VERSION; 27 import android.os.Build.VERSION_CODES; 28 import android.os.Bundle; 29 import android.os.PersistableBundle; 30 import android.os.SystemClock; 31 import android.os.Trace; 32 import android.support.annotation.IntDef; 33 import android.support.annotation.NonNull; 34 import android.support.annotation.Nullable; 35 import android.support.annotation.VisibleForTesting; 36 import android.support.v4.os.BuildCompat; 37 import android.telecom.Call; 38 import android.telecom.Call.Details; 39 import android.telecom.Call.RttCall; 40 import android.telecom.CallAudioState; 41 import android.telecom.Connection; 42 import android.telecom.DisconnectCause; 43 import android.telecom.GatewayInfo; 44 import android.telecom.InCallService.VideoCall; 45 import android.telecom.PhoneAccount; 46 import android.telecom.PhoneAccountHandle; 47 import android.telecom.StatusHints; 48 import android.telecom.TelecomManager; 49 import android.telecom.VideoProfile; 50 import android.text.TextUtils; 51 import android.widget.Toast; 52 import com.android.contacts.common.compat.CallCompat; 53 import com.android.dialer.assisteddialing.ConcreteCreator; 54 import com.android.dialer.assisteddialing.TransformationInfo; 55 import com.android.dialer.blocking.FilteredNumbersUtil; 56 import com.android.dialer.callintent.CallInitiationType; 57 import com.android.dialer.callintent.CallIntentParser; 58 import com.android.dialer.callintent.CallSpecificAppData; 59 import com.android.dialer.common.Assert; 60 import com.android.dialer.common.LogUtil; 61 import com.android.dialer.common.concurrent.DefaultFutureCallback; 62 import com.android.dialer.compat.telephony.TelephonyManagerCompat; 63 import com.android.dialer.configprovider.ConfigProviderComponent; 64 import com.android.dialer.duo.DuoComponent; 65 import com.android.dialer.enrichedcall.EnrichedCallCapabilities; 66 import com.android.dialer.enrichedcall.EnrichedCallComponent; 67 import com.android.dialer.enrichedcall.EnrichedCallManager; 68 import com.android.dialer.enrichedcall.EnrichedCallManager.CapabilitiesListener; 69 import com.android.dialer.enrichedcall.EnrichedCallManager.Filter; 70 import com.android.dialer.enrichedcall.EnrichedCallManager.StateChangedListener; 71 import com.android.dialer.enrichedcall.Session; 72 import com.android.dialer.location.GeoUtil; 73 import com.android.dialer.logging.ContactLookupResult; 74 import com.android.dialer.logging.ContactLookupResult.Type; 75 import com.android.dialer.logging.DialerImpression; 76 import com.android.dialer.logging.Logger; 77 import com.android.dialer.preferredsim.PreferredAccountRecorder; 78 import com.android.dialer.rtt.RttTranscript; 79 import com.android.dialer.rtt.RttTranscriptUtil; 80 import com.android.dialer.spam.status.SpamStatus; 81 import com.android.dialer.telecom.TelecomCallUtil; 82 import com.android.dialer.telecom.TelecomUtil; 83 import com.android.dialer.theme.common.R; 84 import com.android.dialer.time.Clock; 85 import com.android.dialer.util.PermissionsUtil; 86 import com.android.incallui.audiomode.AudioModeProvider; 87 import com.android.incallui.call.state.DialerCallState; 88 import com.android.incallui.latencyreport.LatencyReport; 89 import com.android.incallui.rtt.protocol.RttChatMessage; 90 import com.android.incallui.videotech.VideoTech; 91 import com.android.incallui.videotech.VideoTech.VideoTechListener; 92 import com.android.incallui.videotech.duo.DuoVideoTech; 93 import com.android.incallui.videotech.empty.EmptyVideoTech; 94 import com.android.incallui.videotech.ims.ImsVideoTech; 95 import com.android.incallui.videotech.utils.VideoUtils; 96 import com.google.common.base.Optional; 97 import com.google.common.util.concurrent.Futures; 98 import com.google.common.util.concurrent.MoreExecutors; 99 import java.io.IOException; 100 import java.lang.annotation.Retention; 101 import java.lang.annotation.RetentionPolicy; 102 import java.util.ArrayList; 103 import java.util.List; 104 import java.util.Locale; 105 import java.util.Objects; 106 import java.util.UUID; 107 import java.util.concurrent.CopyOnWriteArrayList; 108 import java.util.concurrent.TimeUnit; 109 110 /** Describes a single call and its state. */ 111 public class DialerCall implements VideoTechListener, StateChangedListener, CapabilitiesListener { 112 113 public static final int CALL_HISTORY_STATUS_UNKNOWN = 0; 114 public static final int CALL_HISTORY_STATUS_PRESENT = 1; 115 public static final int CALL_HISTORY_STATUS_NOT_PRESENT = 2; 116 117 // Hard coded property for {@code Call}. Upstreamed change from Motorola. 118 // TODO(a bug): Move it to Telecom in framework. 119 public static final int PROPERTY_CODEC_KNOWN = 0x04000000; 120 121 private static final String ID_PREFIX = "DialerCall_"; 122 123 @VisibleForTesting 124 public static final String CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS = 125 "emergency_callback_window_millis"; 126 127 private static int idCounter = 0; 128 129 /** 130 * A counter used to append to restricted/private/hidden calls so that users can identify them in 131 * a conversation. This value is reset in {@link CallList#onCallRemoved(Context, Call)} when there 132 * are no live calls. 133 */ 134 private static int hiddenCounter; 135 136 /** 137 * The unique call ID for every call. This will help us to identify each call and allow us the 138 * ability to stitch impressions to calls if needed. 139 */ 140 private final String uniqueCallId = UUID.randomUUID().toString(); 141 142 private final Call telecomCall; 143 private final LatencyReport latencyReport; 144 private final String id; 145 private final int hiddenId; 146 private final List<String> childCallIds = new ArrayList<>(); 147 private final LogState logState = new LogState(); 148 private final Context context; 149 private final DialerCallDelegate dialerCallDelegate; 150 private final List<DialerCallListener> listeners = new CopyOnWriteArrayList<>(); 151 private final List<CannedTextResponsesLoadedListener> cannedTextResponsesLoadedListeners = 152 new CopyOnWriteArrayList<>(); 153 private final VideoTechManager videoTechManager; 154 155 private boolean isSpeakEasyCall; 156 private boolean isEmergencyCall; 157 private Uri handle; 158 private int state = DialerCallState.INVALID; 159 private DisconnectCause disconnectCause; 160 161 private boolean hasShownLteToWiFiHandoverToast; 162 private boolean hasShownWiFiToLteHandoverToast; 163 private boolean doNotShowDialogForHandoffToWifiFailure; 164 165 private String childNumber; 166 private String lastForwardedNumber; 167 private boolean isCallForwarded; 168 private String callSubject; 169 @Nullable private PhoneAccountHandle phoneAccountHandle; 170 @CallHistoryStatus private int callHistoryStatus = CALL_HISTORY_STATUS_UNKNOWN; 171 172 @Nullable private SpamStatus spamStatus; 173 private boolean isBlocked; 174 175 private boolean didShowCameraPermission; 176 private boolean didDismissVideoChargesAlertDialog; 177 private PersistableBundle carrierConfig; 178 private String callProviderLabel; 179 private String callbackNumber; 180 private int cameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN; 181 private EnrichedCallCapabilities enrichedCallCapabilities; 182 private Session enrichedCallSession; 183 184 private int answerAndReleaseButtonDisplayedTimes = 0; 185 private boolean releasedByAnsweringSecondCall = false; 186 // Times when a second call is received but AnswerAndRelease button is not shown 187 // since it's not supported. 188 private int secondCallWithoutAnswerAndReleasedButtonTimes = 0; 189 private VideoTech videoTech; 190 191 private com.android.dialer.logging.VideoTech.Type selectedAvailableVideoTechType = 192 com.android.dialer.logging.VideoTech.Type.NONE; 193 private boolean isVoicemailNumber; 194 private List<PhoneAccountHandle> callCapableAccounts; 195 private String countryIso; 196 197 private volatile boolean feedbackRequested = false; 198 199 private Clock clock = System::currentTimeMillis; 200 201 @Nullable private PreferredAccountRecorder preferredAccountRecorder; 202 private boolean isCallRemoved; 203 getNumberFromHandle(Uri handle)204 public static String getNumberFromHandle(Uri handle) { 205 return handle == null ? "" : handle.getSchemeSpecificPart(); 206 } 207 208 /** 209 * Whether the call is put on hold by remote party. This is different than the {@link 210 * DialerCallState#ONHOLD} state which indicates that the call is being held locally on the 211 * device. 212 */ 213 private boolean isRemotelyHeld; 214 215 /** Indicates whether this call is currently in the process of being merged into a conference. */ 216 private boolean isMergeInProcess; 217 218 /** 219 * Indicates whether the phone account associated with this call supports specifying a call 220 * subject. 221 */ 222 private boolean isCallSubjectSupported; 223 getRttTranscript()224 public RttTranscript getRttTranscript() { 225 return rttTranscript; 226 } 227 setRttTranscript(RttTranscript rttTranscript)228 public void setRttTranscript(RttTranscript rttTranscript) { 229 this.rttTranscript = rttTranscript; 230 } 231 232 private RttTranscript rttTranscript; 233 234 private final Call.Callback telecomCallCallback = 235 new Call.Callback() { 236 @Override 237 public void onStateChanged(Call call, int newState) { 238 LogUtil.v("TelecomCallCallback.onStateChanged", "call=" + call + " newState=" + newState); 239 update(); 240 } 241 242 @Override 243 public void onParentChanged(Call call, Call newParent) { 244 LogUtil.v( 245 "TelecomCallCallback.onParentChanged", "call=" + call + " newParent=" + newParent); 246 update(); 247 } 248 249 @Override 250 public void onChildrenChanged(Call call, List<Call> children) { 251 update(); 252 } 253 254 @Override 255 public void onDetailsChanged(Call call, Call.Details details) { 256 LogUtil.v( 257 "TelecomCallCallback.onDetailsChanged", " call=" + call + " details=" + details); 258 update(); 259 } 260 261 @Override 262 public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) { 263 LogUtil.v( 264 "TelecomCallCallback.onCannedTextResponsesLoaded", 265 "call=" + call + " cannedTextResponses=" + cannedTextResponses); 266 for (CannedTextResponsesLoadedListener listener : cannedTextResponsesLoadedListeners) { 267 listener.onCannedTextResponsesLoaded(DialerCall.this); 268 } 269 } 270 271 @Override 272 public void onPostDialWait(Call call, String remainingPostDialSequence) { 273 LogUtil.v( 274 "TelecomCallCallback.onPostDialWait", 275 "call=" + call + " remainingPostDialSequence=" + remainingPostDialSequence); 276 update(); 277 } 278 279 @Override 280 public void onVideoCallChanged(Call call, VideoCall videoCall) { 281 LogUtil.v( 282 "TelecomCallCallback.onVideoCallChanged", "call=" + call + " videoCall=" + videoCall); 283 update(); 284 } 285 286 @Override 287 public void onCallDestroyed(Call call) { 288 LogUtil.v("TelecomCallCallback.onCallDestroyed", "call=" + call); 289 unregisterCallback(); 290 } 291 292 @Override 293 public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) { 294 LogUtil.v( 295 "TelecomCallCallback.onConferenceableCallsChanged", 296 "call %s, conferenceable calls: %d", 297 call, 298 conferenceableCalls.size()); 299 update(); 300 } 301 302 @Override 303 public void onRttModeChanged(Call call, int mode) { 304 LogUtil.v("TelecomCallCallback.onRttModeChanged", "mode=%d", mode); 305 } 306 307 @Override 308 public void onRttRequest(Call call, int id) { 309 LogUtil.v("TelecomCallCallback.onRttRequest", "id=%d", id); 310 for (DialerCallListener listener : listeners) { 311 listener.onDialerCallUpgradeToRtt(id); 312 } 313 } 314 315 @Override 316 public void onRttInitiationFailure(Call call, int reason) { 317 LogUtil.v("TelecomCallCallback.onRttInitiationFailure", "reason=%d", reason); 318 Toast.makeText(context, R.string.rtt_call_not_available_toast, Toast.LENGTH_LONG).show(); 319 update(); 320 } 321 322 @Override 323 public void onRttStatusChanged(Call call, boolean enabled, RttCall rttCall) { 324 LogUtil.v("TelecomCallCallback.onRttStatusChanged", "enabled=%b", enabled); 325 if (enabled) { 326 Logger.get(context) 327 .logCallImpression( 328 DialerImpression.Type.RTT_MID_CALL_ENABLED, 329 getUniqueCallId(), 330 getTimeAddedMs()); 331 } 332 update(); 333 } 334 335 @Override 336 public void onConnectionEvent(android.telecom.Call call, String event, Bundle extras) { 337 LogUtil.v( 338 "TelecomCallCallback.onConnectionEvent", 339 "Call: " + call + ", Event: " + event + ", Extras: " + extras); 340 switch (event) { 341 // The Previous attempt to Merge two calls together has failed in Telecom. We must 342 // now update the UI to possibly re-enable the Merge button based on the number of 343 // currently conferenceable calls available or Connection Capabilities. 344 case android.telecom.Connection.EVENT_CALL_MERGE_FAILED: 345 update(); 346 break; 347 case TelephonyManagerCompat.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE: 348 notifyWiFiToLteHandover(); 349 break; 350 case TelephonyManagerCompat.EVENT_HANDOVER_VIDEO_FROM_LTE_TO_WIFI: 351 onLteToWifiHandover(); 352 break; 353 case TelephonyManagerCompat.EVENT_HANDOVER_TO_WIFI_FAILED: 354 notifyHandoverToWifiFailed(); 355 break; 356 case TelephonyManagerCompat.EVENT_CALL_REMOTELY_HELD: 357 isRemotelyHeld = true; 358 update(); 359 break; 360 case TelephonyManagerCompat.EVENT_CALL_REMOTELY_UNHELD: 361 isRemotelyHeld = false; 362 update(); 363 break; 364 case TelephonyManagerCompat.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC: 365 notifyInternationalCallOnWifi(); 366 break; 367 case TelephonyManagerCompat.EVENT_MERGE_START: 368 LogUtil.i("DialerCall.onConnectionEvent", "merge start"); 369 isMergeInProcess = true; 370 break; 371 case TelephonyManagerCompat.EVENT_MERGE_COMPLETE: 372 LogUtil.i("DialerCall.onConnectionEvent", "merge complete"); 373 isMergeInProcess = false; 374 break; 375 case TelephonyManagerCompat.EVENT_CALL_FORWARDED: 376 // Only handle this event for P+ since it's unreliable pre-P. 377 if (BuildCompat.isAtLeastP()) { 378 isCallForwarded = true; 379 update(); 380 } 381 break; 382 default: 383 break; 384 } 385 } 386 }; 387 388 private long timeAddedMs; 389 DialerCall( Context context, DialerCallDelegate dialerCallDelegate, Call telecomCall, LatencyReport latencyReport, boolean registerCallback)390 public DialerCall( 391 Context context, 392 DialerCallDelegate dialerCallDelegate, 393 Call telecomCall, 394 LatencyReport latencyReport, 395 boolean registerCallback) { 396 Assert.isNotNull(context); 397 this.context = context; 398 this.dialerCallDelegate = dialerCallDelegate; 399 this.telecomCall = telecomCall; 400 this.latencyReport = latencyReport; 401 id = ID_PREFIX + Integer.toString(idCounter++); 402 403 // Must be after assigning mTelecomCall 404 videoTechManager = new VideoTechManager(this); 405 406 updateFromTelecomCall(); 407 if (isHiddenNumber() && TextUtils.isEmpty(getNumber())) { 408 hiddenId = ++hiddenCounter; 409 } else { 410 hiddenId = 0; 411 } 412 413 if (registerCallback) { 414 this.telecomCall.registerCallback(telecomCallCallback); 415 } 416 417 timeAddedMs = System.currentTimeMillis(); 418 parseCallSpecificAppData(); 419 420 updateEnrichedCallSession(); 421 } 422 translateState(int state)423 private static int translateState(int state) { 424 switch (state) { 425 case Call.STATE_NEW: 426 case Call.STATE_CONNECTING: 427 return DialerCallState.CONNECTING; 428 case Call.STATE_SELECT_PHONE_ACCOUNT: 429 return DialerCallState.SELECT_PHONE_ACCOUNT; 430 case Call.STATE_DIALING: 431 return DialerCallState.DIALING; 432 case Call.STATE_PULLING_CALL: 433 return DialerCallState.PULLING; 434 case Call.STATE_RINGING: 435 return DialerCallState.INCOMING; 436 case Call.STATE_ACTIVE: 437 return DialerCallState.ACTIVE; 438 case Call.STATE_HOLDING: 439 return DialerCallState.ONHOLD; 440 case Call.STATE_DISCONNECTED: 441 return DialerCallState.DISCONNECTED; 442 case Call.STATE_DISCONNECTING: 443 return DialerCallState.DISCONNECTING; 444 default: 445 return DialerCallState.INVALID; 446 } 447 } 448 areSame(DialerCall call1, DialerCall call2)449 public static boolean areSame(DialerCall call1, DialerCall call2) { 450 if (call1 == null && call2 == null) { 451 return true; 452 } else if (call1 == null || call2 == null) { 453 return false; 454 } 455 456 // otherwise compare call Ids 457 return call1.getId().equals(call2.getId()); 458 } 459 addListener(DialerCallListener listener)460 public void addListener(DialerCallListener listener) { 461 Assert.isMainThread(); 462 listeners.add(listener); 463 } 464 removeListener(DialerCallListener listener)465 public void removeListener(DialerCallListener listener) { 466 Assert.isMainThread(); 467 listeners.remove(listener); 468 } 469 addCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener)470 public void addCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) { 471 Assert.isMainThread(); 472 cannedTextResponsesLoadedListeners.add(listener); 473 } 474 removeCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener)475 public void removeCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) { 476 Assert.isMainThread(); 477 cannedTextResponsesLoadedListeners.remove(listener); 478 } 479 onLteToWifiHandover()480 private void onLteToWifiHandover() { 481 LogUtil.enterBlock("DialerCall.onLteToWifiHandover"); 482 if (hasShownLteToWiFiHandoverToast) { 483 return; 484 } 485 486 Toast.makeText(context, R.string.video_call_lte_to_wifi_handover_toast, Toast.LENGTH_LONG) 487 .show(); 488 hasShownLteToWiFiHandoverToast = true; 489 } 490 notifyWiFiToLteHandover()491 public void notifyWiFiToLteHandover() { 492 LogUtil.i("DialerCall.notifyWiFiToLteHandover", ""); 493 for (DialerCallListener listener : listeners) { 494 listener.onWiFiToLteHandover(); 495 } 496 } 497 notifyHandoverToWifiFailed()498 public void notifyHandoverToWifiFailed() { 499 LogUtil.i("DialerCall.notifyHandoverToWifiFailed", ""); 500 for (DialerCallListener listener : listeners) { 501 listener.onHandoverToWifiFailure(); 502 } 503 } 504 notifyInternationalCallOnWifi()505 public void notifyInternationalCallOnWifi() { 506 LogUtil.enterBlock("DialerCall.notifyInternationalCallOnWifi"); 507 for (DialerCallListener dialerCallListener : listeners) { 508 dialerCallListener.onInternationalCallOnWifi(); 509 } 510 } 511 getTelecomCall()512 /* package-private */ Call getTelecomCall() { 513 return telecomCall; 514 } 515 getStatusHints()516 public StatusHints getStatusHints() { 517 return telecomCall.getDetails().getStatusHints(); 518 } 519 getCameraDir()520 public int getCameraDir() { 521 return cameraDirection; 522 } 523 setCameraDir(int cameraDir)524 public void setCameraDir(int cameraDir) { 525 if (cameraDir == CameraDirection.CAMERA_DIRECTION_FRONT_FACING 526 || cameraDir == CameraDirection.CAMERA_DIRECTION_BACK_FACING) { 527 cameraDirection = cameraDir; 528 } else { 529 cameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN; 530 } 531 } 532 wasParentCall()533 public boolean wasParentCall() { 534 return logState.conferencedCalls != 0; 535 } 536 isVoiceMailNumber()537 public boolean isVoiceMailNumber() { 538 return isVoicemailNumber; 539 } 540 getCallCapableAccounts()541 public List<PhoneAccountHandle> getCallCapableAccounts() { 542 return callCapableAccounts; 543 } 544 getCountryIso()545 public String getCountryIso() { 546 return countryIso; 547 } 548 updateIsVoiceMailNumber()549 private void updateIsVoiceMailNumber() { 550 if (getHandle() != null && PhoneAccount.SCHEME_VOICEMAIL.equals(getHandle().getScheme())) { 551 isVoicemailNumber = true; 552 return; 553 } 554 555 if (!PermissionsUtil.hasPermission(context, permission.READ_PHONE_STATE)) { 556 isVoicemailNumber = false; 557 return; 558 } 559 560 isVoicemailNumber = TelecomUtil.isVoicemailNumber(context, getAccountHandle(), getNumber()); 561 } 562 update()563 private void update() { 564 Trace.beginSection("DialerCall.update"); 565 int oldState = getState(); 566 // Clear any cache here that could potentially change on update. 567 videoTech = null; 568 // We want to potentially register a video call callback here. 569 updateFromTelecomCall(); 570 if (oldState != getState() && getState() == DialerCallState.DISCONNECTED) { 571 for (DialerCallListener listener : listeners) { 572 listener.onDialerCallDisconnect(); 573 } 574 EnrichedCallComponent.get(context) 575 .getEnrichedCallManager() 576 .unregisterCapabilitiesListener(this); 577 EnrichedCallComponent.get(context) 578 .getEnrichedCallManager() 579 .unregisterStateChangedListener(this); 580 } else { 581 for (DialerCallListener listener : listeners) { 582 listener.onDialerCallUpdate(); 583 } 584 } 585 Trace.endSection(); 586 } 587 588 @SuppressWarnings("MissingPermission") updateFromTelecomCall()589 private void updateFromTelecomCall() { 590 Trace.beginSection("DialerCall.updateFromTelecomCall"); 591 LogUtil.v("DialerCall.updateFromTelecomCall", telecomCall.toString()); 592 593 videoTechManager.dispatchCallStateChanged(telecomCall.getState(), getAccountHandle()); 594 595 final int translatedState = translateState(telecomCall.getState()); 596 if (state != DialerCallState.BLOCKED) { 597 setState(translatedState); 598 setDisconnectCause(telecomCall.getDetails().getDisconnectCause()); 599 } 600 601 childCallIds.clear(); 602 final int numChildCalls = telecomCall.getChildren().size(); 603 for (int i = 0; i < numChildCalls; i++) { 604 childCallIds.add( 605 dialerCallDelegate 606 .getDialerCallFromTelecomCall(telecomCall.getChildren().get(i)) 607 .getId()); 608 } 609 610 // The number of conferenced calls can change over the course of the call, so use the 611 // maximum number of conferenced child calls as the metric for conference call usage. 612 logState.conferencedCalls = Math.max(numChildCalls, logState.conferencedCalls); 613 614 updateFromCallExtras(telecomCall.getDetails().getExtras()); 615 616 // If the handle of the call has changed, update state for the call determining if it is an 617 // emergency call. 618 Uri newHandle = telecomCall.getDetails().getHandle(); 619 if (!Objects.equals(handle, newHandle)) { 620 handle = newHandle; 621 updateEmergencyCallState(); 622 } 623 624 TelecomManager telecomManager = context.getSystemService(TelecomManager.class); 625 // If the phone account handle of the call is set, cache capability bit indicating whether 626 // the phone account supports call subjects. 627 PhoneAccountHandle newPhoneAccountHandle = telecomCall.getDetails().getAccountHandle(); 628 if (!Objects.equals(phoneAccountHandle, newPhoneAccountHandle)) { 629 phoneAccountHandle = newPhoneAccountHandle; 630 631 if (phoneAccountHandle != null) { 632 PhoneAccount phoneAccount = telecomManager.getPhoneAccount(phoneAccountHandle); 633 if (phoneAccount != null) { 634 isCallSubjectSupported = 635 phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT); 636 if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 637 cacheCarrierConfiguration(phoneAccountHandle); 638 } 639 } 640 } 641 } 642 if (PermissionsUtil.hasPermission(context, permission.READ_PHONE_STATE)) { 643 updateIsVoiceMailNumber(); 644 callCapableAccounts = telecomManager.getCallCapablePhoneAccounts(); 645 countryIso = GeoUtil.getCurrentCountryIso(context); 646 } 647 Trace.endSection(); 648 } 649 650 /** 651 * Caches frequently used carrier configuration locally. 652 * 653 * @param accountHandle The PhoneAccount handle. 654 */ 655 @SuppressLint("MissingPermission") cacheCarrierConfiguration(PhoneAccountHandle accountHandle)656 private void cacheCarrierConfiguration(PhoneAccountHandle accountHandle) { 657 if (!PermissionsUtil.hasPermission(context, permission.READ_PHONE_STATE)) { 658 return; 659 } 660 if (VERSION.SDK_INT < VERSION_CODES.O) { 661 return; 662 } 663 // TODO(a bug): This may take several seconds to complete, revisit it to move it to worker 664 // thread. 665 carrierConfig = 666 TelephonyManagerCompat.getTelephonyManagerForPhoneAccountHandle(context, accountHandle) 667 .getCarrierConfig(); 668 } 669 670 /** 671 * Tests corruption of the {@code callExtras} bundle by calling {@link 672 * Bundle#containsKey(String)}. If the bundle is corrupted a {@link IllegalArgumentException} will 673 * be thrown and caught by this function. 674 * 675 * @param callExtras the bundle to verify 676 * @return {@code true} if the bundle is corrupted, {@code false} otherwise. 677 */ areCallExtrasCorrupted(Bundle callExtras)678 protected boolean areCallExtrasCorrupted(Bundle callExtras) { 679 /** 680 * There's currently a bug in Telephony service (a bug) that could corrupt the extras 681 * bundle, resulting in a IllegalArgumentException while validating data under {@link 682 * Bundle#containsKey(String)}. 683 */ 684 try { 685 callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS); 686 return false; 687 } catch (IllegalArgumentException e) { 688 LogUtil.e( 689 "DialerCall.areCallExtrasCorrupted", "callExtras is corrupted, ignoring exception", e); 690 return true; 691 } 692 } 693 updateFromCallExtras(Bundle callExtras)694 protected void updateFromCallExtras(Bundle callExtras) { 695 if (callExtras == null || areCallExtrasCorrupted(callExtras)) { 696 /** 697 * If the bundle is corrupted, abandon information update as a work around. These are not 698 * critical for the dialer to function. 699 */ 700 return; 701 } 702 // Check for a change in the child address and notify any listeners. 703 if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) { 704 String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS); 705 if (!Objects.equals(childNumber, this.childNumber)) { 706 this.childNumber = childNumber; 707 for (DialerCallListener listener : listeners) { 708 listener.onDialerCallChildNumberChange(); 709 } 710 } 711 } 712 713 // Last forwarded number comes in as an array of strings. We want to choose the 714 // last item in the array. The forwarding numbers arrive independently of when the 715 // call is originally set up, so we need to notify the the UI of the change. 716 if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) { 717 ArrayList<String> lastForwardedNumbers = 718 callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER); 719 720 if (lastForwardedNumbers != null) { 721 String lastForwardedNumber = null; 722 if (!lastForwardedNumbers.isEmpty()) { 723 lastForwardedNumber = lastForwardedNumbers.get(lastForwardedNumbers.size() - 1); 724 } 725 726 if (!Objects.equals(lastForwardedNumber, this.lastForwardedNumber)) { 727 this.lastForwardedNumber = lastForwardedNumber; 728 for (DialerCallListener listener : listeners) { 729 listener.onDialerCallLastForwardedNumberChange(); 730 } 731 } 732 } 733 } 734 735 // DialerCall subject is present in the extras at the start of call, so we do not need to 736 // notify any other listeners of this. 737 if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) { 738 String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT); 739 if (!Objects.equals(this.callSubject, callSubject)) { 740 this.callSubject = callSubject; 741 } 742 } 743 } 744 getId()745 public String getId() { 746 return id; 747 } 748 749 /** 750 * @return name appended with a number if the number is restricted/unknown and the user has 751 * received more than one restricted/unknown call. 752 */ 753 @Nullable updateNameIfRestricted(@ullable String name)754 public String updateNameIfRestricted(@Nullable String name) { 755 if (name != null && isHiddenNumber() && hiddenId != 0 && hiddenCounter > 1) { 756 return context.getString(R.string.unknown_counter, name, hiddenId); 757 } 758 return name; 759 } 760 clearRestrictedCount()761 public static void clearRestrictedCount() { 762 hiddenCounter = 0; 763 } 764 isHiddenNumber()765 private boolean isHiddenNumber() { 766 return getNumberPresentation() == TelecomManager.PRESENTATION_RESTRICTED 767 || getNumberPresentation() == TelecomManager.PRESENTATION_UNKNOWN; 768 } 769 hasShownWiFiToLteHandoverToast()770 public boolean hasShownWiFiToLteHandoverToast() { 771 return hasShownWiFiToLteHandoverToast; 772 } 773 setHasShownWiFiToLteHandoverToast()774 public void setHasShownWiFiToLteHandoverToast() { 775 hasShownWiFiToLteHandoverToast = true; 776 } 777 showWifiHandoverAlertAsToast()778 public boolean showWifiHandoverAlertAsToast() { 779 return doNotShowDialogForHandoffToWifiFailure; 780 } 781 setDoNotShowDialogForHandoffToWifiFailure(boolean bool)782 public void setDoNotShowDialogForHandoffToWifiFailure(boolean bool) { 783 doNotShowDialogForHandoffToWifiFailure = bool; 784 } 785 showVideoChargesAlertDialog()786 public boolean showVideoChargesAlertDialog() { 787 if (carrierConfig == null) { 788 return false; 789 } 790 return carrierConfig.getBoolean( 791 TelephonyManagerCompat.CARRIER_CONFIG_KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL); 792 } 793 getTimeAddedMs()794 public long getTimeAddedMs() { 795 return timeAddedMs; 796 } 797 798 @Nullable getNumber()799 public String getNumber() { 800 return TelecomCallUtil.getNumber(telecomCall); 801 } 802 blockCall()803 public void blockCall() { 804 telecomCall.reject(false, null); 805 setState(DialerCallState.BLOCKED); 806 } 807 808 @Nullable getHandle()809 public Uri getHandle() { 810 return telecomCall == null ? null : telecomCall.getDetails().getHandle(); 811 } 812 isEmergencyCall()813 public boolean isEmergencyCall() { 814 return isEmergencyCall; 815 } 816 isPotentialEmergencyCallback()817 public boolean isPotentialEmergencyCallback() { 818 // The property PROPERTY_EMERGENCY_CALLBACK_MODE is only set for CDMA calls when the system 819 // is actually in emergency callback mode (ie data is disabled). 820 if (hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE)) { 821 return true; 822 } 823 824 // Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS is available starting in O 825 if (VERSION.SDK_INT < VERSION_CODES.O) { 826 long timestampMillis = FilteredNumbersUtil.getLastEmergencyCallTimeMillis(context); 827 return isInEmergencyCallbackWindow(timestampMillis); 828 } 829 830 // We want to treat any incoming call that arrives a short time after an outgoing emergency call 831 // as a potential emergency callback. 832 if (getExtras() != null 833 && getExtras().getLong(Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0) > 0) { 834 long lastEmergencyCallMillis = 835 getExtras().getLong(Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0); 836 if (isInEmergencyCallbackWindow(lastEmergencyCallMillis)) { 837 return true; 838 } 839 } 840 return false; 841 } 842 isInEmergencyCallbackWindow(long timestampMillis)843 boolean isInEmergencyCallbackWindow(long timestampMillis) { 844 long emergencyCallbackWindowMillis = 845 ConfigProviderComponent.get(context) 846 .getConfigProvider() 847 .getLong(CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS, TimeUnit.MINUTES.toMillis(5)); 848 return System.currentTimeMillis() - timestampMillis < emergencyCallbackWindowMillis; 849 } 850 getState()851 public int getState() { 852 if (telecomCall != null && telecomCall.getParent() != null) { 853 return DialerCallState.CONFERENCED; 854 } else { 855 return state; 856 } 857 } 858 getNonConferenceState()859 public int getNonConferenceState() { 860 return state; 861 } 862 setState(int state)863 public void setState(int state) { 864 if (state == DialerCallState.INCOMING) { 865 logState.isIncoming = true; 866 } 867 updateCallTiming(state); 868 869 this.state = state; 870 } 871 updateCallTiming(int newState)872 private void updateCallTiming(int newState) { 873 if (newState == DialerCallState.ACTIVE) { 874 if (this.state == DialerCallState.ACTIVE) { 875 LogUtil.i("DialerCall.updateCallTiming", "state is already active"); 876 return; 877 } 878 logState.dialerConnectTimeMillis = clock.currentTimeMillis(); 879 logState.dialerConnectTimeMillisElapsedRealtime = SystemClock.elapsedRealtime(); 880 } 881 882 if (newState == DialerCallState.DISCONNECTED) { 883 long newDuration = 884 getConnectTimeMillis() == 0 ? 0 : clock.currentTimeMillis() - getConnectTimeMillis(); 885 if (this.state == DialerCallState.DISCONNECTED) { 886 LogUtil.i( 887 "DialerCall.setState", 888 "ignoring state transition from DISCONNECTED to DISCONNECTED." 889 + " Duration would have changed from %s to %s", 890 logState.telecomDurationMillis, 891 newDuration); 892 return; 893 } 894 logState.telecomDurationMillis = newDuration; 895 logState.dialerDurationMillis = 896 logState.dialerConnectTimeMillis == 0 897 ? 0 898 : clock.currentTimeMillis() - logState.dialerConnectTimeMillis; 899 logState.dialerDurationMillisElapsedRealtime = 900 logState.dialerConnectTimeMillisElapsedRealtime == 0 901 ? 0 902 : SystemClock.elapsedRealtime() - logState.dialerConnectTimeMillisElapsedRealtime; 903 } 904 } 905 906 @VisibleForTesting setClock(Clock clock)907 void setClock(Clock clock) { 908 this.clock = clock; 909 } 910 getNumberPresentation()911 public int getNumberPresentation() { 912 return telecomCall == null ? -1 : telecomCall.getDetails().getHandlePresentation(); 913 } 914 getCnapNamePresentation()915 public int getCnapNamePresentation() { 916 return telecomCall == null ? -1 : telecomCall.getDetails().getCallerDisplayNamePresentation(); 917 } 918 919 @Nullable getCnapName()920 public String getCnapName() { 921 return telecomCall == null ? null : getTelecomCall().getDetails().getCallerDisplayName(); 922 } 923 getIntentExtras()924 public Bundle getIntentExtras() { 925 return telecomCall.getDetails().getIntentExtras(); 926 } 927 928 @Nullable getExtras()929 public Bundle getExtras() { 930 return telecomCall == null ? null : telecomCall.getDetails().getExtras(); 931 } 932 933 /** @return The child number for the call, or {@code null} if none specified. */ getChildNumber()934 public String getChildNumber() { 935 return childNumber; 936 } 937 938 /** @return The last forwarded number for the call, or {@code null} if none specified. */ getLastForwardedNumber()939 public String getLastForwardedNumber() { 940 return lastForwardedNumber; 941 } 942 isCallForwarded()943 public boolean isCallForwarded() { 944 return isCallForwarded; 945 } 946 947 /** @return The call subject, or {@code null} if none specified. */ getCallSubject()948 public String getCallSubject() { 949 return callSubject; 950 } 951 952 /** 953 * @return {@code true} if the call's phone account supports call subjects, {@code false} 954 * otherwise. 955 */ isCallSubjectSupported()956 public boolean isCallSubjectSupported() { 957 return isCallSubjectSupported; 958 } 959 960 /** Returns call disconnect cause, defined by {@link DisconnectCause}. */ getDisconnectCause()961 public DisconnectCause getDisconnectCause() { 962 if (state == DialerCallState.DISCONNECTED || state == DialerCallState.IDLE) { 963 return disconnectCause; 964 } 965 966 return new DisconnectCause(DisconnectCause.UNKNOWN); 967 } 968 setDisconnectCause(DisconnectCause disconnectCause)969 public void setDisconnectCause(DisconnectCause disconnectCause) { 970 this.disconnectCause = disconnectCause; 971 logState.disconnectCause = this.disconnectCause; 972 } 973 974 /** Returns the possible text message responses. */ getCannedSmsResponses()975 public List<String> getCannedSmsResponses() { 976 return telecomCall.getCannedTextResponses(); 977 } 978 979 /** Checks if the call supports the given set of capabilities supplied as a bit mask. */ 980 @TargetApi(28) can(int capabilities)981 public boolean can(int capabilities) { 982 int supportedCapabilities = telecomCall.getDetails().getCallCapabilities(); 983 984 if ((capabilities & Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) { 985 boolean hasConferenceableCall = false; 986 // RTT call is not conferenceable, it's a bug (a bug) in Telecom and we work around it 987 // here before it's fixed in Telecom. 988 for (Call call : telecomCall.getConferenceableCalls()) { 989 if (!(BuildCompat.isAtLeastP() && call.isRttActive())) { 990 hasConferenceableCall = true; 991 break; 992 } 993 } 994 // We allow you to merge if the capabilities allow it or if it is a call with 995 // conferenceable calls. 996 if (!hasConferenceableCall 997 && ((Call.Details.CAPABILITY_MERGE_CONFERENCE & supportedCapabilities) == 0)) { 998 // Cannot merge calls if there are no calls to merge with. 999 return false; 1000 } 1001 capabilities &= ~Call.Details.CAPABILITY_MERGE_CONFERENCE; 1002 } 1003 return (capabilities == (capabilities & supportedCapabilities)); 1004 } 1005 hasProperty(int property)1006 public boolean hasProperty(int property) { 1007 return telecomCall.getDetails().hasProperty(property); 1008 } 1009 1010 @NonNull getUniqueCallId()1011 public String getUniqueCallId() { 1012 return uniqueCallId; 1013 } 1014 1015 /** Gets the time when the call first became active. */ getConnectTimeMillis()1016 public long getConnectTimeMillis() { 1017 return telecomCall.getDetails().getConnectTimeMillis(); 1018 } 1019 1020 /** 1021 * Gets the time when the call is created (see {@link Details#getCreationTimeMillis()}). This is 1022 * the same time that is logged as the start time in the Call Log (see {@link 1023 * android.provider.CallLog.Calls#DATE}). 1024 */ 1025 @TargetApi(VERSION_CODES.O) getCreationTimeMillis()1026 public long getCreationTimeMillis() { 1027 return telecomCall.getDetails().getCreationTimeMillis(); 1028 } 1029 isConferenceCall()1030 public boolean isConferenceCall() { 1031 return hasProperty(Call.Details.PROPERTY_CONFERENCE); 1032 } 1033 1034 @Nullable getGatewayInfo()1035 public GatewayInfo getGatewayInfo() { 1036 return telecomCall == null ? null : telecomCall.getDetails().getGatewayInfo(); 1037 } 1038 1039 @Nullable getAccountHandle()1040 public PhoneAccountHandle getAccountHandle() { 1041 return telecomCall == null ? null : telecomCall.getDetails().getAccountHandle(); 1042 } 1043 1044 /** @return The {@link VideoCall} instance associated with the {@link Call}. */ getVideoCall()1045 public VideoCall getVideoCall() { 1046 return telecomCall == null ? null : telecomCall.getVideoCall(); 1047 } 1048 getChildCallIds()1049 public List<String> getChildCallIds() { 1050 return childCallIds; 1051 } 1052 getParentId()1053 public String getParentId() { 1054 Call parentCall = telecomCall.getParent(); 1055 if (parentCall != null) { 1056 return dialerCallDelegate.getDialerCallFromTelecomCall(parentCall).getId(); 1057 } 1058 return null; 1059 } 1060 getVideoState()1061 public int getVideoState() { 1062 return telecomCall.getDetails().getVideoState(); 1063 } 1064 isVideoCall()1065 public boolean isVideoCall() { 1066 return getVideoTech().isTransmittingOrReceiving() || VideoProfile.isVideo(getVideoState()); 1067 } 1068 1069 @TargetApi(28) isActiveRttCall()1070 public boolean isActiveRttCall() { 1071 if (BuildCompat.isAtLeastP()) { 1072 return getTelecomCall().isRttActive(); 1073 } else { 1074 return false; 1075 } 1076 } 1077 1078 @TargetApi(28) 1079 @Nullable getRttCall()1080 public RttCall getRttCall() { 1081 if (!isActiveRttCall()) { 1082 return null; 1083 } 1084 return getTelecomCall().getRttCall(); 1085 } 1086 1087 @TargetApi(28) isPhoneAccountRttCapable()1088 public boolean isPhoneAccountRttCapable() { 1089 PhoneAccount phoneAccount = getPhoneAccount(); 1090 if (phoneAccount == null) { 1091 return false; 1092 } 1093 if (!phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) { 1094 return false; 1095 } 1096 return true; 1097 } 1098 1099 @TargetApi(28) canUpgradeToRttCall()1100 public boolean canUpgradeToRttCall() { 1101 if (!isPhoneAccountRttCapable()) { 1102 return false; 1103 } 1104 if (isActiveRttCall()) { 1105 return false; 1106 } 1107 if (isVideoCall()) { 1108 return false; 1109 } 1110 if (isConferenceCall()) { 1111 return false; 1112 } 1113 if (CallList.getInstance().hasActiveRttCall()) { 1114 return false; 1115 } 1116 return true; 1117 } 1118 1119 @TargetApi(28) sendRttUpgradeRequest()1120 public void sendRttUpgradeRequest() { 1121 getTelecomCall().sendRttRequest(); 1122 } 1123 1124 @TargetApi(28) respondToRttRequest(boolean accept, int rttRequestId)1125 public void respondToRttRequest(boolean accept, int rttRequestId) { 1126 Logger.get(context) 1127 .logCallImpression( 1128 accept 1129 ? DialerImpression.Type.RTT_MID_CALL_ACCEPTED 1130 : DialerImpression.Type.RTT_MID_CALL_REJECTED, 1131 getUniqueCallId(), 1132 getTimeAddedMs()); 1133 getTelecomCall().respondToRttRequest(rttRequestId, accept); 1134 } 1135 1136 @TargetApi(28) saveRttTranscript()1137 private void saveRttTranscript() { 1138 if (!BuildCompat.isAtLeastP()) { 1139 return; 1140 } 1141 if (getRttCall() != null) { 1142 // Save any remaining text in the buffer that's not shown by UI yet. 1143 // This may happen when the call is switched to background before disconnect. 1144 try { 1145 String messageLeft = getRttCall().readImmediately(); 1146 if (!TextUtils.isEmpty(messageLeft)) { 1147 rttTranscript = 1148 RttChatMessage.getRttTranscriptWithNewRemoteMessage(rttTranscript, messageLeft); 1149 } 1150 } catch (IOException e) { 1151 LogUtil.e("DialerCall.saveRttTranscript", "error when reading remaining message", e); 1152 } 1153 } 1154 // Don't save transcript if it's empty. 1155 if (rttTranscript.getMessagesCount() == 0) { 1156 return; 1157 } 1158 Futures.addCallback( 1159 RttTranscriptUtil.saveRttTranscript(context, rttTranscript), 1160 new DefaultFutureCallback<>(), 1161 MoreExecutors.directExecutor()); 1162 } 1163 hasReceivedVideoUpgradeRequest()1164 public boolean hasReceivedVideoUpgradeRequest() { 1165 return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState()); 1166 } 1167 hasSentVideoUpgradeRequest()1168 public boolean hasSentVideoUpgradeRequest() { 1169 return VideoUtils.hasSentVideoUpgradeRequest(getVideoTech().getSessionModificationState()); 1170 } 1171 hasSentRttUpgradeRequest()1172 public boolean hasSentRttUpgradeRequest() { 1173 return false; 1174 } 1175 1176 /** 1177 * Determines if the call handle is an emergency number or not and caches the result to avoid 1178 * repeated calls to isEmergencyNumber. 1179 */ updateEmergencyCallState()1180 private void updateEmergencyCallState() { 1181 isEmergencyCall = TelecomCallUtil.isEmergencyCall(telecomCall); 1182 } 1183 getLogState()1184 public LogState getLogState() { 1185 return logState; 1186 } 1187 1188 /** 1189 * Determines if the call is an external call. 1190 * 1191 * <p>An external call is one which does not exist locally for the {@link 1192 * android.telecom.ConnectionService} it is associated with. 1193 * 1194 * @return {@code true} if the call is an external call, {@code false} otherwise. 1195 */ isExternalCall()1196 boolean isExternalCall() { 1197 return hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL); 1198 } 1199 1200 /** 1201 * Determines if answering this call will cause an ongoing video call to be dropped. 1202 * 1203 * @return {@code true} if answering this call will drop an ongoing video call, {@code false} 1204 * otherwise. 1205 */ answeringDisconnectsForegroundVideoCall()1206 public boolean answeringDisconnectsForegroundVideoCall() { 1207 Bundle extras = getExtras(); 1208 if (extras == null 1209 || !extras.containsKey(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL)) { 1210 return false; 1211 } 1212 return extras.getBoolean(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL); 1213 } 1214 parseCallSpecificAppData()1215 private void parseCallSpecificAppData() { 1216 if (isExternalCall()) { 1217 return; 1218 } 1219 1220 logState.callSpecificAppData = CallIntentParser.getCallSpecificAppData(getIntentExtras()); 1221 if (logState.callSpecificAppData == null) { 1222 1223 logState.callSpecificAppData = 1224 CallSpecificAppData.newBuilder() 1225 .setCallInitiationType(CallInitiationType.Type.EXTERNAL_INITIATION) 1226 .build(); 1227 } 1228 if (getState() == DialerCallState.INCOMING) { 1229 logState.callSpecificAppData = 1230 logState 1231 .callSpecificAppData 1232 .toBuilder() 1233 .setCallInitiationType(CallInitiationType.Type.INCOMING_INITIATION) 1234 .build(); 1235 } 1236 } 1237 1238 @Override toString()1239 public String toString() { 1240 if (telecomCall == null) { 1241 // This should happen only in testing since otherwise we would never have a null 1242 // Telecom call. 1243 return String.valueOf(id); 1244 } 1245 1246 return String.format( 1247 Locale.US, 1248 "[%s, %s, %s, %s, children:%s, parent:%s, " 1249 + "conferenceable:%s, videoState:%s, mSessionModificationState:%d, CameraDir:%s]", 1250 id, 1251 DialerCallState.toString(getState()), 1252 Details.capabilitiesToString(telecomCall.getDetails().getCallCapabilities()), 1253 Details.propertiesToString(telecomCall.getDetails().getCallProperties()), 1254 childCallIds, 1255 getParentId(), 1256 this.telecomCall.getConferenceableCalls(), 1257 VideoProfile.videoStateToString(telecomCall.getDetails().getVideoState()), 1258 getVideoTech().getSessionModificationState(), 1259 getCameraDir()); 1260 } 1261 toSimpleString()1262 public String toSimpleString() { 1263 return super.toString(); 1264 } 1265 1266 @CallHistoryStatus getCallHistoryStatus()1267 public int getCallHistoryStatus() { 1268 return callHistoryStatus; 1269 } 1270 setCallHistoryStatus(@allHistoryStatus int callHistoryStatus)1271 public void setCallHistoryStatus(@CallHistoryStatus int callHistoryStatus) { 1272 this.callHistoryStatus = callHistoryStatus; 1273 } 1274 didShowCameraPermission()1275 public boolean didShowCameraPermission() { 1276 return didShowCameraPermission; 1277 } 1278 setDidShowCameraPermission(boolean didShow)1279 public void setDidShowCameraPermission(boolean didShow) { 1280 didShowCameraPermission = didShow; 1281 } 1282 didDismissVideoChargesAlertDialog()1283 public boolean didDismissVideoChargesAlertDialog() { 1284 return didDismissVideoChargesAlertDialog; 1285 } 1286 setDidDismissVideoChargesAlertDialog(boolean didDismiss)1287 public void setDidDismissVideoChargesAlertDialog(boolean didDismiss) { 1288 didDismissVideoChargesAlertDialog = didDismiss; 1289 } 1290 setSpamStatus(@ullable SpamStatus spamStatus)1291 public void setSpamStatus(@Nullable SpamStatus spamStatus) { 1292 this.spamStatus = spamStatus; 1293 } 1294 getSpamStatus()1295 public Optional<SpamStatus> getSpamStatus() { 1296 return Optional.fromNullable(spamStatus); 1297 } 1298 isSpam()1299 public boolean isSpam() { 1300 if (spamStatus == null || !spamStatus.isSpam()) { 1301 return false; 1302 } 1303 1304 if (!isIncoming()) { 1305 return false; 1306 } 1307 1308 if (isPotentialEmergencyCallback()) { 1309 return false; 1310 } 1311 1312 return true; 1313 } 1314 isBlocked()1315 public boolean isBlocked() { 1316 return isBlocked; 1317 } 1318 setBlockedStatus(boolean isBlocked)1319 public void setBlockedStatus(boolean isBlocked) { 1320 this.isBlocked = isBlocked; 1321 } 1322 isRemotelyHeld()1323 public boolean isRemotelyHeld() { 1324 return isRemotelyHeld; 1325 } 1326 isMergeInProcess()1327 public boolean isMergeInProcess() { 1328 return isMergeInProcess; 1329 } 1330 isIncoming()1331 public boolean isIncoming() { 1332 return logState.isIncoming; 1333 } 1334 1335 /** 1336 * Try and determine if the call used assisted dialing. 1337 * 1338 * <p>We will not be able to verify a call underwent assisted dialing until the Platform 1339 * implmentation is complete in P+. 1340 * 1341 * @return a boolean indicating assisted dialing may have been performed 1342 */ isAssistedDialed()1343 public boolean isAssistedDialed() { 1344 if (getIntentExtras() != null) { 1345 // P and below uses the existence of USE_ASSISTED_DIALING to indicate assisted dialing 1346 // was used. The Dialer client is responsible for performing assisted dialing before 1347 // placing the outgoing call. 1348 // 1349 // The existence of the assisted dialing extras indicates that assisted dialing took place. 1350 if (getIntentExtras().getBoolean(TelephonyManagerCompat.USE_ASSISTED_DIALING, false) 1351 && getAssistedDialingExtras() != null 1352 && Build.VERSION.SDK_INT <= ConcreteCreator.BUILD_CODE_CEILING) { 1353 return true; 1354 } 1355 } 1356 1357 return false; 1358 } 1359 1360 @Nullable getAssistedDialingExtras()1361 public TransformationInfo getAssistedDialingExtras() { 1362 if (getIntentExtras() == null) { 1363 return null; 1364 } 1365 1366 if (getIntentExtras().getBundle(TelephonyManagerCompat.ASSISTED_DIALING_EXTRAS) == null) { 1367 return null; 1368 } 1369 1370 // Used in N-OMR1 1371 return TransformationInfo.newInstanceFromBundle( 1372 getIntentExtras().getBundle(TelephonyManagerCompat.ASSISTED_DIALING_EXTRAS)); 1373 } 1374 getLatencyReport()1375 public LatencyReport getLatencyReport() { 1376 return latencyReport; 1377 } 1378 getAnswerAndReleaseButtonDisplayedTimes()1379 public int getAnswerAndReleaseButtonDisplayedTimes() { 1380 return answerAndReleaseButtonDisplayedTimes; 1381 } 1382 increaseAnswerAndReleaseButtonDisplayedTimes()1383 public void increaseAnswerAndReleaseButtonDisplayedTimes() { 1384 answerAndReleaseButtonDisplayedTimes++; 1385 } 1386 getReleasedByAnsweringSecondCall()1387 public boolean getReleasedByAnsweringSecondCall() { 1388 return releasedByAnsweringSecondCall; 1389 } 1390 setReleasedByAnsweringSecondCall(boolean releasedByAnsweringSecondCall)1391 public void setReleasedByAnsweringSecondCall(boolean releasedByAnsweringSecondCall) { 1392 this.releasedByAnsweringSecondCall = releasedByAnsweringSecondCall; 1393 } 1394 getSecondCallWithoutAnswerAndReleasedButtonTimes()1395 public int getSecondCallWithoutAnswerAndReleasedButtonTimes() { 1396 return secondCallWithoutAnswerAndReleasedButtonTimes; 1397 } 1398 increaseSecondCallWithoutAnswerAndReleasedButtonTimes()1399 public void increaseSecondCallWithoutAnswerAndReleasedButtonTimes() { 1400 secondCallWithoutAnswerAndReleasedButtonTimes++; 1401 } 1402 1403 @Nullable getEnrichedCallCapabilities()1404 public EnrichedCallCapabilities getEnrichedCallCapabilities() { 1405 return enrichedCallCapabilities; 1406 } 1407 setEnrichedCallCapabilities( @ullable EnrichedCallCapabilities mEnrichedCallCapabilities)1408 public void setEnrichedCallCapabilities( 1409 @Nullable EnrichedCallCapabilities mEnrichedCallCapabilities) { 1410 this.enrichedCallCapabilities = mEnrichedCallCapabilities; 1411 } 1412 1413 @Nullable getEnrichedCallSession()1414 public Session getEnrichedCallSession() { 1415 return enrichedCallSession; 1416 } 1417 setEnrichedCallSession(@ullable Session mEnrichedCallSession)1418 public void setEnrichedCallSession(@Nullable Session mEnrichedCallSession) { 1419 this.enrichedCallSession = mEnrichedCallSession; 1420 } 1421 unregisterCallback()1422 public void unregisterCallback() { 1423 telecomCall.unregisterCallback(telecomCallCallback); 1424 } 1425 phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault)1426 public void phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault) { 1427 LogUtil.i( 1428 "DialerCall.phoneAccountSelected", 1429 "accountHandle: %s, setDefault: %b", 1430 accountHandle, 1431 setDefault); 1432 telecomCall.phoneAccountSelected(accountHandle, setDefault); 1433 } 1434 disconnect()1435 public void disconnect() { 1436 LogUtil.i("DialerCall.disconnect", ""); 1437 setState(DialerCallState.DISCONNECTING); 1438 for (DialerCallListener listener : listeners) { 1439 listener.onDialerCallUpdate(); 1440 } 1441 telecomCall.disconnect(); 1442 } 1443 hold()1444 public void hold() { 1445 LogUtil.i("DialerCall.hold", ""); 1446 telecomCall.hold(); 1447 } 1448 unhold()1449 public void unhold() { 1450 LogUtil.i("DialerCall.unhold", ""); 1451 telecomCall.unhold(); 1452 } 1453 splitFromConference()1454 public void splitFromConference() { 1455 LogUtil.i("DialerCall.splitFromConference", ""); 1456 telecomCall.splitFromConference(); 1457 } 1458 answer(int videoState)1459 public void answer(int videoState) { 1460 LogUtil.i("DialerCall.answer", "videoState: " + videoState); 1461 telecomCall.answer(videoState); 1462 } 1463 answer()1464 public void answer() { 1465 answer(telecomCall.getDetails().getVideoState()); 1466 } 1467 reject(boolean rejectWithMessage, String message)1468 public void reject(boolean rejectWithMessage, String message) { 1469 LogUtil.i("DialerCall.reject", ""); 1470 telecomCall.reject(rejectWithMessage, message); 1471 } 1472 1473 /** Return the string label to represent the call provider */ getCallProviderLabel()1474 public String getCallProviderLabel() { 1475 if (callProviderLabel == null) { 1476 PhoneAccount account = getPhoneAccount(); 1477 if (account != null && !TextUtils.isEmpty(account.getLabel())) { 1478 if (callCapableAccounts != null && callCapableAccounts.size() > 1) { 1479 callProviderLabel = account.getLabel().toString(); 1480 } 1481 } 1482 if (callProviderLabel == null) { 1483 callProviderLabel = ""; 1484 } 1485 } 1486 return callProviderLabel; 1487 } 1488 getPhoneAccount()1489 private PhoneAccount getPhoneAccount() { 1490 PhoneAccountHandle accountHandle = getAccountHandle(); 1491 if (accountHandle == null) { 1492 return null; 1493 } 1494 return context.getSystemService(TelecomManager.class).getPhoneAccount(accountHandle); 1495 } 1496 getVideoTech()1497 public VideoTech getVideoTech() { 1498 if (videoTech == null) { 1499 videoTech = videoTechManager.getVideoTech(getAccountHandle()); 1500 1501 // Only store the first video tech type found to be available during the life of the call. 1502 if (selectedAvailableVideoTechType == com.android.dialer.logging.VideoTech.Type.NONE) { 1503 // Update the video tech. 1504 selectedAvailableVideoTechType = videoTech.getVideoTechType(); 1505 } 1506 } 1507 return videoTech; 1508 } 1509 getCallbackNumber()1510 public String getCallbackNumber() { 1511 if (callbackNumber == null) { 1512 // Show the emergency callback number if either: 1513 // 1. This is an emergency call. 1514 // 2. The phone is in Emergency Callback Mode, which means we should show the callback 1515 // number. 1516 boolean showCallbackNumber = hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE); 1517 1518 if (isEmergencyCall() || showCallbackNumber) { 1519 callbackNumber = 1520 context.getSystemService(TelecomManager.class).getLine1Number(getAccountHandle()); 1521 } 1522 1523 if (callbackNumber == null) { 1524 callbackNumber = ""; 1525 } 1526 } 1527 return callbackNumber; 1528 } 1529 getSimCountryIso()1530 public String getSimCountryIso() { 1531 String simCountryIso = 1532 TelephonyManagerCompat.getTelephonyManagerForPhoneAccountHandle(context, getAccountHandle()) 1533 .getSimCountryIso(); 1534 if (!TextUtils.isEmpty(simCountryIso)) { 1535 simCountryIso = simCountryIso.toUpperCase(Locale.US); 1536 } 1537 return simCountryIso; 1538 } 1539 1540 @Override onVideoTechStateChanged()1541 public void onVideoTechStateChanged() { 1542 update(); 1543 } 1544 1545 @Override onSessionModificationStateChanged()1546 public void onSessionModificationStateChanged() { 1547 Trace.beginSection("DialerCall.onSessionModificationStateChanged"); 1548 for (DialerCallListener listener : listeners) { 1549 listener.onDialerCallSessionModificationStateChange(); 1550 } 1551 Trace.endSection(); 1552 } 1553 1554 @Override onCameraDimensionsChanged(int width, int height)1555 public void onCameraDimensionsChanged(int width, int height) { 1556 InCallVideoCallCallbackNotifier.getInstance().cameraDimensionsChanged(this, width, height); 1557 } 1558 1559 @Override onPeerDimensionsChanged(int width, int height)1560 public void onPeerDimensionsChanged(int width, int height) { 1561 InCallVideoCallCallbackNotifier.getInstance().peerDimensionsChanged(this, width, height); 1562 } 1563 1564 @Override onVideoUpgradeRequestReceived()1565 public void onVideoUpgradeRequestReceived() { 1566 LogUtil.enterBlock("DialerCall.onVideoUpgradeRequestReceived"); 1567 1568 for (DialerCallListener listener : listeners) { 1569 listener.onDialerCallUpgradeToVideo(); 1570 } 1571 1572 update(); 1573 1574 Logger.get(context) 1575 .logCallImpression( 1576 DialerImpression.Type.VIDEO_CALL_REQUEST_RECEIVED, getUniqueCallId(), getTimeAddedMs()); 1577 } 1578 1579 @Override onUpgradedToVideo(boolean switchToSpeaker)1580 public void onUpgradedToVideo(boolean switchToSpeaker) { 1581 LogUtil.enterBlock("DialerCall.onUpgradedToVideo"); 1582 1583 if (!switchToSpeaker) { 1584 return; 1585 } 1586 1587 CallAudioState audioState = AudioModeProvider.getInstance().getAudioState(); 1588 1589 if (0 != (CallAudioState.ROUTE_BLUETOOTH & audioState.getSupportedRouteMask())) { 1590 LogUtil.e( 1591 "DialerCall.onUpgradedToVideo", 1592 "toggling speakerphone not allowed when bluetooth supported."); 1593 return; 1594 } 1595 1596 if (audioState.getRoute() == CallAudioState.ROUTE_SPEAKER) { 1597 return; 1598 } 1599 1600 TelecomAdapter.getInstance().setAudioRoute(CallAudioState.ROUTE_SPEAKER); 1601 } 1602 1603 @Override onCapabilitiesUpdated()1604 public void onCapabilitiesUpdated() { 1605 if (getNumber() == null) { 1606 return; 1607 } 1608 EnrichedCallCapabilities capabilities = 1609 EnrichedCallComponent.get(context).getEnrichedCallManager().getCapabilities(getNumber()); 1610 if (capabilities != null) { 1611 setEnrichedCallCapabilities(capabilities); 1612 update(); 1613 } 1614 } 1615 1616 @Override onEnrichedCallStateChanged()1617 public void onEnrichedCallStateChanged() { 1618 updateEnrichedCallSession(); 1619 } 1620 1621 @Override onImpressionLoggingNeeded(DialerImpression.Type impressionType)1622 public void onImpressionLoggingNeeded(DialerImpression.Type impressionType) { 1623 Logger.get(context).logCallImpression(impressionType, getUniqueCallId(), getTimeAddedMs()); 1624 if (impressionType == DialerImpression.Type.LIGHTBRINGER_UPGRADE_REQUESTED) { 1625 if (getLogState().contactLookupResult == Type.NOT_FOUND) { 1626 Logger.get(context) 1627 .logCallImpression( 1628 DialerImpression.Type.LIGHTBRINGER_NON_CONTACT_UPGRADE_REQUESTED, 1629 getUniqueCallId(), 1630 getTimeAddedMs()); 1631 } 1632 } 1633 } 1634 updateEnrichedCallSession()1635 private void updateEnrichedCallSession() { 1636 if (getNumber() == null) { 1637 return; 1638 } 1639 if (getEnrichedCallSession() != null) { 1640 // State changes to existing sessions are currently handled by the UI components (which have 1641 // their own listeners). Someday instead we could remove those and just call update() here and 1642 // have the usual onDialerCallUpdate update the UI. 1643 dispatchOnEnrichedCallSessionUpdate(); 1644 return; 1645 } 1646 1647 EnrichedCallManager manager = EnrichedCallComponent.get(context).getEnrichedCallManager(); 1648 1649 Filter filter = 1650 isIncoming() 1651 ? manager.createIncomingCallComposerFilter() 1652 : manager.createOutgoingCallComposerFilter(); 1653 1654 Session session = manager.getSession(getUniqueCallId(), getNumber(), filter); 1655 if (session == null) { 1656 return; 1657 } 1658 1659 session.setUniqueDialerCallId(getUniqueCallId()); 1660 setEnrichedCallSession(session); 1661 1662 LogUtil.i( 1663 "DialerCall.updateEnrichedCallSession", 1664 "setting session %d's dialer id to %s", 1665 session.getSessionId(), 1666 getUniqueCallId()); 1667 1668 dispatchOnEnrichedCallSessionUpdate(); 1669 } 1670 dispatchOnEnrichedCallSessionUpdate()1671 private void dispatchOnEnrichedCallSessionUpdate() { 1672 for (DialerCallListener listener : listeners) { 1673 listener.onEnrichedCallSessionUpdate(); 1674 } 1675 } 1676 onRemovedFromCallList()1677 void onRemovedFromCallList() { 1678 LogUtil.enterBlock("DialerCall.onRemovedFromCallList"); 1679 // Ensure we clean up when this call is removed. 1680 if (videoTechManager != null) { 1681 videoTechManager.dispatchRemovedFromCallList(); 1682 } 1683 // TODO(wangqi): Consider moving this to a DialerCallListener. 1684 if (rttTranscript != null && !isCallRemoved) { 1685 saveRttTranscript(); 1686 } 1687 isCallRemoved = true; 1688 } 1689 getSelectedAvailableVideoTechType()1690 public com.android.dialer.logging.VideoTech.Type getSelectedAvailableVideoTechType() { 1691 return selectedAvailableVideoTechType; 1692 } 1693 markFeedbackRequested()1694 public void markFeedbackRequested() { 1695 feedbackRequested = true; 1696 } 1697 isFeedbackRequested()1698 public boolean isFeedbackRequested() { 1699 return feedbackRequested; 1700 } 1701 1702 /** 1703 * If the in call UI has shown the phone account selection dialog for the call, the {@link 1704 * PreferredAccountRecorder} to record the result from the dialog. 1705 */ 1706 @Nullable getPreferredAccountRecorder()1707 public PreferredAccountRecorder getPreferredAccountRecorder() { 1708 return preferredAccountRecorder; 1709 } 1710 setPreferredAccountRecorder(PreferredAccountRecorder preferredAccountRecorder)1711 public void setPreferredAccountRecorder(PreferredAccountRecorder preferredAccountRecorder) { 1712 this.preferredAccountRecorder = preferredAccountRecorder; 1713 } 1714 1715 /** Indicates the call is eligible for SpeakEasy */ isSpeakEasyEligible()1716 public boolean isSpeakEasyEligible() { 1717 1718 PhoneAccount phoneAccount = getPhoneAccount(); 1719 1720 if (phoneAccount == null) { 1721 return false; 1722 } 1723 1724 if (!phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 1725 return false; 1726 } 1727 1728 return !isPotentialEmergencyCallback() 1729 && !isEmergencyCall() 1730 && !isActiveRttCall() 1731 && !isConferenceCall() 1732 && !isVideoCall() 1733 && !isVoiceMailNumber() 1734 && !hasReceivedVideoUpgradeRequest() 1735 && !isVoipCallNotSupportedBySpeakeasy(); 1736 } 1737 isVoipCallNotSupportedBySpeakeasy()1738 private boolean isVoipCallNotSupportedBySpeakeasy() { 1739 Bundle extras = getIntentExtras(); 1740 1741 if (extras == null) { 1742 return false; 1743 } 1744 1745 // Indicates an VOIP call. 1746 String callid = extras.getString("callid"); 1747 1748 if (TextUtils.isEmpty(callid)) { 1749 LogUtil.i("DialerCall.isVoipCallNotSupportedBySpeakeasy", "callid was empty"); 1750 return false; 1751 } 1752 1753 LogUtil.i("DialerCall.isVoipCallNotSupportedBySpeakeasy", "call is not eligible"); 1754 return true; 1755 } 1756 1757 /** Indicates the user has selected SpeakEasy */ isSpeakEasyCall()1758 public boolean isSpeakEasyCall() { 1759 if (!isSpeakEasyEligible()) { 1760 return false; 1761 } 1762 return isSpeakEasyCall; 1763 } 1764 1765 /** Sets the user preference for SpeakEasy */ setIsSpeakEasyCall(boolean isSpeakEasyCall)1766 public void setIsSpeakEasyCall(boolean isSpeakEasyCall) { 1767 this.isSpeakEasyCall = isSpeakEasyCall; 1768 if (listeners != null) { 1769 for (DialerCallListener listener : listeners) { 1770 listener.onDialerCallSpeakEasyStateChange(); 1771 } 1772 } 1773 } 1774 1775 /** 1776 * Specifies whether a number is in the call history or not. {@link #CALL_HISTORY_STATUS_UNKNOWN} 1777 * means there is no result. 1778 */ 1779 @IntDef({ 1780 CALL_HISTORY_STATUS_UNKNOWN, 1781 CALL_HISTORY_STATUS_PRESENT, 1782 CALL_HISTORY_STATUS_NOT_PRESENT 1783 }) 1784 @Retention(RetentionPolicy.SOURCE) 1785 public @interface CallHistoryStatus {} 1786 1787 /** Camera direction constants */ 1788 public static class CameraDirection { 1789 public static final int CAMERA_DIRECTION_UNKNOWN = -1; 1790 public static final int CAMERA_DIRECTION_FRONT_FACING = CameraCharacteristics.LENS_FACING_FRONT; 1791 public static final int CAMERA_DIRECTION_BACK_FACING = CameraCharacteristics.LENS_FACING_BACK; 1792 } 1793 1794 /** 1795 * Tracks any state variables that is useful for logging. There is some amount of overlap with 1796 * existing call member variables, but this duplication helps to ensure that none of these logging 1797 * variables will interface with/and affect call logic. 1798 */ 1799 public static class LogState { 1800 1801 public DisconnectCause disconnectCause; 1802 public boolean isIncoming = false; 1803 public ContactLookupResult.Type contactLookupResult = 1804 ContactLookupResult.Type.UNKNOWN_LOOKUP_RESULT_TYPE; 1805 public CallSpecificAppData callSpecificAppData; 1806 // If this was a conference call, the total number of calls involved in the conference. 1807 public int conferencedCalls = 0; 1808 public boolean isLogged = false; 1809 1810 // Result of subtracting android.telecom.Call.Details#getConnectTimeMillis from the current time 1811 public long telecomDurationMillis = 0; 1812 1813 // Result of a call to System.currentTimeMillis when Dialer sees that a call 1814 // moves to the ACTIVE state 1815 long dialerConnectTimeMillis = 0; 1816 1817 // Same as dialer_connect_time_millis, using SystemClock.elapsedRealtime 1818 // instead 1819 long dialerConnectTimeMillisElapsedRealtime = 0; 1820 1821 // Result of subtracting dialer_connect_time_millis from System.currentTimeMillis 1822 public long dialerDurationMillis = 0; 1823 1824 // Same as dialerDurationMillis, using SystemClock.elapsedRealtime instead 1825 public long dialerDurationMillisElapsedRealtime = 0; 1826 lookupToString(ContactLookupResult.Type lookupType)1827 private static String lookupToString(ContactLookupResult.Type lookupType) { 1828 switch (lookupType) { 1829 case LOCAL_CONTACT: 1830 return "Local"; 1831 case LOCAL_CACHE: 1832 return "Cache"; 1833 case REMOTE: 1834 return "Remote"; 1835 case EMERGENCY: 1836 return "Emergency"; 1837 case VOICEMAIL: 1838 return "Voicemail"; 1839 default: 1840 return "Not found"; 1841 } 1842 } 1843 initiationToString(CallSpecificAppData callSpecificAppData)1844 private static String initiationToString(CallSpecificAppData callSpecificAppData) { 1845 if (callSpecificAppData == null) { 1846 return "null"; 1847 } 1848 switch (callSpecificAppData.getCallInitiationType()) { 1849 case INCOMING_INITIATION: 1850 return "Incoming"; 1851 case DIALPAD: 1852 return "Dialpad"; 1853 case SPEED_DIAL: 1854 return "Speed Dial"; 1855 case REMOTE_DIRECTORY: 1856 return "Remote Directory"; 1857 case SMART_DIAL: 1858 return "Smart Dial"; 1859 case REGULAR_SEARCH: 1860 return "Regular Search"; 1861 case CALL_LOG: 1862 return "DialerCall Log"; 1863 case CALL_LOG_FILTER: 1864 return "DialerCall Log Filter"; 1865 case VOICEMAIL_LOG: 1866 return "Voicemail Log"; 1867 case CALL_DETAILS: 1868 return "DialerCall Details"; 1869 case QUICK_CONTACTS: 1870 return "Quick Contacts"; 1871 case EXTERNAL_INITIATION: 1872 return "External"; 1873 case LAUNCHER_SHORTCUT: 1874 return "Launcher Shortcut"; 1875 default: 1876 return "Unknown: " + callSpecificAppData.getCallInitiationType(); 1877 } 1878 } 1879 1880 @Override toString()1881 public String toString() { 1882 return String.format( 1883 Locale.US, 1884 "[" 1885 + "%s, " // DisconnectCause toString already describes the object type 1886 + "isIncoming: %s, " 1887 + "contactLookup: %s, " 1888 + "callInitiation: %s, " 1889 + "duration: %s" 1890 + "]", 1891 disconnectCause, 1892 isIncoming, 1893 lookupToString(contactLookupResult), 1894 initiationToString(callSpecificAppData), 1895 telecomDurationMillis); 1896 } 1897 } 1898 1899 /** Coordinates the available VideoTech implementations for a call. */ 1900 @VisibleForTesting 1901 public static class VideoTechManager { 1902 private final Context context; 1903 private final EmptyVideoTech emptyVideoTech = new EmptyVideoTech(); 1904 private final VideoTech rcsVideoShare; 1905 private final List<VideoTech> videoTechs; 1906 private VideoTech savedTech; 1907 1908 @VisibleForTesting VideoTechManager(DialerCall call)1909 public VideoTechManager(DialerCall call) { 1910 this.context = call.context; 1911 1912 String phoneNumber = call.getNumber(); 1913 phoneNumber = phoneNumber != null ? phoneNumber : ""; 1914 phoneNumber = phoneNumber.replaceAll("[^+0-9]", ""); 1915 1916 // Insert order here determines the priority of that video tech option 1917 videoTechs = new ArrayList<>(); 1918 1919 videoTechs.add(new ImsVideoTech(Logger.get(call.context), call, call.telecomCall)); 1920 1921 rcsVideoShare = 1922 EnrichedCallComponent.get(call.context) 1923 .getRcsVideoShareFactory() 1924 .newRcsVideoShare( 1925 EnrichedCallComponent.get(call.context).getEnrichedCallManager(), 1926 call, 1927 phoneNumber); 1928 videoTechs.add(rcsVideoShare); 1929 1930 videoTechs.add( 1931 new DuoVideoTech( 1932 DuoComponent.get(call.context).getDuo(), call, call.telecomCall, phoneNumber)); 1933 1934 savedTech = emptyVideoTech; 1935 } 1936 1937 @VisibleForTesting getVideoTech(PhoneAccountHandle phoneAccountHandle)1938 public VideoTech getVideoTech(PhoneAccountHandle phoneAccountHandle) { 1939 if (savedTech == emptyVideoTech) { 1940 for (VideoTech tech : videoTechs) { 1941 if (tech.isAvailable(context, phoneAccountHandle)) { 1942 savedTech = tech; 1943 savedTech.becomePrimary(); 1944 break; 1945 } 1946 } 1947 } else if (savedTech instanceof DuoVideoTech 1948 && rcsVideoShare.isAvailable(context, phoneAccountHandle)) { 1949 // RCS Video Share will become available after the capability exchange which is slower than 1950 // Duo reading local contacts for reachability. If Video Share becomes available and we are 1951 // not in the middle of any session changes, let it take over. 1952 savedTech = rcsVideoShare; 1953 rcsVideoShare.becomePrimary(); 1954 } 1955 1956 return savedTech; 1957 } 1958 1959 @VisibleForTesting dispatchCallStateChanged(int newState, PhoneAccountHandle phoneAccountHandle)1960 public void dispatchCallStateChanged(int newState, PhoneAccountHandle phoneAccountHandle) { 1961 for (VideoTech videoTech : videoTechs) { 1962 videoTech.onCallStateChanged(context, newState, phoneAccountHandle); 1963 } 1964 } 1965 dispatchRemovedFromCallList()1966 void dispatchRemovedFromCallList() { 1967 for (VideoTech videoTech : videoTechs) { 1968 videoTech.onRemovedFromCallList(); 1969 } 1970 } 1971 } 1972 1973 /** Called when canned text responses have been loaded. */ 1974 public interface CannedTextResponsesLoadedListener { onCannedTextResponsesLoaded(DialerCall call)1975 void onCannedTextResponsesLoaded(DialerCall call); 1976 } 1977 } 1978