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