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