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