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