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