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