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