Call.java revision 37a4dca8ca58f7877540bd2bdac17e4ded5d85f4
1/* 2 * Copyright (C) 2014 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 17package com.android.server.telecom; 18 19import android.content.Context; 20import android.content.Intent; 21import android.graphics.Bitmap; 22import android.graphics.drawable.Drawable; 23import android.net.Uri; 24import android.os.Build; 25import android.os.Bundle; 26import android.os.Handler; 27import android.os.Looper; 28import android.os.ParcelFileDescriptor; 29import android.os.Parcelable; 30import android.os.RemoteException; 31import android.os.SystemClock; 32import android.os.Trace; 33import android.provider.ContactsContract.Contacts; 34import android.telecom.CallAudioState; 35import android.telecom.Conference; 36import android.telecom.ConnectionService; 37import android.telecom.DisconnectCause; 38import android.telecom.Connection; 39import android.telecom.GatewayInfo; 40import android.telecom.Log; 41import android.telecom.Logging.EventManager; 42import android.telecom.ParcelableConnection; 43import android.telecom.PhoneAccount; 44import android.telecom.PhoneAccountHandle; 45import android.telecom.Response; 46import android.telecom.StatusHints; 47import android.telecom.TelecomManager; 48import android.telecom.VideoProfile; 49import android.telephony.PhoneNumberUtils; 50import android.text.TextUtils; 51import android.os.UserHandle; 52 53import com.android.internal.annotations.VisibleForTesting; 54import com.android.internal.telecom.IVideoProvider; 55import com.android.internal.telephony.CallerInfo; 56import com.android.internal.telephony.SmsApplication; 57import com.android.internal.util.Preconditions; 58 59import java.io.IOException; 60import java.lang.String; 61import java.text.SimpleDateFormat; 62import java.util.ArrayList; 63import java.util.Collections; 64import java.util.Date; 65import java.util.LinkedList; 66import java.util.List; 67import java.util.Locale; 68import java.util.Objects; 69import java.util.Set; 70import java.util.concurrent.ConcurrentHashMap; 71 72/** 73 * Encapsulates all aspects of a given phone call throughout its lifecycle, starting 74 * from the time the call intent was received by Telecom (vs. the time the call was 75 * connected etc). 76 */ 77@VisibleForTesting 78public class Call implements CreateConnectionResponse, EventManager.Loggable, 79 ConnectionServiceFocusManager.CallFocus { 80 public final static String CALL_ID_UNKNOWN = "-1"; 81 public final static long DATA_USAGE_NOT_SET = -1; 82 83 public static final int CALL_DIRECTION_UNDEFINED = 0; 84 public static final int CALL_DIRECTION_OUTGOING = 1; 85 public static final int CALL_DIRECTION_INCOMING = 2; 86 public static final int CALL_DIRECTION_UNKNOWN = 3; 87 88 /** Identifies extras changes which originated from a connection service. */ 89 public static final int SOURCE_CONNECTION_SERVICE = 1; 90 /** Identifies extras changes which originated from an incall service. */ 91 public static final int SOURCE_INCALL_SERVICE = 2; 92 93 private static final int RTT_PIPE_READ_SIDE_INDEX = 0; 94 private static final int RTT_PIPE_WRITE_SIDE_INDEX = 1; 95 96 private static final int INVALID_RTT_REQUEST_ID = -1; 97 98 private static final char NO_DTMF_TONE = '\0'; 99 100 /** 101 * Listener for events on the call. 102 */ 103 @VisibleForTesting 104 public interface Listener { 105 void onSuccessfulOutgoingCall(Call call, int callState); 106 void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause); 107 void onSuccessfulIncomingCall(Call call); 108 void onFailedIncomingCall(Call call); 109 void onSuccessfulUnknownCall(Call call, int callState); 110 void onFailedUnknownCall(Call call); 111 void onRingbackRequested(Call call, boolean ringbackRequested); 112 void onPostDialWait(Call call, String remaining); 113 void onPostDialChar(Call call, char nextChar); 114 void onConnectionCapabilitiesChanged(Call call); 115 void onConnectionPropertiesChanged(Call call, boolean didRttChange); 116 void onParentChanged(Call call); 117 void onChildrenChanged(Call call); 118 void onCannedSmsResponsesLoaded(Call call); 119 void onVideoCallProviderChanged(Call call); 120 void onCallerInfoChanged(Call call); 121 void onIsVoipAudioModeChanged(Call call); 122 void onStatusHintsChanged(Call call); 123 void onExtrasChanged(Call c, int source, Bundle extras); 124 void onExtrasRemoved(Call c, int source, List<String> keys); 125 void onHandleChanged(Call call); 126 void onCallerDisplayNameChanged(Call call); 127 void onVideoStateChanged(Call call, int previousVideoState, int newVideoState); 128 void onTargetPhoneAccountChanged(Call call); 129 void onConnectionManagerPhoneAccountChanged(Call call); 130 void onPhoneAccountChanged(Call call); 131 void onConferenceableCallsChanged(Call call); 132 boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout); 133 void onHoldToneRequested(Call call); 134 void onConnectionEvent(Call call, String event, Bundle extras); 135 void onExternalCallChanged(Call call, boolean isExternalCall); 136 void onRttInitiationFailure(Call call, int reason); 137 void onRemoteRttRequest(Call call, int requestId); 138 void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState, 139 Bundle extras, boolean isLegacy); 140 void onHandoverFailed(Call call, int error); 141 void onHandoverComplete(Call call); 142 } 143 144 public abstract static class ListenerBase implements Listener { 145 @Override 146 public void onSuccessfulOutgoingCall(Call call, int callState) {} 147 @Override 148 public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {} 149 @Override 150 public void onSuccessfulIncomingCall(Call call) {} 151 @Override 152 public void onFailedIncomingCall(Call call) {} 153 @Override 154 public void onSuccessfulUnknownCall(Call call, int callState) {} 155 @Override 156 public void onFailedUnknownCall(Call call) {} 157 @Override 158 public void onRingbackRequested(Call call, boolean ringbackRequested) {} 159 @Override 160 public void onPostDialWait(Call call, String remaining) {} 161 @Override 162 public void onPostDialChar(Call call, char nextChar) {} 163 @Override 164 public void onConnectionCapabilitiesChanged(Call call) {} 165 @Override 166 public void onConnectionPropertiesChanged(Call call, boolean didRttChange) {} 167 @Override 168 public void onParentChanged(Call call) {} 169 @Override 170 public void onChildrenChanged(Call call) {} 171 @Override 172 public void onCannedSmsResponsesLoaded(Call call) {} 173 @Override 174 public void onVideoCallProviderChanged(Call call) {} 175 @Override 176 public void onCallerInfoChanged(Call call) {} 177 @Override 178 public void onIsVoipAudioModeChanged(Call call) {} 179 @Override 180 public void onStatusHintsChanged(Call call) {} 181 @Override 182 public void onExtrasChanged(Call c, int source, Bundle extras) {} 183 @Override 184 public void onExtrasRemoved(Call c, int source, List<String> keys) {} 185 @Override 186 public void onHandleChanged(Call call) {} 187 @Override 188 public void onCallerDisplayNameChanged(Call call) {} 189 @Override 190 public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {} 191 @Override 192 public void onTargetPhoneAccountChanged(Call call) {} 193 @Override 194 public void onConnectionManagerPhoneAccountChanged(Call call) {} 195 @Override 196 public void onPhoneAccountChanged(Call call) {} 197 @Override 198 public void onConferenceableCallsChanged(Call call) {} 199 @Override 200 public boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout) { 201 return false; 202 } 203 @Override 204 public void onHoldToneRequested(Call call) {} 205 @Override 206 public void onConnectionEvent(Call call, String event, Bundle extras) {} 207 @Override 208 public void onExternalCallChanged(Call call, boolean isExternalCall) {} 209 @Override 210 public void onRttInitiationFailure(Call call, int reason) {} 211 @Override 212 public void onRemoteRttRequest(Call call, int requestId) {} 213 @Override 214 public void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState, 215 Bundle extras, boolean isLegacy) {} 216 @Override 217 public void onHandoverFailed(Call call, int error) {} 218 @Override 219 public void onHandoverComplete(Call call) {} 220 } 221 222 private final CallerInfoLookupHelper.OnQueryCompleteListener mCallerInfoQueryListener = 223 new CallerInfoLookupHelper.OnQueryCompleteListener() { 224 /** ${inheritDoc} */ 225 @Override 226 public void onCallerInfoQueryComplete(Uri handle, CallerInfo callerInfo) { 227 synchronized (mLock) { 228 Call.this.setCallerInfo(handle, callerInfo); 229 } 230 } 231 232 @Override 233 public void onContactPhotoQueryComplete(Uri handle, CallerInfo callerInfo) { 234 synchronized (mLock) { 235 Call.this.setCallerInfo(handle, callerInfo); 236 } 237 } 238 }; 239 240 /** 241 * One of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING, or CALL_DIRECTION_UNKNOWN 242 */ 243 private final int mCallDirection; 244 245 /** 246 * The post-dial digits that were dialed after the network portion of the number 247 */ 248 private final String mPostDialDigits; 249 250 /** 251 * The secondary line number that an incoming call has been received on if the SIM subscription 252 * has multiple associated numbers. 253 */ 254 private String mViaNumber = ""; 255 256 /** 257 * The wall clock time this call was created. Beyond logging and such, may also be used for 258 * bookkeeping and specifically for marking certain call attempts as failed attempts. 259 * Note: This timestamp should NOT be used for calculating call duration. 260 */ 261 private long mCreationTimeMillis; 262 263 /** The time this call was made active. */ 264 private long mConnectTimeMillis = 0; 265 266 /** 267 * The time, in millis, since boot when this call was connected. This should ONLY be used when 268 * calculating the duration of the call. 269 * 270 * The reason for this is that the {@link SystemClock#elapsedRealtime()} is based on the 271 * elapsed time since the device was booted. Changes to the system clock (e.g. due to NITZ 272 * time sync, time zone changes user initiated clock changes) would cause a duration calculated 273 * based on {@link #mConnectTimeMillis} to change based on the delta in the time. 274 * Using the {@link SystemClock#elapsedRealtime()} ensures that changes to the wall clock do 275 * not impact the call duration. 276 */ 277 private long mConnectElapsedTimeMillis = 0; 278 279 /** The wall clock time this call was disconnected. */ 280 private long mDisconnectTimeMillis = 0; 281 282 /** 283 * The elapsed time since boot when this call was disconnected. Recorded as the 284 * {@link SystemClock#elapsedRealtime()}. This ensures that the call duration is not impacted 285 * by changes in the wall time clock. 286 */ 287 private long mDisconnectElapsedTimeMillis = 0; 288 289 /** The gateway information associated with this call. This stores the original call handle 290 * that the user is attempting to connect to via the gateway, the actual handle to dial in 291 * order to connect the call via the gateway, as well as the package name of the gateway 292 * service. */ 293 private GatewayInfo mGatewayInfo; 294 295 private PhoneAccountHandle mConnectionManagerPhoneAccountHandle; 296 297 private PhoneAccountHandle mTargetPhoneAccountHandle; 298 299 private UserHandle mInitiatingUser; 300 301 private final Handler mHandler = new Handler(Looper.getMainLooper()); 302 303 private final List<Call> mConferenceableCalls = new ArrayList<>(); 304 305 /** The state of the call. */ 306 private int mState; 307 308 /** The handle with which to establish this call. */ 309 private Uri mHandle; 310 311 /** 312 * The presentation requirements for the handle. See {@link TelecomManager} for valid values. 313 */ 314 private int mHandlePresentation; 315 316 /** The caller display name (CNAP) set by the connection service. */ 317 private String mCallerDisplayName; 318 319 /** 320 * The presentation requirements for the handle. See {@link TelecomManager} for valid values. 321 */ 322 private int mCallerDisplayNamePresentation; 323 324 /** 325 * The connection service which is attempted or already connecting this call. 326 */ 327 private ConnectionServiceWrapper mConnectionService; 328 329 private boolean mIsEmergencyCall; 330 331 private boolean mSpeakerphoneOn; 332 333 private boolean mIsDisconnectingChildCall = false; 334 335 /** 336 * Tracks the video states which were applicable over the duration of a call. 337 * See {@link VideoProfile} for a list of valid video states. 338 * <p> 339 * Video state history is tracked when the call is active, and when a call is rejected or 340 * missed. 341 */ 342 private int mVideoStateHistory; 343 344 private int mVideoState; 345 346 /** 347 * Disconnect cause for the call. Only valid if the state of the call is STATE_DISCONNECTED. 348 * See {@link android.telecom.DisconnectCause}. 349 */ 350 private DisconnectCause mDisconnectCause = new DisconnectCause(DisconnectCause.UNKNOWN); 351 352 private Bundle mIntentExtras = new Bundle(); 353 354 /** 355 * The {@link Intent} which originally created this call. Only populated when we are putting a 356 * call into a pending state and need to pick up initiation of the call later. 357 */ 358 private Intent mOriginalCallIntent = null; 359 360 /** Set of listeners on this call. 361 * 362 * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is 363 * load factor before resizing, 1 means we only expect a single thread to 364 * access the map so make only a single shard 365 */ 366 private final Set<Listener> mListeners = Collections.newSetFromMap( 367 new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1)); 368 369 private CreateConnectionProcessor mCreateConnectionProcessor; 370 371 /** Caller information retrieved from the latest contact query. */ 372 private CallerInfo mCallerInfo; 373 374 /** The latest token used with a contact info query. */ 375 private int mQueryToken = 0; 376 377 /** Whether this call is requesting that Telecom play the ringback tone on its behalf. */ 378 private boolean mRingbackRequested = false; 379 380 /** Whether direct-to-voicemail query is pending. */ 381 private boolean mDirectToVoicemailQueryPending; 382 383 private int mConnectionCapabilities; 384 385 private int mConnectionProperties; 386 387 private int mSupportedAudioRoutes = CallAudioState.ROUTE_ALL; 388 389 private boolean mIsConference = false; 390 391 private final boolean mShouldAttachToExistingConnection; 392 393 private Call mParentCall = null; 394 395 private List<Call> mChildCalls = new LinkedList<>(); 396 397 /** Set of text message responses allowed for this call, if applicable. */ 398 private List<String> mCannedSmsResponses = Collections.EMPTY_LIST; 399 400 /** Whether an attempt has been made to load the text message responses. */ 401 private boolean mCannedSmsResponsesLoadingStarted = false; 402 403 private IVideoProvider mVideoProvider; 404 private VideoProviderProxy mVideoProviderProxy; 405 406 private boolean mIsVoipAudioMode; 407 private StatusHints mStatusHints; 408 private Bundle mExtras; 409 private final ConnectionServiceRepository mRepository; 410 private final Context mContext; 411 private final CallsManager mCallsManager; 412 private final ClockProxy mClockProxy; 413 private final TelecomSystem.SyncRoot mLock; 414 private final String mId; 415 private String mConnectionId; 416 private Analytics.CallInfo mAnalytics; 417 private char mPlayingDtmfTone; 418 419 private boolean mWasConferencePreviouslyMerged = false; 420 private boolean mWasHighDefAudio = false; 421 422 // For conferences which support merge/swap at their level, we retain a notion of an active 423 // call. This is used for BluetoothPhoneService. In order to support hold/merge, it must have 424 // the notion of the current "active" call within the conference call. This maintains the 425 // "active" call and switches every time the user hits "swap". 426 private Call mConferenceLevelActiveCall = null; 427 428 private boolean mIsLocallyDisconnecting = false; 429 430 /** 431 * Tracks the current call data usage as reported by the video provider. 432 */ 433 private long mCallDataUsage = DATA_USAGE_NOT_SET; 434 435 private boolean mIsWorkCall; 436 437 // Set to true once the NewOutgoingCallIntentBroadcast comes back and is processed. 438 private boolean mIsNewOutgoingCallIntentBroadcastDone = false; 439 440 /** 441 * Indicates whether the call is remotely held. A call is considered remotely held when 442 * {@link #onConnectionEvent(String)} receives the {@link Connection#EVENT_ON_HOLD_TONE_START} 443 * event. 444 */ 445 private boolean mIsRemotelyHeld = false; 446 447 /** 448 * Indicates whether the {@link PhoneAccount} associated with this call is self-managed. 449 * See {@link PhoneAccount#CAPABILITY_SELF_MANAGED} for more information. 450 */ 451 private boolean mIsSelfManaged = false; 452 453 /** 454 * Indicates whether the {@link PhoneAccount} associated with this call supports video calling. 455 * {@code True} if the phone account supports video calling, {@code false} otherwise. 456 */ 457 private boolean mIsVideoCallingSupported = false; 458 459 private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter; 460 461 /** 462 * For {@link Connection}s or {@link android.telecom.Conference}s added via a ConnectionManager 463 * using the {@link android.telecom.ConnectionService#addExistingConnection(PhoneAccountHandle, 464 * Connection)} or {@link android.telecom.ConnectionService#addConference(Conference)}, 465 * indicates the ID of this call as it was referred to by the {@code ConnectionService} which 466 * originally created it. 467 * 468 * See {@link Connection#EXTRA_ORIGINAL_CONNECTION_ID} for more information. 469 */ 470 private String mOriginalConnectionId; 471 472 /** 473 * Two pairs of {@link android.os.ParcelFileDescriptor}s that handle RTT text communication 474 * between the in-call app and the connection service. If both non-null, this call should be 475 * treated as an RTT call. 476 * Each array should be of size 2. First one is the read side and the second one is the write 477 * side. 478 */ 479 private ParcelFileDescriptor[] mInCallToConnectionServiceStreams; 480 private ParcelFileDescriptor[] mConnectionServiceToInCallStreams; 481 /** 482 * Integer constant from {@link android.telecom.Call.RttCall}. Describes the current RTT mode. 483 */ 484 private int mRttMode; 485 /** 486 * True if the call was ever an RTT call. 487 */ 488 private boolean mWasEverRtt = false; 489 490 /** 491 * Integer indicating the remote RTT request ID that is pending a response from the user. 492 */ 493 private int mPendingRttRequestId = INVALID_RTT_REQUEST_ID; 494 495 /** 496 * When a call handover has been initiated via {@link #requestHandover(PhoneAccountHandle, 497 * int, Bundle, boolean)}, contains the call which this call is being handed over to. 498 */ 499 private Call mHandoverDestinationCall = null; 500 501 /** 502 * When a call handover has been initiated via {@link #requestHandover(PhoneAccountHandle, 503 * int, Bundle, boolean)}, contains the call which this call is being handed over from. 504 */ 505 private Call mHandoverSourceCall = null; 506 507 /** 508 * Indicates the current state of this call if it is in the process of a handover. 509 */ 510 private int mHandoverState = HandoverState.HANDOVER_NONE; 511 512 /** 513 * Persists the specified parameters and initializes the new instance. 514 * @param context The context. 515 * @param repository The connection service repository. 516 * @param handle The handle to dial. 517 * @param gatewayInfo Gateway information to use for the call. 518 * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call. 519* This account must be one that was registered with the 520* {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag. 521 * @param targetPhoneAccountHandle Account information to use for the call. This account must be 522* one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag. 523 * @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING, 524* or CALL_DIRECTION_UNKNOWN. 525 * @param shouldAttachToExistingConnection Set to true to attach the call to an existing 526 * @param clockProxy 527 */ 528 public Call( 529 String callId, 530 Context context, 531 CallsManager callsManager, 532 TelecomSystem.SyncRoot lock, 533 ConnectionServiceRepository repository, 534 ContactsAsyncHelper contactsAsyncHelper, 535 CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory, 536 PhoneNumberUtilsAdapter phoneNumberUtilsAdapter, 537 Uri handle, 538 GatewayInfo gatewayInfo, 539 PhoneAccountHandle connectionManagerPhoneAccountHandle, 540 PhoneAccountHandle targetPhoneAccountHandle, 541 int callDirection, 542 boolean shouldAttachToExistingConnection, 543 boolean isConference, 544 ClockProxy clockProxy) { 545 mId = callId; 546 mConnectionId = callId; 547 mState = isConference ? CallState.ACTIVE : CallState.NEW; 548 mContext = context; 549 mCallsManager = callsManager; 550 mLock = lock; 551 mRepository = repository; 552 mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter; 553 setHandle(handle); 554 mPostDialDigits = handle != null 555 ? PhoneNumberUtils.extractPostDialPortion(handle.getSchemeSpecificPart()) : ""; 556 mGatewayInfo = gatewayInfo; 557 setConnectionManagerPhoneAccount(connectionManagerPhoneAccountHandle); 558 setTargetPhoneAccount(targetPhoneAccountHandle); 559 mCallDirection = callDirection; 560 mIsConference = isConference; 561 mShouldAttachToExistingConnection = shouldAttachToExistingConnection 562 || callDirection == CALL_DIRECTION_INCOMING; 563 maybeLoadCannedSmsResponses(); 564 mAnalytics = new Analytics.CallInfo(); 565 mClockProxy = clockProxy; 566 mCreationTimeMillis = mClockProxy.currentTimeMillis(); 567 } 568 569 /** 570 * Persists the specified parameters and initializes the new instance. 571 * @param context The context. 572 * @param repository The connection service repository. 573 * @param handle The handle to dial. 574 * @param gatewayInfo Gateway information to use for the call. 575 * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call. 576* This account must be one that was registered with the 577* {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag. 578 * @param targetPhoneAccountHandle Account information to use for the call. This account must be 579* one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag. 580 * @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING, 581* or CALL_DIRECTION_UNKNOWN 582 * @param shouldAttachToExistingConnection Set to true to attach the call to an existing 583* connection, regardless of whether it's incoming or outgoing. 584 * @param connectTimeMillis The connection time of the call. 585 * @param clockProxy 586 */ 587 Call( 588 String callId, 589 Context context, 590 CallsManager callsManager, 591 TelecomSystem.SyncRoot lock, 592 ConnectionServiceRepository repository, 593 ContactsAsyncHelper contactsAsyncHelper, 594 CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory, 595 PhoneNumberUtilsAdapter phoneNumberUtilsAdapter, 596 Uri handle, 597 GatewayInfo gatewayInfo, 598 PhoneAccountHandle connectionManagerPhoneAccountHandle, 599 PhoneAccountHandle targetPhoneAccountHandle, 600 int callDirection, 601 boolean shouldAttachToExistingConnection, 602 boolean isConference, 603 long connectTimeMillis, 604 long connectElapsedTimeMillis, 605 ClockProxy clockProxy) { 606 this(callId, context, callsManager, lock, repository, contactsAsyncHelper, 607 callerInfoAsyncQueryFactory, phoneNumberUtilsAdapter, handle, gatewayInfo, 608 connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection, 609 shouldAttachToExistingConnection, isConference, clockProxy); 610 611 mConnectTimeMillis = connectTimeMillis; 612 mConnectElapsedTimeMillis = connectElapsedTimeMillis; 613 mAnalytics.setCallStartTime(connectTimeMillis); 614 } 615 616 public void addListener(Listener listener) { 617 mListeners.add(listener); 618 } 619 620 public void removeListener(Listener listener) { 621 if (listener != null) { 622 mListeners.remove(listener); 623 } 624 } 625 626 public void initAnalytics() { 627 int analyticsDirection; 628 switch (mCallDirection) { 629 case CALL_DIRECTION_OUTGOING: 630 analyticsDirection = Analytics.OUTGOING_DIRECTION; 631 break; 632 case CALL_DIRECTION_INCOMING: 633 analyticsDirection = Analytics.INCOMING_DIRECTION; 634 break; 635 case CALL_DIRECTION_UNKNOWN: 636 case CALL_DIRECTION_UNDEFINED: 637 default: 638 analyticsDirection = Analytics.UNKNOWN_DIRECTION; 639 } 640 mAnalytics = Analytics.initiateCallAnalytics(mId, analyticsDirection); 641 Log.addEvent(this, LogUtils.Events.CREATED); 642 } 643 644 public Analytics.CallInfo getAnalytics() { 645 return mAnalytics; 646 } 647 648 public void destroy() { 649 // We should not keep these bitmaps around because the Call objects may be held for logging 650 // purposes. 651 // TODO: Make a container object that only stores the information we care about for Logging. 652 if (mCallerInfo != null) { 653 mCallerInfo.cachedPhotoIcon = null; 654 mCallerInfo.cachedPhoto = null; 655 } 656 Log.addEvent(this, LogUtils.Events.DESTROYED); 657 } 658 659 /** {@inheritDoc} */ 660 @Override 661 public String toString() { 662 String component = null; 663 if (mConnectionService != null && mConnectionService.getComponentName() != null) { 664 component = mConnectionService.getComponentName().flattenToShortString(); 665 } 666 667 return String.format(Locale.US, "[%s, %s, %s, %s, %s, childs(%d), has_parent(%b), %s, %s]", 668 mId, 669 CallState.toString(mState), 670 component, 671 Log.piiHandle(mHandle), 672 getVideoStateDescription(getVideoState()), 673 getChildCalls().size(), 674 getParentCall() != null, 675 Connection.capabilitiesToString(getConnectionCapabilities()), 676 Connection.propertiesToString(getConnectionProperties())); 677 } 678 679 @Override 680 public String getDescription() { 681 StringBuilder s = new StringBuilder(); 682 if (isSelfManaged()) { 683 s.append("SelfMgd Call"); 684 } else if (isExternalCall()) { 685 s.append("External Call"); 686 } else { 687 s.append("Call"); 688 } 689 s.append(getId()); 690 s.append(" ["); 691 s.append(SimpleDateFormat.getDateTimeInstance().format(new Date(getCreationTimeMillis()))); 692 s.append("]"); 693 s.append(isIncoming() ? "(MT - incoming)" : "(MO - outgoing)"); 694 s.append("\n\tVia PhoneAccount: "); 695 PhoneAccountHandle targetPhoneAccountHandle = getTargetPhoneAccount(); 696 if (targetPhoneAccountHandle != null) { 697 s.append(targetPhoneAccountHandle); 698 s.append(" ("); 699 s.append(getTargetPhoneAccountLabel()); 700 s.append(")"); 701 } else { 702 s.append("not set"); 703 } 704 705 s.append("\n\tTo address: "); 706 s.append(Log.piiHandle(getHandle())); 707 s.append(" Presentation: "); 708 switch (getHandlePresentation()) { 709 case TelecomManager.PRESENTATION_ALLOWED: 710 s.append("Allowed"); 711 break; 712 case TelecomManager.PRESENTATION_PAYPHONE: 713 s.append("Payphone"); 714 break; 715 case TelecomManager.PRESENTATION_RESTRICTED: 716 s.append("Restricted"); 717 break; 718 case TelecomManager.PRESENTATION_UNKNOWN: 719 s.append("Unknown"); 720 break; 721 default: 722 s.append("<undefined>"); 723 } 724 s.append("\n"); 725 return s.toString(); 726 } 727 728 /** 729 * Builds a debug-friendly description string for a video state. 730 * <p> 731 * A = audio active, T = video transmission active, R = video reception active, P = video 732 * paused. 733 * 734 * @param videoState The video state. 735 * @return A string indicating which bits are set in the video state. 736 */ 737 private String getVideoStateDescription(int videoState) { 738 StringBuilder sb = new StringBuilder(); 739 sb.append("A"); 740 741 if (VideoProfile.isTransmissionEnabled(videoState)) { 742 sb.append("T"); 743 } 744 745 if (VideoProfile.isReceptionEnabled(videoState)) { 746 sb.append("R"); 747 } 748 749 if (VideoProfile.isPaused(videoState)) { 750 sb.append("P"); 751 } 752 753 return sb.toString(); 754 } 755 756 @Override 757 public ConnectionServiceFocusManager.ConnectionServiceFocus getConnectionServiceWrapper() { 758 return mConnectionService; 759 } 760 761 @VisibleForTesting 762 public int getState() { 763 return mState; 764 } 765 766 private boolean shouldContinueProcessingAfterDisconnect() { 767 // Stop processing once the call is active. 768 if (!CreateConnectionTimeout.isCallBeingPlaced(this)) { 769 return false; 770 } 771 772 // Only Redial a Call in the case of it being an Emergency Call. 773 if(!isEmergencyCall()) { 774 return false; 775 } 776 777 // Make sure that there are additional connection services to process. 778 if (mCreateConnectionProcessor == null 779 || !mCreateConnectionProcessor.isProcessingComplete() 780 || !mCreateConnectionProcessor.hasMorePhoneAccounts()) { 781 return false; 782 } 783 784 if (mDisconnectCause == null) { 785 return false; 786 } 787 788 // Continue processing if the current attempt failed or timed out. 789 return mDisconnectCause.getCode() == DisconnectCause.ERROR || 790 mCreateConnectionProcessor.isCallTimedOut(); 791 } 792 793 /** 794 * Returns the unique ID for this call as it exists in Telecom. 795 * @return The call ID. 796 */ 797 public String getId() { 798 return mId; 799 } 800 801 /** 802 * Returns the unique ID for this call (see {@link #getId}) along with an attempt indicator that 803 * iterates based on attempts to establish a {@link Connection} using createConnectionProcessor. 804 * @return The call ID with an appended attempt id. 805 */ 806 public String getConnectionId() { 807 if(mCreateConnectionProcessor != null) { 808 mConnectionId = mId + "_" + 809 String.valueOf(mCreateConnectionProcessor.getConnectionAttempt()); 810 return mConnectionId; 811 } else { 812 return mConnectionId; 813 } 814 } 815 816 /** 817 * Sets the call state. Although there exists the notion of appropriate state transitions 818 * (see {@link CallState}), in practice those expectations break down when cellular systems 819 * misbehave and they do this very often. The result is that we do not enforce state transitions 820 * and instead keep the code resilient to unexpected state changes. 821 */ 822 public void setState(int newState, String tag) { 823 if (mState != newState) { 824 Log.v(this, "setState %s -> %s", mState, newState); 825 826 if (newState == CallState.DISCONNECTED && shouldContinueProcessingAfterDisconnect()) { 827 Log.w(this, "continuing processing disconnected call with another service"); 828 mCreateConnectionProcessor.continueProcessingIfPossible(this, mDisconnectCause); 829 return; 830 } 831 832 updateVideoHistoryViaState(mState, newState); 833 834 mState = newState; 835 maybeLoadCannedSmsResponses(); 836 837 if (mState == CallState.ACTIVE || mState == CallState.ON_HOLD) { 838 if (mConnectTimeMillis == 0) { 839 // We check to see if mConnectTime is already set to prevent the 840 // call from resetting active time when it goes in and out of 841 // ACTIVE/ON_HOLD 842 mConnectTimeMillis = mClockProxy.currentTimeMillis(); 843 mConnectElapsedTimeMillis = mClockProxy.elapsedRealtime(); 844 mAnalytics.setCallStartTime(mConnectTimeMillis); 845 } 846 847 // We're clearly not disconnected, so reset the disconnected time. 848 mDisconnectTimeMillis = 0; 849 mDisconnectElapsedTimeMillis = 0; 850 } else if (mState == CallState.DISCONNECTED) { 851 mDisconnectTimeMillis = mClockProxy.currentTimeMillis(); 852 mDisconnectElapsedTimeMillis = mClockProxy.elapsedRealtime(); 853 mAnalytics.setCallEndTime(mDisconnectTimeMillis); 854 setLocallyDisconnecting(false); 855 fixParentAfterDisconnect(); 856 } 857 858 // Log the state transition event 859 String event = null; 860 Object data = null; 861 switch (newState) { 862 case CallState.ACTIVE: 863 event = LogUtils.Events.SET_ACTIVE; 864 break; 865 case CallState.CONNECTING: 866 event = LogUtils.Events.SET_CONNECTING; 867 break; 868 case CallState.DIALING: 869 event = LogUtils.Events.SET_DIALING; 870 break; 871 case CallState.PULLING: 872 event = LogUtils.Events.SET_PULLING; 873 break; 874 case CallState.DISCONNECTED: 875 event = LogUtils.Events.SET_DISCONNECTED; 876 data = getDisconnectCause(); 877 break; 878 case CallState.DISCONNECTING: 879 event = LogUtils.Events.SET_DISCONNECTING; 880 break; 881 case CallState.ON_HOLD: 882 event = LogUtils.Events.SET_HOLD; 883 break; 884 case CallState.SELECT_PHONE_ACCOUNT: 885 event = LogUtils.Events.SET_SELECT_PHONE_ACCOUNT; 886 break; 887 case CallState.RINGING: 888 event = LogUtils.Events.SET_RINGING; 889 break; 890 } 891 if (event != null) { 892 // The string data should be just the tag. 893 String stringData = tag; 894 if (data != null) { 895 // If data exists, add it to tag. If no tag, just use data.toString(). 896 stringData = stringData == null ? data.toString() : stringData + "> " + data; 897 } 898 Log.addEvent(this, event, stringData); 899 } 900 } 901 } 902 903 void setRingbackRequested(boolean ringbackRequested) { 904 mRingbackRequested = ringbackRequested; 905 for (Listener l : mListeners) { 906 l.onRingbackRequested(this, mRingbackRequested); 907 } 908 } 909 910 boolean isRingbackRequested() { 911 return mRingbackRequested; 912 } 913 914 @VisibleForTesting 915 public boolean isConference() { 916 return mIsConference; 917 } 918 919 public Uri getHandle() { 920 return mHandle; 921 } 922 923 public String getPostDialDigits() { 924 return mPostDialDigits; 925 } 926 927 public String getViaNumber() { 928 return mViaNumber; 929 } 930 931 public void setViaNumber(String viaNumber) { 932 // If at any point the via number is not empty throughout the call, save that via number. 933 if (!TextUtils.isEmpty(viaNumber)) { 934 mViaNumber = viaNumber; 935 } 936 } 937 938 int getHandlePresentation() { 939 return mHandlePresentation; 940 } 941 942 943 void setHandle(Uri handle) { 944 setHandle(handle, TelecomManager.PRESENTATION_ALLOWED); 945 } 946 947 public void setHandle(Uri handle, int presentation) { 948 if (!Objects.equals(handle, mHandle) || presentation != mHandlePresentation) { 949 mHandlePresentation = presentation; 950 if (mHandlePresentation == TelecomManager.PRESENTATION_RESTRICTED || 951 mHandlePresentation == TelecomManager.PRESENTATION_UNKNOWN) { 952 mHandle = null; 953 } else { 954 mHandle = handle; 955 if (mHandle != null && !PhoneAccount.SCHEME_VOICEMAIL.equals(mHandle.getScheme()) 956 && TextUtils.isEmpty(mHandle.getSchemeSpecificPart())) { 957 // If the number is actually empty, set it to null, unless this is a 958 // SCHEME_VOICEMAIL uri which always has an empty number. 959 mHandle = null; 960 } 961 } 962 963 // Let's not allow resetting of the emergency flag. Once a call becomes an emergency 964 // call, it will remain so for the rest of it's lifetime. 965 if (!mIsEmergencyCall) { 966 mIsEmergencyCall = mHandle != null && 967 mPhoneNumberUtilsAdapter.isLocalEmergencyNumber(mContext, 968 mHandle.getSchemeSpecificPart()); 969 } 970 startCallerInfoLookup(); 971 for (Listener l : mListeners) { 972 l.onHandleChanged(this); 973 } 974 } 975 } 976 977 public String getCallerDisplayName() { 978 return mCallerDisplayName; 979 } 980 981 public int getCallerDisplayNamePresentation() { 982 return mCallerDisplayNamePresentation; 983 } 984 985 void setCallerDisplayName(String callerDisplayName, int presentation) { 986 if (!TextUtils.equals(callerDisplayName, mCallerDisplayName) || 987 presentation != mCallerDisplayNamePresentation) { 988 mCallerDisplayName = callerDisplayName; 989 mCallerDisplayNamePresentation = presentation; 990 for (Listener l : mListeners) { 991 l.onCallerDisplayNameChanged(this); 992 } 993 } 994 } 995 996 public String getName() { 997 return mCallerInfo == null ? null : mCallerInfo.name; 998 } 999 1000 public String getPhoneNumber() { 1001 return mCallerInfo == null ? null : mCallerInfo.phoneNumber; 1002 } 1003 1004 public Bitmap getPhotoIcon() { 1005 return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon; 1006 } 1007 1008 public Drawable getPhoto() { 1009 return mCallerInfo == null ? null : mCallerInfo.cachedPhoto; 1010 } 1011 1012 /** 1013 * @param disconnectCause The reason for the disconnection, represented by 1014 * {@link android.telecom.DisconnectCause}. 1015 */ 1016 public void setDisconnectCause(DisconnectCause disconnectCause) { 1017 // TODO: Consider combining this method with a setDisconnected() method that is totally 1018 // separate from setState. 1019 mAnalytics.setCallDisconnectCause(disconnectCause); 1020 mDisconnectCause = disconnectCause; 1021 } 1022 1023 public DisconnectCause getDisconnectCause() { 1024 return mDisconnectCause; 1025 } 1026 1027 @VisibleForTesting 1028 public boolean isEmergencyCall() { 1029 return mIsEmergencyCall; 1030 } 1031 1032 /** 1033 * @return The original handle this call is associated with. In-call services should use this 1034 * handle when indicating in their UI the handle that is being called. 1035 */ 1036 public Uri getOriginalHandle() { 1037 if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) { 1038 return mGatewayInfo.getOriginalAddress(); 1039 } 1040 return getHandle(); 1041 } 1042 1043 @VisibleForTesting 1044 public GatewayInfo getGatewayInfo() { 1045 return mGatewayInfo; 1046 } 1047 1048 void setGatewayInfo(GatewayInfo gatewayInfo) { 1049 mGatewayInfo = gatewayInfo; 1050 } 1051 1052 @VisibleForTesting 1053 public PhoneAccountHandle getConnectionManagerPhoneAccount() { 1054 return mConnectionManagerPhoneAccountHandle; 1055 } 1056 1057 @VisibleForTesting 1058 public void setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle) { 1059 if (!Objects.equals(mConnectionManagerPhoneAccountHandle, accountHandle)) { 1060 mConnectionManagerPhoneAccountHandle = accountHandle; 1061 for (Listener l : mListeners) { 1062 l.onConnectionManagerPhoneAccountChanged(this); 1063 } 1064 } 1065 1066 } 1067 1068 @VisibleForTesting 1069 public PhoneAccountHandle getTargetPhoneAccount() { 1070 return mTargetPhoneAccountHandle; 1071 } 1072 1073 @VisibleForTesting 1074 public void setTargetPhoneAccount(PhoneAccountHandle accountHandle) { 1075 if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) { 1076 mTargetPhoneAccountHandle = accountHandle; 1077 for (Listener l : mListeners) { 1078 l.onTargetPhoneAccountChanged(this); 1079 } 1080 configureIsWorkCall(); 1081 } 1082 checkIfVideoCapable(); 1083 } 1084 1085 public CharSequence getTargetPhoneAccountLabel() { 1086 if (getTargetPhoneAccount() == null) { 1087 return null; 1088 } 1089 PhoneAccount phoneAccount = mCallsManager.getPhoneAccountRegistrar() 1090 .getPhoneAccountUnchecked(getTargetPhoneAccount()); 1091 1092 if (phoneAccount == null) { 1093 return null; 1094 } 1095 1096 return phoneAccount.getLabel(); 1097 } 1098 1099 /** 1100 * Determines if this Call should be written to the call log. 1101 * @return {@code true} for managed calls or for self-managed calls which have the 1102 * {@link PhoneAccount#EXTRA_LOG_SELF_MANAGED_CALLS} extra set. 1103 */ 1104 public boolean isLoggedSelfManaged() { 1105 if (!isSelfManaged()) { 1106 // Managed calls are always logged. 1107 return true; 1108 } 1109 if (getTargetPhoneAccount() == null) { 1110 return false; 1111 } 1112 PhoneAccount phoneAccount = mCallsManager.getPhoneAccountRegistrar() 1113 .getPhoneAccountUnchecked(getTargetPhoneAccount()); 1114 1115 if (phoneAccount == null) { 1116 return false; 1117 } 1118 1119 return phoneAccount.getExtras() != null && phoneAccount.getExtras().getBoolean( 1120 PhoneAccount.EXTRA_LOG_SELF_MANAGED_CALLS, false); 1121 } 1122 1123 @VisibleForTesting 1124 public boolean isIncoming() { 1125 return mCallDirection == CALL_DIRECTION_INCOMING; 1126 } 1127 1128 public boolean isExternalCall() { 1129 return (getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) == 1130 Connection.PROPERTY_IS_EXTERNAL_CALL; 1131 } 1132 1133 public boolean isWorkCall() { 1134 return mIsWorkCall; 1135 } 1136 1137 public boolean isVideoCallingSupported() { 1138 return mIsVideoCallingSupported; 1139 } 1140 1141 public boolean isSelfManaged() { 1142 return mIsSelfManaged; 1143 } 1144 1145 public void setIsSelfManaged(boolean isSelfManaged) { 1146 mIsSelfManaged = isSelfManaged; 1147 1148 // Connection properties will add/remove the PROPERTY_SELF_MANAGED. 1149 setConnectionProperties(getConnectionProperties()); 1150 } 1151 1152 public void markFinishedHandoverStateAndCleanup(int handoverState) { 1153 if (mHandoverSourceCall != null) { 1154 mHandoverSourceCall.setHandoverState(handoverState); 1155 } else if (mHandoverDestinationCall != null) { 1156 mHandoverDestinationCall.setHandoverState(handoverState); 1157 } 1158 setHandoverState(handoverState); 1159 maybeCleanupHandover(); 1160 } 1161 1162 public void maybeCleanupHandover() { 1163 if (mHandoverSourceCall != null) { 1164 mHandoverSourceCall.setHandoverSourceCall(null); 1165 mHandoverSourceCall.setHandoverDestinationCall(null); 1166 mHandoverSourceCall = null; 1167 } else if (mHandoverDestinationCall != null) { 1168 mHandoverDestinationCall.setHandoverSourceCall(null); 1169 mHandoverDestinationCall.setHandoverDestinationCall(null); 1170 mHandoverDestinationCall = null; 1171 } 1172 } 1173 1174 public boolean isHandoverInProgress() { 1175 return mHandoverSourceCall != null || mHandoverDestinationCall != null; 1176 } 1177 1178 public Call getHandoverDestinationCall() { 1179 return mHandoverDestinationCall; 1180 } 1181 1182 public void setHandoverDestinationCall(Call call) { 1183 mHandoverDestinationCall = call; 1184 } 1185 1186 public Call getHandoverSourceCall() { 1187 return mHandoverSourceCall; 1188 } 1189 1190 public void setHandoverSourceCall(Call call) { 1191 mHandoverSourceCall = call; 1192 } 1193 1194 public void setHandoverState(int handoverState) { 1195 Log.d(this, "setHandoverState: callId=%s, handoverState=%s", getId(), 1196 HandoverState.stateToString(handoverState)); 1197 mHandoverState = handoverState; 1198 } 1199 1200 public int getHandoverState() { 1201 return mHandoverState; 1202 } 1203 1204 private void configureIsWorkCall() { 1205 PhoneAccountRegistrar phoneAccountRegistrar = mCallsManager.getPhoneAccountRegistrar(); 1206 boolean isWorkCall = false; 1207 PhoneAccount phoneAccount = 1208 phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle); 1209 if (phoneAccount != null) { 1210 final UserHandle userHandle; 1211 if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { 1212 userHandle = mInitiatingUser; 1213 } else { 1214 userHandle = mTargetPhoneAccountHandle.getUserHandle(); 1215 } 1216 if (userHandle != null) { 1217 isWorkCall = UserUtil.isManagedProfile(mContext, userHandle); 1218 } 1219 } 1220 mIsWorkCall = isWorkCall; 1221 } 1222 1223 /** 1224 * Caches the state of the {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} {@link PhoneAccount} 1225 * capability and ensures that the video state is updated if the phone account does not support 1226 * video calling. 1227 */ 1228 private void checkIfVideoCapable() { 1229 PhoneAccountRegistrar phoneAccountRegistrar = mCallsManager.getPhoneAccountRegistrar(); 1230 if (mTargetPhoneAccountHandle == null) { 1231 // If no target phone account handle is specified, assume we can potentially perform a 1232 // video call; once the phone account is set, we can confirm that it is video capable. 1233 mIsVideoCallingSupported = true; 1234 Log.d(this, "checkIfVideoCapable: no phone account selected; assume video capable."); 1235 return; 1236 } 1237 PhoneAccount phoneAccount = 1238 phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle); 1239 mIsVideoCallingSupported = phoneAccount != null && phoneAccount.hasCapabilities( 1240 PhoneAccount.CAPABILITY_VIDEO_CALLING); 1241 1242 if (!mIsVideoCallingSupported && VideoProfile.isVideo(getVideoState())) { 1243 // The PhoneAccount for the Call was set to one which does not support video calling, 1244 // and the current call is configured to be a video call; downgrade to audio-only. 1245 setVideoState(VideoProfile.STATE_AUDIO_ONLY); 1246 Log.d(this, "checkIfVideoCapable: selected phone account doesn't support video."); 1247 } 1248 } 1249 1250 boolean shouldAttachToExistingConnection() { 1251 return mShouldAttachToExistingConnection; 1252 } 1253 1254 /** 1255 * Note: This method relies on {@link #mConnectElapsedTimeMillis} and 1256 * {@link #mDisconnectElapsedTimeMillis} which are independent of the wall clock (which could 1257 * change due to clock changes). 1258 * @return The "age" of this call object in milliseconds, which typically also represents the 1259 * period since this call was added to the set pending outgoing calls. 1260 */ 1261 @VisibleForTesting 1262 public long getAgeMillis() { 1263 if (mState == CallState.DISCONNECTED && 1264 (mDisconnectCause.getCode() == DisconnectCause.REJECTED || 1265 mDisconnectCause.getCode() == DisconnectCause.MISSED)) { 1266 // Rejected and missed calls have no age. They're immortal!! 1267 return 0; 1268 } else if (mConnectElapsedTimeMillis == 0) { 1269 // Age is measured in the amount of time the call was active. A zero connect time 1270 // indicates that we never went active, so return 0 for the age. 1271 return 0; 1272 } else if (mDisconnectElapsedTimeMillis == 0) { 1273 // We connected, but have not yet disconnected 1274 return mClockProxy.elapsedRealtime() - mConnectElapsedTimeMillis; 1275 } 1276 1277 return mDisconnectElapsedTimeMillis - mConnectElapsedTimeMillis; 1278 } 1279 1280 /** 1281 * @return The time when this call object was created and added to the set of pending outgoing 1282 * calls. 1283 */ 1284 public long getCreationTimeMillis() { 1285 return mCreationTimeMillis; 1286 } 1287 1288 public void setCreationTimeMillis(long time) { 1289 mCreationTimeMillis = time; 1290 } 1291 1292 long getConnectTimeMillis() { 1293 return mConnectTimeMillis; 1294 } 1295 1296 int getConnectionCapabilities() { 1297 return mConnectionCapabilities; 1298 } 1299 1300 int getConnectionProperties() { 1301 return mConnectionProperties; 1302 } 1303 1304 void setConnectionCapabilities(int connectionCapabilities) { 1305 setConnectionCapabilities(connectionCapabilities, false /* forceUpdate */); 1306 } 1307 1308 void setConnectionCapabilities(int connectionCapabilities, boolean forceUpdate) { 1309 Log.v(this, "setConnectionCapabilities: %s", Connection.capabilitiesToString( 1310 connectionCapabilities)); 1311 if (forceUpdate || mConnectionCapabilities != connectionCapabilities) { 1312 // If the phone account does not support video calling, and the connection capabilities 1313 // passed in indicate that the call supports video, remove those video capabilities. 1314 if (!isVideoCallingSupported() && doesCallSupportVideo(connectionCapabilities)) { 1315 Log.w(this, "setConnectionCapabilities: attempt to set connection as video " + 1316 "capable when not supported by the phone account."); 1317 connectionCapabilities = removeVideoCapabilities(connectionCapabilities); 1318 } 1319 1320 int previousCapabilities = mConnectionCapabilities; 1321 mConnectionCapabilities = connectionCapabilities; 1322 for (Listener l : mListeners) { 1323 l.onConnectionCapabilitiesChanged(this); 1324 } 1325 1326 int xorCaps = previousCapabilities ^ mConnectionCapabilities; 1327 Log.addEvent(this, LogUtils.Events.CAPABILITY_CHANGE, 1328 "Current: [%s], Removed [%s], Added [%s]", 1329 Connection.capabilitiesToStringShort(mConnectionCapabilities), 1330 Connection.capabilitiesToStringShort(previousCapabilities & xorCaps), 1331 Connection.capabilitiesToStringShort(mConnectionCapabilities & xorCaps)); 1332 } 1333 } 1334 1335 void setConnectionProperties(int connectionProperties) { 1336 Log.v(this, "setConnectionProperties: %s", Connection.propertiesToString( 1337 connectionProperties)); 1338 1339 // Ensure the ConnectionService can't change the state of the self-managed property. 1340 if (isSelfManaged()) { 1341 connectionProperties |= Connection.PROPERTY_SELF_MANAGED; 1342 } else { 1343 connectionProperties &= ~Connection.PROPERTY_SELF_MANAGED; 1344 } 1345 1346 int changedProperties = mConnectionProperties ^ connectionProperties; 1347 1348 if (changedProperties != 0) { 1349 int previousProperties = mConnectionProperties; 1350 mConnectionProperties = connectionProperties; 1351 setRttStreams((mConnectionProperties & Connection.PROPERTY_IS_RTT) == 1352 Connection.PROPERTY_IS_RTT); 1353 mWasHighDefAudio = (connectionProperties & Connection.PROPERTY_HIGH_DEF_AUDIO) == 1354 Connection.PROPERTY_HIGH_DEF_AUDIO; 1355 boolean didRttChange = 1356 (changedProperties & Connection.PROPERTY_IS_RTT) == Connection.PROPERTY_IS_RTT; 1357 for (Listener l : mListeners) { 1358 l.onConnectionPropertiesChanged(this, didRttChange); 1359 } 1360 1361 boolean wasExternal = (previousProperties & Connection.PROPERTY_IS_EXTERNAL_CALL) 1362 == Connection.PROPERTY_IS_EXTERNAL_CALL; 1363 boolean isExternal = (connectionProperties & Connection.PROPERTY_IS_EXTERNAL_CALL) 1364 == Connection.PROPERTY_IS_EXTERNAL_CALL; 1365 if (wasExternal != isExternal) { 1366 Log.v(this, "setConnectionProperties: external call changed isExternal = %b", 1367 isExternal); 1368 Log.addEvent(this, LogUtils.Events.IS_EXTERNAL, isExternal); 1369 for (Listener l : mListeners) { 1370 l.onExternalCallChanged(this, isExternal); 1371 } 1372 } 1373 1374 mAnalytics.addCallProperties(mConnectionProperties); 1375 1376 int xorProps = previousProperties ^ mConnectionProperties; 1377 Log.addEvent(this, LogUtils.Events.PROPERTY_CHANGE, 1378 "Current: [%s], Removed [%s], Added [%s]", 1379 Connection.propertiesToStringShort(mConnectionProperties), 1380 Connection.propertiesToStringShort(previousProperties & xorProps), 1381 Connection.propertiesToStringShort(mConnectionProperties & xorProps)); 1382 } 1383 } 1384 1385 public int getSupportedAudioRoutes() { 1386 return mSupportedAudioRoutes; 1387 } 1388 1389 void setSupportedAudioRoutes(int audioRoutes) { 1390 if (mSupportedAudioRoutes != audioRoutes) { 1391 mSupportedAudioRoutes = audioRoutes; 1392 } 1393 } 1394 1395 @VisibleForTesting 1396 public Call getParentCall() { 1397 return mParentCall; 1398 } 1399 1400 @VisibleForTesting 1401 public List<Call> getChildCalls() { 1402 return mChildCalls; 1403 } 1404 1405 @VisibleForTesting 1406 public boolean wasConferencePreviouslyMerged() { 1407 return mWasConferencePreviouslyMerged; 1408 } 1409 1410 public boolean isDisconnectingChildCall() { 1411 return mIsDisconnectingChildCall; 1412 } 1413 1414 /** 1415 * Sets whether this call is a child call. 1416 */ 1417 private void maybeSetCallAsDisconnectingChild() { 1418 if (mParentCall != null) { 1419 mIsDisconnectingChildCall = true; 1420 } 1421 } 1422 1423 @VisibleForTesting 1424 public Call getConferenceLevelActiveCall() { 1425 return mConferenceLevelActiveCall; 1426 } 1427 1428 @VisibleForTesting 1429 public ConnectionServiceWrapper getConnectionService() { 1430 return mConnectionService; 1431 } 1432 1433 /** 1434 * Retrieves the {@link Context} for the call. 1435 * 1436 * @return The {@link Context}. 1437 */ 1438 public Context getContext() { 1439 return mContext; 1440 } 1441 1442 @VisibleForTesting 1443 public void setConnectionService(ConnectionServiceWrapper service) { 1444 Preconditions.checkNotNull(service); 1445 1446 clearConnectionService(); 1447 1448 service.incrementAssociatedCallCount(); 1449 mConnectionService = service; 1450 mAnalytics.setCallConnectionService(service.getComponentName().flattenToShortString()); 1451 mConnectionService.addCall(this); 1452 } 1453 1454 /** 1455 * Perform an in-place replacement of the {@link ConnectionServiceWrapper} for this Call. 1456 * Removes the call from its former {@link ConnectionServiceWrapper}, ensuring that the 1457 * ConnectionService is NOT unbound if the call count hits zero. 1458 * This is used by the {@link ConnectionServiceWrapper} when handling {@link Connection} and 1459 * {@link Conference} additions via a ConnectionManager. 1460 * The original {@link android.telecom.ConnectionService} will directly add external calls and 1461 * conferences to Telecom as well as the ConnectionManager, which will add to Telecom. In these 1462 * cases since its first added to via the original CS, we want to change the CS responsible for 1463 * the call to the ConnectionManager rather than adding it again as another call/conference. 1464 * 1465 * @param service The new {@link ConnectionServiceWrapper}. 1466 */ 1467 public void replaceConnectionService(ConnectionServiceWrapper service) { 1468 Preconditions.checkNotNull(service); 1469 1470 if (mConnectionService != null) { 1471 ConnectionServiceWrapper serviceTemp = mConnectionService; 1472 mConnectionService = null; 1473 serviceTemp.removeCall(this); 1474 serviceTemp.decrementAssociatedCallCount(true /*isSuppressingUnbind*/); 1475 } 1476 1477 service.incrementAssociatedCallCount(); 1478 mConnectionService = service; 1479 mAnalytics.setCallConnectionService(service.getComponentName().flattenToShortString()); 1480 } 1481 1482 /** 1483 * Clears the associated connection service. 1484 */ 1485 void clearConnectionService() { 1486 if (mConnectionService != null) { 1487 ConnectionServiceWrapper serviceTemp = mConnectionService; 1488 mConnectionService = null; 1489 serviceTemp.removeCall(this); 1490 1491 // Decrementing the count can cause the service to unbind, which itself can trigger the 1492 // service-death code. Since the service death code tries to clean up any associated 1493 // calls, we need to make sure to remove that information (e.g., removeCall()) before 1494 // we decrement. Technically, invoking removeCall() prior to decrementing is all that is 1495 // necessary, but cleaning up mConnectionService prior to triggering an unbind is good 1496 // to do. 1497 decrementAssociatedCallCount(serviceTemp); 1498 } 1499 } 1500 1501 /** 1502 * Starts the create connection sequence. Upon completion, there should exist an active 1503 * connection through a connection service (or the call will have failed). 1504 * 1505 * @param phoneAccountRegistrar The phone account registrar. 1506 */ 1507 void startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar) { 1508 if (mCreateConnectionProcessor != null) { 1509 Log.w(this, "mCreateConnectionProcessor in startCreateConnection is not null. This is" + 1510 " due to a race between NewOutgoingCallIntentBroadcaster and " + 1511 "phoneAccountSelected, but is harmlessly resolved by ignoring the second " + 1512 "invocation."); 1513 return; 1514 } 1515 mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this, 1516 phoneAccountRegistrar, mContext); 1517 mCreateConnectionProcessor.process(); 1518 } 1519 1520 @Override 1521 public void handleCreateConnectionSuccess( 1522 CallIdMapper idMapper, 1523 ParcelableConnection connection) { 1524 Log.v(this, "handleCreateConnectionSuccessful %s", connection); 1525 setTargetPhoneAccount(connection.getPhoneAccount()); 1526 setHandle(connection.getHandle(), connection.getHandlePresentation()); 1527 setCallerDisplayName( 1528 connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation()); 1529 1530 setConnectionCapabilities(connection.getConnectionCapabilities()); 1531 setConnectionProperties(connection.getConnectionProperties()); 1532 setSupportedAudioRoutes(connection.getSupportedAudioRoutes()); 1533 setVideoProvider(connection.getVideoProvider()); 1534 setVideoState(connection.getVideoState()); 1535 setRingbackRequested(connection.isRingbackRequested()); 1536 setStatusHints(connection.getStatusHints()); 1537 putExtras(SOURCE_CONNECTION_SERVICE, connection.getExtras()); 1538 1539 mConferenceableCalls.clear(); 1540 for (String id : connection.getConferenceableConnectionIds()) { 1541 mConferenceableCalls.add(idMapper.getCall(id)); 1542 } 1543 1544 switch (mCallDirection) { 1545 case CALL_DIRECTION_INCOMING: 1546 // Listeners (just CallsManager for now) will be responsible for checking whether 1547 // the call should be blocked. 1548 for (Listener l : mListeners) { 1549 l.onSuccessfulIncomingCall(this); 1550 } 1551 break; 1552 case CALL_DIRECTION_OUTGOING: 1553 for (Listener l : mListeners) { 1554 l.onSuccessfulOutgoingCall(this, 1555 getStateFromConnectionState(connection.getState())); 1556 } 1557 break; 1558 case CALL_DIRECTION_UNKNOWN: 1559 for (Listener l : mListeners) { 1560 l.onSuccessfulUnknownCall(this, getStateFromConnectionState(connection 1561 .getState())); 1562 } 1563 break; 1564 } 1565 } 1566 1567 @Override 1568 public void handleCreateConnectionFailure(DisconnectCause disconnectCause) { 1569 clearConnectionService(); 1570 setDisconnectCause(disconnectCause); 1571 mCallsManager.markCallAsDisconnected(this, disconnectCause); 1572 1573 switch (mCallDirection) { 1574 case CALL_DIRECTION_INCOMING: 1575 for (Listener listener : mListeners) { 1576 listener.onFailedIncomingCall(this); 1577 } 1578 break; 1579 case CALL_DIRECTION_OUTGOING: 1580 for (Listener listener : mListeners) { 1581 listener.onFailedOutgoingCall(this, disconnectCause); 1582 } 1583 break; 1584 case CALL_DIRECTION_UNKNOWN: 1585 for (Listener listener : mListeners) { 1586 listener.onFailedUnknownCall(this); 1587 } 1588 break; 1589 } 1590 } 1591 1592 /** 1593 * Plays the specified DTMF tone. 1594 */ 1595 @VisibleForTesting 1596 public void playDtmfTone(char digit) { 1597 if (mConnectionService == null) { 1598 Log.w(this, "playDtmfTone() request on a call without a connection service."); 1599 } else { 1600 Log.i(this, "Send playDtmfTone to connection service for call %s", this); 1601 mConnectionService.playDtmfTone(this, digit); 1602 Log.addEvent(this, LogUtils.Events.START_DTMF, Log.pii(digit)); 1603 } 1604 mPlayingDtmfTone = digit; 1605 } 1606 1607 /** 1608 * Stops playing any currently playing DTMF tone. 1609 */ 1610 @VisibleForTesting 1611 public void stopDtmfTone() { 1612 if (mConnectionService == null) { 1613 Log.w(this, "stopDtmfTone() request on a call without a connection service."); 1614 } else { 1615 Log.i(this, "Send stopDtmfTone to connection service for call %s", this); 1616 Log.addEvent(this, LogUtils.Events.STOP_DTMF); 1617 mConnectionService.stopDtmfTone(this); 1618 } 1619 mPlayingDtmfTone = NO_DTMF_TONE; 1620 } 1621 1622 /** 1623 * @return {@code true} if a DTMF tone has been started via {@link #playDtmfTone(char)} but has 1624 * not been stopped via {@link #stopDtmfTone()}, {@code false} otherwise. 1625 */ 1626 boolean isDtmfTonePlaying() { 1627 return mPlayingDtmfTone != NO_DTMF_TONE; 1628 } 1629 1630 /** 1631 * Silences the ringer. 1632 */ 1633 void silence() { 1634 if (mConnectionService == null) { 1635 Log.w(this, "silence() request on a call without a connection service."); 1636 } else { 1637 Log.i(this, "Send silence to connection service for call %s", this); 1638 Log.addEvent(this, LogUtils.Events.SILENCE); 1639 mConnectionService.silence(this); 1640 } 1641 } 1642 1643 @VisibleForTesting 1644 public void disconnect() { 1645 disconnect(0); 1646 } 1647 1648 /** 1649 * Attempts to disconnect the call through the connection service. 1650 */ 1651 @VisibleForTesting 1652 public void disconnect(long disconnectionTimeout) { 1653 Log.addEvent(this, LogUtils.Events.REQUEST_DISCONNECT); 1654 1655 // Track that the call is now locally disconnecting. 1656 setLocallyDisconnecting(true); 1657 maybeSetCallAsDisconnectingChild(); 1658 1659 if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT || 1660 mState == CallState.CONNECTING) { 1661 Log.v(this, "Aborting call %s", this); 1662 abort(disconnectionTimeout); 1663 } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) { 1664 if (mConnectionService == null) { 1665 Log.e(this, new Exception(), "disconnect() request on a call without a" 1666 + " connection service."); 1667 } else { 1668 Log.i(this, "Send disconnect to connection service for call: %s", this); 1669 // The call isn't officially disconnected until the connection service 1670 // confirms that the call was actually disconnected. Only then is the 1671 // association between call and connection service severed, see 1672 // {@link CallsManager#markCallAsDisconnected}. 1673 mConnectionService.disconnect(this); 1674 } 1675 } 1676 } 1677 1678 void abort(long disconnectionTimeout) { 1679 if (mCreateConnectionProcessor != null && 1680 !mCreateConnectionProcessor.isProcessingComplete()) { 1681 mCreateConnectionProcessor.abort(); 1682 } else if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT 1683 || mState == CallState.CONNECTING) { 1684 if (disconnectionTimeout > 0) { 1685 // If the cancelation was from NEW_OUTGOING_CALL with a timeout of > 0 1686 // milliseconds, do not destroy the call. 1687 // Instead, we announce the cancellation and CallsManager handles 1688 // it through a timer. Since apps often cancel calls through NEW_OUTGOING_CALL and 1689 // then re-dial them quickly using a gateway, allowing the first call to end 1690 // causes jank. This timeout allows CallsManager to transition the first call into 1691 // the second call so that in-call only ever sees a single call...eliminating the 1692 // jank altogether. The app will also be able to set the timeout via an extra on 1693 // the ordered broadcast. 1694 for (Listener listener : mListeners) { 1695 if (listener.onCanceledViaNewOutgoingCallBroadcast( 1696 this, disconnectionTimeout)) { 1697 // The first listener to handle this wins. A return value of true means that 1698 // the listener will handle the disconnection process later and so we 1699 // should not continue it here. 1700 setLocallyDisconnecting(false); 1701 return; 1702 } 1703 } 1704 } 1705 1706 handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED)); 1707 } else { 1708 Log.v(this, "Cannot abort a call which is neither SELECT_PHONE_ACCOUNT or CONNECTING"); 1709 } 1710 } 1711 1712 /** 1713 * Answers the call if it is ringing. 1714 * 1715 * @param videoState The video state in which to answer the call. 1716 */ 1717 @VisibleForTesting 1718 public void answer(int videoState) { 1719 // Check to verify that the call is still in the ringing state. A call can change states 1720 // between the time the user hits 'answer' and Telecom receives the command. 1721 if (isRinging("answer")) { 1722 if (!isVideoCallingSupported() && VideoProfile.isVideo(videoState)) { 1723 // Video calling is not supported, yet the InCallService is attempting to answer as 1724 // video. We will simply answer as audio-only. 1725 videoState = VideoProfile.STATE_AUDIO_ONLY; 1726 } 1727 // At this point, we are asking the connection service to answer but we don't assume 1728 // that it will work. Instead, we wait until confirmation from the connectino service 1729 // that the call is in a non-STATE_RINGING state before changing the UI. See 1730 // {@link ConnectionServiceAdapter#setActive} and other set* methods. 1731 if (mConnectionService != null) { 1732 mConnectionService.answer(this, videoState); 1733 } else { 1734 Log.e(this, new NullPointerException(), 1735 "answer call failed due to null CS callId=%s", getId()); 1736 } 1737 Log.addEvent(this, LogUtils.Events.REQUEST_ACCEPT); 1738 } 1739 } 1740 1741 /** 1742 * Rejects the call if it is ringing. 1743 * 1744 * @param rejectWithMessage Whether to send a text message as part of the call rejection. 1745 * @param textMessage An optional text message to send as part of the rejection. 1746 */ 1747 @VisibleForTesting 1748 public void reject(boolean rejectWithMessage, String textMessage) { 1749 // Check to verify that the call is still in the ringing state. A call can change states 1750 // between the time the user hits 'reject' and Telecomm receives the command. 1751 if (isRinging("reject")) { 1752 // Ensure video state history tracks video state at time of rejection. 1753 mVideoStateHistory |= mVideoState; 1754 1755 if (mConnectionService != null) { 1756 mConnectionService.reject(this, rejectWithMessage, textMessage); 1757 } else { 1758 Log.e(this, new NullPointerException(), 1759 "reject call failed due to null CS callId=%s", getId()); 1760 } 1761 Log.addEvent(this, LogUtils.Events.REQUEST_REJECT); 1762 1763 } 1764 } 1765 1766 /** 1767 * Puts the call on hold if it is currently active. 1768 */ 1769 @VisibleForTesting 1770 public void hold() { 1771 if (mState == CallState.ACTIVE) { 1772 if (mConnectionService != null) { 1773 mConnectionService.hold(this); 1774 } else { 1775 Log.e(this, new NullPointerException(), 1776 "hold call failed due to null CS callId=%s", getId()); 1777 } 1778 Log.addEvent(this, LogUtils.Events.REQUEST_HOLD); 1779 } 1780 } 1781 1782 /** 1783 * Releases the call from hold if it is currently active. 1784 */ 1785 @VisibleForTesting 1786 public void unhold() { 1787 if (mState == CallState.ON_HOLD) { 1788 if (mConnectionService != null) { 1789 mConnectionService.unhold(this); 1790 } else { 1791 Log.e(this, new NullPointerException(), 1792 "unhold call failed due to null CS callId=%s", getId()); 1793 } 1794 Log.addEvent(this, LogUtils.Events.REQUEST_UNHOLD); 1795 } 1796 } 1797 1798 /** Checks if this is a live call or not. */ 1799 @VisibleForTesting 1800 public boolean isAlive() { 1801 switch (mState) { 1802 case CallState.NEW: 1803 case CallState.RINGING: 1804 case CallState.DISCONNECTED: 1805 case CallState.ABORTED: 1806 return false; 1807 default: 1808 return true; 1809 } 1810 } 1811 1812 boolean isActive() { 1813 return mState == CallState.ACTIVE; 1814 } 1815 1816 Bundle getExtras() { 1817 return mExtras; 1818 } 1819 1820 /** 1821 * Adds extras to the extras bundle associated with this {@link Call}. 1822 * 1823 * Note: this method needs to know the source of the extras change (see 1824 * {@link #SOURCE_CONNECTION_SERVICE}, {@link #SOURCE_INCALL_SERVICE}). Extras changes which 1825 * originate from a connection service will only be notified to incall services. Likewise, 1826 * changes originating from the incall services will only notify the connection service of the 1827 * change. 1828 * 1829 * @param source The source of the extras addition. 1830 * @param extras The extras. 1831 */ 1832 void putExtras(int source, Bundle extras) { 1833 if (extras == null) { 1834 return; 1835 } 1836 if (mExtras == null) { 1837 mExtras = new Bundle(); 1838 } 1839 mExtras.putAll(extras); 1840 1841 for (Listener l : mListeners) { 1842 l.onExtrasChanged(this, source, extras); 1843 } 1844 1845 // If the change originated from an InCallService, notify the connection service. 1846 if (source == SOURCE_INCALL_SERVICE) { 1847 if (mConnectionService != null) { 1848 mConnectionService.onExtrasChanged(this, mExtras); 1849 } else { 1850 Log.e(this, new NullPointerException(), 1851 "putExtras failed due to null CS callId=%s", getId()); 1852 } 1853 } 1854 } 1855 1856 /** 1857 * Removes extras from the extras bundle associated with this {@link Call}. 1858 * 1859 * Note: this method needs to know the source of the extras change (see 1860 * {@link #SOURCE_CONNECTION_SERVICE}, {@link #SOURCE_INCALL_SERVICE}). Extras changes which 1861 * originate from a connection service will only be notified to incall services. Likewise, 1862 * changes originating from the incall services will only notify the connection service of the 1863 * change. 1864 * 1865 * @param source The source of the extras removal. 1866 * @param keys The extra keys to remove. 1867 */ 1868 void removeExtras(int source, List<String> keys) { 1869 if (mExtras == null) { 1870 return; 1871 } 1872 for (String key : keys) { 1873 mExtras.remove(key); 1874 } 1875 1876 for (Listener l : mListeners) { 1877 l.onExtrasRemoved(this, source, keys); 1878 } 1879 1880 // If the change originated from an InCallService, notify the connection service. 1881 if (source == SOURCE_INCALL_SERVICE) { 1882 if (mConnectionService != null) { 1883 mConnectionService.onExtrasChanged(this, mExtras); 1884 } else { 1885 Log.e(this, new NullPointerException(), 1886 "removeExtras failed due to null CS callId=%s", getId()); 1887 } 1888 } 1889 } 1890 1891 @VisibleForTesting 1892 public Bundle getIntentExtras() { 1893 return mIntentExtras; 1894 } 1895 1896 void setIntentExtras(Bundle extras) { 1897 mIntentExtras = extras; 1898 } 1899 1900 public Intent getOriginalCallIntent() { 1901 return mOriginalCallIntent; 1902 } 1903 1904 public void setOriginalCallIntent(Intent intent) { 1905 mOriginalCallIntent = intent; 1906 } 1907 1908 /** 1909 * @return the uri of the contact associated with this call. 1910 */ 1911 @VisibleForTesting 1912 public Uri getContactUri() { 1913 if (mCallerInfo == null || !mCallerInfo.contactExists) { 1914 return getHandle(); 1915 } 1916 return Contacts.getLookupUri(mCallerInfo.contactIdOrZero, mCallerInfo.lookupKey); 1917 } 1918 1919 Uri getRingtone() { 1920 return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri; 1921 } 1922 1923 void onPostDialWait(String remaining) { 1924 for (Listener l : mListeners) { 1925 l.onPostDialWait(this, remaining); 1926 } 1927 } 1928 1929 void onPostDialChar(char nextChar) { 1930 for (Listener l : mListeners) { 1931 l.onPostDialChar(this, nextChar); 1932 } 1933 } 1934 1935 void postDialContinue(boolean proceed) { 1936 if (mConnectionService != null) { 1937 mConnectionService.onPostDialContinue(this, proceed); 1938 } else { 1939 Log.e(this, new NullPointerException(), 1940 "postDialContinue failed due to null CS callId=%s", getId()); 1941 } 1942 } 1943 1944 void conferenceWith(Call otherCall) { 1945 if (mConnectionService == null) { 1946 Log.w(this, "conference requested on a call without a connection service."); 1947 } else { 1948 Log.addEvent(this, LogUtils.Events.CONFERENCE_WITH, otherCall); 1949 mConnectionService.conference(this, otherCall); 1950 } 1951 } 1952 1953 void splitFromConference() { 1954 if (mConnectionService == null) { 1955 Log.w(this, "splitting from conference call without a connection service"); 1956 } else { 1957 Log.addEvent(this, LogUtils.Events.SPLIT_FROM_CONFERENCE); 1958 mConnectionService.splitFromConference(this); 1959 } 1960 } 1961 1962 @VisibleForTesting 1963 public void mergeConference() { 1964 if (mConnectionService == null) { 1965 Log.w(this, "merging conference calls without a connection service."); 1966 } else if (can(Connection.CAPABILITY_MERGE_CONFERENCE)) { 1967 Log.addEvent(this, LogUtils.Events.CONFERENCE_WITH); 1968 mConnectionService.mergeConference(this); 1969 mWasConferencePreviouslyMerged = true; 1970 } 1971 } 1972 1973 @VisibleForTesting 1974 public void swapConference() { 1975 if (mConnectionService == null) { 1976 Log.w(this, "swapping conference calls without a connection service."); 1977 } else if (can(Connection.CAPABILITY_SWAP_CONFERENCE)) { 1978 Log.addEvent(this, LogUtils.Events.SWAP); 1979 mConnectionService.swapConference(this); 1980 switch (mChildCalls.size()) { 1981 case 1: 1982 mConferenceLevelActiveCall = mChildCalls.get(0); 1983 break; 1984 case 2: 1985 // swap 1986 mConferenceLevelActiveCall = mChildCalls.get(0) == mConferenceLevelActiveCall ? 1987 mChildCalls.get(1) : mChildCalls.get(0); 1988 break; 1989 default: 1990 // For anything else 0, or 3+, set it to null since it is impossible to tell. 1991 mConferenceLevelActiveCall = null; 1992 break; 1993 } 1994 } 1995 } 1996 1997 /** 1998 * Initiates a request to the connection service to pull this call. 1999 * <p> 2000 * This method can only be used for calls that have the 2001 * {@link android.telecom.Connection#CAPABILITY_CAN_PULL_CALL} capability and 2002 * {@link android.telecom.Connection#PROPERTY_IS_EXTERNAL_CALL} property set. 2003 * <p> 2004 * An external call is a representation of a call which is taking place on another device 2005 * associated with a PhoneAccount on this device. Issuing a request to pull the external call 2006 * tells the {@link android.telecom.ConnectionService} that it should move the call from the 2007 * other device to this one. An example of this is the IMS multi-endpoint functionality. A 2008 * user may have two phones with the same phone number. If the user is engaged in an active 2009 * call on their first device, the network will inform the second device of that ongoing call in 2010 * the form of an external call. The user may wish to continue their conversation on the second 2011 * device, so will issue a request to pull the call to the second device. 2012 * <p> 2013 * Requests to pull a call which is not external, or a call which is not pullable are ignored. 2014 */ 2015 public void pullExternalCall() { 2016 if (mConnectionService == null) { 2017 Log.w(this, "pulling a call without a connection service."); 2018 } 2019 2020 if (!hasProperty(Connection.PROPERTY_IS_EXTERNAL_CALL)) { 2021 Log.w(this, "pullExternalCall - call %s is not an external call.", mId); 2022 return; 2023 } 2024 2025 if (!can(Connection.CAPABILITY_CAN_PULL_CALL)) { 2026 Log.w(this, "pullExternalCall - call %s is external but cannot be pulled.", mId); 2027 return; 2028 } 2029 Log.addEvent(this, LogUtils.Events.REQUEST_PULL); 2030 mConnectionService.pullExternalCall(this); 2031 } 2032 2033 /** 2034 * Sends a call event to the {@link ConnectionService} for this call. This function is 2035 * called for event other than {@link Call#EVENT_REQUEST_HANDOVER} 2036 * 2037 * @param event The call event. 2038 * @param extras Associated extras. 2039 */ 2040 public void sendCallEvent(String event, Bundle extras) { 2041 sendCallEvent(event, 0/*For Event != EVENT_REQUEST_HANDOVER*/, extras); 2042 } 2043 2044 /** 2045 * Sends a call event to the {@link ConnectionService} for this call. 2046 * 2047 * See {@link Call#sendCallEvent(String, Bundle)}. 2048 * 2049 * @param event The call event. 2050 * @param targetSdkVer SDK version of the app calling this api 2051 * @param extras Associated extras. 2052 */ 2053 public void sendCallEvent(String event, int targetSdkVer, Bundle extras) { 2054 if (mConnectionService != null) { 2055 if (android.telecom.Call.EVENT_REQUEST_HANDOVER.equals(event)) { 2056 if (targetSdkVer > Build.VERSION_CODES.O_MR1) { 2057 Log.e(this, new Exception(), "sendCallEvent failed. Use public api handoverTo" + 2058 " for API > 27(O-MR1)"); 2059 // TODO: Add "return" after DUO team adds new API support for handover 2060 } 2061 2062 // Handover requests are targeted at Telecom, not the ConnectionService. 2063 if (extras == null) { 2064 Log.w(this, "sendCallEvent: %s event received with null extras.", 2065 android.telecom.Call.EVENT_REQUEST_HANDOVER); 2066 mConnectionService.sendCallEvent(this, 2067 android.telecom.Call.EVENT_HANDOVER_FAILED, null); 2068 return; 2069 } 2070 Parcelable parcelable = extras.getParcelable( 2071 android.telecom.Call.EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE); 2072 if (!(parcelable instanceof PhoneAccountHandle) || parcelable == null) { 2073 Log.w(this, "sendCallEvent: %s event received with invalid handover acct.", 2074 android.telecom.Call.EVENT_REQUEST_HANDOVER); 2075 mConnectionService.sendCallEvent(this, 2076 android.telecom.Call.EVENT_HANDOVER_FAILED, null); 2077 return; 2078 } 2079 PhoneAccountHandle phoneAccountHandle = (PhoneAccountHandle) parcelable; 2080 int videoState = extras.getInt(android.telecom.Call.EXTRA_HANDOVER_VIDEO_STATE, 2081 VideoProfile.STATE_AUDIO_ONLY); 2082 Parcelable handoverExtras = extras.getParcelable( 2083 android.telecom.Call.EXTRA_HANDOVER_EXTRAS); 2084 Bundle handoverExtrasBundle = null; 2085 if (handoverExtras instanceof Bundle) { 2086 handoverExtrasBundle = (Bundle) handoverExtras; 2087 } 2088 requestHandover(phoneAccountHandle, videoState, handoverExtrasBundle, true); 2089 } else { 2090 Log.addEvent(this, LogUtils.Events.CALL_EVENT, event); 2091 mConnectionService.sendCallEvent(this, event, extras); 2092 } 2093 } else { 2094 Log.e(this, new NullPointerException(), 2095 "sendCallEvent failed due to null CS callId=%s", getId()); 2096 } 2097 } 2098 2099 /** 2100 * Initiates a handover of this Call to the {@link ConnectionService} identified 2101 * by destAcct. 2102 * @param destAcct ConnectionService to which the call should be handed over. 2103 * @param videoState The video state desired after the handover. 2104 * @param extras Extra information to be passed to ConnectionService 2105 */ 2106 public void handoverTo(PhoneAccountHandle destAcct, int videoState, Bundle extras) { 2107 requestHandover(destAcct, videoState, extras, false); 2108 } 2109 2110 /** 2111 * Sets this {@link Call} to has the specified {@code parentCall}. Also sets the parent to 2112 * have this call as a child. 2113 * @param parentCall 2114 */ 2115 void setParentAndChildCall(Call parentCall) { 2116 boolean isParentChanging = (mParentCall != parentCall); 2117 setParentCall(parentCall); 2118 setChildOf(parentCall); 2119 if (isParentChanging) { 2120 notifyParentChanged(parentCall); 2121 } 2122 } 2123 2124 /** 2125 * Notifies listeners when the parent call changes. 2126 * Used by {@link #setParentAndChildCall(Call)}, and in {@link CallsManager}. 2127 * @param parentCall The new parent call for this call. 2128 */ 2129 void notifyParentChanged(Call parentCall) { 2130 Log.addEvent(this, LogUtils.Events.SET_PARENT, parentCall); 2131 for (Listener l : mListeners) { 2132 l.onParentChanged(this); 2133 } 2134 } 2135 2136 /** 2137 * Unlike {@link #setParentAndChildCall(Call)}, only sets the parent call but does NOT set 2138 * the child. 2139 * TODO: This is only required when adding existing connections as a workaround so that we 2140 * can avoid sending the "onParentChanged" callback until later. 2141 * @param parentCall The new parent call. 2142 */ 2143 void setParentCall(Call parentCall) { 2144 if (parentCall == this) { 2145 Log.e(this, new Exception(), "setting the parent to self"); 2146 return; 2147 } 2148 if (parentCall == mParentCall) { 2149 // nothing to do 2150 return; 2151 } 2152 if (mParentCall != null) { 2153 mParentCall.removeChildCall(this); 2154 } 2155 mParentCall = parentCall; 2156 } 2157 2158 /** 2159 * To be called after {@link #setParentCall(Call)} to complete setting the parent by adding 2160 * this call as a child of another call. 2161 * <p> 2162 * Note: if using this method alone, the caller must call {@link #notifyParentChanged(Call)} to 2163 * ensure the InCall UI is updated with the change in parent. 2164 * @param parentCall The new parent for this call. 2165 */ 2166 void setChildOf(Call parentCall) { 2167 if (parentCall != null && !parentCall.getChildCalls().contains(this)) { 2168 parentCall.addChildCall(this); 2169 } 2170 } 2171 2172 void setConferenceableCalls(List<Call> conferenceableCalls) { 2173 mConferenceableCalls.clear(); 2174 mConferenceableCalls.addAll(conferenceableCalls); 2175 2176 for (Listener l : mListeners) { 2177 l.onConferenceableCallsChanged(this); 2178 } 2179 } 2180 2181 @VisibleForTesting 2182 public List<Call> getConferenceableCalls() { 2183 return mConferenceableCalls; 2184 } 2185 2186 @VisibleForTesting 2187 public boolean can(int capability) { 2188 return (mConnectionCapabilities & capability) == capability; 2189 } 2190 2191 @VisibleForTesting 2192 public boolean hasProperty(int property) { 2193 return (mConnectionProperties & property) == property; 2194 } 2195 2196 private void addChildCall(Call call) { 2197 if (!mChildCalls.contains(call)) { 2198 // Set the pseudo-active call to the latest child added to the conference. 2199 // See definition of mConferenceLevelActiveCall for more detail. 2200 mConferenceLevelActiveCall = call; 2201 mChildCalls.add(call); 2202 2203 Log.addEvent(this, LogUtils.Events.ADD_CHILD, call); 2204 2205 for (Listener l : mListeners) { 2206 l.onChildrenChanged(this); 2207 } 2208 } 2209 } 2210 2211 private void removeChildCall(Call call) { 2212 if (mChildCalls.remove(call)) { 2213 Log.addEvent(this, LogUtils.Events.REMOVE_CHILD, call); 2214 for (Listener l : mListeners) { 2215 l.onChildrenChanged(this); 2216 } 2217 } 2218 } 2219 2220 /** 2221 * Return whether the user can respond to this {@code Call} via an SMS message. 2222 * 2223 * @return true if the "Respond via SMS" feature should be enabled 2224 * for this incoming call. 2225 * 2226 * The general rule is that we *do* allow "Respond via SMS" except for 2227 * the few (relatively rare) cases where we know for sure it won't 2228 * work, namely: 2229 * - a bogus or blank incoming number 2230 * - a call from a SIP address 2231 * - a "call presentation" that doesn't allow the number to be revealed 2232 * 2233 * In all other cases, we allow the user to respond via SMS. 2234 * 2235 * Note that this behavior isn't perfect; for example we have no way 2236 * to detect whether the incoming call is from a landline (with most 2237 * networks at least), so we still enable this feature even though 2238 * SMSes to that number will silently fail. 2239 */ 2240 boolean isRespondViaSmsCapable() { 2241 if (mState != CallState.RINGING) { 2242 return false; 2243 } 2244 2245 if (getHandle() == null) { 2246 // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in 2247 // other words, the user should not be able to see the incoming phone number. 2248 return false; 2249 } 2250 2251 if (mPhoneNumberUtilsAdapter.isUriNumber(getHandle().toString())) { 2252 // The incoming number is actually a URI (i.e. a SIP address), 2253 // not a regular PSTN phone number, and we can't send SMSes to 2254 // SIP addresses. 2255 // (TODO: That might still be possible eventually, though. Is 2256 // there some SIP-specific equivalent to sending a text message?) 2257 return false; 2258 } 2259 2260 // Is there a valid SMS application on the phone? 2261 if (SmsApplication.getDefaultRespondViaMessageApplication(mContext, 2262 true /*updateIfNeeded*/) == null) { 2263 return false; 2264 } 2265 2266 // TODO: with some carriers (in certain countries) you *can* actually 2267 // tell whether a given number is a mobile phone or not. So in that 2268 // case we could potentially return false here if the incoming call is 2269 // from a land line. 2270 2271 // If none of the above special cases apply, it's OK to enable the 2272 // "Respond via SMS" feature. 2273 return true; 2274 } 2275 2276 List<String> getCannedSmsResponses() { 2277 return mCannedSmsResponses; 2278 } 2279 2280 /** 2281 * We need to make sure that before we move a call to the disconnected state, it no 2282 * longer has any parent/child relationships. We want to do this to ensure that the InCall 2283 * Service always has the right data in the right order. We also want to do it in telecom so 2284 * that the insurance policy lives in the framework side of things. 2285 */ 2286 private void fixParentAfterDisconnect() { 2287 setParentAndChildCall(null); 2288 } 2289 2290 /** 2291 * @return True if the call is ringing, else logs the action name. 2292 */ 2293 private boolean isRinging(String actionName) { 2294 if (mState == CallState.RINGING) { 2295 return true; 2296 } 2297 2298 Log.i(this, "Request to %s a non-ringing call %s", actionName, this); 2299 return false; 2300 } 2301 2302 @SuppressWarnings("rawtypes") 2303 private void decrementAssociatedCallCount(ServiceBinder binder) { 2304 if (binder != null) { 2305 binder.decrementAssociatedCallCount(); 2306 } 2307 } 2308 2309 /** 2310 * Looks up contact information based on the current handle. 2311 */ 2312 private void startCallerInfoLookup() { 2313 mCallerInfo = null; 2314 mCallsManager.getCallerInfoLookupHelper().startLookup(mHandle, mCallerInfoQueryListener); 2315 } 2316 2317 /** 2318 * Saves the specified caller info if the specified token matches that of the last query 2319 * that was made. 2320 * 2321 * @param callerInfo The new caller information to set. 2322 */ 2323 private void setCallerInfo(Uri handle, CallerInfo callerInfo) { 2324 Trace.beginSection("setCallerInfo"); 2325 if (callerInfo == null) { 2326 Log.i(this, "CallerInfo lookup returned null, skipping update"); 2327 return; 2328 } 2329 2330 if ((handle != null) && !handle.equals(mHandle)) { 2331 Log.i(this, "setCallerInfo received stale caller info for an old handle. Ignoring."); 2332 return; 2333 } 2334 2335 mCallerInfo = callerInfo; 2336 Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo); 2337 2338 if (mCallerInfo.contactDisplayPhotoUri == null || 2339 mCallerInfo.cachedPhotoIcon != null || mCallerInfo.cachedPhoto != null) { 2340 for (Listener l : mListeners) { 2341 l.onCallerInfoChanged(this); 2342 } 2343 } 2344 2345 Trace.endSection(); 2346 } 2347 2348 public CallerInfo getCallerInfo() { 2349 return mCallerInfo; 2350 } 2351 2352 private void maybeLoadCannedSmsResponses() { 2353 if (mCallDirection == CALL_DIRECTION_INCOMING 2354 && isRespondViaSmsCapable() 2355 && !mCannedSmsResponsesLoadingStarted) { 2356 Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages"); 2357 mCannedSmsResponsesLoadingStarted = true; 2358 mCallsManager.getRespondViaSmsManager().loadCannedTextMessages( 2359 new Response<Void, List<String>>() { 2360 @Override 2361 public void onResult(Void request, List<String>... result) { 2362 if (result.length > 0) { 2363 Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]); 2364 mCannedSmsResponses = result[0]; 2365 for (Listener l : mListeners) { 2366 l.onCannedSmsResponsesLoaded(Call.this); 2367 } 2368 } 2369 } 2370 2371 @Override 2372 public void onError(Void request, int code, String msg) { 2373 Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code, 2374 msg); 2375 } 2376 }, 2377 mContext 2378 ); 2379 } else { 2380 Log.d(this, "maybeLoadCannedSmsResponses: doing nothing"); 2381 } 2382 } 2383 2384 /** 2385 * Sets speakerphone option on when call begins. 2386 */ 2387 public void setStartWithSpeakerphoneOn(boolean startWithSpeakerphone) { 2388 mSpeakerphoneOn = startWithSpeakerphone; 2389 } 2390 2391 /** 2392 * Returns speakerphone option. 2393 * 2394 * @return Whether or not speakerphone should be set automatically when call begins. 2395 */ 2396 public boolean getStartWithSpeakerphoneOn() { 2397 return mSpeakerphoneOn; 2398 } 2399 2400 public void stopRtt() { 2401 if (mConnectionService != null) { 2402 mConnectionService.stopRtt(this); 2403 } else { 2404 // If this gets called by the in-call app before the connection service is set, we'll 2405 // just ignore it since it's really not supposed to happen. 2406 Log.w(this, "stopRtt() called before connection service is set."); 2407 } 2408 } 2409 2410 public void sendRttRequest() { 2411 setRttStreams(true); 2412 mConnectionService.startRtt(this, getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs()); 2413 } 2414 2415 public void setRttStreams(boolean shouldBeRtt) { 2416 boolean areStreamsInitialized = mInCallToConnectionServiceStreams != null 2417 && mConnectionServiceToInCallStreams != null; 2418 if (shouldBeRtt && !areStreamsInitialized) { 2419 try { 2420 mWasEverRtt = true; 2421 mInCallToConnectionServiceStreams = ParcelFileDescriptor.createReliablePipe(); 2422 mConnectionServiceToInCallStreams = ParcelFileDescriptor.createReliablePipe(); 2423 } catch (IOException e) { 2424 Log.e(this, e, "Failed to create pipes for RTT call."); 2425 } 2426 } else if (!shouldBeRtt && areStreamsInitialized) { 2427 closeRttPipes(); 2428 mInCallToConnectionServiceStreams = null; 2429 mConnectionServiceToInCallStreams = null; 2430 } 2431 } 2432 2433 public void onRttConnectionFailure(int reason) { 2434 setRttStreams(false); 2435 for (Listener l : mListeners) { 2436 l.onRttInitiationFailure(this, reason); 2437 } 2438 } 2439 2440 public void onRemoteRttRequest() { 2441 if (isRttCall()) { 2442 Log.w(this, "Remote RTT request on a call that's already RTT"); 2443 return; 2444 } 2445 2446 mPendingRttRequestId = mCallsManager.getNextRttRequestId(); 2447 for (Listener l : mListeners) { 2448 l.onRemoteRttRequest(this, mPendingRttRequestId); 2449 } 2450 } 2451 2452 public void handleRttRequestResponse(int id, boolean accept) { 2453 if (mPendingRttRequestId == INVALID_RTT_REQUEST_ID) { 2454 Log.w(this, "Response received to a nonexistent RTT request: %d", id); 2455 return; 2456 } 2457 if (id != mPendingRttRequestId) { 2458 Log.w(this, "Response ID %d does not match expected %d", id, mPendingRttRequestId); 2459 return; 2460 } 2461 setRttStreams(accept); 2462 if (accept) { 2463 Log.i(this, "RTT request %d accepted.", id); 2464 mConnectionService.respondToRttRequest( 2465 this, getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs()); 2466 } else { 2467 Log.i(this, "RTT request %d rejected.", id); 2468 mConnectionService.respondToRttRequest(this, null, null); 2469 } 2470 } 2471 2472 public void closeRttPipes() { 2473 // TODO: may defer this until call is removed? 2474 } 2475 2476 public boolean isRttCall() { 2477 return (mConnectionProperties & Connection.PROPERTY_IS_RTT) == Connection.PROPERTY_IS_RTT; 2478 } 2479 2480 public boolean wasEverRttCall() { 2481 return mWasEverRtt; 2482 } 2483 2484 public ParcelFileDescriptor getCsToInCallRttPipeForCs() { 2485 return mConnectionServiceToInCallStreams == null ? null 2486 : mConnectionServiceToInCallStreams[RTT_PIPE_WRITE_SIDE_INDEX]; 2487 } 2488 2489 public ParcelFileDescriptor getInCallToCsRttPipeForCs() { 2490 return mInCallToConnectionServiceStreams == null ? null 2491 : mInCallToConnectionServiceStreams[RTT_PIPE_READ_SIDE_INDEX]; 2492 } 2493 2494 public ParcelFileDescriptor getCsToInCallRttPipeForInCall() { 2495 return mConnectionServiceToInCallStreams == null ? null 2496 : mConnectionServiceToInCallStreams[RTT_PIPE_READ_SIDE_INDEX]; 2497 } 2498 2499 public ParcelFileDescriptor getInCallToCsRttPipeForInCall() { 2500 return mInCallToConnectionServiceStreams == null ? null 2501 : mInCallToConnectionServiceStreams[RTT_PIPE_WRITE_SIDE_INDEX]; 2502 } 2503 2504 public int getRttMode() { 2505 return mRttMode; 2506 } 2507 2508 /** 2509 * Sets a video call provider for the call. 2510 */ 2511 public void setVideoProvider(IVideoProvider videoProvider) { 2512 Log.v(this, "setVideoProvider"); 2513 2514 if (videoProvider != null ) { 2515 try { 2516 mVideoProviderProxy = new VideoProviderProxy(mLock, videoProvider, this, 2517 mCallsManager); 2518 } catch (RemoteException ignored) { 2519 // Ignore RemoteException. 2520 } 2521 } else { 2522 mVideoProviderProxy = null; 2523 } 2524 2525 mVideoProvider = videoProvider; 2526 2527 for (Listener l : mListeners) { 2528 l.onVideoCallProviderChanged(Call.this); 2529 } 2530 } 2531 2532 /** 2533 * @return The {@link Connection.VideoProvider} binder. 2534 */ 2535 public IVideoProvider getVideoProvider() { 2536 if (mVideoProviderProxy == null) { 2537 return null; 2538 } 2539 2540 return mVideoProviderProxy.getInterface(); 2541 } 2542 2543 /** 2544 * @return The {@link VideoProviderProxy} for this call. 2545 */ 2546 public VideoProviderProxy getVideoProviderProxy() { 2547 return mVideoProviderProxy; 2548 } 2549 2550 /** 2551 * The current video state for the call. 2552 * See {@link VideoProfile} for a list of valid video states. 2553 */ 2554 public int getVideoState() { 2555 return mVideoState; 2556 } 2557 2558 /** 2559 * Returns the video states which were applicable over the duration of a call. 2560 * See {@link VideoProfile} for a list of valid video states. 2561 * 2562 * @return The video states applicable over the duration of the call. 2563 */ 2564 public int getVideoStateHistory() { 2565 return mVideoStateHistory; 2566 } 2567 2568 /** 2569 * Determines the current video state for the call. 2570 * For an outgoing call determines the desired video state for the call. 2571 * Valid values: see {@link VideoProfile} 2572 * 2573 * @param videoState The video state for the call. 2574 */ 2575 public void setVideoState(int videoState) { 2576 // If the phone account associated with this call does not support video calling, then we 2577 // will automatically set the video state to audio-only. 2578 if (!isVideoCallingSupported()) { 2579 Log.d(this, "setVideoState: videoState=%s defaulted to audio (video not supported)", 2580 VideoProfile.videoStateToString(videoState)); 2581 videoState = VideoProfile.STATE_AUDIO_ONLY; 2582 } 2583 2584 // Track Video State history during the duration of the call. 2585 // Only update the history when the call is active or disconnected. This ensures we do 2586 // not include the video state history when: 2587 // - Call is incoming (but not answered). 2588 // - Call it outgoing (but not answered). 2589 // We include the video state when disconnected to ensure that rejected calls reflect the 2590 // appropriate video state. 2591 // For all other times we add to the video state history, see #setState. 2592 if (isActive() || getState() == CallState.DISCONNECTED) { 2593 mVideoStateHistory = mVideoStateHistory | videoState; 2594 } 2595 2596 int previousVideoState = mVideoState; 2597 mVideoState = videoState; 2598 if (mVideoState != previousVideoState) { 2599 Log.addEvent(this, LogUtils.Events.VIDEO_STATE_CHANGED, 2600 VideoProfile.videoStateToString(videoState)); 2601 for (Listener l : mListeners) { 2602 l.onVideoStateChanged(this, previousVideoState, mVideoState); 2603 } 2604 } 2605 2606 if (VideoProfile.isVideo(videoState)) { 2607 mAnalytics.setCallIsVideo(true); 2608 } 2609 } 2610 2611 public boolean getIsVoipAudioMode() { 2612 return mIsVoipAudioMode; 2613 } 2614 2615 public void setIsVoipAudioMode(boolean audioModeIsVoip) { 2616 mIsVoipAudioMode = audioModeIsVoip; 2617 for (Listener l : mListeners) { 2618 l.onIsVoipAudioModeChanged(this); 2619 } 2620 } 2621 2622 public StatusHints getStatusHints() { 2623 return mStatusHints; 2624 } 2625 2626 public void setStatusHints(StatusHints statusHints) { 2627 mStatusHints = statusHints; 2628 for (Listener l : mListeners) { 2629 l.onStatusHintsChanged(this); 2630 } 2631 } 2632 2633 public boolean isUnknown() { 2634 return mCallDirection == CALL_DIRECTION_UNKNOWN; 2635 } 2636 2637 /** 2638 * Determines if this call is in a disconnecting state. 2639 * 2640 * @return {@code true} if this call is locally disconnecting. 2641 */ 2642 public boolean isLocallyDisconnecting() { 2643 return mIsLocallyDisconnecting; 2644 } 2645 2646 /** 2647 * Sets whether this call is in a disconnecting state. 2648 * 2649 * @param isLocallyDisconnecting {@code true} if this call is locally disconnecting. 2650 */ 2651 private void setLocallyDisconnecting(boolean isLocallyDisconnecting) { 2652 mIsLocallyDisconnecting = isLocallyDisconnecting; 2653 } 2654 2655 /** 2656 * @return user handle of user initiating the outgoing call. 2657 */ 2658 public UserHandle getInitiatingUser() { 2659 return mInitiatingUser; 2660 } 2661 2662 /** 2663 * Set the user handle of user initiating the outgoing call. 2664 * @param initiatingUser 2665 */ 2666 public void setInitiatingUser(UserHandle initiatingUser) { 2667 Preconditions.checkNotNull(initiatingUser); 2668 mInitiatingUser = initiatingUser; 2669 } 2670 2671 static int getStateFromConnectionState(int state) { 2672 switch (state) { 2673 case Connection.STATE_INITIALIZING: 2674 return CallState.CONNECTING; 2675 case Connection.STATE_ACTIVE: 2676 return CallState.ACTIVE; 2677 case Connection.STATE_DIALING: 2678 return CallState.DIALING; 2679 case Connection.STATE_PULLING_CALL: 2680 return CallState.PULLING; 2681 case Connection.STATE_DISCONNECTED: 2682 return CallState.DISCONNECTED; 2683 case Connection.STATE_HOLDING: 2684 return CallState.ON_HOLD; 2685 case Connection.STATE_NEW: 2686 return CallState.NEW; 2687 case Connection.STATE_RINGING: 2688 return CallState.RINGING; 2689 } 2690 return CallState.DISCONNECTED; 2691 } 2692 2693 /** 2694 * Determines if this call is in disconnected state and waiting to be destroyed. 2695 * 2696 * @return {@code true} if this call is disconected. 2697 */ 2698 public boolean isDisconnected() { 2699 return (getState() == CallState.DISCONNECTED || getState() == CallState.ABORTED); 2700 } 2701 2702 /** 2703 * Determines if this call has just been created and has not been configured properly yet. 2704 * 2705 * @return {@code true} if this call is new. 2706 */ 2707 public boolean isNew() { 2708 return getState() == CallState.NEW; 2709 } 2710 2711 /** 2712 * Sets the call data usage for the call. 2713 * 2714 * @param callDataUsage The new call data usage (in bytes). 2715 */ 2716 public void setCallDataUsage(long callDataUsage) { 2717 mCallDataUsage = callDataUsage; 2718 } 2719 2720 /** 2721 * Returns the call data usage for the call. 2722 * 2723 * @return The call data usage (in bytes). 2724 */ 2725 public long getCallDataUsage() { 2726 return mCallDataUsage; 2727 } 2728 2729 public void setRttMode(int mode) { 2730 mRttMode = mode; 2731 // TODO: hook this up to CallAudioManager 2732 } 2733 2734 /** 2735 * Returns true if the call is outgoing and the NEW_OUTGOING_CALL ordered broadcast intent 2736 * has come back to telecom and was processed. 2737 */ 2738 public boolean isNewOutgoingCallIntentBroadcastDone() { 2739 return mIsNewOutgoingCallIntentBroadcastDone; 2740 } 2741 2742 public void setNewOutgoingCallIntentBroadcastIsDone() { 2743 mIsNewOutgoingCallIntentBroadcastDone = true; 2744 } 2745 2746 /** 2747 * Determines if the call has been held by the remote party. 2748 * 2749 * @return {@code true} if the call is remotely held, {@code false} otherwise. 2750 */ 2751 public boolean isRemotelyHeld() { 2752 return mIsRemotelyHeld; 2753 } 2754 2755 /** 2756 * Handles Connection events received from a {@link ConnectionService}. 2757 * 2758 * @param event The event. 2759 * @param extras The extras. 2760 */ 2761 public void onConnectionEvent(String event, Bundle extras) { 2762 Log.addEvent(this, LogUtils.Events.CONNECTION_EVENT, event); 2763 if (Connection.EVENT_ON_HOLD_TONE_START.equals(event)) { 2764 mIsRemotelyHeld = true; 2765 Log.addEvent(this, LogUtils.Events.REMOTELY_HELD); 2766 // Inform listeners of the fact that a call hold tone was received. This will trigger 2767 // the CallAudioManager to play a tone via the InCallTonePlayer. 2768 for (Listener l : mListeners) { 2769 l.onHoldToneRequested(this); 2770 } 2771 } else if (Connection.EVENT_ON_HOLD_TONE_END.equals(event)) { 2772 mIsRemotelyHeld = false; 2773 Log.addEvent(this, LogUtils.Events.REMOTELY_UNHELD); 2774 for (Listener l : mListeners) { 2775 l.onHoldToneRequested(this); 2776 } 2777 } else { 2778 for (Listener l : mListeners) { 2779 l.onConnectionEvent(this, event, extras); 2780 } 2781 } 2782 } 2783 2784 /** 2785 * Notifies interested parties that the handover has completed. 2786 * Notifies: 2787 * 1. {@link InCallController} which communicates this to the 2788 * {@link android.telecom.InCallService} via {@link Listener#onHandoverComplete()}. 2789 * 2. {@link ConnectionServiceWrapper} which informs the {@link android.telecom.Connection} of 2790 * the successful handover. 2791 */ 2792 public void onHandoverComplete() { 2793 Log.i(this, "onHandoverComplete; callId=%s", getId()); 2794 if (mConnectionService != null) { 2795 mConnectionService.handoverComplete(this); 2796 } 2797 for (Listener l : mListeners) { 2798 l.onHandoverComplete(this); 2799 } 2800 } 2801 2802 public void onHandoverFailed(int handoverError) { 2803 Log.i(this, "onHandoverFailed; callId=%s, handoverError=%d", getId(), handoverError); 2804 for (Listener l : mListeners) { 2805 l.onHandoverFailed(this, handoverError); 2806 } 2807 } 2808 2809 public void setOriginalConnectionId(String originalConnectionId) { 2810 mOriginalConnectionId = originalConnectionId; 2811 } 2812 2813 /** 2814 * For calls added via a ConnectionManager using the 2815 * {@link android.telecom.ConnectionService#addExistingConnection(PhoneAccountHandle, 2816 * Connection)}, or {@link android.telecom.ConnectionService#addConference(Conference)} APIS, 2817 * indicates the ID of this call as it was referred to by the {@code ConnectionService} which 2818 * originally created it. 2819 * 2820 * See {@link Connection#EXTRA_ORIGINAL_CONNECTION_ID}. 2821 * @return The original connection ID. 2822 */ 2823 public String getOriginalConnectionId() { 2824 return mOriginalConnectionId; 2825 } 2826 2827 ConnectionServiceFocusManager getConnectionServiceFocusManager() { 2828 return mCallsManager.getConnectionServiceFocusManager(); 2829 } 2830 2831 /** 2832 * Determines if a {@link Call}'s capabilities bitmask indicates that video is supported either 2833 * remotely or locally. 2834 * 2835 * @param capabilities The {@link Connection} capabilities for the call. 2836 * @return {@code true} if video is supported, {@code false} otherwise. 2837 */ 2838 private boolean doesCallSupportVideo(int capabilities) { 2839 return (capabilities & Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL) != 0 || 2840 (capabilities & Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL) != 0; 2841 } 2842 2843 /** 2844 * Remove any video capabilities set on a {@link Connection} capabilities bitmask. 2845 * 2846 * @param capabilities The capabilities. 2847 * @return The bitmask with video capabilities removed. 2848 */ 2849 private int removeVideoCapabilities(int capabilities) { 2850 return capabilities & ~(Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL | 2851 Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL); 2852 } 2853 2854 /** 2855 * Initiates a handover of this {@link Call} to another {@link PhoneAccount}. 2856 * @param handoverToHandle The {@link PhoneAccountHandle} to handover to. 2857 * @param videoState The video state of the call when handed over. 2858 * @param extras Optional extras {@link Bundle} provided by the initiating 2859 * {@link android.telecom.InCallService}. 2860 */ 2861 private void requestHandover(PhoneAccountHandle handoverToHandle, int videoState, 2862 Bundle extras, boolean isLegacy) { 2863 for (Listener l : mListeners) { 2864 l.onHandoverRequested(this, handoverToHandle, videoState, extras, isLegacy); 2865 } 2866 } 2867 2868 /** 2869 * Sets the video history based on the state and state transitions of the call. Always add the 2870 * current video state to the video state history during a call transition except for the 2871 * transitions DIALING->ACTIVE and RINGING->ACTIVE. In these cases, clear the history. If a 2872 * call starts dialing/ringing as a VT call and gets downgraded to audio, we need to record 2873 * the history as an audio call. 2874 */ 2875 private void updateVideoHistoryViaState(int oldState, int newState) { 2876 if ((oldState == CallState.DIALING || oldState == CallState.RINGING) 2877 && newState == CallState.ACTIVE) { 2878 mVideoStateHistory = mVideoState; 2879 } 2880 2881 mVideoStateHistory |= mVideoState; 2882 } 2883 2884 /** 2885 * Returns whether or not high definition audio was used. 2886 * 2887 * @return true if high definition audio was used during this call. 2888 */ 2889 boolean wasHighDefAudio() { 2890 return mWasHighDefAudio; 2891 } 2892} 2893