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