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