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