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