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