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