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