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