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