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