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