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