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