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