Call.java revision cf5b2918ca58b155911a66222ae5b4d21cb5b8ec
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 mState = newState; 386 maybeLoadCannedSmsResponses(); 387 388 if (mState == CallState.DISCONNECTED) { 389 setLocallyDisconnecting(false); 390 fixParentAfterDisconnect(); 391 } 392 } 393 } 394 395 void setRingbackRequested(boolean ringbackRequested) { 396 mRingbackRequested = ringbackRequested; 397 for (Listener l : mListeners) { 398 l.onRingbackRequested(this, mRingbackRequested); 399 } 400 } 401 402 boolean isRingbackRequested() { 403 return mRingbackRequested; 404 } 405 406 boolean isConference() { 407 return mIsConference; 408 } 409 410 Uri getHandle() { 411 return mHandle; 412 } 413 414 int getHandlePresentation() { 415 return mHandlePresentation; 416 } 417 418 419 void setHandle(Uri handle) { 420 setHandle(handle, TelecomManager.PRESENTATION_ALLOWED); 421 } 422 423 void setHandle(Uri handle, int presentation) { 424 if (!Objects.equals(handle, mHandle) || presentation != mHandlePresentation) { 425 mHandle = handle; 426 mHandlePresentation = presentation; 427 mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(mContext, 428 mHandle.getSchemeSpecificPart()); 429 startCallerInfoLookup(); 430 for (Listener l : mListeners) { 431 l.onHandleChanged(this); 432 } 433 } 434 } 435 436 String getCallerDisplayName() { 437 return mCallerDisplayName; 438 } 439 440 int getCallerDisplayNamePresentation() { 441 return mCallerDisplayNamePresentation; 442 } 443 444 void setCallerDisplayName(String callerDisplayName, int presentation) { 445 if (!TextUtils.equals(callerDisplayName, mCallerDisplayName) || 446 presentation != mCallerDisplayNamePresentation) { 447 mCallerDisplayName = callerDisplayName; 448 mCallerDisplayNamePresentation = presentation; 449 for (Listener l : mListeners) { 450 l.onCallerDisplayNameChanged(this); 451 } 452 } 453 } 454 455 String getName() { 456 return mCallerInfo == null ? null : mCallerInfo.name; 457 } 458 459 Bitmap getPhotoIcon() { 460 return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon; 461 } 462 463 Drawable getPhoto() { 464 return mCallerInfo == null ? null : mCallerInfo.cachedPhoto; 465 } 466 467 /** 468 * @param disconnectCause The reason for the disconnection, represented by 469 * {@link android.telecom.DisconnectCause}. 470 */ 471 void setDisconnectCause(DisconnectCause disconnectCause) { 472 // TODO: Consider combining this method with a setDisconnected() method that is totally 473 // separate from setState. 474 mDisconnectCause = disconnectCause; 475 } 476 477 DisconnectCause getDisconnectCause() { 478 return mDisconnectCause; 479 } 480 481 boolean isEmergencyCall() { 482 return mIsEmergencyCall; 483 } 484 485 /** 486 * @return The original handle this call is associated with. In-call services should use this 487 * handle when indicating in their UI the handle that is being called. 488 */ 489 public Uri getOriginalHandle() { 490 if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) { 491 return mGatewayInfo.getOriginalAddress(); 492 } 493 return getHandle(); 494 } 495 496 GatewayInfo getGatewayInfo() { 497 return mGatewayInfo; 498 } 499 500 void setGatewayInfo(GatewayInfo gatewayInfo) { 501 mGatewayInfo = gatewayInfo; 502 } 503 504 PhoneAccountHandle getConnectionManagerPhoneAccount() { 505 return mConnectionManagerPhoneAccountHandle; 506 } 507 508 void setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle) { 509 if (!Objects.equals(mConnectionManagerPhoneAccountHandle, accountHandle)) { 510 mConnectionManagerPhoneAccountHandle = accountHandle; 511 for (Listener l : mListeners) { 512 l.onConnectionManagerPhoneAccountChanged(this); 513 } 514 } 515 516 } 517 518 PhoneAccountHandle getTargetPhoneAccount() { 519 return mTargetPhoneAccountHandle; 520 } 521 522 void setTargetPhoneAccount(PhoneAccountHandle accountHandle) { 523 if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) { 524 mTargetPhoneAccountHandle = accountHandle; 525 for (Listener l : mListeners) { 526 l.onTargetPhoneAccountChanged(this); 527 } 528 } 529 } 530 531 boolean isIncoming() { 532 return mIsIncoming; 533 } 534 535 /** 536 * @return The "age" of this call object in milliseconds, which typically also represents the 537 * period since this call was added to the set pending outgoing calls, see 538 * mCreationTimeMillis. 539 */ 540 long getAgeMillis() { 541 return System.currentTimeMillis() - mCreationTimeMillis; 542 } 543 544 /** 545 * @return The time when this call object was created and added to the set of pending outgoing 546 * calls. 547 */ 548 long getCreationTimeMillis() { 549 return mCreationTimeMillis; 550 } 551 552 void setCreationTimeMillis(long time) { 553 mCreationTimeMillis = time; 554 } 555 556 long getConnectTimeMillis() { 557 return mConnectTimeMillis; 558 } 559 560 void setConnectTimeMillis(long connectTimeMillis) { 561 mConnectTimeMillis = connectTimeMillis; 562 } 563 564 int getCallCapabilities() { 565 return mCallCapabilities; 566 } 567 568 void setCallCapabilities(int callCapabilities) { 569 setCallCapabilities(callCapabilities, false /* forceUpdate */); 570 } 571 572 void setCallCapabilities(int callCapabilities, boolean forceUpdate) { 573 Log.v(this, "setCallCapabilities: %s", PhoneCapabilities.toString(callCapabilities)); 574 if (forceUpdate || mCallCapabilities != callCapabilities) { 575 mCallCapabilities = callCapabilities; 576 for (Listener l : mListeners) { 577 l.onCallCapabilitiesChanged(this); 578 } 579 } 580 } 581 582 Call getParentCall() { 583 return mParentCall; 584 } 585 586 List<Call> getChildCalls() { 587 return mChildCalls; 588 } 589 590 boolean wasConferencePreviouslyMerged() { 591 return mWasConferencePreviouslyMerged; 592 } 593 594 Call getConferenceLevelActiveCall() { 595 return mConferenceLevelActiveCall; 596 } 597 598 ConnectionServiceWrapper getConnectionService() { 599 return mConnectionService; 600 } 601 602 /** 603 * Retrieves the {@link Context} for the call. 604 * 605 * @return The {@link Context}. 606 */ 607 Context getContext() { 608 return mContext; 609 } 610 611 void setConnectionService(ConnectionServiceWrapper service) { 612 Preconditions.checkNotNull(service); 613 614 clearConnectionService(); 615 616 service.incrementAssociatedCallCount(); 617 mConnectionService = service; 618 mConnectionService.addCall(this); 619 } 620 621 /** 622 * Clears the associated connection service. 623 */ 624 void clearConnectionService() { 625 if (mConnectionService != null) { 626 ConnectionServiceWrapper serviceTemp = mConnectionService; 627 mConnectionService = null; 628 serviceTemp.removeCall(this); 629 630 // Decrementing the count can cause the service to unbind, which itself can trigger the 631 // service-death code. Since the service death code tries to clean up any associated 632 // calls, we need to make sure to remove that information (e.g., removeCall()) before 633 // we decrement. Technically, invoking removeCall() prior to decrementing is all that is 634 // necessary, but cleaning up mConnectionService prior to triggering an unbind is good 635 // to do. 636 decrementAssociatedCallCount(serviceTemp); 637 } 638 } 639 640 private void processDirectToVoicemail() { 641 if (mDirectToVoicemailQueryPending) { 642 if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) { 643 Log.i(this, "Directing call to voicemail: %s.", this); 644 // TODO: Once we move State handling from CallsManager to Call, we 645 // will not need to set STATE_RINGING state prior to calling reject. 646 setState(CallState.RINGING); 647 reject(false, null); 648 } else { 649 // TODO: Make this class (not CallsManager) responsible for changing 650 // the call state to STATE_RINGING. 651 652 // TODO: Replace this with state transition to STATE_RINGING. 653 for (Listener l : mListeners) { 654 l.onSuccessfulIncomingCall(this); 655 } 656 } 657 658 mDirectToVoicemailQueryPending = false; 659 } 660 } 661 662 /** 663 * Starts the create connection sequence. Upon completion, there should exist an active 664 * connection through a connection service (or the call will have failed). 665 * 666 * @param phoneAccountRegistrar The phone account registrar. 667 */ 668 void startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar) { 669 Preconditions.checkState(mCreateConnectionProcessor == null); 670 mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this, 671 phoneAccountRegistrar, mContext); 672 mCreateConnectionProcessor.process(); 673 } 674 675 @Override 676 public void handleCreateConnectionSuccess( 677 CallIdMapper idMapper, 678 ParcelableConnection connection) { 679 Log.v(this, "handleCreateConnectionSuccessful %s", connection); 680 mCreateConnectionProcessor = null; 681 setTargetPhoneAccount(connection.getPhoneAccount()); 682 setHandle(connection.getHandle(), connection.getHandlePresentation()); 683 setCallerDisplayName( 684 connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation()); 685 setCallCapabilities(connection.getCapabilities()); 686 setVideoProvider(connection.getVideoProvider()); 687 setVideoState(connection.getVideoState()); 688 setRingbackRequested(connection.isRingbackRequested()); 689 setIsVoipAudioMode(connection.getIsVoipAudioMode()); 690 setStatusHints(connection.getStatusHints()); 691 692 mConferenceableCalls.clear(); 693 for (String id : connection.getConferenceableConnectionIds()) { 694 mConferenceableCalls.add(idMapper.getCall(id)); 695 } 696 697 if (mIsUnknown) { 698 for (Listener l : mListeners) { 699 l.onSuccessfulUnknownCall(this, getStateFromConnectionState(connection.getState())); 700 } 701 } else if (mIsIncoming) { 702 // We do not handle incoming calls immediately when they are verified by the connection 703 // service. We allow the caller-info-query code to execute first so that we can read the 704 // direct-to-voicemail property before deciding if we want to show the incoming call to 705 // the user or if we want to reject the call. 706 mDirectToVoicemailQueryPending = true; 707 708 // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before 709 // showing the user the incoming call screen. 710 mHandler.postDelayed(mDirectToVoicemailRunnable, Timeouts.getDirectToVoicemailMillis( 711 mContext.getContentResolver())); 712 } else { 713 for (Listener l : mListeners) { 714 l.onSuccessfulOutgoingCall(this, 715 getStateFromConnectionState(connection.getState())); 716 } 717 } 718 } 719 720 @Override 721 public void handleCreateConnectionFailure(DisconnectCause disconnectCause) { 722 mCreateConnectionProcessor = null; 723 clearConnectionService(); 724 setDisconnectCause(disconnectCause); 725 CallsManager.getInstance().markCallAsDisconnected(this, disconnectCause); 726 727 if (mIsUnknown) { 728 for (Listener listener : mListeners) { 729 listener.onFailedUnknownCall(this); 730 } 731 } else if (mIsIncoming) { 732 for (Listener listener : mListeners) { 733 listener.onFailedIncomingCall(this); 734 } 735 } else { 736 for (Listener listener : mListeners) { 737 listener.onFailedOutgoingCall(this, disconnectCause); 738 } 739 } 740 } 741 742 /** 743 * Plays the specified DTMF tone. 744 */ 745 void playDtmfTone(char digit) { 746 if (mConnectionService == null) { 747 Log.w(this, "playDtmfTone() request on a call without a connection service."); 748 } else { 749 Log.i(this, "Send playDtmfTone to connection service for call %s", this); 750 mConnectionService.playDtmfTone(this, digit); 751 } 752 } 753 754 /** 755 * Stops playing any currently playing DTMF tone. 756 */ 757 void stopDtmfTone() { 758 if (mConnectionService == null) { 759 Log.w(this, "stopDtmfTone() request on a call without a connection service."); 760 } else { 761 Log.i(this, "Send stopDtmfTone to connection service for call %s", this); 762 mConnectionService.stopDtmfTone(this); 763 } 764 } 765 766 void disconnect() { 767 disconnect(false); 768 } 769 770 /** 771 * Attempts to disconnect the call through the connection service. 772 */ 773 void disconnect(boolean wasViaNewOutgoingCallBroadcaster) { 774 // Track that the call is now locally disconnecting. 775 setLocallyDisconnecting(true); 776 777 if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT || 778 mState == CallState.CONNECTING) { 779 Log.v(this, "Aborting call %s", this); 780 abort(wasViaNewOutgoingCallBroadcaster); 781 } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) { 782 if (mConnectionService == null) { 783 Log.e(this, new Exception(), "disconnect() request on a call without a" 784 + " connection service."); 785 } else { 786 Log.i(this, "Send disconnect to connection service for call: %s", this); 787 // The call isn't officially disconnected until the connection service 788 // confirms that the call was actually disconnected. Only then is the 789 // association between call and connection service severed, see 790 // {@link CallsManager#markCallAsDisconnected}. 791 mConnectionService.disconnect(this); 792 } 793 } 794 } 795 796 void abort(boolean wasViaNewOutgoingCallBroadcaster) { 797 if (mCreateConnectionProcessor != null) { 798 mCreateConnectionProcessor.abort(); 799 } else if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT 800 || mState == CallState.CONNECTING) { 801 if (wasViaNewOutgoingCallBroadcaster) { 802 // If the cancelation was from NEW_OUTGOING_CALL, then we do not automatically 803 // destroy the call. Instead, we announce the cancelation and CallsManager handles 804 // it through a timer. Since apps often cancel calls through NEW_OUTGOING_CALL and 805 // then re-dial them quickly using a gateway, allowing the first call to end 806 // causes jank. This timeout allows CallsManager to transition the first call into 807 // the second call so that in-call only ever sees a single call...eliminating the 808 // jank altogether. 809 for (Listener listener : mListeners) { 810 if (listener.onCanceledViaNewOutgoingCallBroadcast(this)) { 811 // The first listener to handle this wins. A return value of true means that 812 // the listener will handle the disconnection process later and so we 813 // should not continue it here. 814 setLocallyDisconnecting(false); 815 return; 816 } 817 } 818 } 819 820 handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED)); 821 } else { 822 Log.v(this, "Cannot abort a call which isn't either PRE_DIAL_WAIT or CONNECTING"); 823 } 824 } 825 826 /** 827 * Answers the call if it is ringing. 828 * 829 * @param videoState The video state in which to answer the call. 830 */ 831 void answer(int videoState) { 832 Preconditions.checkNotNull(mConnectionService); 833 834 // Check to verify that the call is still in the ringing state. A call can change states 835 // between the time the user hits 'answer' and Telecom receives the command. 836 if (isRinging("answer")) { 837 // At this point, we are asking the connection service to answer but we don't assume 838 // that it will work. Instead, we wait until confirmation from the connectino service 839 // that the call is in a non-STATE_RINGING state before changing the UI. See 840 // {@link ConnectionServiceAdapter#setActive} and other set* methods. 841 mConnectionService.answer(this, videoState); 842 } 843 } 844 845 /** 846 * Rejects the call if it is ringing. 847 * 848 * @param rejectWithMessage Whether to send a text message as part of the call rejection. 849 * @param textMessage An optional text message to send as part of the rejection. 850 */ 851 void reject(boolean rejectWithMessage, String textMessage) { 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 'reject' and Telecomm receives the command. 856 if (isRinging("reject")) { 857 mConnectionService.reject(this); 858 } 859 } 860 861 /** 862 * Puts the call on hold if it is currently active. 863 */ 864 void hold() { 865 Preconditions.checkNotNull(mConnectionService); 866 867 if (mState == CallState.ACTIVE) { 868 mConnectionService.hold(this); 869 } 870 } 871 872 /** 873 * Releases the call from hold if it is currently active. 874 */ 875 void unhold() { 876 Preconditions.checkNotNull(mConnectionService); 877 878 if (mState == CallState.ON_HOLD) { 879 mConnectionService.unhold(this); 880 } 881 } 882 883 /** Checks if this is a live call or not. */ 884 boolean isAlive() { 885 switch (mState) { 886 case CallState.NEW: 887 case CallState.RINGING: 888 case CallState.DISCONNECTED: 889 case CallState.ABORTED: 890 return false; 891 default: 892 return true; 893 } 894 } 895 896 boolean isActive() { 897 return mState == CallState.ACTIVE; 898 } 899 900 Bundle getExtras() { 901 return mExtras; 902 } 903 904 void setExtras(Bundle extras) { 905 mExtras = extras; 906 } 907 908 /** 909 * @return the uri of the contact associated with this call. 910 */ 911 Uri getContactUri() { 912 if (mCallerInfo == null || !mCallerInfo.contactExists) { 913 return getHandle(); 914 } 915 return Contacts.getLookupUri(mCallerInfo.contactIdOrZero, mCallerInfo.lookupKey); 916 } 917 918 Uri getRingtone() { 919 return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri; 920 } 921 922 void onPostDialWait(String remaining) { 923 for (Listener l : mListeners) { 924 l.onPostDialWait(this, remaining); 925 } 926 } 927 928 void postDialContinue(boolean proceed) { 929 mConnectionService.onPostDialContinue(this, proceed); 930 } 931 932 void conferenceWith(Call otherCall) { 933 if (mConnectionService == null) { 934 Log.w(this, "conference requested on a call without a connection service."); 935 } else { 936 mConnectionService.conference(this, otherCall); 937 } 938 } 939 940 void splitFromConference() { 941 if (mConnectionService == null) { 942 Log.w(this, "splitting from conference call without a connection service"); 943 } else { 944 mConnectionService.splitFromConference(this); 945 } 946 } 947 948 void mergeConference() { 949 if (mConnectionService == null) { 950 Log.w(this, "merging conference calls without a connection service."); 951 } else if (can(PhoneCapabilities.MERGE_CONFERENCE)) { 952 mConnectionService.mergeConference(this); 953 mWasConferencePreviouslyMerged = true; 954 } 955 } 956 957 void swapConference() { 958 if (mConnectionService == null) { 959 Log.w(this, "swapping conference calls without a connection service."); 960 } else if (can(PhoneCapabilities.SWAP_CONFERENCE)) { 961 mConnectionService.swapConference(this); 962 switch (mChildCalls.size()) { 963 case 1: 964 mConferenceLevelActiveCall = mChildCalls.get(0); 965 break; 966 case 2: 967 // swap 968 mConferenceLevelActiveCall = mChildCalls.get(0) == mConferenceLevelActiveCall ? 969 mChildCalls.get(1) : mChildCalls.get(0); 970 break; 971 default: 972 // For anything else 0, or 3+, set it to null since it is impossible to tell. 973 mConferenceLevelActiveCall = null; 974 break; 975 } 976 } 977 } 978 979 void setParentCall(Call parentCall) { 980 if (parentCall == this) { 981 Log.e(this, new Exception(), "setting the parent to self"); 982 return; 983 } 984 if (parentCall == mParentCall) { 985 // nothing to do 986 return; 987 } 988 Preconditions.checkState(parentCall == null || mParentCall == null); 989 990 Call oldParent = mParentCall; 991 if (mParentCall != null) { 992 mParentCall.removeChildCall(this); 993 } 994 mParentCall = parentCall; 995 if (mParentCall != null) { 996 mParentCall.addChildCall(this); 997 } 998 999 for (Listener l : mListeners) { 1000 l.onParentChanged(this); 1001 } 1002 } 1003 1004 void setConferenceableCalls(List<Call> conferenceableCalls) { 1005 mConferenceableCalls.clear(); 1006 mConferenceableCalls.addAll(conferenceableCalls); 1007 1008 for (Listener l : mListeners) { 1009 l.onConferenceableCallsChanged(this); 1010 } 1011 } 1012 1013 List<Call> getConferenceableCalls() { 1014 return mConferenceableCalls; 1015 } 1016 1017 boolean can(int capability) { 1018 return (mCallCapabilities & capability) == capability; 1019 } 1020 1021 private void addChildCall(Call call) { 1022 if (!mChildCalls.contains(call)) { 1023 // Set the pseudo-active call to the latest child added to the conference. 1024 // See definition of mConferenceLevelActiveCall for more detail. 1025 mConferenceLevelActiveCall = call; 1026 mChildCalls.add(call); 1027 1028 for (Listener l : mListeners) { 1029 l.onChildrenChanged(this); 1030 } 1031 } 1032 } 1033 1034 private void removeChildCall(Call call) { 1035 if (mChildCalls.remove(call)) { 1036 for (Listener l : mListeners) { 1037 l.onChildrenChanged(this); 1038 } 1039 } 1040 } 1041 1042 /** 1043 * Return whether the user can respond to this {@code Call} via an SMS message. 1044 * 1045 * @return true if the "Respond via SMS" feature should be enabled 1046 * for this incoming call. 1047 * 1048 * The general rule is that we *do* allow "Respond via SMS" except for 1049 * the few (relatively rare) cases where we know for sure it won't 1050 * work, namely: 1051 * - a bogus or blank incoming number 1052 * - a call from a SIP address 1053 * - a "call presentation" that doesn't allow the number to be revealed 1054 * 1055 * In all other cases, we allow the user to respond via SMS. 1056 * 1057 * Note that this behavior isn't perfect; for example we have no way 1058 * to detect whether the incoming call is from a landline (with most 1059 * networks at least), so we still enable this feature even though 1060 * SMSes to that number will silently fail. 1061 */ 1062 boolean isRespondViaSmsCapable() { 1063 if (mState != CallState.RINGING) { 1064 return false; 1065 } 1066 1067 if (getHandle() == null) { 1068 // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in 1069 // other words, the user should not be able to see the incoming phone number. 1070 return false; 1071 } 1072 1073 if (PhoneNumberUtils.isUriNumber(getHandle().toString())) { 1074 // The incoming number is actually a URI (i.e. a SIP address), 1075 // not a regular PSTN phone number, and we can't send SMSes to 1076 // SIP addresses. 1077 // (TODO: That might still be possible eventually, though. Is 1078 // there some SIP-specific equivalent to sending a text message?) 1079 return false; 1080 } 1081 1082 // Is there a valid SMS application on the phone? 1083 if (SmsApplication.getDefaultRespondViaMessageApplication(mContext, 1084 true /*updateIfNeeded*/) == null) { 1085 return false; 1086 } 1087 1088 // TODO: with some carriers (in certain countries) you *can* actually 1089 // tell whether a given number is a mobile phone or not. So in that 1090 // case we could potentially return false here if the incoming call is 1091 // from a land line. 1092 1093 // If none of the above special cases apply, it's OK to enable the 1094 // "Respond via SMS" feature. 1095 return true; 1096 } 1097 1098 List<String> getCannedSmsResponses() { 1099 return mCannedSmsResponses; 1100 } 1101 1102 /** 1103 * We need to make sure that before we move a call to the disconnected state, it no 1104 * longer has any parent/child relationships. We want to do this to ensure that the InCall 1105 * Service always has the right data in the right order. We also want to do it in telecom so 1106 * that the insurance policy lives in the framework side of things. 1107 */ 1108 private void fixParentAfterDisconnect() { 1109 setParentCall(null); 1110 } 1111 1112 /** 1113 * @return True if the call is ringing, else logs the action name. 1114 */ 1115 private boolean isRinging(String actionName) { 1116 if (mState == CallState.RINGING) { 1117 return true; 1118 } 1119 1120 Log.i(this, "Request to %s a non-ringing call %s", actionName, this); 1121 return false; 1122 } 1123 1124 @SuppressWarnings("rawtypes") 1125 private void decrementAssociatedCallCount(ServiceBinder binder) { 1126 if (binder != null) { 1127 binder.decrementAssociatedCallCount(); 1128 } 1129 } 1130 1131 /** 1132 * Looks up contact information based on the current handle. 1133 */ 1134 private void startCallerInfoLookup() { 1135 String number = mHandle == null ? null : mHandle.getSchemeSpecificPart(); 1136 1137 mQueryToken++; // Updated so that previous queries can no longer set the information. 1138 mCallerInfo = null; 1139 if (!TextUtils.isEmpty(number)) { 1140 Log.v(this, "Looking up information for: %s.", Log.piiHandle(number)); 1141 CallerInfoAsyncQuery.startQuery( 1142 mQueryToken, 1143 mContext, 1144 number, 1145 sCallerInfoQueryListener, 1146 this); 1147 } 1148 } 1149 1150 /** 1151 * Saves the specified caller info if the specified token matches that of the last query 1152 * that was made. 1153 * 1154 * @param callerInfo The new caller information to set. 1155 * @param token The token used with this query. 1156 */ 1157 private void setCallerInfo(CallerInfo callerInfo, int token) { 1158 Preconditions.checkNotNull(callerInfo); 1159 1160 if (mQueryToken == token) { 1161 mCallerInfo = callerInfo; 1162 Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo); 1163 1164 if (mCallerInfo.contactDisplayPhotoUri != null) { 1165 Log.d(this, "Searching person uri %s for call %s", 1166 mCallerInfo.contactDisplayPhotoUri, this); 1167 ContactsAsyncHelper.startObtainPhotoAsync( 1168 token, 1169 mContext, 1170 mCallerInfo.contactDisplayPhotoUri, 1171 sPhotoLoadListener, 1172 this); 1173 // Do not call onCallerInfoChanged yet in this case. We call it in setPhoto(). 1174 } else { 1175 for (Listener l : mListeners) { 1176 l.onCallerInfoChanged(this); 1177 } 1178 } 1179 1180 processDirectToVoicemail(); 1181 } 1182 } 1183 1184 CallerInfo getCallerInfo() { 1185 return mCallerInfo; 1186 } 1187 1188 /** 1189 * Saves the specified photo information if the specified token matches that of the last query. 1190 * 1191 * @param photo The photo as a drawable. 1192 * @param photoIcon The photo as a small icon. 1193 * @param token The token used with this query. 1194 */ 1195 private void setPhoto(Drawable photo, Bitmap photoIcon, int token) { 1196 if (mQueryToken == token) { 1197 mCallerInfo.cachedPhoto = photo; 1198 mCallerInfo.cachedPhotoIcon = photoIcon; 1199 1200 for (Listener l : mListeners) { 1201 l.onCallerInfoChanged(this); 1202 } 1203 } 1204 } 1205 1206 private void maybeLoadCannedSmsResponses() { 1207 if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) { 1208 Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages"); 1209 mCannedSmsResponsesLoadingStarted = true; 1210 RespondViaSmsManager.getInstance().loadCannedTextMessages( 1211 new Response<Void, List<String>>() { 1212 @Override 1213 public void onResult(Void request, List<String>... result) { 1214 if (result.length > 0) { 1215 Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]); 1216 mCannedSmsResponses = result[0]; 1217 for (Listener l : mListeners) { 1218 l.onCannedSmsResponsesLoaded(Call.this); 1219 } 1220 } 1221 } 1222 1223 @Override 1224 public void onError(Void request, int code, String msg) { 1225 Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code, 1226 msg); 1227 } 1228 }, 1229 mContext 1230 ); 1231 } else { 1232 Log.d(this, "maybeLoadCannedSmsResponses: doing nothing"); 1233 } 1234 } 1235 1236 /** 1237 * Sets speakerphone option on when call begins. 1238 */ 1239 public void setStartWithSpeakerphoneOn(boolean startWithSpeakerphone) { 1240 mSpeakerphoneOn = startWithSpeakerphone; 1241 } 1242 1243 /** 1244 * Returns speakerphone option. 1245 * 1246 * @return Whether or not speakerphone should be set automatically when call begins. 1247 */ 1248 public boolean getStartWithSpeakerphoneOn() { 1249 return mSpeakerphoneOn; 1250 } 1251 1252 /** 1253 * Sets a video call provider for the call. 1254 */ 1255 public void setVideoProvider(IVideoProvider videoProvider) { 1256 mVideoProvider = videoProvider; 1257 for (Listener l : mListeners) { 1258 l.onVideoCallProviderChanged(Call.this); 1259 } 1260 } 1261 1262 /** 1263 * @return Return the {@link Connection.VideoProvider} binder. 1264 */ 1265 public IVideoProvider getVideoProvider() { 1266 return mVideoProvider; 1267 } 1268 1269 /** 1270 * The current video state for the call. 1271 * Valid values: see {@link VideoProfile.VideoState}. 1272 */ 1273 public int getVideoState() { 1274 return mVideoState; 1275 } 1276 1277 /** 1278 * Returns the video states which were applicable over the duration of a call. 1279 * See {@link VideoProfile} for a list of valid video states. 1280 * 1281 * @return The video states applicable over the duration of the call. 1282 */ 1283 public int getVideoStateHistory() { 1284 return mVideoStateHistory; 1285 } 1286 1287 /** 1288 * Determines the current video state for the call. 1289 * For an outgoing call determines the desired video state for the call. 1290 * Valid values: see {@link VideoProfile.VideoState} 1291 * 1292 * @param videoState The video state for the call. 1293 */ 1294 public void setVideoState(int videoState) { 1295 // Track which video states were applicable over the duration of the call. 1296 mVideoStateHistory = mVideoStateHistory | videoState; 1297 1298 mVideoState = videoState; 1299 for (Listener l : mListeners) { 1300 l.onVideoStateChanged(this); 1301 } 1302 } 1303 1304 public boolean getIsVoipAudioMode() { 1305 return mIsVoipAudioMode; 1306 } 1307 1308 public void setIsVoipAudioMode(boolean audioModeIsVoip) { 1309 mIsVoipAudioMode = audioModeIsVoip; 1310 for (Listener l : mListeners) { 1311 l.onIsVoipAudioModeChanged(this); 1312 } 1313 } 1314 1315 public StatusHints getStatusHints() { 1316 return mStatusHints; 1317 } 1318 1319 public void setStatusHints(StatusHints statusHints) { 1320 mStatusHints = statusHints; 1321 for (Listener l : mListeners) { 1322 l.onStatusHintsChanged(this); 1323 } 1324 } 1325 1326 public boolean isUnknown() { 1327 return mIsUnknown; 1328 } 1329 1330 public void setIsUnknown(boolean isUnknown) { 1331 mIsUnknown = isUnknown; 1332 } 1333 1334 /** 1335 * Determines if this call is in a disconnecting state. 1336 * 1337 * @return {@code true} if this call is locally disconnecting. 1338 */ 1339 public boolean isLocallyDisconnecting() { 1340 return mIsLocallyDisconnecting; 1341 } 1342 1343 /** 1344 * Sets whether this call is in a disconnecting state. 1345 * 1346 * @param isLocallyDisconnecting {@code true} if this call is locally disconnecting. 1347 */ 1348 private void setLocallyDisconnecting(boolean isLocallyDisconnecting) { 1349 mIsLocallyDisconnecting = isLocallyDisconnecting; 1350 } 1351 1352 static int getStateFromConnectionState(int state) { 1353 switch (state) { 1354 case Connection.STATE_INITIALIZING: 1355 return CallState.CONNECTING; 1356 case Connection.STATE_ACTIVE: 1357 return CallState.ACTIVE; 1358 case Connection.STATE_DIALING: 1359 return CallState.DIALING; 1360 case Connection.STATE_DISCONNECTED: 1361 return CallState.DISCONNECTED; 1362 case Connection.STATE_HOLDING: 1363 return CallState.ON_HOLD; 1364 case Connection.STATE_NEW: 1365 return CallState.NEW; 1366 case Connection.STATE_RINGING: 1367 return CallState.RINGING; 1368 } 1369 return CallState.DISCONNECTED; 1370 } 1371} 1372