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