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