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