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; 18 19 import android.content.Context; 20 import android.hardware.camera2.CameraCharacteristics; 21 import android.net.Uri; 22 import android.os.Bundle; 23 import android.os.Trace; 24 import android.telecom.Call.Details; 25 import android.telecom.Connection; 26 import android.telecom.DisconnectCause; 27 import android.telecom.GatewayInfo; 28 import android.telecom.InCallService.VideoCall; 29 import android.telecom.PhoneAccount; 30 import android.telecom.PhoneAccountHandle; 31 import android.telecom.TelecomManager; 32 import android.telecom.VideoProfile; 33 import android.text.TextUtils; 34 35 import com.android.contacts.common.CallUtil; 36 import com.android.contacts.common.compat.SdkVersionOverride; 37 import com.android.contacts.common.compat.telecom.TelecomManagerCompat; 38 import com.android.contacts.common.testing.NeededForTesting; 39 import com.android.dialer.util.IntentUtil; 40 import com.android.incallui.util.TelecomCallUtil; 41 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.Locale; 45 import java.util.Objects; 46 47 /** 48 * Describes a single call and its state. 49 */ 50 @NeededForTesting 51 public class Call { 52 /* Defines different states of this call */ 53 public static class State { 54 public static final int INVALID = 0; 55 public static final int NEW = 1; /* The call is new. */ 56 public static final int IDLE = 2; /* The call is idle. Nothing active */ 57 public static final int ACTIVE = 3; /* There is an active call */ 58 public static final int INCOMING = 4; /* A normal incoming phone call */ 59 public static final int CALL_WAITING = 5; /* Incoming call while another is active */ 60 public static final int DIALING = 6; /* An outgoing call during dial phase */ 61 public static final int REDIALING = 7; /* Subsequent dialing attempt after a failure */ 62 public static final int ONHOLD = 8; /* An active phone call placed on hold */ 63 public static final int DISCONNECTING = 9; /* A call is being ended. */ 64 public static final int DISCONNECTED = 10; /* State after a call disconnects */ 65 public static final int CONFERENCED = 11; /* Call part of a conference call */ 66 public static final int SELECT_PHONE_ACCOUNT = 12; /* Waiting for account selection */ 67 public static final int CONNECTING = 13; /* Waiting for Telecom broadcast to finish */ 68 public static final int BLOCKED = 14; /* The number was found on the block list */ 69 70 isConnectingOrConnected(int state)71 public static boolean isConnectingOrConnected(int state) { 72 switch(state) { 73 case ACTIVE: 74 case INCOMING: 75 case CALL_WAITING: 76 case CONNECTING: 77 case DIALING: 78 case REDIALING: 79 case ONHOLD: 80 case CONFERENCED: 81 return true; 82 default: 83 } 84 return false; 85 } 86 isDialing(int state)87 public static boolean isDialing(int state) { 88 return state == DIALING || state == REDIALING; 89 } 90 toString(int state)91 public static String toString(int state) { 92 switch (state) { 93 case INVALID: 94 return "INVALID"; 95 case NEW: 96 return "NEW"; 97 case IDLE: 98 return "IDLE"; 99 case ACTIVE: 100 return "ACTIVE"; 101 case INCOMING: 102 return "INCOMING"; 103 case CALL_WAITING: 104 return "CALL_WAITING"; 105 case DIALING: 106 return "DIALING"; 107 case REDIALING: 108 return "REDIALING"; 109 case ONHOLD: 110 return "ONHOLD"; 111 case DISCONNECTING: 112 return "DISCONNECTING"; 113 case DISCONNECTED: 114 return "DISCONNECTED"; 115 case CONFERENCED: 116 return "CONFERENCED"; 117 case SELECT_PHONE_ACCOUNT: 118 return "SELECT_PHONE_ACCOUNT"; 119 case CONNECTING: 120 return "CONNECTING"; 121 case BLOCKED: 122 return "BLOCKED"; 123 default: 124 return "UNKNOWN"; 125 } 126 } 127 } 128 129 /** 130 * Defines different states of session modify requests, which are used to upgrade to video, or 131 * downgrade to audio. 132 */ 133 public static class SessionModificationState { 134 public static final int NO_REQUEST = 0; 135 public static final int WAITING_FOR_RESPONSE = 1; 136 public static final int REQUEST_FAILED = 2; 137 public static final int RECEIVED_UPGRADE_TO_VIDEO_REQUEST = 3; 138 public static final int UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT = 4; 139 public static final int REQUEST_REJECTED = 5; 140 } 141 142 public static class VideoSettings { 143 public static final int CAMERA_DIRECTION_UNKNOWN = -1; 144 public static final int CAMERA_DIRECTION_FRONT_FACING = 145 CameraCharacteristics.LENS_FACING_FRONT; 146 public static final int CAMERA_DIRECTION_BACK_FACING = 147 CameraCharacteristics.LENS_FACING_BACK; 148 149 private int mCameraDirection = CAMERA_DIRECTION_UNKNOWN; 150 151 /** 152 * Sets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN, 153 * the video state of the call should be used to infer the camera direction. 154 * 155 * @see {@link CameraCharacteristics#LENS_FACING_FRONT} 156 * @see {@link CameraCharacteristics#LENS_FACING_BACK} 157 */ setCameraDir(int cameraDirection)158 public void setCameraDir(int cameraDirection) { 159 if (cameraDirection == CAMERA_DIRECTION_FRONT_FACING 160 || cameraDirection == CAMERA_DIRECTION_BACK_FACING) { 161 mCameraDirection = cameraDirection; 162 } else { 163 mCameraDirection = CAMERA_DIRECTION_UNKNOWN; 164 } 165 } 166 167 /** 168 * Gets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN, 169 * the video state of the call should be used to infer the camera direction. 170 * 171 * @see {@link CameraCharacteristics#LENS_FACING_FRONT} 172 * @see {@link CameraCharacteristics#LENS_FACING_BACK} 173 */ getCameraDir()174 public int getCameraDir() { 175 return mCameraDirection; 176 } 177 178 @Override toString()179 public String toString() { 180 return "(CameraDir:" + getCameraDir() + ")"; 181 } 182 } 183 184 /** 185 * Tracks any state variables that is useful for logging. There is some amount of overlap with 186 * existing call member variables, but this duplication helps to ensure that none of these 187 * logging variables will interface with/and affect call logic. 188 */ 189 public static class LogState { 190 191 // Contact lookup type constants 192 // Unknown lookup result (lookup not completed yet?) 193 public static final int LOOKUP_UNKNOWN = 0; 194 public static final int LOOKUP_NOT_FOUND = 1; 195 public static final int LOOKUP_LOCAL_CONTACT = 2; 196 public static final int LOOKUP_LOCAL_CACHE = 3; 197 public static final int LOOKUP_REMOTE_CONTACT = 4; 198 public static final int LOOKUP_EMERGENCY = 5; 199 public static final int LOOKUP_VOICEMAIL = 6; 200 201 // Call initiation type constants 202 public static final int INITIATION_UNKNOWN = 0; 203 public static final int INITIATION_INCOMING = 1; 204 public static final int INITIATION_DIALPAD = 2; 205 public static final int INITIATION_SPEED_DIAL = 3; 206 public static final int INITIATION_REMOTE_DIRECTORY = 4; 207 public static final int INITIATION_SMART_DIAL = 5; 208 public static final int INITIATION_REGULAR_SEARCH = 6; 209 public static final int INITIATION_CALL_LOG = 7; 210 public static final int INITIATION_CALL_LOG_FILTER = 8; 211 public static final int INITIATION_VOICEMAIL_LOG = 9; 212 public static final int INITIATION_CALL_DETAILS = 10; 213 public static final int INITIATION_QUICK_CONTACTS = 11; 214 public static final int INITIATION_EXTERNAL = 12; 215 216 public DisconnectCause disconnectCause; 217 public boolean isIncoming = false; 218 public int contactLookupResult = LOOKUP_UNKNOWN; 219 public int callInitiationMethod = INITIATION_EXTERNAL; 220 // If this was a conference call, the total number of calls involved in the conference. 221 public int conferencedCalls = 0; 222 public long duration = 0; 223 public boolean isLogged = false; 224 225 @Override toString()226 public String toString() { 227 return String.format(Locale.US, "[" 228 + "%s, " // DisconnectCause toString already describes the object type 229 + "isIncoming: %s, " 230 + "contactLookup: %s, " 231 + "callInitiation: %s, " 232 + "duration: %s" 233 + "]", 234 disconnectCause, 235 isIncoming, 236 lookupToString(contactLookupResult), 237 initiationToString(callInitiationMethod), 238 duration); 239 } 240 lookupToString(int lookupType)241 private static String lookupToString(int lookupType) { 242 switch (lookupType) { 243 case LOOKUP_LOCAL_CONTACT: 244 return "Local"; 245 case LOOKUP_LOCAL_CACHE: 246 return "Cache"; 247 case LOOKUP_REMOTE_CONTACT: 248 return "Remote"; 249 case LOOKUP_EMERGENCY: 250 return "Emergency"; 251 case LOOKUP_VOICEMAIL: 252 return "Voicemail"; 253 default: 254 return "Not found"; 255 } 256 } 257 initiationToString(int initiationType)258 private static String initiationToString(int initiationType) { 259 switch (initiationType) { 260 case INITIATION_INCOMING: 261 return "Incoming"; 262 case INITIATION_DIALPAD: 263 return "Dialpad"; 264 case INITIATION_SPEED_DIAL: 265 return "Speed Dial"; 266 case INITIATION_REMOTE_DIRECTORY: 267 return "Remote Directory"; 268 case INITIATION_SMART_DIAL: 269 return "Smart Dial"; 270 case INITIATION_REGULAR_SEARCH: 271 return "Regular Search"; 272 case INITIATION_CALL_LOG: 273 return "Call Log"; 274 case INITIATION_CALL_LOG_FILTER: 275 return "Call Log Filter"; 276 case INITIATION_VOICEMAIL_LOG: 277 return "Voicemail Log"; 278 case INITIATION_CALL_DETAILS: 279 return "Call Details"; 280 case INITIATION_QUICK_CONTACTS: 281 return "Quick Contacts"; 282 default: 283 return "Unknown"; 284 } 285 } 286 } 287 288 289 private static final String ID_PREFIX = Call.class.getSimpleName() + "_"; 290 private static int sIdCounter = 0; 291 292 private final android.telecom.Call.Callback mTelecomCallCallback = 293 new android.telecom.Call.Callback() { 294 @Override 295 public void onStateChanged(android.telecom.Call call, int newState) { 296 Log.d(this, "TelecomCallCallback onStateChanged call=" + call + " newState=" 297 + newState); 298 update(); 299 } 300 301 @Override 302 public void onParentChanged(android.telecom.Call call, 303 android.telecom.Call newParent) { 304 Log.d(this, "TelecomCallCallback onParentChanged call=" + call + " newParent=" 305 + newParent); 306 update(); 307 } 308 309 @Override 310 public void onChildrenChanged(android.telecom.Call call, 311 List<android.telecom.Call> children) { 312 update(); 313 } 314 315 @Override 316 public void onDetailsChanged(android.telecom.Call call, 317 android.telecom.Call.Details details) { 318 Log.d(this, "TelecomCallCallback onStateChanged call=" + call + " details=" 319 + details); 320 update(); 321 } 322 323 @Override 324 public void onCannedTextResponsesLoaded(android.telecom.Call call, 325 List<String> cannedTextResponses) { 326 Log.d(this, "TelecomCallCallback onStateChanged call=" + call 327 + " cannedTextResponses=" + cannedTextResponses); 328 update(); 329 } 330 331 @Override 332 public void onPostDialWait(android.telecom.Call call, 333 String remainingPostDialSequence) { 334 Log.d(this, "TelecomCallCallback onStateChanged call=" + call 335 + " remainingPostDialSequence=" + remainingPostDialSequence); 336 update(); 337 } 338 339 @Override 340 public void onVideoCallChanged(android.telecom.Call call, 341 VideoCall videoCall) { 342 Log.d(this, "TelecomCallCallback onStateChanged call=" + call + " videoCall=" 343 + videoCall); 344 update(); 345 } 346 347 @Override 348 public void onCallDestroyed(android.telecom.Call call) { 349 Log.d(this, "TelecomCallCallback onStateChanged call=" + call); 350 call.unregisterCallback(this); 351 } 352 353 @Override 354 public void onConferenceableCallsChanged(android.telecom.Call call, 355 List<android.telecom.Call> conferenceableCalls) { 356 update(); 357 } 358 }; 359 360 private android.telecom.Call mTelecomCall; 361 private boolean mIsEmergencyCall; 362 private Uri mHandle; 363 private final String mId; 364 private int mState = State.INVALID; 365 private DisconnectCause mDisconnectCause; 366 private int mSessionModificationState; 367 private final List<String> mChildCallIds = new ArrayList<>(); 368 private final VideoSettings mVideoSettings = new VideoSettings(); 369 private int mVideoState; 370 371 /** 372 * mRequestedVideoState is used to store requested upgrade / downgrade video state 373 */ 374 private int mRequestedVideoState = VideoProfile.STATE_AUDIO_ONLY; 375 376 private InCallVideoCallCallback mVideoCallCallback; 377 private boolean mIsVideoCallCallbackRegistered; 378 private String mChildNumber; 379 private String mLastForwardedNumber; 380 private String mCallSubject; 381 private PhoneAccountHandle mPhoneAccountHandle; 382 383 /** 384 * Indicates whether the phone account associated with this call supports specifying a call 385 * subject. 386 */ 387 private boolean mIsCallSubjectSupported; 388 389 private long mTimeAddedMs; 390 391 private LogState mLogState = new LogState(); 392 393 /** 394 * Used only to create mock calls for testing 395 */ 396 @NeededForTesting Call(int state)397 Call(int state) { 398 mTelecomCall = null; 399 mId = ID_PREFIX + Integer.toString(sIdCounter++); 400 setState(state); 401 } 402 Call(android.telecom.Call telecomCall)403 public Call(android.telecom.Call telecomCall) { 404 mTelecomCall = telecomCall; 405 mId = ID_PREFIX + Integer.toString(sIdCounter++); 406 407 updateFromTelecomCall(); 408 409 mTelecomCall.registerCallback(mTelecomCallCallback); 410 411 mTimeAddedMs = System.currentTimeMillis(); 412 } 413 getTelecomCall()414 public android.telecom.Call getTelecomCall() { 415 return mTelecomCall; 416 } 417 418 /** 419 * @return video settings of the call, null if the call is not a video call. 420 * @see VideoProfile 421 */ getVideoSettings()422 public VideoSettings getVideoSettings() { 423 return mVideoSettings; 424 } 425 update()426 private void update() { 427 Trace.beginSection("Update"); 428 int oldState = getState(); 429 updateFromTelecomCall(); 430 if (oldState != getState() && getState() == Call.State.DISCONNECTED) { 431 CallList.getInstance().onDisconnect(this); 432 } else { 433 CallList.getInstance().onUpdate(this); 434 } 435 Trace.endSection(); 436 } 437 updateFromTelecomCall()438 private void updateFromTelecomCall() { 439 Log.d(this, "updateFromTelecomCall: " + mTelecomCall.toString()); 440 final int translatedState = translateState(mTelecomCall.getState()); 441 if (mState != State.BLOCKED) { 442 setState(translatedState); 443 setDisconnectCause(mTelecomCall.getDetails().getDisconnectCause()); 444 maybeCancelVideoUpgrade(mTelecomCall.getDetails().getVideoState()); 445 } 446 447 if (mTelecomCall.getVideoCall() != null) { 448 if (mVideoCallCallback == null) { 449 mVideoCallCallback = new InCallVideoCallCallback(this); 450 } 451 mTelecomCall.getVideoCall().registerCallback(mVideoCallCallback); 452 mIsVideoCallCallbackRegistered = true; 453 } 454 455 mChildCallIds.clear(); 456 final int numChildCalls = mTelecomCall.getChildren().size(); 457 for (int i = 0; i < numChildCalls; i++) { 458 mChildCallIds.add( 459 CallList.getInstance().getCallByTelecomCall( 460 mTelecomCall.getChildren().get(i)).getId()); 461 } 462 463 // The number of conferenced calls can change over the course of the call, so use the 464 // maximum number of conferenced child calls as the metric for conference call usage. 465 mLogState.conferencedCalls = Math.max(numChildCalls, mLogState.conferencedCalls); 466 467 updateFromCallExtras(mTelecomCall.getDetails().getExtras()); 468 469 // If the handle of the call has changed, update state for the call determining if it is an 470 // emergency call. 471 Uri newHandle = mTelecomCall.getDetails().getHandle(); 472 if (!Objects.equals(mHandle, newHandle)) { 473 mHandle = newHandle; 474 updateEmergencyCallState(); 475 } 476 477 // If the phone account handle of the call is set, cache capability bit indicating whether 478 // the phone account supports call subjects. 479 PhoneAccountHandle newPhoneAccountHandle = mTelecomCall.getDetails().getAccountHandle(); 480 if (!Objects.equals(mPhoneAccountHandle, newPhoneAccountHandle)) { 481 mPhoneAccountHandle = newPhoneAccountHandle; 482 483 if (mPhoneAccountHandle != null) { 484 TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager(); 485 PhoneAccount phoneAccount = 486 TelecomManagerCompat.getPhoneAccount(mgr, mPhoneAccountHandle); 487 if (phoneAccount != null) { 488 mIsCallSubjectSupported = phoneAccount.hasCapabilities( 489 PhoneAccount.CAPABILITY_CALL_SUBJECT); 490 } 491 } 492 } 493 } 494 495 /** 496 * Tests corruption of the {@code callExtras} bundle by calling {@link 497 * Bundle#containsKey(String)}. If the bundle is corrupted a {@link IllegalArgumentException} 498 * will be thrown and caught by this function. 499 * 500 * @param callExtras the bundle to verify 501 * @returns {@code true} if the bundle is corrupted, {@code false} otherwise. 502 */ areCallExtrasCorrupted(Bundle callExtras)503 protected boolean areCallExtrasCorrupted(Bundle callExtras) { 504 /** 505 * There's currently a bug in Telephony service (b/25613098) that could corrupt the 506 * extras bundle, resulting in a IllegalArgumentException while validating data under 507 * {@link Bundle#containsKey(String)}. 508 */ 509 try { 510 callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS); 511 return false; 512 } catch (IllegalArgumentException e) { 513 Log.e(this, "CallExtras is corrupted, ignoring exception", e); 514 return true; 515 } 516 } 517 updateFromCallExtras(Bundle callExtras)518 protected void updateFromCallExtras(Bundle callExtras) { 519 if (callExtras == null || areCallExtrasCorrupted(callExtras)) { 520 /** 521 * If the bundle is corrupted, abandon information update as a work around. These are 522 * not critical for the dialer to function. 523 */ 524 return; 525 } 526 // Check for a change in the child address and notify any listeners. 527 if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) { 528 String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS); 529 if (!Objects.equals(childNumber, mChildNumber)) { 530 mChildNumber = childNumber; 531 CallList.getInstance().onChildNumberChange(this); 532 } 533 } 534 535 // Last forwarded number comes in as an array of strings. We want to choose the 536 // last item in the array. The forwarding numbers arrive independently of when the 537 // call is originally set up, so we need to notify the the UI of the change. 538 if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) { 539 ArrayList<String> lastForwardedNumbers = 540 callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER); 541 542 if (lastForwardedNumbers != null) { 543 String lastForwardedNumber = null; 544 if (!lastForwardedNumbers.isEmpty()) { 545 lastForwardedNumber = lastForwardedNumbers.get( 546 lastForwardedNumbers.size() - 1); 547 } 548 549 if (!Objects.equals(lastForwardedNumber, mLastForwardedNumber)) { 550 mLastForwardedNumber = lastForwardedNumber; 551 CallList.getInstance().onLastForwardedNumberChange(this); 552 } 553 } 554 } 555 556 // Call subject is present in the extras at the start of call, so we do not need to 557 // notify any other listeners of this. 558 if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) { 559 String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT); 560 if (!Objects.equals(mCallSubject, callSubject)) { 561 mCallSubject = callSubject; 562 } 563 } 564 } 565 566 /** 567 * Determines if a received upgrade to video request should be cancelled. This can happen if 568 * another InCall UI responds to the upgrade to video request. 569 * 570 * @param newVideoState The new video state. 571 */ maybeCancelVideoUpgrade(int newVideoState)572 private void maybeCancelVideoUpgrade(int newVideoState) { 573 boolean isVideoStateChanged = mVideoState != newVideoState; 574 575 if (mSessionModificationState == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST 576 && isVideoStateChanged) { 577 578 Log.v(this, "maybeCancelVideoUpgrade : cancelling upgrade notification"); 579 setSessionModificationState(SessionModificationState.NO_REQUEST); 580 } 581 mVideoState = newVideoState; 582 } translateState(int state)583 private static int translateState(int state) { 584 switch (state) { 585 case android.telecom.Call.STATE_NEW: 586 case android.telecom.Call.STATE_CONNECTING: 587 return Call.State.CONNECTING; 588 case android.telecom.Call.STATE_SELECT_PHONE_ACCOUNT: 589 return Call.State.SELECT_PHONE_ACCOUNT; 590 case android.telecom.Call.STATE_DIALING: 591 return Call.State.DIALING; 592 case android.telecom.Call.STATE_RINGING: 593 return Call.State.INCOMING; 594 case android.telecom.Call.STATE_ACTIVE: 595 return Call.State.ACTIVE; 596 case android.telecom.Call.STATE_HOLDING: 597 return Call.State.ONHOLD; 598 case android.telecom.Call.STATE_DISCONNECTED: 599 return Call.State.DISCONNECTED; 600 case android.telecom.Call.STATE_DISCONNECTING: 601 return Call.State.DISCONNECTING; 602 default: 603 return Call.State.INVALID; 604 } 605 } 606 getId()607 public String getId() { 608 return mId; 609 } 610 getTimeAddedMs()611 public long getTimeAddedMs() { 612 return mTimeAddedMs; 613 } 614 getNumber()615 public String getNumber() { 616 return TelecomCallUtil.getNumber(mTelecomCall); 617 } 618 blockCall()619 public void blockCall() { 620 mTelecomCall.reject(false, null); 621 setState(State.BLOCKED); 622 } 623 getHandle()624 public Uri getHandle() { 625 return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandle(); 626 } 627 isEmergencyCall()628 public boolean isEmergencyCall() { 629 return mIsEmergencyCall; 630 } 631 getState()632 public int getState() { 633 if (mTelecomCall != null && mTelecomCall.getParent() != null) { 634 return State.CONFERENCED; 635 } else { 636 return mState; 637 } 638 } 639 setState(int state)640 public void setState(int state) { 641 mState = state; 642 if (mState == State.INCOMING) { 643 mLogState.isIncoming = true; 644 } else if (mState == State.DISCONNECTED) { 645 mLogState.duration = getConnectTimeMillis() == 0 ? 646 0: System.currentTimeMillis() - getConnectTimeMillis(); 647 } 648 } 649 getNumberPresentation()650 public int getNumberPresentation() { 651 return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandlePresentation(); 652 } 653 getCnapNamePresentation()654 public int getCnapNamePresentation() { 655 return mTelecomCall == null ? null 656 : mTelecomCall.getDetails().getCallerDisplayNamePresentation(); 657 } 658 getCnapName()659 public String getCnapName() { 660 return mTelecomCall == null ? null 661 : getTelecomCall().getDetails().getCallerDisplayName(); 662 } 663 getIntentExtras()664 public Bundle getIntentExtras() { 665 return mTelecomCall.getDetails().getIntentExtras(); 666 } 667 getExtras()668 public Bundle getExtras() { 669 return mTelecomCall == null ? null : mTelecomCall.getDetails().getExtras(); 670 } 671 672 /** 673 * @return The child number for the call, or {@code null} if none specified. 674 */ getChildNumber()675 public String getChildNumber() { 676 return mChildNumber; 677 } 678 679 /** 680 * @return The last forwarded number for the call, or {@code null} if none specified. 681 */ getLastForwardedNumber()682 public String getLastForwardedNumber() { 683 return mLastForwardedNumber; 684 } 685 686 /** 687 * @return The call subject, or {@code null} if none specified. 688 */ getCallSubject()689 public String getCallSubject() { 690 return mCallSubject; 691 } 692 693 /** 694 * @return {@code true} if the call's phone account supports call subjects, {@code false} 695 * otherwise. 696 */ isCallSubjectSupported()697 public boolean isCallSubjectSupported() { 698 return mIsCallSubjectSupported; 699 } 700 701 /** Returns call disconnect cause, defined by {@link DisconnectCause}. */ getDisconnectCause()702 public DisconnectCause getDisconnectCause() { 703 if (mState == State.DISCONNECTED || mState == State.IDLE) { 704 return mDisconnectCause; 705 } 706 707 return new DisconnectCause(DisconnectCause.UNKNOWN); 708 } 709 setDisconnectCause(DisconnectCause disconnectCause)710 public void setDisconnectCause(DisconnectCause disconnectCause) { 711 mDisconnectCause = disconnectCause; 712 mLogState.disconnectCause = mDisconnectCause; 713 } 714 715 /** Returns the possible text message responses. */ getCannedSmsResponses()716 public List<String> getCannedSmsResponses() { 717 return mTelecomCall.getCannedTextResponses(); 718 } 719 720 /** Checks if the call supports the given set of capabilities supplied as a bit mask. */ can(int capabilities)721 public boolean can(int capabilities) { 722 int supportedCapabilities = mTelecomCall.getDetails().getCallCapabilities(); 723 724 if ((capabilities & android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) { 725 // We allow you to merge if the capabilities allow it or if it is a call with 726 // conferenceable calls. 727 if (mTelecomCall.getConferenceableCalls().isEmpty() && 728 ((android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE 729 & supportedCapabilities) == 0)) { 730 // Cannot merge calls if there are no calls to merge with. 731 return false; 732 } 733 capabilities &= ~android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE; 734 } 735 return (capabilities == (capabilities & mTelecomCall.getDetails().getCallCapabilities())); 736 } 737 hasProperty(int property)738 public boolean hasProperty(int property) { 739 return mTelecomCall.getDetails().hasProperty(property); 740 } 741 742 /** Gets the time when the call first became active. */ getConnectTimeMillis()743 public long getConnectTimeMillis() { 744 return mTelecomCall.getDetails().getConnectTimeMillis(); 745 } 746 isConferenceCall()747 public boolean isConferenceCall() { 748 return hasProperty(android.telecom.Call.Details.PROPERTY_CONFERENCE); 749 } 750 getGatewayInfo()751 public GatewayInfo getGatewayInfo() { 752 return mTelecomCall == null ? null : mTelecomCall.getDetails().getGatewayInfo(); 753 } 754 getAccountHandle()755 public PhoneAccountHandle getAccountHandle() { 756 return mTelecomCall == null ? null : mTelecomCall.getDetails().getAccountHandle(); 757 } 758 759 /** 760 * @return The {@link VideoCall} instance associated with the {@link android.telecom.Call}. 761 * Will return {@code null} until {@link #updateFromTelecomCall()} has registered a valid 762 * callback on the {@link VideoCall}. 763 */ getVideoCall()764 public VideoCall getVideoCall() { 765 return mTelecomCall == null || !mIsVideoCallCallbackRegistered ? null 766 : mTelecomCall.getVideoCall(); 767 } 768 getChildCallIds()769 public List<String> getChildCallIds() { 770 return mChildCallIds; 771 } 772 getParentId()773 public String getParentId() { 774 android.telecom.Call parentCall = mTelecomCall.getParent(); 775 if (parentCall != null) { 776 return CallList.getInstance().getCallByTelecomCall(parentCall).getId(); 777 } 778 return null; 779 } 780 getVideoState()781 public int getVideoState() { 782 return mTelecomCall.getDetails().getVideoState(); 783 } 784 isVideoCall(Context context)785 public boolean isVideoCall(Context context) { 786 return CallUtil.isVideoEnabled(context) && 787 VideoUtils.isVideoCall(getVideoState()); 788 } 789 790 /** 791 * Handles incoming session modification requests. Stores the pending video request and sets 792 * the session modification state to 793 * {@link SessionModificationState#RECEIVED_UPGRADE_TO_VIDEO_REQUEST} so that we can keep track 794 * of the fact the request was received. Only upgrade requests require user confirmation and 795 * will be handled by this method. The remote user can turn off their own camera without 796 * confirmation. 797 * 798 * @param videoState The requested video state. 799 */ setRequestedVideoState(int videoState)800 public void setRequestedVideoState(int videoState) { 801 Log.d(this, "setRequestedVideoState - video state= " + videoState); 802 if (videoState == getVideoState()) { 803 mSessionModificationState = Call.SessionModificationState.NO_REQUEST; 804 Log.w(this,"setRequestedVideoState - Clearing session modification state"); 805 return; 806 } 807 808 mSessionModificationState = Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST; 809 mRequestedVideoState = videoState; 810 CallList.getInstance().onUpgradeToVideo(this); 811 812 Log.d(this, "setRequestedVideoState - mSessionModificationState=" 813 + mSessionModificationState + " video state= " + videoState); 814 update(); 815 } 816 817 /** 818 * Set the session modification state. Used to keep track of pending video session modification 819 * operations and to inform listeners of these changes. 820 * 821 * @param state the new session modification state. 822 */ setSessionModificationState(int state)823 public void setSessionModificationState(int state) { 824 boolean hasChanged = mSessionModificationState != state; 825 mSessionModificationState = state; 826 Log.d(this, "setSessionModificationState " + state + " mSessionModificationState=" 827 + mSessionModificationState); 828 if (hasChanged) { 829 CallList.getInstance().onSessionModificationStateChange(this, state); 830 } 831 } 832 833 /** 834 * Determines if the call handle is an emergency number or not and caches the result to avoid 835 * repeated calls to isEmergencyNumber. 836 */ updateEmergencyCallState()837 private void updateEmergencyCallState() { 838 mIsEmergencyCall = TelecomCallUtil.isEmergencyCall(mTelecomCall); 839 } 840 841 /** 842 * Gets the video state which was requested via a session modification request. 843 * 844 * @return The video state. 845 */ getRequestedVideoState()846 public int getRequestedVideoState() { 847 return mRequestedVideoState; 848 } 849 areSame(Call call1, Call call2)850 public static boolean areSame(Call call1, Call call2) { 851 if (call1 == null && call2 == null) { 852 return true; 853 } else if (call1 == null || call2 == null) { 854 return false; 855 } 856 857 // otherwise compare call Ids 858 return call1.getId().equals(call2.getId()); 859 } 860 areSameNumber(Call call1, Call call2)861 public static boolean areSameNumber(Call call1, Call call2) { 862 if (call1 == null && call2 == null) { 863 return true; 864 } else if (call1 == null || call2 == null) { 865 return false; 866 } 867 868 // otherwise compare call Numbers 869 return TextUtils.equals(call1.getNumber(), call2.getNumber()); 870 } 871 872 /** 873 * Gets the current video session modification state. 874 * 875 * @return The session modification state. 876 */ getSessionModificationState()877 public int getSessionModificationState() { 878 return mSessionModificationState; 879 } 880 getLogState()881 public LogState getLogState() { 882 return mLogState; 883 } 884 885 /** 886 * Logging utility methods 887 */ logCallInitiationType()888 public void logCallInitiationType() { 889 if (getState() == State.INCOMING) { 890 getLogState().callInitiationMethod = LogState.INITIATION_INCOMING; 891 } else if (getIntentExtras() != null) { 892 getLogState().callInitiationMethod = 893 getIntentExtras().getInt(IntentUtil.EXTRA_CALL_INITIATION_TYPE, 894 LogState.INITIATION_EXTERNAL); 895 } 896 } 897 898 @Override toString()899 public String toString() { 900 if (mTelecomCall == null) { 901 // This should happen only in testing since otherwise we would never have a null 902 // Telecom call. 903 return String.valueOf(mId); 904 } 905 906 return String.format(Locale.US, "[%s, %s, %s, children:%s, parent:%s, conferenceable:%s, " + 907 "videoState:%s, mSessionModificationState:%d, VideoSettings:%s]", 908 mId, 909 State.toString(getState()), 910 Details.capabilitiesToString(mTelecomCall.getDetails().getCallCapabilities()), 911 mChildCallIds, 912 getParentId(), 913 this.mTelecomCall.getConferenceableCalls(), 914 VideoProfile.videoStateToString(mTelecomCall.getDetails().getVideoState()), 915 mSessionModificationState, 916 getVideoSettings()); 917 } 918 toSimpleString()919 public String toSimpleString() { 920 return super.toString(); 921 } 922 } 923