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