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