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