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