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