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