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