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