Call.java revision 92694519535c54f542b4ef3973e9c1934f2feeff
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     */
253    private int mVideoStateHistory;
254
255    private int mVideoState;
256
257    /**
258     * Disconnect cause for the call. Only valid if the state of the call is STATE_DISCONNECTED.
259     * See {@link android.telecom.DisconnectCause}.
260     */
261    private DisconnectCause mDisconnectCause = new DisconnectCause(DisconnectCause.UNKNOWN);
262
263    /** Info used by the connection services. */
264    private Bundle mExtras = new Bundle();
265
266    /** Set of listeners on this call.
267     *
268     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
269     * load factor before resizing, 1 means we only expect a single thread to
270     * access the map so make only a single shard
271     */
272    private final Set<Listener> mListeners = Collections.newSetFromMap(
273            new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
274
275    private CreateConnectionProcessor mCreateConnectionProcessor;
276
277    /** Caller information retrieved from the latest contact query. */
278    private CallerInfo mCallerInfo;
279
280    /** The latest token used with a contact info query. */
281    private int mQueryToken = 0;
282
283    /** Whether this call is requesting that Telecom play the ringback tone on its behalf. */
284    private boolean mRingbackRequested = false;
285
286    /** Whether direct-to-voicemail query is pending. */
287    private boolean mDirectToVoicemailQueryPending;
288
289    private int mConnectionCapabilities;
290
291    private boolean mIsConference = false;
292
293    private Call mParentCall = null;
294
295    private List<Call> mChildCalls = new LinkedList<>();
296
297    /** Set of text message responses allowed for this call, if applicable. */
298    private List<String> mCannedSmsResponses = Collections.EMPTY_LIST;
299
300    /** Whether an attempt has been made to load the text message responses. */
301    private boolean mCannedSmsResponsesLoadingStarted = false;
302
303    private IVideoProvider mVideoProvider;
304
305    private boolean mIsVoipAudioMode;
306    private StatusHints mStatusHints;
307    private final ConnectionServiceRepository mRepository;
308    private final ContactsAsyncHelper mContactsAsyncHelper;
309    private final Context mContext;
310    private final CallsManager mCallsManager;
311    private final TelecomSystem.SyncRoot mLock;
312    private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
313
314    private boolean mWasConferencePreviouslyMerged = false;
315
316    // For conferences which support merge/swap at their level, we retain a notion of an active
317    // call. This is used for BluetoothPhoneService.  In order to support hold/merge, it must have
318    // the notion of the current "active" call within the conference call. This maintains the
319    // "active" call and switches every time the user hits "swap".
320    private Call mConferenceLevelActiveCall = null;
321
322    private boolean mIsLocallyDisconnecting = false;
323
324    /**
325     * Persists the specified parameters and initializes the new instance.
326     *
327     * @param context The context.
328     * @param repository The connection service repository.
329     * @param handle The handle to dial.
330     * @param gatewayInfo Gateway information to use for the call.
331     * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
332     *         This account must be one that was registered with the
333     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
334     * @param targetPhoneAccountHandle Account information to use for the call. This account must be
335     *         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
336     * @param isIncoming True if this is an incoming call.
337     */
338    public Call(
339            Context context,
340            CallsManager callsManager,
341            TelecomSystem.SyncRoot lock,
342            ConnectionServiceRepository repository,
343            ContactsAsyncHelper contactsAsyncHelper,
344            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
345            Uri handle,
346            GatewayInfo gatewayInfo,
347            PhoneAccountHandle connectionManagerPhoneAccountHandle,
348            PhoneAccountHandle targetPhoneAccountHandle,
349            boolean isIncoming,
350            boolean isConference) {
351        mState = isConference ? CallState.ACTIVE : CallState.NEW;
352        mContext = context;
353        mCallsManager = callsManager;
354        mLock = lock;
355        mRepository = repository;
356        mContactsAsyncHelper = contactsAsyncHelper;
357        mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
358        setHandle(handle);
359        setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
360        mGatewayInfo = gatewayInfo;
361        setConnectionManagerPhoneAccount(connectionManagerPhoneAccountHandle);
362        setTargetPhoneAccount(targetPhoneAccountHandle);
363        mIsIncoming = isIncoming;
364        mIsConference = isConference;
365        maybeLoadCannedSmsResponses();
366
367        Log.event(this, Log.Events.CREATED);
368    }
369
370    /**
371     * Persists the specified parameters and initializes the new instance.
372     *
373     * @param context The context.
374     * @param repository The connection service repository.
375     * @param handle The handle to dial.
376     * @param gatewayInfo Gateway information to use for the call.
377     * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
378     *         This account must be one that was registered with the
379     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
380     * @param targetPhoneAccountHandle Account information to use for the call. This account must be
381     *         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
382     * @param isIncoming True if this is an incoming call.
383     * @param connectTimeMillis The connection time of the call.
384     */
385    Call(
386            Context context,
387            CallsManager callsManager,
388            TelecomSystem.SyncRoot lock,
389            ConnectionServiceRepository repository,
390            ContactsAsyncHelper contactsAsyncHelper,
391            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
392            Uri handle,
393            GatewayInfo gatewayInfo,
394            PhoneAccountHandle connectionManagerPhoneAccountHandle,
395            PhoneAccountHandle targetPhoneAccountHandle,
396            boolean isIncoming,
397            boolean isConference,
398            long connectTimeMillis) {
399        this(context, callsManager, lock, repository, contactsAsyncHelper,
400                callerInfoAsyncQueryFactory, handle, gatewayInfo,
401                connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, isIncoming,
402                isConference);
403
404        mConnectTimeMillis = connectTimeMillis;
405    }
406
407    public void addListener(Listener listener) {
408        mListeners.add(listener);
409    }
410
411    public void removeListener(Listener listener) {
412        if (listener != null) {
413            mListeners.remove(listener);
414        }
415    }
416
417    public void destroy() {
418        Log.event(this, Log.Events.DESTROYED);
419    }
420
421    /** {@inheritDoc} */
422    @Override
423    public String toString() {
424        String component = null;
425        if (mConnectionService != null && mConnectionService.getComponentName() != null) {
426            component = mConnectionService.getComponentName().flattenToShortString();
427        }
428
429
430
431        return String.format(Locale.US, "[%s, %s, %s, %s, %s, childs(%d), has_parent(%b), [%s]]",
432                System.identityHashCode(this),
433                CallState.toString(mState),
434                component,
435                Log.piiHandle(mHandle),
436                getVideoStateDescription(getVideoState()),
437                getChildCalls().size(),
438                getParentCall() != null,
439                Connection.capabilitiesToString(getConnectionCapabilities()));
440    }
441
442    /**
443     * Builds a debug-friendly description string for a video state.
444     * <p>
445     * A = audio active, T = video transmission active, R = video reception active, P = video
446     * paused.
447     *
448     * @param videoState The video state.
449     * @return A string indicating which bits are set in the video state.
450     */
451    private String getVideoStateDescription(int videoState) {
452        StringBuilder sb = new StringBuilder();
453        sb.append("A");
454
455        if (VideoProfile.VideoState.isTransmissionEnabled(videoState)) {
456            sb.append("T");
457        }
458
459        if (VideoProfile.VideoState.isReceptionEnabled(videoState)) {
460            sb.append("R");
461        }
462
463        if (VideoProfile.VideoState.isPaused(videoState)) {
464            sb.append("P");
465        }
466
467        return sb.toString();
468    }
469
470    int getState() {
471        return mState;
472    }
473
474    private boolean shouldContinueProcessingAfterDisconnect() {
475        // Stop processing once the call is active.
476        if (!CreateConnectionTimeout.isCallBeingPlaced(this)) {
477            return false;
478        }
479
480        // Make sure that there are additional connection services to process.
481        if (mCreateConnectionProcessor == null
482            || !mCreateConnectionProcessor.isProcessingComplete()
483            || !mCreateConnectionProcessor.hasMorePhoneAccounts()) {
484            return false;
485        }
486
487        if (mDisconnectCause == null) {
488            return false;
489        }
490
491        // Continue processing if the current attempt failed or timed out.
492        return mDisconnectCause.getCode() == DisconnectCause.ERROR ||
493            mCreateConnectionProcessor.isCallTimedOut();
494    }
495
496    /**
497     * Sets the call state. Although there exists the notion of appropriate state transitions
498     * (see {@link CallState}), in practice those expectations break down when cellular systems
499     * misbehave and they do this very often. The result is that we do not enforce state transitions
500     * and instead keep the code resilient to unexpected state changes.
501     */
502    public void setState(int newState) {
503        if (mState != newState) {
504            Log.v(this, "setState %s -> %s", mState, newState);
505
506            if (newState == CallState.DISCONNECTED && shouldContinueProcessingAfterDisconnect()) {
507                Log.w(this, "continuing processing disconnected call with another service");
508                mCreateConnectionProcessor.continueProcessingIfPossible(this, mDisconnectCause);
509                return;
510            }
511
512            mState = newState;
513            maybeLoadCannedSmsResponses();
514
515            if (mState == CallState.ACTIVE || mState == CallState.ON_HOLD) {
516                if (mConnectTimeMillis == 0) {
517                    // We check to see if mConnectTime is already set to prevent the
518                    // call from resetting active time when it goes in and out of
519                    // ACTIVE/ON_HOLD
520                    mConnectTimeMillis = System.currentTimeMillis();
521                }
522
523                // We're clearly not disconnected, so reset the disconnected time.
524                mDisconnectTimeMillis = 0;
525            } else if (mState == CallState.DISCONNECTED) {
526                mDisconnectTimeMillis = System.currentTimeMillis();
527                setLocallyDisconnecting(false);
528                fixParentAfterDisconnect();
529            }
530
531            // Log the state transition event
532            String event = null;
533            switch (newState) {
534                case CallState.ACTIVE:
535                    event = Log.Events.SET_ACTIVE;
536                    break;
537                case CallState.CONNECTING:
538                    event = Log.Events.SET_CONNECTING;
539                    break;
540                case CallState.DIALING:
541                    event = Log.Events.SET_DIALING;
542                    break;
543                case CallState.DISCONNECTED:
544                    event = Log.Events.SET_DISCONNECTED;
545                    break;
546                case CallState.DISCONNECTING:
547                    event = Log.Events.SET_DISCONNECTING;
548                    break;
549                case CallState.ON_HOLD:
550                    event = Log.Events.SET_HOLD;
551                    break;
552                case CallState.SELECT_PHONE_ACCOUNT:
553                    event = Log.Events.SET_SELECT_PHONE_ACCOUNT;
554                    break;
555                case CallState.RINGING:
556                    event = Log.Events.SET_RINGING;
557                    break;
558            }
559            if (event != null) {
560                Log.event(this, event);
561            }
562        }
563    }
564
565    void setRingbackRequested(boolean ringbackRequested) {
566        mRingbackRequested = ringbackRequested;
567        for (Listener l : mListeners) {
568            l.onRingbackRequested(this, mRingbackRequested);
569        }
570    }
571
572    boolean isRingbackRequested() {
573        return mRingbackRequested;
574    }
575
576    boolean isConference() {
577        return mIsConference;
578    }
579
580    public Uri getHandle() {
581        return mHandle;
582    }
583
584    int getHandlePresentation() {
585        return mHandlePresentation;
586    }
587
588
589    void setHandle(Uri handle) {
590        setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
591    }
592
593    public void setHandle(Uri handle, int presentation) {
594        if (!Objects.equals(handle, mHandle) || presentation != mHandlePresentation) {
595            mHandlePresentation = presentation;
596            if (mHandlePresentation == TelecomManager.PRESENTATION_RESTRICTED ||
597                    mHandlePresentation == TelecomManager.PRESENTATION_UNKNOWN) {
598                mHandle = null;
599            } else {
600                mHandle = handle;
601                if (mHandle != null && !PhoneAccount.SCHEME_VOICEMAIL.equals(mHandle.getScheme())
602                        && TextUtils.isEmpty(mHandle.getSchemeSpecificPart())) {
603                    // If the number is actually empty, set it to null, unless this is a
604                    // SCHEME_VOICEMAIL uri which always has an empty number.
605                    mHandle = null;
606                }
607            }
608
609            mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(mContext,
610                    mHandle.getSchemeSpecificPart());
611            startCallerInfoLookup();
612            for (Listener l : mListeners) {
613                l.onHandleChanged(this);
614            }
615        }
616    }
617
618    String getCallerDisplayName() {
619        return mCallerDisplayName;
620    }
621
622    int getCallerDisplayNamePresentation() {
623        return mCallerDisplayNamePresentation;
624    }
625
626    void setCallerDisplayName(String callerDisplayName, int presentation) {
627        if (!TextUtils.equals(callerDisplayName, mCallerDisplayName) ||
628                presentation != mCallerDisplayNamePresentation) {
629            mCallerDisplayName = callerDisplayName;
630            mCallerDisplayNamePresentation = presentation;
631            for (Listener l : mListeners) {
632                l.onCallerDisplayNameChanged(this);
633            }
634        }
635    }
636
637    public String getName() {
638        return mCallerInfo == null ? null : mCallerInfo.name;
639    }
640
641    public Bitmap getPhotoIcon() {
642        return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
643    }
644
645    public Drawable getPhoto() {
646        return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
647    }
648
649    /**
650     * @param disconnectCause The reason for the disconnection, represented by
651     *         {@link android.telecom.DisconnectCause}.
652     */
653    public void setDisconnectCause(DisconnectCause disconnectCause) {
654        // TODO: Consider combining this method with a setDisconnected() method that is totally
655        // separate from setState.
656        mDisconnectCause = disconnectCause;
657    }
658
659    public DisconnectCause getDisconnectCause() {
660        return mDisconnectCause;
661    }
662
663    boolean isEmergencyCall() {
664        return mIsEmergencyCall;
665    }
666
667    /**
668     * @return The original handle this call is associated with. In-call services should use this
669     * handle when indicating in their UI the handle that is being called.
670     */
671    public Uri getOriginalHandle() {
672        if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
673            return mGatewayInfo.getOriginalAddress();
674        }
675        return getHandle();
676    }
677
678    GatewayInfo getGatewayInfo() {
679        return mGatewayInfo;
680    }
681
682    void setGatewayInfo(GatewayInfo gatewayInfo) {
683        mGatewayInfo = gatewayInfo;
684    }
685
686    PhoneAccountHandle getConnectionManagerPhoneAccount() {
687        return mConnectionManagerPhoneAccountHandle;
688    }
689
690    void setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle) {
691        if (!Objects.equals(mConnectionManagerPhoneAccountHandle, accountHandle)) {
692            mConnectionManagerPhoneAccountHandle = accountHandle;
693            for (Listener l : mListeners) {
694                l.onConnectionManagerPhoneAccountChanged(this);
695            }
696        }
697
698    }
699
700    PhoneAccountHandle getTargetPhoneAccount() {
701        return mTargetPhoneAccountHandle;
702    }
703
704    void setTargetPhoneAccount(PhoneAccountHandle accountHandle) {
705        if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) {
706            mTargetPhoneAccountHandle = accountHandle;
707            for (Listener l : mListeners) {
708                l.onTargetPhoneAccountChanged(this);
709            }
710        }
711    }
712
713    boolean isIncoming() {
714        return mIsIncoming;
715    }
716
717    /**
718     * @return The "age" of this call object in milliseconds, which typically also represents the
719     *     period since this call was added to the set pending outgoing calls, see
720     *     mCreationTimeMillis.
721     */
722    long getAgeMillis() {
723        if (mState == CallState.DISCONNECTED &&
724                (mDisconnectCause.getCode() == DisconnectCause.REJECTED ||
725                 mDisconnectCause.getCode() == DisconnectCause.MISSED)) {
726            // Rejected and missed calls have no age. They're immortal!!
727            return 0;
728        } else if (mConnectTimeMillis == 0) {
729            // Age is measured in the amount of time the call was active. A zero connect time
730            // indicates that we never went active, so return 0 for the age.
731            return 0;
732        } else if (mDisconnectTimeMillis == 0) {
733            // We connected, but have not yet disconnected
734            return System.currentTimeMillis() - mConnectTimeMillis;
735        }
736
737        return mDisconnectTimeMillis - mConnectTimeMillis;
738    }
739
740    /**
741     * @return The time when this call object was created and added to the set of pending outgoing
742     *     calls.
743     */
744    public long getCreationTimeMillis() {
745        return mCreationTimeMillis;
746    }
747
748    public void setCreationTimeMillis(long time) {
749        mCreationTimeMillis = time;
750    }
751
752    long getConnectTimeMillis() {
753        return mConnectTimeMillis;
754    }
755
756    int getConnectionCapabilities() {
757        return mConnectionCapabilities;
758    }
759
760    void setConnectionCapabilities(int connectionCapabilities) {
761        setConnectionCapabilities(connectionCapabilities, false /* forceUpdate */);
762    }
763
764    void setConnectionCapabilities(int connectionCapabilities, boolean forceUpdate) {
765        Log.v(this, "setConnectionCapabilities: %s", Connection.capabilitiesToString(
766                connectionCapabilities));
767        if (forceUpdate || mConnectionCapabilities != connectionCapabilities) {
768           mConnectionCapabilities = connectionCapabilities;
769            for (Listener l : mListeners) {
770                l.onConnectionCapabilitiesChanged(this);
771            }
772        }
773    }
774
775    Call getParentCall() {
776        return mParentCall;
777    }
778
779    List<Call> getChildCalls() {
780        return mChildCalls;
781    }
782
783    boolean wasConferencePreviouslyMerged() {
784        return mWasConferencePreviouslyMerged;
785    }
786
787    Call getConferenceLevelActiveCall() {
788        return mConferenceLevelActiveCall;
789    }
790
791    ConnectionServiceWrapper getConnectionService() {
792        return mConnectionService;
793    }
794
795    /**
796     * Retrieves the {@link Context} for the call.
797     *
798     * @return The {@link Context}.
799     */
800    Context getContext() {
801        return mContext;
802    }
803
804    void setConnectionService(ConnectionServiceWrapper service) {
805        Preconditions.checkNotNull(service);
806
807        clearConnectionService();
808
809        service.incrementAssociatedCallCount();
810        mConnectionService = service;
811        mConnectionService.addCall(this);
812    }
813
814    /**
815     * Clears the associated connection service.
816     */
817    void clearConnectionService() {
818        if (mConnectionService != null) {
819            ConnectionServiceWrapper serviceTemp = mConnectionService;
820            mConnectionService = null;
821            serviceTemp.removeCall(this);
822
823            // Decrementing the count can cause the service to unbind, which itself can trigger the
824            // service-death code.  Since the service death code tries to clean up any associated
825            // calls, we need to make sure to remove that information (e.g., removeCall()) before
826            // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
827            // necessary, but cleaning up mConnectionService prior to triggering an unbind is good
828            // to do.
829            decrementAssociatedCallCount(serviceTemp);
830        }
831    }
832
833    private void processDirectToVoicemail() {
834        if (mDirectToVoicemailQueryPending) {
835            if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
836                Log.i(this, "Directing call to voicemail: %s.", this);
837                // TODO: Once we move State handling from CallsManager to Call, we
838                // will not need to set STATE_RINGING state prior to calling reject.
839                setState(CallState.RINGING);
840                reject(false, null);
841            } else {
842                // TODO: Make this class (not CallsManager) responsible for changing
843                // the call state to STATE_RINGING.
844
845                // TODO: Replace this with state transition to STATE_RINGING.
846                for (Listener l : mListeners) {
847                    l.onSuccessfulIncomingCall(this);
848                }
849            }
850
851            mDirectToVoicemailQueryPending = false;
852        }
853    }
854
855    /**
856     * Starts the create connection sequence. Upon completion, there should exist an active
857     * connection through a connection service (or the call will have failed).
858     *
859     * @param phoneAccountRegistrar The phone account registrar.
860     */
861    void startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar) {
862        Preconditions.checkState(mCreateConnectionProcessor == null);
863        mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this,
864                phoneAccountRegistrar, mContext);
865        mCreateConnectionProcessor.process();
866    }
867
868    @Override
869    public void handleCreateConnectionSuccess(
870            CallIdMapper idMapper,
871            ParcelableConnection connection) {
872        Log.v(this, "handleCreateConnectionSuccessful %s", connection);
873        setTargetPhoneAccount(connection.getPhoneAccount());
874        setHandle(connection.getHandle(), connection.getHandlePresentation());
875        setCallerDisplayName(
876                connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());
877        setConnectionCapabilities(connection.getConnectionCapabilities());
878        setVideoProvider(connection.getVideoProvider());
879        setVideoState(connection.getVideoState());
880        setRingbackRequested(connection.isRingbackRequested());
881        setIsVoipAudioMode(connection.getIsVoipAudioMode());
882        setStatusHints(connection.getStatusHints());
883
884        mConferenceableCalls.clear();
885        for (String id : connection.getConferenceableConnectionIds()) {
886            mConferenceableCalls.add(idMapper.getCall(id));
887        }
888
889        if (mIsUnknown) {
890            for (Listener l : mListeners) {
891                l.onSuccessfulUnknownCall(this, getStateFromConnectionState(connection.getState()));
892            }
893        } else if (mIsIncoming) {
894            // We do not handle incoming calls immediately when they are verified by the connection
895            // service. We allow the caller-info-query code to execute first so that we can read the
896            // direct-to-voicemail property before deciding if we want to show the incoming call to
897            // the user or if we want to reject the call.
898            mDirectToVoicemailQueryPending = true;
899
900            // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
901            // showing the user the incoming call screen.
902            mHandler.postDelayed(mDirectToVoicemailRunnable, Timeouts.getDirectToVoicemailMillis(
903                    mContext.getContentResolver()));
904        } else {
905            for (Listener l : mListeners) {
906                l.onSuccessfulOutgoingCall(this,
907                        getStateFromConnectionState(connection.getState()));
908            }
909        }
910    }
911
912    @Override
913    public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
914        clearConnectionService();
915        setDisconnectCause(disconnectCause);
916        mCallsManager.markCallAsDisconnected(this, disconnectCause);
917
918        if (mIsUnknown) {
919            for (Listener listener : mListeners) {
920                listener.onFailedUnknownCall(this);
921            }
922        } else if (mIsIncoming) {
923            for (Listener listener : mListeners) {
924                listener.onFailedIncomingCall(this);
925            }
926        } else {
927            for (Listener listener : mListeners) {
928                listener.onFailedOutgoingCall(this, disconnectCause);
929            }
930        }
931    }
932
933    /**
934     * Plays the specified DTMF tone.
935     */
936    void playDtmfTone(char digit) {
937        if (mConnectionService == null) {
938            Log.w(this, "playDtmfTone() request on a call without a connection service.");
939        } else {
940            Log.i(this, "Send playDtmfTone to connection service for call %s", this);
941            mConnectionService.playDtmfTone(this, digit);
942            Log.event(this, Log.Events.START_DTMF, Log.pii(digit));
943        }
944    }
945
946    /**
947     * Stops playing any currently playing DTMF tone.
948     */
949    void stopDtmfTone() {
950        if (mConnectionService == null) {
951            Log.w(this, "stopDtmfTone() request on a call without a connection service.");
952        } else {
953            Log.i(this, "Send stopDtmfTone to connection service for call %s", this);
954            Log.event(this, Log.Events.STOP_DTMF);
955            mConnectionService.stopDtmfTone(this);
956        }
957    }
958
959    void disconnect() {
960        disconnect(false);
961    }
962
963    /**
964     * Attempts to disconnect the call through the connection service.
965     */
966    void disconnect(boolean wasViaNewOutgoingCallBroadcaster) {
967        Log.event(this, Log.Events.REQUEST_DISCONNECT);
968
969        // Track that the call is now locally disconnecting.
970        setLocallyDisconnecting(true);
971
972        if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT ||
973                mState == CallState.CONNECTING) {
974            Log.v(this, "Aborting call %s", this);
975            abort(wasViaNewOutgoingCallBroadcaster);
976        } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
977            if (mConnectionService == null) {
978                Log.e(this, new Exception(), "disconnect() request on a call without a"
979                        + " connection service.");
980            } else {
981                Log.i(this, "Send disconnect to connection service for call: %s", this);
982                // The call isn't officially disconnected until the connection service
983                // confirms that the call was actually disconnected. Only then is the
984                // association between call and connection service severed, see
985                // {@link CallsManager#markCallAsDisconnected}.
986                mConnectionService.disconnect(this);
987            }
988        }
989    }
990
991    void abort(boolean wasViaNewOutgoingCallBroadcaster) {
992        if (mCreateConnectionProcessor != null &&
993                !mCreateConnectionProcessor.isProcessingComplete()) {
994            mCreateConnectionProcessor.abort();
995        } else if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT
996                || mState == CallState.CONNECTING) {
997            if (wasViaNewOutgoingCallBroadcaster) {
998                // If the cancelation was from NEW_OUTGOING_CALL, then we do not automatically
999                // destroy the call.  Instead, we announce the cancelation and CallsManager handles
1000                // it through a timer. Since apps often cancel calls through NEW_OUTGOING_CALL and
1001                // then re-dial them quickly using a gateway, allowing the first call to end
1002                // causes jank. This timeout allows CallsManager to transition the first call into
1003                // the second call so that in-call only ever sees a single call...eliminating the
1004                // jank altogether.
1005                for (Listener listener : mListeners) {
1006                    if (listener.onCanceledViaNewOutgoingCallBroadcast(this)) {
1007                        // The first listener to handle this wins. A return value of true means that
1008                        // the listener will handle the disconnection process later and so we
1009                        // should not continue it here.
1010                        setLocallyDisconnecting(false);
1011                        return;
1012                    }
1013                }
1014            }
1015
1016            handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
1017        } else {
1018            Log.v(this, "Cannot abort a call which is neither SELECT_PHONE_ACCOUNT or CONNECTING");
1019        }
1020    }
1021
1022    /**
1023     * Answers the call if it is ringing.
1024     *
1025     * @param videoState The video state in which to answer the call.
1026     */
1027    void answer(int videoState) {
1028        Preconditions.checkNotNull(mConnectionService);
1029
1030        // Check to verify that the call is still in the ringing state. A call can change states
1031        // between the time the user hits 'answer' and Telecom receives the command.
1032        if (isRinging("answer")) {
1033            // At this point, we are asking the connection service to answer but we don't assume
1034            // that it will work. Instead, we wait until confirmation from the connectino service
1035            // that the call is in a non-STATE_RINGING state before changing the UI. See
1036            // {@link ConnectionServiceAdapter#setActive} and other set* methods.
1037            mConnectionService.answer(this, videoState);
1038            Log.event(this, Log.Events.REQUEST_ACCEPT);
1039        }
1040    }
1041
1042    /**
1043     * Rejects the call if it is ringing.
1044     *
1045     * @param rejectWithMessage Whether to send a text message as part of the call rejection.
1046     * @param textMessage An optional text message to send as part of the rejection.
1047     */
1048    void reject(boolean rejectWithMessage, String textMessage) {
1049        Preconditions.checkNotNull(mConnectionService);
1050
1051        // Check to verify that the call is still in the ringing state. A call can change states
1052        // between the time the user hits 'reject' and Telecomm receives the command.
1053        if (isRinging("reject")) {
1054            mConnectionService.reject(this);
1055            Log.event(this, Log.Events.REQUEST_REJECT);
1056        }
1057    }
1058
1059    /**
1060     * Puts the call on hold if it is currently active.
1061     */
1062    void hold() {
1063        Preconditions.checkNotNull(mConnectionService);
1064
1065        if (mState == CallState.ACTIVE) {
1066            mConnectionService.hold(this);
1067            Log.event(this, Log.Events.REQUEST_HOLD);
1068        }
1069    }
1070
1071    /**
1072     * Releases the call from hold if it is currently active.
1073     */
1074    void unhold() {
1075        Preconditions.checkNotNull(mConnectionService);
1076
1077        if (mState == CallState.ON_HOLD) {
1078            mConnectionService.unhold(this);
1079            Log.event(this, Log.Events.REQUEST_UNHOLD);
1080        }
1081    }
1082
1083    /** Checks if this is a live call or not. */
1084    boolean isAlive() {
1085        switch (mState) {
1086            case CallState.NEW:
1087            case CallState.RINGING:
1088            case CallState.DISCONNECTED:
1089            case CallState.ABORTED:
1090                return false;
1091            default:
1092                return true;
1093        }
1094    }
1095
1096    boolean isActive() {
1097        return mState == CallState.ACTIVE;
1098    }
1099
1100    Bundle getExtras() {
1101        return mExtras;
1102    }
1103
1104    void setExtras(Bundle extras) {
1105        mExtras = 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     * Valid values: see {@link VideoProfile.VideoState}.
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.VideoState}
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