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