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