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