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