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