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