Call.java revision ffe558c570ae5736d47fded2caccc7da145e7d92
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.RemoteException;
27import android.os.Trace;
28import android.provider.ContactsContract.Contacts;
29import android.telecom.CallAudioState;
30import android.telecom.DisconnectCause;
31import android.telecom.Connection;
32import android.telecom.GatewayInfo;
33import android.telecom.Log;
34import android.telecom.Logging.EventManager;
35import android.telecom.ParcelableConnection;
36import android.telecom.PhoneAccount;
37import android.telecom.PhoneAccountHandle;
38import android.telecom.Response;
39import android.telecom.StatusHints;
40import android.telecom.TelecomManager;
41import android.telecom.VideoProfile;
42import android.telephony.PhoneNumberUtils;
43import android.text.TextUtils;
44import android.os.UserHandle;
45
46import com.android.internal.annotations.VisibleForTesting;
47import com.android.internal.telecom.IVideoProvider;
48import com.android.internal.telephony.CallerInfo;
49import com.android.internal.telephony.SmsApplication;
50import com.android.internal.util.Preconditions;
51
52import java.lang.String;
53import java.text.SimpleDateFormat;
54import java.util.ArrayList;
55import java.util.Collections;
56import java.util.Date;
57import java.util.LinkedList;
58import java.util.List;
59import java.util.Locale;
60import java.util.Objects;
61import java.util.Set;
62import java.util.concurrent.ConcurrentHashMap;
63
64/**
65 *  Encapsulates all aspects of a given phone call throughout its lifecycle, starting
66 *  from the time the call intent was received by Telecom (vs. the time the call was
67 *  connected etc).
68 */
69@VisibleForTesting
70public class Call implements CreateConnectionResponse, EventManager.Loggable {
71    public final static String CALL_ID_UNKNOWN = "-1";
72    public final static long DATA_USAGE_NOT_SET = -1;
73
74    public static final int CALL_DIRECTION_UNDEFINED = 0;
75    public static final int CALL_DIRECTION_OUTGOING = 1;
76    public static final int CALL_DIRECTION_INCOMING = 2;
77    public static final int CALL_DIRECTION_UNKNOWN = 3;
78
79    /** Identifies extras changes which originated from a connection service. */
80    public static final int SOURCE_CONNECTION_SERVICE = 1;
81    /** Identifies extras changes which originated from an incall service. */
82    public static final int SOURCE_INCALL_SERVICE = 2;
83
84    /**
85     * Listener for events on the call.
86     */
87    @VisibleForTesting
88    public interface Listener {
89        void onSuccessfulOutgoingCall(Call call, int callState);
90        void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause);
91        void onSuccessfulIncomingCall(Call call);
92        void onFailedIncomingCall(Call call);
93        void onSuccessfulUnknownCall(Call call, int callState);
94        void onFailedUnknownCall(Call call);
95        void onRingbackRequested(Call call, boolean ringbackRequested);
96        void onPostDialWait(Call call, String remaining);
97        void onPostDialChar(Call call, char nextChar);
98        void onConnectionCapabilitiesChanged(Call call);
99        void onConnectionPropertiesChanged(Call call);
100        void onParentChanged(Call call);
101        void onChildrenChanged(Call call);
102        void onCannedSmsResponsesLoaded(Call call);
103        void onVideoCallProviderChanged(Call call);
104        void onCallerInfoChanged(Call call);
105        void onIsVoipAudioModeChanged(Call call);
106        void onStatusHintsChanged(Call call);
107        void onExtrasChanged(Call c, int source, Bundle extras);
108        void onExtrasRemoved(Call c, int source, List<String> keys);
109        void onHandleChanged(Call call);
110        void onCallerDisplayNameChanged(Call call);
111        void onVideoStateChanged(Call call, int previousVideoState, int newVideoState);
112        void onTargetPhoneAccountChanged(Call call);
113        void onConnectionManagerPhoneAccountChanged(Call call);
114        void onPhoneAccountChanged(Call call);
115        void onConferenceableCallsChanged(Call call);
116        boolean onCanceledViaNewOutgoingCallBroadcast(Call call);
117        void onHoldToneRequested(Call call);
118        void onConnectionEvent(Call call, String event, Bundle extras);
119        void onExternalCallChanged(Call call, boolean isExternalCall);
120    }
121
122    public abstract static class ListenerBase implements Listener {
123        @Override
124        public void onSuccessfulOutgoingCall(Call call, int callState) {}
125        @Override
126        public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {}
127        @Override
128        public void onSuccessfulIncomingCall(Call call) {}
129        @Override
130        public void onFailedIncomingCall(Call call) {}
131        @Override
132        public void onSuccessfulUnknownCall(Call call, int callState) {}
133        @Override
134        public void onFailedUnknownCall(Call call) {}
135        @Override
136        public void onRingbackRequested(Call call, boolean ringbackRequested) {}
137        @Override
138        public void onPostDialWait(Call call, String remaining) {}
139        @Override
140        public void onPostDialChar(Call call, char nextChar) {}
141        @Override
142        public void onConnectionCapabilitiesChanged(Call call) {}
143        @Override
144        public void onConnectionPropertiesChanged(Call call) {}
145        @Override
146        public void onParentChanged(Call call) {}
147        @Override
148        public void onChildrenChanged(Call call) {}
149        @Override
150        public void onCannedSmsResponsesLoaded(Call call) {}
151        @Override
152        public void onVideoCallProviderChanged(Call call) {}
153        @Override
154        public void onCallerInfoChanged(Call call) {}
155        @Override
156        public void onIsVoipAudioModeChanged(Call call) {}
157        @Override
158        public void onStatusHintsChanged(Call call) {}
159        @Override
160        public void onExtrasChanged(Call c, int source, Bundle extras) {}
161        @Override
162        public void onExtrasRemoved(Call c, int source, List<String> keys) {}
163        @Override
164        public void onHandleChanged(Call call) {}
165        @Override
166        public void onCallerDisplayNameChanged(Call call) {}
167        @Override
168        public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {}
169        @Override
170        public void onTargetPhoneAccountChanged(Call call) {}
171        @Override
172        public void onConnectionManagerPhoneAccountChanged(Call call) {}
173        @Override
174        public void onPhoneAccountChanged(Call call) {}
175        @Override
176        public void onConferenceableCallsChanged(Call call) {}
177        @Override
178        public boolean onCanceledViaNewOutgoingCallBroadcast(Call call) {
179            return false;
180        }
181
182        @Override
183        public void onHoldToneRequested(Call call) {}
184        @Override
185        public void onConnectionEvent(Call call, String event, Bundle extras) {}
186        @Override
187        public void onExternalCallChanged(Call call, boolean isExternalCall) {}
188    }
189
190    private final CallerInfoLookupHelper.OnQueryCompleteListener mCallerInfoQueryListener =
191            new CallerInfoLookupHelper.OnQueryCompleteListener() {
192                /** ${inheritDoc} */
193                @Override
194                public void onCallerInfoQueryComplete(Uri handle, CallerInfo callerInfo) {
195                    synchronized (mLock) {
196                        Call.this.setCallerInfo(handle, callerInfo);
197                    }
198                }
199
200                @Override
201                public void onContactPhotoQueryComplete(Uri handle, CallerInfo callerInfo) {
202                    synchronized (mLock) {
203                        Call.this.setCallerInfo(handle, callerInfo);
204                    }
205                }
206            };
207
208    /**
209     * One of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING, or CALL_DIRECTION_UNKNOWN
210     */
211    private final int mCallDirection;
212
213    /**
214     * The post-dial digits that were dialed after the network portion of the number
215     */
216    private final String mPostDialDigits;
217
218    /**
219     * The secondary line number that an incoming call has been received on if the SIM subscription
220     * has multiple associated numbers.
221     */
222    private String mViaNumber = "";
223
224    /**
225     * The time this call was created. Beyond logging and such, may also be used for bookkeeping
226     * and specifically for marking certain call attempts as failed attempts.
227     */
228    private long mCreationTimeMillis = System.currentTimeMillis();
229
230    /** The time this call was made active. */
231    private long mConnectTimeMillis = 0;
232
233    /** The time this call was disconnected. */
234    private long mDisconnectTimeMillis = 0;
235
236    /** The gateway information associated with this call. This stores the original call handle
237     * that the user is attempting to connect to via the gateway, the actual handle to dial in
238     * order to connect the call via the gateway, as well as the package name of the gateway
239     * service. */
240    private GatewayInfo mGatewayInfo;
241
242    private PhoneAccountHandle mConnectionManagerPhoneAccountHandle;
243
244    private PhoneAccountHandle mTargetPhoneAccountHandle;
245
246    private UserHandle mInitiatingUser;
247
248    private final Handler mHandler = new Handler(Looper.getMainLooper());
249
250    private final List<Call> mConferenceableCalls = new ArrayList<>();
251
252    /** The state of the call. */
253    private int mState;
254
255    /** The handle with which to establish this call. */
256    private Uri mHandle;
257
258    /**
259     * The presentation requirements for the handle. See {@link TelecomManager} for valid values.
260     */
261    private int mHandlePresentation;
262
263    /** The caller display name (CNAP) set by the connection service. */
264    private String mCallerDisplayName;
265
266    /**
267     * The presentation requirements for the handle. See {@link TelecomManager} for valid values.
268     */
269    private int mCallerDisplayNamePresentation;
270
271    /**
272     * The connection service which is attempted or already connecting this call.
273     */
274    private ConnectionServiceWrapper mConnectionService;
275
276    private boolean mIsEmergencyCall;
277
278    private boolean mSpeakerphoneOn;
279
280    /**
281     * Tracks the video states which were applicable over the duration of a call.
282     * See {@link VideoProfile} for a list of valid video states.
283     * <p>
284     * Video state history is tracked when the call is active, and when a call is rejected or
285     * missed.
286     */
287    private int mVideoStateHistory;
288
289    private int mVideoState;
290
291    /**
292     * Disconnect cause for the call. Only valid if the state of the call is STATE_DISCONNECTED.
293     * See {@link android.telecom.DisconnectCause}.
294     */
295    private DisconnectCause mDisconnectCause = new DisconnectCause(DisconnectCause.UNKNOWN);
296
297    private Bundle mIntentExtras = new Bundle();
298
299    /** Set of listeners on this call.
300     *
301     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
302     * load factor before resizing, 1 means we only expect a single thread to
303     * access the map so make only a single shard
304     */
305    private final Set<Listener> mListeners = Collections.newSetFromMap(
306            new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
307
308    private CreateConnectionProcessor mCreateConnectionProcessor;
309
310    /** Caller information retrieved from the latest contact query. */
311    private CallerInfo mCallerInfo;
312
313    /** The latest token used with a contact info query. */
314    private int mQueryToken = 0;
315
316    /** Whether this call is requesting that Telecom play the ringback tone on its behalf. */
317    private boolean mRingbackRequested = false;
318
319    /** Whether direct-to-voicemail query is pending. */
320    private boolean mDirectToVoicemailQueryPending;
321
322    private int mConnectionCapabilities;
323
324    private int mConnectionProperties;
325
326    private int mSupportedAudioRoutes = CallAudioState.ROUTE_ALL;
327
328    private boolean mIsConference = false;
329
330    private final boolean mShouldAttachToExistingConnection;
331
332    private Call mParentCall = null;
333
334    private List<Call> mChildCalls = new LinkedList<>();
335
336    /** Set of text message responses allowed for this call, if applicable. */
337    private List<String> mCannedSmsResponses = Collections.EMPTY_LIST;
338
339    /** Whether an attempt has been made to load the text message responses. */
340    private boolean mCannedSmsResponsesLoadingStarted = false;
341
342    private IVideoProvider mVideoProvider;
343    private VideoProviderProxy mVideoProviderProxy;
344
345    private boolean mIsVoipAudioMode;
346    private StatusHints mStatusHints;
347    private Bundle mExtras;
348    private final ConnectionServiceRepository mRepository;
349    private final Context mContext;
350    private final CallsManager mCallsManager;
351    private final TelecomSystem.SyncRoot mLock;
352    private final String mId;
353    private String mConnectionId;
354    private Analytics.CallInfo mAnalytics;
355
356    private boolean mWasConferencePreviouslyMerged = false;
357
358    // For conferences which support merge/swap at their level, we retain a notion of an active
359    // call. This is used for BluetoothPhoneService.  In order to support hold/merge, it must have
360    // the notion of the current "active" call within the conference call. This maintains the
361    // "active" call and switches every time the user hits "swap".
362    private Call mConferenceLevelActiveCall = null;
363
364    private boolean mIsLocallyDisconnecting = false;
365
366    /**
367     * Tracks the current call data usage as reported by the video provider.
368     */
369    private long mCallDataUsage = DATA_USAGE_NOT_SET;
370
371    private boolean mIsWorkCall;
372
373    // Set to true once the NewOutgoingCallIntentBroadcast comes back and is processed.
374    private boolean mIsNewOutgoingCallIntentBroadcastDone = false;
375
376
377    /**
378     * Indicates whether the call is remotely held.  A call is considered remotely held when
379     * {@link #onConnectionEvent(String)} receives the {@link Connection#EVENT_ON_HOLD_TONE_START}
380     * event.
381     */
382    private boolean mIsRemotelyHeld = false;
383
384    /**
385     * Indicates whether the {@link PhoneAccount} associated with this call supports video calling.
386     * {@code True} if the phone account supports video calling, {@code false} otherwise.
387     */
388    private boolean mIsVideoCallingSupported = false;
389
390    private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
391
392    /**
393     * Persists the specified parameters and initializes the new instance.
394     *
395     * @param context The context.
396     * @param repository The connection service repository.
397     * @param handle The handle to dial.
398     * @param gatewayInfo Gateway information to use for the call.
399     * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
400     *         This account must be one that was registered with the
401     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
402     * @param targetPhoneAccountHandle Account information to use for the call. This account must be
403     *         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
404     * @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING,
405     *         or CALL_DIRECTION_UNKNOWN.
406     * @param shouldAttachToExistingConnection Set to true to attach the call to an existing
407     *         connection, regardless of whether it's incoming or outgoing.
408     */
409    public Call(
410            String callId,
411            Context context,
412            CallsManager callsManager,
413            TelecomSystem.SyncRoot lock,
414            ConnectionServiceRepository repository,
415            ContactsAsyncHelper contactsAsyncHelper,
416            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
417            PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
418            Uri handle,
419            GatewayInfo gatewayInfo,
420            PhoneAccountHandle connectionManagerPhoneAccountHandle,
421            PhoneAccountHandle targetPhoneAccountHandle,
422            int callDirection,
423            boolean shouldAttachToExistingConnection,
424            boolean isConference) {
425        mId = callId;
426        mConnectionId = callId;
427        mState = isConference ? CallState.ACTIVE : CallState.NEW;
428        mContext = context;
429        mCallsManager = callsManager;
430        mLock = lock;
431        mRepository = repository;
432        mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
433        setHandle(handle);
434        mPostDialDigits = handle != null
435                ? PhoneNumberUtils.extractPostDialPortion(handle.getSchemeSpecificPart()) : "";
436        mGatewayInfo = gatewayInfo;
437        setConnectionManagerPhoneAccount(connectionManagerPhoneAccountHandle);
438        setTargetPhoneAccount(targetPhoneAccountHandle);
439        mCallDirection = callDirection;
440        mIsConference = isConference;
441        mShouldAttachToExistingConnection = shouldAttachToExistingConnection
442                || callDirection == CALL_DIRECTION_INCOMING;
443        maybeLoadCannedSmsResponses();
444        mAnalytics = new Analytics.CallInfo();
445    }
446
447    /**
448     * Persists the specified parameters and initializes the new instance.
449     *
450     * @param context The context.
451     * @param repository The connection service repository.
452     * @param handle The handle to dial.
453     * @param gatewayInfo Gateway information to use for the call.
454     * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
455     *         This account must be one that was registered with the
456     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
457     * @param targetPhoneAccountHandle Account information to use for the call. This account must be
458     *         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
459     * @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING,
460     *         or CALL_DIRECTION_UNKNOWN
461     * @param shouldAttachToExistingConnection Set to true to attach the call to an existing
462     *         connection, regardless of whether it's incoming or outgoing.
463     * @param connectTimeMillis The connection time of the call.
464     */
465    Call(
466            String callId,
467            Context context,
468            CallsManager callsManager,
469            TelecomSystem.SyncRoot lock,
470            ConnectionServiceRepository repository,
471            ContactsAsyncHelper contactsAsyncHelper,
472            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
473            PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
474            Uri handle,
475            GatewayInfo gatewayInfo,
476            PhoneAccountHandle connectionManagerPhoneAccountHandle,
477            PhoneAccountHandle targetPhoneAccountHandle,
478            int callDirection,
479            boolean shouldAttachToExistingConnection,
480            boolean isConference,
481            long connectTimeMillis) {
482        this(callId, context, callsManager, lock, repository, contactsAsyncHelper,
483                callerInfoAsyncQueryFactory, phoneNumberUtilsAdapter, handle, gatewayInfo,
484                connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
485                shouldAttachToExistingConnection, isConference);
486
487        mConnectTimeMillis = connectTimeMillis;
488        mAnalytics.setCallStartTime(connectTimeMillis);
489    }
490
491    public void addListener(Listener listener) {
492        mListeners.add(listener);
493    }
494
495    public void removeListener(Listener listener) {
496        if (listener != null) {
497            mListeners.remove(listener);
498        }
499    }
500
501    public void initAnalytics() {
502        int analyticsDirection;
503        switch (mCallDirection) {
504            case CALL_DIRECTION_OUTGOING:
505                analyticsDirection = Analytics.OUTGOING_DIRECTION;
506                break;
507            case CALL_DIRECTION_INCOMING:
508                analyticsDirection = Analytics.INCOMING_DIRECTION;
509                break;
510            case CALL_DIRECTION_UNKNOWN:
511            case CALL_DIRECTION_UNDEFINED:
512            default:
513                analyticsDirection = Analytics.UNKNOWN_DIRECTION;
514        }
515        mAnalytics = Analytics.initiateCallAnalytics(mId, analyticsDirection);
516        Log.addEvent(this, LogUtils.Events.CREATED);
517    }
518
519    public Analytics.CallInfo getAnalytics() {
520        return mAnalytics;
521    }
522
523    public void destroy() {
524        Log.addEvent(this, LogUtils.Events.DESTROYED);
525    }
526
527    /** {@inheritDoc} */
528    @Override
529    public String toString() {
530        String component = null;
531        if (mConnectionService != null && mConnectionService.getComponentName() != null) {
532            component = mConnectionService.getComponentName().flattenToShortString();
533        }
534
535        return String.format(Locale.US, "[%s, %s, %s, %s, %s, childs(%d), has_parent(%b), %s, %s]",
536                mId,
537                CallState.toString(mState),
538                component,
539                Log.piiHandle(mHandle),
540                getVideoStateDescription(getVideoState()),
541                getChildCalls().size(),
542                getParentCall() != null,
543                Connection.capabilitiesToString(getConnectionCapabilities()),
544                Connection.propertiesToString(getConnectionProperties()));
545    }
546
547    @Override
548    public String getDescription() {
549        StringBuilder s = new StringBuilder("Call ");
550        s.append(getId());
551        s.append(" [");
552        s.append(SimpleDateFormat.getDateTimeInstance().format(new Date(getCreationTimeMillis())));
553        s.append("]");
554        s.append(isIncoming() ? "(MT - incoming)" : "(MO - outgoing)");
555        s.append("\n\tTo address: ");
556        s.append(Log.piiHandle(getHandle()));
557        s.append("\n");
558        return s.toString();
559    }
560
561    /**
562     * Builds a debug-friendly description string for a video state.
563     * <p>
564     * A = audio active, T = video transmission active, R = video reception active, P = video
565     * paused.
566     *
567     * @param videoState The video state.
568     * @return A string indicating which bits are set in the video state.
569     */
570    private String getVideoStateDescription(int videoState) {
571        StringBuilder sb = new StringBuilder();
572        sb.append("A");
573
574        if (VideoProfile.isTransmissionEnabled(videoState)) {
575            sb.append("T");
576        }
577
578        if (VideoProfile.isReceptionEnabled(videoState)) {
579            sb.append("R");
580        }
581
582        if (VideoProfile.isPaused(videoState)) {
583            sb.append("P");
584        }
585
586        return sb.toString();
587    }
588
589    @VisibleForTesting
590    public int getState() {
591        return mState;
592    }
593
594    private boolean shouldContinueProcessingAfterDisconnect() {
595        // Stop processing once the call is active.
596        if (!CreateConnectionTimeout.isCallBeingPlaced(this)) {
597            return false;
598        }
599
600        // Only Redial a Call in the case of it being an Emergency Call.
601        if(!isEmergencyCall()) {
602            return false;
603        }
604
605        // Make sure that there are additional connection services to process.
606        if (mCreateConnectionProcessor == null
607            || !mCreateConnectionProcessor.isProcessingComplete()
608            || !mCreateConnectionProcessor.hasMorePhoneAccounts()) {
609            return false;
610        }
611
612        if (mDisconnectCause == null) {
613            return false;
614        }
615
616        // Continue processing if the current attempt failed or timed out.
617        return mDisconnectCause.getCode() == DisconnectCause.ERROR ||
618            mCreateConnectionProcessor.isCallTimedOut();
619    }
620
621    /**
622     * Returns the unique ID for this call as it exists in Telecom.
623     * @return The call ID.
624     */
625    public String getId() {
626        return mId;
627    }
628
629    /**
630     * Returns the unique ID for this call (see {@link #getId}) along with an attempt indicator that
631     * iterates based on attempts to establish a {@link Connection} using createConnectionProcessor.
632     * @return The call ID with an appended attempt id.
633     */
634    public String getConnectionId() {
635        if(mCreateConnectionProcessor != null) {
636            mConnectionId = mId + "_" +
637                    String.valueOf(mCreateConnectionProcessor.getConnectionAttempt());
638            return mConnectionId;
639        } else {
640            return mConnectionId;
641        }
642    }
643
644    /**
645     * Sets the call state. Although there exists the notion of appropriate state transitions
646     * (see {@link CallState}), in practice those expectations break down when cellular systems
647     * misbehave and they do this very often. The result is that we do not enforce state transitions
648     * and instead keep the code resilient to unexpected state changes.
649     */
650    public void setState(int newState, String tag) {
651        if (mState != newState) {
652            Log.v(this, "setState %s -> %s", mState, newState);
653
654            if (newState == CallState.DISCONNECTED && shouldContinueProcessingAfterDisconnect()) {
655                Log.w(this, "continuing processing disconnected call with another service");
656                mCreateConnectionProcessor.continueProcessingIfPossible(this, mDisconnectCause);
657                return;
658            }
659
660            mState = newState;
661            maybeLoadCannedSmsResponses();
662
663            if (mState == CallState.ACTIVE || mState == CallState.ON_HOLD) {
664                if (mConnectTimeMillis == 0) {
665                    // We check to see if mConnectTime is already set to prevent the
666                    // call from resetting active time when it goes in and out of
667                    // ACTIVE/ON_HOLD
668                    mConnectTimeMillis = System.currentTimeMillis();
669                    mAnalytics.setCallStartTime(mConnectTimeMillis);
670                }
671
672                // Video state changes are normally tracked against history when a call is active.
673                // When the call goes active we need to be sure we track the history in case the
674                // state never changes during the duration of the call -- we want to ensure we
675                // always know the state at the start of the call.
676                mVideoStateHistory = mVideoStateHistory | mVideoState;
677
678                // We're clearly not disconnected, so reset the disconnected time.
679                mDisconnectTimeMillis = 0;
680            } else if (mState == CallState.DISCONNECTED) {
681                mDisconnectTimeMillis = System.currentTimeMillis();
682                mAnalytics.setCallEndTime(mDisconnectTimeMillis);
683                setLocallyDisconnecting(false);
684                fixParentAfterDisconnect();
685            }
686            if (mState == CallState.DISCONNECTED &&
687                    mDisconnectCause.getCode() == DisconnectCause.MISSED) {
688                // Ensure when an incoming call is missed that the video state history is updated.
689                mVideoStateHistory |= mVideoState;
690            }
691
692            // Log the state transition event
693            String event = null;
694            Object data = null;
695            switch (newState) {
696                case CallState.ACTIVE:
697                    event = LogUtils.Events.SET_ACTIVE;
698                    break;
699                case CallState.CONNECTING:
700                    event = LogUtils.Events.SET_CONNECTING;
701                    break;
702                case CallState.DIALING:
703                    event = LogUtils.Events.SET_DIALING;
704                    break;
705                case CallState.PULLING:
706                    event = LogUtils.Events.SET_PULLING;
707                    break;
708                case CallState.DISCONNECTED:
709                    event = LogUtils.Events.SET_DISCONNECTED;
710                    data = getDisconnectCause();
711                    break;
712                case CallState.DISCONNECTING:
713                    event = LogUtils.Events.SET_DISCONNECTING;
714                    break;
715                case CallState.ON_HOLD:
716                    event = LogUtils.Events.SET_HOLD;
717                    break;
718                case CallState.SELECT_PHONE_ACCOUNT:
719                    event = LogUtils.Events.SET_SELECT_PHONE_ACCOUNT;
720                    break;
721                case CallState.RINGING:
722                    event = LogUtils.Events.SET_RINGING;
723                    break;
724            }
725            if (event != null) {
726                // The string data should be just the tag.
727                String stringData = tag;
728                if (data != null) {
729                    // If data exists, add it to tag.  If no tag, just use data.toString().
730                    stringData = stringData == null ? data.toString() : stringData + "> " + data;
731                }
732                Log.addEvent(this, event, stringData);
733            }
734        }
735    }
736
737    void setRingbackRequested(boolean ringbackRequested) {
738        mRingbackRequested = ringbackRequested;
739        for (Listener l : mListeners) {
740            l.onRingbackRequested(this, mRingbackRequested);
741        }
742    }
743
744    boolean isRingbackRequested() {
745        return mRingbackRequested;
746    }
747
748    @VisibleForTesting
749    public boolean isConference() {
750        return mIsConference;
751    }
752
753    public Uri getHandle() {
754        return mHandle;
755    }
756
757    public String getPostDialDigits() {
758        return mPostDialDigits;
759    }
760
761    public String getViaNumber() {
762        return mViaNumber;
763    }
764
765    public void setViaNumber(String viaNumber) {
766        // If at any point the via number is not empty throughout the call, save that via number.
767        if (!TextUtils.isEmpty(viaNumber)) {
768            mViaNumber = viaNumber;
769        }
770    }
771
772    int getHandlePresentation() {
773        return mHandlePresentation;
774    }
775
776
777    void setHandle(Uri handle) {
778        setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
779    }
780
781    public void setHandle(Uri handle, int presentation) {
782        if (!Objects.equals(handle, mHandle) || presentation != mHandlePresentation) {
783            mHandlePresentation = presentation;
784            if (mHandlePresentation == TelecomManager.PRESENTATION_RESTRICTED ||
785                    mHandlePresentation == TelecomManager.PRESENTATION_UNKNOWN) {
786                mHandle = null;
787            } else {
788                mHandle = handle;
789                if (mHandle != null && !PhoneAccount.SCHEME_VOICEMAIL.equals(mHandle.getScheme())
790                        && TextUtils.isEmpty(mHandle.getSchemeSpecificPart())) {
791                    // If the number is actually empty, set it to null, unless this is a
792                    // SCHEME_VOICEMAIL uri which always has an empty number.
793                    mHandle = null;
794                }
795            }
796
797            // Let's not allow resetting of the emergency flag. Once a call becomes an emergency
798            // call, it will remain so for the rest of it's lifetime.
799            if (!mIsEmergencyCall) {
800                mIsEmergencyCall = mHandle != null &&
801                        mPhoneNumberUtilsAdapter.isLocalEmergencyNumber(mContext,
802                                mHandle.getSchemeSpecificPart());
803            }
804            startCallerInfoLookup();
805            for (Listener l : mListeners) {
806                l.onHandleChanged(this);
807            }
808        }
809    }
810
811    String getCallerDisplayName() {
812        return mCallerDisplayName;
813    }
814
815    int getCallerDisplayNamePresentation() {
816        return mCallerDisplayNamePresentation;
817    }
818
819    void setCallerDisplayName(String callerDisplayName, int presentation) {
820        if (!TextUtils.equals(callerDisplayName, mCallerDisplayName) ||
821                presentation != mCallerDisplayNamePresentation) {
822            mCallerDisplayName = callerDisplayName;
823            mCallerDisplayNamePresentation = presentation;
824            for (Listener l : mListeners) {
825                l.onCallerDisplayNameChanged(this);
826            }
827        }
828    }
829
830    public String getName() {
831        return mCallerInfo == null ? null : mCallerInfo.name;
832    }
833
834    public String getPhoneNumber() {
835        return mCallerInfo == null ? null : mCallerInfo.phoneNumber;
836    }
837
838    public Bitmap getPhotoIcon() {
839        return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
840    }
841
842    public Drawable getPhoto() {
843        return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
844    }
845
846    /**
847     * @param disconnectCause The reason for the disconnection, represented by
848     *         {@link android.telecom.DisconnectCause}.
849     */
850    public void setDisconnectCause(DisconnectCause disconnectCause) {
851        // TODO: Consider combining this method with a setDisconnected() method that is totally
852        // separate from setState.
853        mAnalytics.setCallDisconnectCause(disconnectCause);
854        mDisconnectCause = disconnectCause;
855    }
856
857    public DisconnectCause getDisconnectCause() {
858        return mDisconnectCause;
859    }
860
861    @VisibleForTesting
862    public boolean isEmergencyCall() {
863        return mIsEmergencyCall;
864    }
865
866    /**
867     * @return The original handle this call is associated with. In-call services should use this
868     * handle when indicating in their UI the handle that is being called.
869     */
870    public Uri getOriginalHandle() {
871        if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
872            return mGatewayInfo.getOriginalAddress();
873        }
874        return getHandle();
875    }
876
877    @VisibleForTesting
878    public GatewayInfo getGatewayInfo() {
879        return mGatewayInfo;
880    }
881
882    void setGatewayInfo(GatewayInfo gatewayInfo) {
883        mGatewayInfo = gatewayInfo;
884    }
885
886    @VisibleForTesting
887    public PhoneAccountHandle getConnectionManagerPhoneAccount() {
888        return mConnectionManagerPhoneAccountHandle;
889    }
890
891    @VisibleForTesting
892    public void setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle) {
893        if (!Objects.equals(mConnectionManagerPhoneAccountHandle, accountHandle)) {
894            mConnectionManagerPhoneAccountHandle = accountHandle;
895            for (Listener l : mListeners) {
896                l.onConnectionManagerPhoneAccountChanged(this);
897            }
898        }
899
900    }
901
902    @VisibleForTesting
903    public PhoneAccountHandle getTargetPhoneAccount() {
904        return mTargetPhoneAccountHandle;
905    }
906
907    @VisibleForTesting
908    public void setTargetPhoneAccount(PhoneAccountHandle accountHandle) {
909        if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) {
910            mTargetPhoneAccountHandle = accountHandle;
911            for (Listener l : mListeners) {
912                l.onTargetPhoneAccountChanged(this);
913            }
914            configureIsWorkCall();
915            checkIfVideoCapable();
916        }
917    }
918
919    @VisibleForTesting
920    public boolean isIncoming() {
921        return mCallDirection == CALL_DIRECTION_INCOMING;
922    }
923
924    public boolean isExternalCall() {
925        return (getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) ==
926                Connection.PROPERTY_IS_EXTERNAL_CALL;
927    }
928
929    public boolean isWorkCall() {
930        return mIsWorkCall;
931    }
932
933    public boolean isVideoCallingSupported() {
934        return mIsVideoCallingSupported;
935    }
936
937    private void configureIsWorkCall() {
938        PhoneAccountRegistrar phoneAccountRegistrar = mCallsManager.getPhoneAccountRegistrar();
939        boolean isWorkCall = false;
940        PhoneAccount phoneAccount =
941                phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle);
942        if (phoneAccount != null) {
943            final UserHandle userHandle;
944            if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
945                userHandle = mInitiatingUser;
946            } else {
947                userHandle = mTargetPhoneAccountHandle.getUserHandle();
948            }
949            if (userHandle != null) {
950                isWorkCall = UserUtil.isManagedProfile(mContext, userHandle);
951            }
952        }
953        mIsWorkCall = isWorkCall;
954    }
955
956    /**
957     * Caches the state of the {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} {@link PhoneAccount}
958     * capability.
959     */
960    private void checkIfVideoCapable() {
961        PhoneAccountRegistrar phoneAccountRegistrar = mCallsManager.getPhoneAccountRegistrar();
962        PhoneAccount phoneAccount =
963                phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle);
964        mIsVideoCallingSupported = phoneAccount != null && phoneAccount.hasCapabilities(
965                    PhoneAccount.CAPABILITY_VIDEO_CALLING);
966    }
967
968    boolean shouldAttachToExistingConnection() {
969        return mShouldAttachToExistingConnection;
970    }
971
972    /**
973     * @return The "age" of this call object in milliseconds, which typically also represents the
974     *     period since this call was added to the set pending outgoing calls, see
975     *     mCreationTimeMillis.
976     */
977    @VisibleForTesting
978    public long getAgeMillis() {
979        if (mState == CallState.DISCONNECTED &&
980                (mDisconnectCause.getCode() == DisconnectCause.REJECTED ||
981                 mDisconnectCause.getCode() == DisconnectCause.MISSED)) {
982            // Rejected and missed calls have no age. They're immortal!!
983            return 0;
984        } else if (mConnectTimeMillis == 0) {
985            // Age is measured in the amount of time the call was active. A zero connect time
986            // indicates that we never went active, so return 0 for the age.
987            return 0;
988        } else if (mDisconnectTimeMillis == 0) {
989            // We connected, but have not yet disconnected
990            return System.currentTimeMillis() - mConnectTimeMillis;
991        }
992
993        return mDisconnectTimeMillis - mConnectTimeMillis;
994    }
995
996    /**
997     * @return The time when this call object was created and added to the set of pending outgoing
998     *     calls.
999     */
1000    public long getCreationTimeMillis() {
1001        return mCreationTimeMillis;
1002    }
1003
1004    public void setCreationTimeMillis(long time) {
1005        mCreationTimeMillis = time;
1006    }
1007
1008    long getConnectTimeMillis() {
1009        return mConnectTimeMillis;
1010    }
1011
1012    int getConnectionCapabilities() {
1013        return mConnectionCapabilities;
1014    }
1015
1016    int getConnectionProperties() {
1017        return mConnectionProperties;
1018    }
1019
1020    void setConnectionCapabilities(int connectionCapabilities) {
1021        setConnectionCapabilities(connectionCapabilities, false /* forceUpdate */);
1022    }
1023
1024    void setConnectionCapabilities(int connectionCapabilities, boolean forceUpdate) {
1025        Log.v(this, "setConnectionCapabilities: %s", Connection.capabilitiesToString(
1026                connectionCapabilities));
1027        if (forceUpdate || mConnectionCapabilities != connectionCapabilities) {
1028            // If the phone account does not support video calling, and the connection capabilities
1029            // passed in indicate that the call supports video, remove those video capabilities.
1030            if (!isVideoCallingSupported() && doesCallSupportVideo(connectionCapabilities)) {
1031                Log.w(this, "setConnectionCapabilities: attempt to set connection as video " +
1032                        "capable when not supported by the phone account.");
1033                connectionCapabilities = removeVideoCapabilities(connectionCapabilities);
1034            }
1035
1036            int previousCapabilities = mConnectionCapabilities;
1037            mConnectionCapabilities = connectionCapabilities;
1038            for (Listener l : mListeners) {
1039                l.onConnectionCapabilitiesChanged(this);
1040            }
1041
1042            int xorCaps = previousCapabilities ^ mConnectionCapabilities;
1043            Log.addEvent(this, LogUtils.Events.CAPABILITY_CHANGE,
1044                    "Current: [%s], Removed [%s], Added [%s]",
1045                    Connection.capabilitiesToStringShort(mConnectionCapabilities),
1046                    Connection.capabilitiesToStringShort(previousCapabilities & xorCaps),
1047                    Connection.capabilitiesToStringShort(mConnectionCapabilities & xorCaps));
1048        }
1049    }
1050
1051    void setConnectionProperties(int connectionProperties) {
1052        Log.v(this, "setConnectionProperties: %s", Connection.propertiesToString(
1053                connectionProperties));
1054        if (mConnectionProperties != connectionProperties) {
1055            int previousProperties = mConnectionProperties;
1056            mConnectionProperties = connectionProperties;
1057            for (Listener l : mListeners) {
1058                l.onConnectionPropertiesChanged(this);
1059            }
1060
1061            boolean wasExternal = (previousProperties & Connection.PROPERTY_IS_EXTERNAL_CALL)
1062                    == Connection.PROPERTY_IS_EXTERNAL_CALL;
1063            boolean isExternal = (connectionProperties & Connection.PROPERTY_IS_EXTERNAL_CALL)
1064                    == Connection.PROPERTY_IS_EXTERNAL_CALL;
1065            if (wasExternal != isExternal) {
1066                Log.v(this, "setConnectionProperties: external call changed isExternal = %b",
1067                        isExternal);
1068                Log.addEvent(this, LogUtils.Events.IS_EXTERNAL, isExternal);
1069                for (Listener l : mListeners) {
1070                    l.onExternalCallChanged(this, isExternal);
1071                }
1072
1073            }
1074
1075            mAnalytics.addCallProperties(mConnectionProperties);
1076
1077            int xorProps = previousProperties ^ mConnectionProperties;
1078            Log.addEvent(this, LogUtils.Events.PROPERTY_CHANGE,
1079                    "Current: [%s], Removed [%s], Added [%s]",
1080                    Connection.propertiesToStringShort(mConnectionProperties),
1081                    Connection.propertiesToStringShort(previousProperties & xorProps),
1082                    Connection.propertiesToStringShort(mConnectionProperties & xorProps));
1083        }
1084    }
1085
1086    int getSupportedAudioRoutes() {
1087        return mSupportedAudioRoutes;
1088    }
1089
1090    void setSupportedAudioRoutes(int audioRoutes) {
1091        if (mSupportedAudioRoutes != audioRoutes) {
1092            mSupportedAudioRoutes = audioRoutes;
1093        }
1094    }
1095
1096    @VisibleForTesting
1097    public Call getParentCall() {
1098        return mParentCall;
1099    }
1100
1101    @VisibleForTesting
1102    public List<Call> getChildCalls() {
1103        return mChildCalls;
1104    }
1105
1106    @VisibleForTesting
1107    public boolean wasConferencePreviouslyMerged() {
1108        return mWasConferencePreviouslyMerged;
1109    }
1110
1111    @VisibleForTesting
1112    public Call getConferenceLevelActiveCall() {
1113        return mConferenceLevelActiveCall;
1114    }
1115
1116    @VisibleForTesting
1117    public ConnectionServiceWrapper getConnectionService() {
1118        return mConnectionService;
1119    }
1120
1121    /**
1122     * Retrieves the {@link Context} for the call.
1123     *
1124     * @return The {@link Context}.
1125     */
1126    Context getContext() {
1127        return mContext;
1128    }
1129
1130    @VisibleForTesting
1131    public void setConnectionService(ConnectionServiceWrapper service) {
1132        Preconditions.checkNotNull(service);
1133
1134        clearConnectionService();
1135
1136        service.incrementAssociatedCallCount();
1137        mConnectionService = service;
1138        mAnalytics.setCallConnectionService(service.getComponentName().flattenToShortString());
1139        mConnectionService.addCall(this);
1140    }
1141
1142    /**
1143     * Clears the associated connection service.
1144     */
1145    void clearConnectionService() {
1146        if (mConnectionService != null) {
1147            ConnectionServiceWrapper serviceTemp = mConnectionService;
1148            mConnectionService = null;
1149            serviceTemp.removeCall(this);
1150
1151            // Decrementing the count can cause the service to unbind, which itself can trigger the
1152            // service-death code.  Since the service death code tries to clean up any associated
1153            // calls, we need to make sure to remove that information (e.g., removeCall()) before
1154            // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
1155            // necessary, but cleaning up mConnectionService prior to triggering an unbind is good
1156            // to do.
1157            decrementAssociatedCallCount(serviceTemp);
1158        }
1159    }
1160
1161    /**
1162     * Starts the create connection sequence. Upon completion, there should exist an active
1163     * connection through a connection service (or the call will have failed).
1164     *
1165     * @param phoneAccountRegistrar The phone account registrar.
1166     */
1167    void startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar) {
1168        if (mCreateConnectionProcessor != null) {
1169            Log.w(this, "mCreateConnectionProcessor in startCreateConnection is not null. This is" +
1170                    " due to a race between NewOutgoingCallIntentBroadcaster and " +
1171                    "phoneAccountSelected, but is harmlessly resolved by ignoring the second " +
1172                    "invocation.");
1173            return;
1174        }
1175        mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this,
1176                phoneAccountRegistrar, mContext);
1177        mCreateConnectionProcessor.process();
1178    }
1179
1180    @Override
1181    public void handleCreateConnectionSuccess(
1182            CallIdMapper idMapper,
1183            ParcelableConnection connection) {
1184        Log.v(this, "handleCreateConnectionSuccessful %s", connection);
1185        setTargetPhoneAccount(connection.getPhoneAccount());
1186        setHandle(connection.getHandle(), connection.getHandlePresentation());
1187        setCallerDisplayName(
1188                connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());
1189
1190        setConnectionCapabilities(connection.getConnectionCapabilities());
1191        setConnectionProperties(connection.getConnectionProperties());
1192        setSupportedAudioRoutes(connection.getSupportedAudioRoutes());
1193        setVideoProvider(connection.getVideoProvider());
1194        setVideoState(connection.getVideoState());
1195        setRingbackRequested(connection.isRingbackRequested());
1196        setIsVoipAudioMode(connection.getIsVoipAudioMode());
1197        setStatusHints(connection.getStatusHints());
1198        putExtras(SOURCE_CONNECTION_SERVICE, connection.getExtras());
1199
1200        mConferenceableCalls.clear();
1201        for (String id : connection.getConferenceableConnectionIds()) {
1202            mConferenceableCalls.add(idMapper.getCall(id));
1203        }
1204
1205        switch (mCallDirection) {
1206            case CALL_DIRECTION_INCOMING:
1207                // Listeners (just CallsManager for now) will be responsible for checking whether
1208                // the call should be blocked.
1209                for (Listener l : mListeners) {
1210                    l.onSuccessfulIncomingCall(this);
1211                }
1212                break;
1213            case CALL_DIRECTION_OUTGOING:
1214                for (Listener l : mListeners) {
1215                    l.onSuccessfulOutgoingCall(this,
1216                            getStateFromConnectionState(connection.getState()));
1217                }
1218                break;
1219            case CALL_DIRECTION_UNKNOWN:
1220                for (Listener l : mListeners) {
1221                    l.onSuccessfulUnknownCall(this, getStateFromConnectionState(connection
1222                            .getState()));
1223                }
1224                break;
1225        }
1226    }
1227
1228    @Override
1229    public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
1230        clearConnectionService();
1231        setDisconnectCause(disconnectCause);
1232        mCallsManager.markCallAsDisconnected(this, disconnectCause);
1233
1234        switch (mCallDirection) {
1235            case CALL_DIRECTION_INCOMING:
1236                for (Listener listener : mListeners) {
1237                    listener.onFailedIncomingCall(this);
1238                }
1239                break;
1240            case CALL_DIRECTION_OUTGOING:
1241                for (Listener listener : mListeners) {
1242                    listener.onFailedOutgoingCall(this, disconnectCause);
1243                }
1244                break;
1245            case CALL_DIRECTION_UNKNOWN:
1246                for (Listener listener : mListeners) {
1247                    listener.onFailedUnknownCall(this);
1248                }
1249                break;
1250        }
1251    }
1252
1253    /**
1254     * Plays the specified DTMF tone.
1255     */
1256    void playDtmfTone(char digit) {
1257        if (mConnectionService == null) {
1258            Log.w(this, "playDtmfTone() request on a call without a connection service.");
1259        } else {
1260            Log.i(this, "Send playDtmfTone to connection service for call %s", this);
1261            mConnectionService.playDtmfTone(this, digit);
1262            Log.addEvent(this, LogUtils.Events.START_DTMF, Log.pii(digit));
1263        }
1264    }
1265
1266    /**
1267     * Stops playing any currently playing DTMF tone.
1268     */
1269    void stopDtmfTone() {
1270        if (mConnectionService == null) {
1271            Log.w(this, "stopDtmfTone() request on a call without a connection service.");
1272        } else {
1273            Log.i(this, "Send stopDtmfTone to connection service for call %s", this);
1274            Log.addEvent(this, LogUtils.Events.STOP_DTMF);
1275            mConnectionService.stopDtmfTone(this);
1276        }
1277    }
1278
1279    /**
1280     * Silences the ringer.
1281     */
1282    void silence() {
1283        if (mConnectionService == null) {
1284            Log.w(this, "silence() request on a call without a connection service.");
1285        } else {
1286            Log.i(this, "Send silence to connection service for call %s", this);
1287            Log.addEvent(this, LogUtils.Events.SILENCE);
1288            mConnectionService.silence(this);
1289        }
1290    }
1291
1292    @VisibleForTesting
1293    public void disconnect() {
1294        disconnect(false);
1295    }
1296
1297    /**
1298     * Attempts to disconnect the call through the connection service.
1299     */
1300    @VisibleForTesting
1301    public void disconnect(boolean wasViaNewOutgoingCallBroadcaster) {
1302        Log.addEvent(this, LogUtils.Events.REQUEST_DISCONNECT);
1303
1304        // Track that the call is now locally disconnecting.
1305        setLocallyDisconnecting(true);
1306
1307        if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT ||
1308                mState == CallState.CONNECTING) {
1309            Log.v(this, "Aborting call %s", this);
1310            abort(wasViaNewOutgoingCallBroadcaster);
1311        } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
1312            if (mConnectionService == null) {
1313                Log.e(this, new Exception(), "disconnect() request on a call without a"
1314                        + " connection service.");
1315            } else {
1316                Log.i(this, "Send disconnect to connection service for call: %s", this);
1317                // The call isn't officially disconnected until the connection service
1318                // confirms that the call was actually disconnected. Only then is the
1319                // association between call and connection service severed, see
1320                // {@link CallsManager#markCallAsDisconnected}.
1321                mConnectionService.disconnect(this);
1322            }
1323        }
1324    }
1325
1326    void abort(boolean wasViaNewOutgoingCallBroadcaster) {
1327        if (mCreateConnectionProcessor != null &&
1328                !mCreateConnectionProcessor.isProcessingComplete()) {
1329            mCreateConnectionProcessor.abort();
1330        } else if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT
1331                || mState == CallState.CONNECTING) {
1332            if (wasViaNewOutgoingCallBroadcaster) {
1333                // If the cancelation was from NEW_OUTGOING_CALL, then we do not automatically
1334                // destroy the call.  Instead, we announce the cancelation and CallsManager handles
1335                // it through a timer. Since apps often cancel calls through NEW_OUTGOING_CALL and
1336                // then re-dial them quickly using a gateway, allowing the first call to end
1337                // causes jank. This timeout allows CallsManager to transition the first call into
1338                // the second call so that in-call only ever sees a single call...eliminating the
1339                // jank altogether.
1340                for (Listener listener : mListeners) {
1341                    if (listener.onCanceledViaNewOutgoingCallBroadcast(this)) {
1342                        // The first listener to handle this wins. A return value of true means that
1343                        // the listener will handle the disconnection process later and so we
1344                        // should not continue it here.
1345                        setLocallyDisconnecting(false);
1346                        return;
1347                    }
1348                }
1349            }
1350
1351            handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
1352        } else {
1353            Log.v(this, "Cannot abort a call which is neither SELECT_PHONE_ACCOUNT or CONNECTING");
1354        }
1355    }
1356
1357    /**
1358     * Answers the call if it is ringing.
1359     *
1360     * @param videoState The video state in which to answer the call.
1361     */
1362    @VisibleForTesting
1363    public void answer(int videoState) {
1364        // Check to verify that the call is still in the ringing state. A call can change states
1365        // between the time the user hits 'answer' and Telecom receives the command.
1366        if (isRinging("answer")) {
1367            if (!isVideoCallingSupported() && VideoProfile.isVideo(videoState)) {
1368                // Video calling is not supported, yet the InCallService is attempting to answer as
1369                // video.  We will simply answer as audio-only.
1370                videoState = VideoProfile.STATE_AUDIO_ONLY;
1371            }
1372            // At this point, we are asking the connection service to answer but we don't assume
1373            // that it will work. Instead, we wait until confirmation from the connectino service
1374            // that the call is in a non-STATE_RINGING state before changing the UI. See
1375            // {@link ConnectionServiceAdapter#setActive} and other set* methods.
1376            if (mConnectionService != null) {
1377                mConnectionService.answer(this, videoState);
1378            } else {
1379                Log.e(this, new NullPointerException(),
1380                        "answer call failed due to null CS callId=%s", getId());
1381            }
1382            Log.addEvent(this, LogUtils.Events.REQUEST_ACCEPT);
1383        }
1384    }
1385
1386    /**
1387     * Rejects the call if it is ringing.
1388     *
1389     * @param rejectWithMessage Whether to send a text message as part of the call rejection.
1390     * @param textMessage An optional text message to send as part of the rejection.
1391     */
1392    @VisibleForTesting
1393    public void reject(boolean rejectWithMessage, String textMessage) {
1394        // Check to verify that the call is still in the ringing state. A call can change states
1395        // between the time the user hits 'reject' and Telecomm receives the command.
1396        if (isRinging("reject")) {
1397            // Ensure video state history tracks video state at time of rejection.
1398            mVideoStateHistory |= mVideoState;
1399
1400            if (mConnectionService != null) {
1401                mConnectionService.reject(this, rejectWithMessage, textMessage);
1402            } else {
1403                Log.e(this, new NullPointerException(),
1404                        "reject call failed due to null CS callId=%s", getId());
1405            }
1406            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT);
1407        }
1408    }
1409
1410    /**
1411     * Puts the call on hold if it is currently active.
1412     */
1413    void hold() {
1414        if (mState == CallState.ACTIVE) {
1415            if (mConnectionService != null) {
1416                mConnectionService.hold(this);
1417            } else {
1418                Log.e(this, new NullPointerException(),
1419                        "hold call failed due to null CS callId=%s", getId());
1420            }
1421            Log.addEvent(this, LogUtils.Events.REQUEST_HOLD);
1422        }
1423    }
1424
1425    /**
1426     * Releases the call from hold if it is currently active.
1427     */
1428    void unhold() {
1429        if (mState == CallState.ON_HOLD) {
1430            if (mConnectionService != null) {
1431                mConnectionService.unhold(this);
1432            } else {
1433                Log.e(this, new NullPointerException(),
1434                        "unhold call failed due to null CS callId=%s", getId());
1435            }
1436            Log.addEvent(this, LogUtils.Events.REQUEST_UNHOLD);
1437        }
1438    }
1439
1440    /** Checks if this is a live call or not. */
1441    @VisibleForTesting
1442    public boolean isAlive() {
1443        switch (mState) {
1444            case CallState.NEW:
1445            case CallState.RINGING:
1446            case CallState.DISCONNECTED:
1447            case CallState.ABORTED:
1448                return false;
1449            default:
1450                return true;
1451        }
1452    }
1453
1454    boolean isActive() {
1455        return mState == CallState.ACTIVE;
1456    }
1457
1458    Bundle getExtras() {
1459        return mExtras;
1460    }
1461
1462    /**
1463     * Adds extras to the extras bundle associated with this {@link Call}.
1464     *
1465     * Note: this method needs to know the source of the extras change (see
1466     * {@link #SOURCE_CONNECTION_SERVICE}, {@link #SOURCE_INCALL_SERVICE}).  Extras changes which
1467     * originate from a connection service will only be notified to incall services.  Likewise,
1468     * changes originating from the incall services will only notify the connection service of the
1469     * change.
1470     *
1471     * @param source The source of the extras addition.
1472     * @param extras The extras.
1473     */
1474    void putExtras(int source, Bundle extras) {
1475        if (extras == null) {
1476            return;
1477        }
1478        if (mExtras == null) {
1479            mExtras = new Bundle();
1480        }
1481        mExtras.putAll(extras);
1482
1483        for (Listener l : mListeners) {
1484            l.onExtrasChanged(this, source, extras);
1485        }
1486
1487        // If the change originated from an InCallService, notify the connection service.
1488        if (source == SOURCE_INCALL_SERVICE) {
1489            if (mConnectionService != null) {
1490                mConnectionService.onExtrasChanged(this, mExtras);
1491            } else {
1492                Log.e(this, new NullPointerException(),
1493                        "putExtras failed due to null CS callId=%s", getId());
1494            }
1495        }
1496    }
1497
1498    /**
1499     * Removes extras from the extras bundle associated with this {@link Call}.
1500     *
1501     * Note: this method needs to know the source of the extras change (see
1502     * {@link #SOURCE_CONNECTION_SERVICE}, {@link #SOURCE_INCALL_SERVICE}).  Extras changes which
1503     * originate from a connection service will only be notified to incall services.  Likewise,
1504     * changes originating from the incall services will only notify the connection service of the
1505     * change.
1506     *
1507     * @param source The source of the extras removal.
1508     * @param keys The extra keys to remove.
1509     */
1510    void removeExtras(int source, List<String> keys) {
1511        if (mExtras == null) {
1512            return;
1513        }
1514        for (String key : keys) {
1515            mExtras.remove(key);
1516        }
1517
1518        for (Listener l : mListeners) {
1519            l.onExtrasRemoved(this, source, keys);
1520        }
1521
1522        // If the change originated from an InCallService, notify the connection service.
1523        if (source == SOURCE_INCALL_SERVICE) {
1524            if (mConnectionService != null) {
1525                mConnectionService.onExtrasChanged(this, mExtras);
1526            } else {
1527                Log.e(this, new NullPointerException(),
1528                        "removeExtras failed due to null CS callId=%s", getId());
1529            }
1530        }
1531    }
1532
1533    @VisibleForTesting
1534    public Bundle getIntentExtras() {
1535        return mIntentExtras;
1536    }
1537
1538    void setIntentExtras(Bundle extras) {
1539        mIntentExtras = extras;
1540    }
1541
1542    /**
1543     * @return the uri of the contact associated with this call.
1544     */
1545    @VisibleForTesting
1546    public Uri getContactUri() {
1547        if (mCallerInfo == null || !mCallerInfo.contactExists) {
1548            return getHandle();
1549        }
1550        return Contacts.getLookupUri(mCallerInfo.contactIdOrZero, mCallerInfo.lookupKey);
1551    }
1552
1553    Uri getRingtone() {
1554        return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
1555    }
1556
1557    void onPostDialWait(String remaining) {
1558        for (Listener l : mListeners) {
1559            l.onPostDialWait(this, remaining);
1560        }
1561    }
1562
1563    void onPostDialChar(char nextChar) {
1564        for (Listener l : mListeners) {
1565            l.onPostDialChar(this, nextChar);
1566        }
1567    }
1568
1569    void postDialContinue(boolean proceed) {
1570        if (mConnectionService != null) {
1571            mConnectionService.onPostDialContinue(this, proceed);
1572        } else {
1573            Log.e(this, new NullPointerException(),
1574                    "postDialContinue failed due to null CS callId=%s", getId());
1575        }
1576    }
1577
1578    void conferenceWith(Call otherCall) {
1579        if (mConnectionService == null) {
1580            Log.w(this, "conference requested on a call without a connection service.");
1581        } else {
1582            Log.addEvent(this, LogUtils.Events.CONFERENCE_WITH, otherCall);
1583            mConnectionService.conference(this, otherCall);
1584        }
1585    }
1586
1587    void splitFromConference() {
1588        if (mConnectionService == null) {
1589            Log.w(this, "splitting from conference call without a connection service");
1590        } else {
1591            Log.addEvent(this, LogUtils.Events.SPLIT_FROM_CONFERENCE);
1592            mConnectionService.splitFromConference(this);
1593        }
1594    }
1595
1596    @VisibleForTesting
1597    public void mergeConference() {
1598        if (mConnectionService == null) {
1599            Log.w(this, "merging conference calls without a connection service.");
1600        } else if (can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
1601            Log.addEvent(this, LogUtils.Events.CONFERENCE_WITH);
1602            mConnectionService.mergeConference(this);
1603            mWasConferencePreviouslyMerged = true;
1604        }
1605    }
1606
1607    @VisibleForTesting
1608    public void swapConference() {
1609        if (mConnectionService == null) {
1610            Log.w(this, "swapping conference calls without a connection service.");
1611        } else if (can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
1612            Log.addEvent(this, LogUtils.Events.SWAP);
1613            mConnectionService.swapConference(this);
1614            switch (mChildCalls.size()) {
1615                case 1:
1616                    mConferenceLevelActiveCall = mChildCalls.get(0);
1617                    break;
1618                case 2:
1619                    // swap
1620                    mConferenceLevelActiveCall = mChildCalls.get(0) == mConferenceLevelActiveCall ?
1621                            mChildCalls.get(1) : mChildCalls.get(0);
1622                    break;
1623                default:
1624                    // For anything else 0, or 3+, set it to null since it is impossible to tell.
1625                    mConferenceLevelActiveCall = null;
1626                    break;
1627            }
1628        }
1629    }
1630
1631    /**
1632     * Initiates a request to the connection service to pull this call.
1633     * <p>
1634     * This method can only be used for calls that have the
1635     * {@link android.telecom.Connection#CAPABILITY_CAN_PULL_CALL} capability and
1636     * {@link android.telecom.Connection#PROPERTY_IS_EXTERNAL_CALL} property set.
1637     * <p>
1638     * An external call is a representation of a call which is taking place on another device
1639     * associated with a PhoneAccount on this device.  Issuing a request to pull the external call
1640     * tells the {@link android.telecom.ConnectionService} that it should move the call from the
1641     * other device to this one.  An example of this is the IMS multi-endpoint functionality.  A
1642     * user may have two phones with the same phone number.  If the user is engaged in an active
1643     * call on their first device, the network will inform the second device of that ongoing call in
1644     * the form of an external call.  The user may wish to continue their conversation on the second
1645     * device, so will issue a request to pull the call to the second device.
1646     * <p>
1647     * Requests to pull a call which is not external, or a call which is not pullable are ignored.
1648     */
1649    public void pullExternalCall() {
1650        if (mConnectionService == null) {
1651            Log.w(this, "pulling a call without a connection service.");
1652        }
1653
1654        if (!hasProperty(Connection.PROPERTY_IS_EXTERNAL_CALL)) {
1655            Log.w(this, "pullExternalCall - call %s is not an external call.", mId);
1656            return;
1657        }
1658
1659        if (!can(Connection.CAPABILITY_CAN_PULL_CALL)) {
1660            Log.w(this, "pullExternalCall - call %s is external but cannot be pulled.", mId);
1661            return;
1662        }
1663
1664        Log.addEvent(this, LogUtils.Events.REQUEST_PULL);
1665        mConnectionService.pullExternalCall(this);
1666    }
1667
1668    /**
1669     * Sends a call event to the {@link ConnectionService} for this call.
1670     *
1671     * See {@link Call#sendCallEvent(String, Bundle)}.
1672     *
1673     * @param event The call event.
1674     * @param extras Associated extras.
1675     */
1676    public void sendCallEvent(String event, Bundle extras) {
1677        if (mConnectionService != null) {
1678            mConnectionService.sendCallEvent(this, event, extras);
1679        } else {
1680            Log.e(this, new NullPointerException(),
1681                    "sendCallEvent failed due to null CS callId=%s", getId());
1682        }
1683    }
1684
1685    void setParentCall(Call parentCall) {
1686        if (parentCall == this) {
1687            Log.e(this, new Exception(), "setting the parent to self");
1688            return;
1689        }
1690        if (parentCall == mParentCall) {
1691            // nothing to do
1692            return;
1693        }
1694        Preconditions.checkState(parentCall == null || mParentCall == null);
1695
1696        Call oldParent = mParentCall;
1697        if (mParentCall != null) {
1698            mParentCall.removeChildCall(this);
1699        }
1700        mParentCall = parentCall;
1701        if (mParentCall != null) {
1702            mParentCall.addChildCall(this);
1703        }
1704
1705        Log.addEvent(this, LogUtils.Events.SET_PARENT, mParentCall);
1706        for (Listener l : mListeners) {
1707            l.onParentChanged(this);
1708        }
1709    }
1710
1711    void setConferenceableCalls(List<Call> conferenceableCalls) {
1712        mConferenceableCalls.clear();
1713        mConferenceableCalls.addAll(conferenceableCalls);
1714
1715        for (Listener l : mListeners) {
1716            l.onConferenceableCallsChanged(this);
1717        }
1718    }
1719
1720    @VisibleForTesting
1721    public List<Call> getConferenceableCalls() {
1722        return mConferenceableCalls;
1723    }
1724
1725    @VisibleForTesting
1726    public boolean can(int capability) {
1727        return (mConnectionCapabilities & capability) == capability;
1728    }
1729
1730    @VisibleForTesting
1731    public boolean hasProperty(int property) {
1732        return (mConnectionProperties & property) == property;
1733    }
1734
1735    private void addChildCall(Call call) {
1736        if (!mChildCalls.contains(call)) {
1737            // Set the pseudo-active call to the latest child added to the conference.
1738            // See definition of mConferenceLevelActiveCall for more detail.
1739            mConferenceLevelActiveCall = call;
1740            mChildCalls.add(call);
1741
1742            Log.addEvent(this, LogUtils.Events.ADD_CHILD, call);
1743
1744            for (Listener l : mListeners) {
1745                l.onChildrenChanged(this);
1746            }
1747        }
1748    }
1749
1750    private void removeChildCall(Call call) {
1751        if (mChildCalls.remove(call)) {
1752            Log.addEvent(this, LogUtils.Events.REMOVE_CHILD, call);
1753            for (Listener l : mListeners) {
1754                l.onChildrenChanged(this);
1755            }
1756        }
1757    }
1758
1759    /**
1760     * Return whether the user can respond to this {@code Call} via an SMS message.
1761     *
1762     * @return true if the "Respond via SMS" feature should be enabled
1763     * for this incoming call.
1764     *
1765     * The general rule is that we *do* allow "Respond via SMS" except for
1766     * the few (relatively rare) cases where we know for sure it won't
1767     * work, namely:
1768     *   - a bogus or blank incoming number
1769     *   - a call from a SIP address
1770     *   - a "call presentation" that doesn't allow the number to be revealed
1771     *
1772     * In all other cases, we allow the user to respond via SMS.
1773     *
1774     * Note that this behavior isn't perfect; for example we have no way
1775     * to detect whether the incoming call is from a landline (with most
1776     * networks at least), so we still enable this feature even though
1777     * SMSes to that number will silently fail.
1778     */
1779    boolean isRespondViaSmsCapable() {
1780        if (mState != CallState.RINGING) {
1781            return false;
1782        }
1783
1784        if (getHandle() == null) {
1785            // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in
1786            // other words, the user should not be able to see the incoming phone number.
1787            return false;
1788        }
1789
1790        if (mPhoneNumberUtilsAdapter.isUriNumber(getHandle().toString())) {
1791            // The incoming number is actually a URI (i.e. a SIP address),
1792            // not a regular PSTN phone number, and we can't send SMSes to
1793            // SIP addresses.
1794            // (TODO: That might still be possible eventually, though. Is
1795            // there some SIP-specific equivalent to sending a text message?)
1796            return false;
1797        }
1798
1799        // Is there a valid SMS application on the phone?
1800        if (SmsApplication.getDefaultRespondViaMessageApplication(mContext,
1801                true /*updateIfNeeded*/) == null) {
1802            return false;
1803        }
1804
1805        // TODO: with some carriers (in certain countries) you *can* actually
1806        // tell whether a given number is a mobile phone or not. So in that
1807        // case we could potentially return false here if the incoming call is
1808        // from a land line.
1809
1810        // If none of the above special cases apply, it's OK to enable the
1811        // "Respond via SMS" feature.
1812        return true;
1813    }
1814
1815    List<String> getCannedSmsResponses() {
1816        return mCannedSmsResponses;
1817    }
1818
1819    /**
1820     * We need to make sure that before we move a call to the disconnected state, it no
1821     * longer has any parent/child relationships.  We want to do this to ensure that the InCall
1822     * Service always has the right data in the right order.  We also want to do it in telecom so
1823     * that the insurance policy lives in the framework side of things.
1824     */
1825    private void fixParentAfterDisconnect() {
1826        setParentCall(null);
1827    }
1828
1829    /**
1830     * @return True if the call is ringing, else logs the action name.
1831     */
1832    private boolean isRinging(String actionName) {
1833        if (mState == CallState.RINGING) {
1834            return true;
1835        }
1836
1837        Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
1838        return false;
1839    }
1840
1841    @SuppressWarnings("rawtypes")
1842    private void decrementAssociatedCallCount(ServiceBinder binder) {
1843        if (binder != null) {
1844            binder.decrementAssociatedCallCount();
1845        }
1846    }
1847
1848    /**
1849     * Looks up contact information based on the current handle.
1850     */
1851    private void startCallerInfoLookup() {
1852        mCallerInfo = null;
1853        mCallsManager.getCallerInfoLookupHelper().startLookup(mHandle, mCallerInfoQueryListener);
1854    }
1855
1856    /**
1857     * Saves the specified caller info if the specified token matches that of the last query
1858     * that was made.
1859     *
1860     * @param callerInfo The new caller information to set.
1861     */
1862    private void setCallerInfo(Uri handle, CallerInfo callerInfo) {
1863        Trace.beginSection("setCallerInfo");
1864        if (callerInfo == null) {
1865            Log.i(this, "CallerInfo lookup returned null, skipping update");
1866            return;
1867        }
1868
1869        if (!handle.equals(mHandle)) {
1870            Log.i(this, "setCallerInfo received stale caller info for an old handle. Ignoring.");
1871            return;
1872        }
1873
1874        mCallerInfo = callerInfo;
1875        Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
1876
1877        if (mCallerInfo.contactDisplayPhotoUri == null ||
1878                mCallerInfo.cachedPhotoIcon != null || mCallerInfo.cachedPhoto != null) {
1879            for (Listener l : mListeners) {
1880                l.onCallerInfoChanged(this);
1881            }
1882        }
1883
1884        Trace.endSection();
1885    }
1886
1887    public CallerInfo getCallerInfo() {
1888        return mCallerInfo;
1889    }
1890
1891    private void maybeLoadCannedSmsResponses() {
1892        if (mCallDirection == CALL_DIRECTION_INCOMING
1893                && isRespondViaSmsCapable()
1894                && !mCannedSmsResponsesLoadingStarted) {
1895            Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
1896            mCannedSmsResponsesLoadingStarted = true;
1897            mCallsManager.getRespondViaSmsManager().loadCannedTextMessages(
1898                    new Response<Void, List<String>>() {
1899                        @Override
1900                        public void onResult(Void request, List<String>... result) {
1901                            if (result.length > 0) {
1902                                Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]);
1903                                mCannedSmsResponses = result[0];
1904                                for (Listener l : mListeners) {
1905                                    l.onCannedSmsResponsesLoaded(Call.this);
1906                                }
1907                            }
1908                        }
1909
1910                        @Override
1911                        public void onError(Void request, int code, String msg) {
1912                            Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code,
1913                                    msg);
1914                        }
1915                    },
1916                    mContext
1917            );
1918        } else {
1919            Log.d(this, "maybeLoadCannedSmsResponses: doing nothing");
1920        }
1921    }
1922
1923    /**
1924     * Sets speakerphone option on when call begins.
1925     */
1926    public void setStartWithSpeakerphoneOn(boolean startWithSpeakerphone) {
1927        mSpeakerphoneOn = startWithSpeakerphone;
1928    }
1929
1930    /**
1931     * Returns speakerphone option.
1932     *
1933     * @return Whether or not speakerphone should be set automatically when call begins.
1934     */
1935    public boolean getStartWithSpeakerphoneOn() {
1936        return mSpeakerphoneOn;
1937    }
1938
1939    /**
1940     * Sets a video call provider for the call.
1941     */
1942    public void setVideoProvider(IVideoProvider videoProvider) {
1943        Log.v(this, "setVideoProvider");
1944
1945        if (videoProvider != null ) {
1946            try {
1947                mVideoProviderProxy = new VideoProviderProxy(mLock, videoProvider, this);
1948            } catch (RemoteException ignored) {
1949                // Ignore RemoteException.
1950            }
1951        } else {
1952            mVideoProviderProxy = null;
1953        }
1954
1955        mVideoProvider = videoProvider;
1956
1957        for (Listener l : mListeners) {
1958            l.onVideoCallProviderChanged(Call.this);
1959        }
1960    }
1961
1962    /**
1963     * @return The {@link Connection.VideoProvider} binder.
1964     */
1965    public IVideoProvider getVideoProvider() {
1966        if (mVideoProviderProxy == null) {
1967            return null;
1968        }
1969
1970        return mVideoProviderProxy.getInterface();
1971    }
1972
1973    /**
1974     * @return The {@link VideoProviderProxy} for this call.
1975     */
1976    public VideoProviderProxy getVideoProviderProxy() {
1977        return mVideoProviderProxy;
1978    }
1979
1980    /**
1981     * The current video state for the call.
1982     * See {@link VideoProfile} for a list of valid video states.
1983     */
1984    public int getVideoState() {
1985        return mVideoState;
1986    }
1987
1988    /**
1989     * Returns the video states which were applicable over the duration of a call.
1990     * See {@link VideoProfile} for a list of valid video states.
1991     *
1992     * @return The video states applicable over the duration of the call.
1993     */
1994    public int getVideoStateHistory() {
1995        return mVideoStateHistory;
1996    }
1997
1998    /**
1999     * Determines the current video state for the call.
2000     * For an outgoing call determines the desired video state for the call.
2001     * Valid values: see {@link VideoProfile}
2002     *
2003     * @param videoState The video state for the call.
2004     */
2005    public void setVideoState(int videoState) {
2006        // If the phone account associated with this call does not support video calling, then we
2007        // will automatically set the video state to audio-only.
2008        if (!isVideoCallingSupported()) {
2009            videoState = VideoProfile.STATE_AUDIO_ONLY;
2010        }
2011
2012        // Track which video states were applicable over the duration of the call.
2013        // Only track the call state when the call is active or disconnected.  This ensures we do
2014        // not include the video state when:
2015        // - Call is incoming (but not answered).
2016        // - Call it outgoing (but not answered).
2017        // We include the video state when disconnected to ensure that rejected calls reflect the
2018        // appropriate video state.
2019        if (isActive() || getState() == CallState.DISCONNECTED) {
2020            mVideoStateHistory = mVideoStateHistory | videoState;
2021        }
2022
2023        int previousVideoState = mVideoState;
2024        mVideoState = videoState;
2025        if (mVideoState != previousVideoState) {
2026            Log.addEvent(this, LogUtils.Events.VIDEO_STATE_CHANGED,
2027                    VideoProfile.videoStateToString(videoState));
2028            for (Listener l : mListeners) {
2029                l.onVideoStateChanged(this, previousVideoState, mVideoState);
2030            }
2031        }
2032
2033        if (VideoProfile.isVideo(videoState)) {
2034            mAnalytics.setCallIsVideo(true);
2035        }
2036    }
2037
2038    public boolean getIsVoipAudioMode() {
2039        return mIsVoipAudioMode;
2040    }
2041
2042    public void setIsVoipAudioMode(boolean audioModeIsVoip) {
2043        mIsVoipAudioMode = audioModeIsVoip;
2044        for (Listener l : mListeners) {
2045            l.onIsVoipAudioModeChanged(this);
2046        }
2047    }
2048
2049    public StatusHints getStatusHints() {
2050        return mStatusHints;
2051    }
2052
2053    public void setStatusHints(StatusHints statusHints) {
2054        mStatusHints = statusHints;
2055        for (Listener l : mListeners) {
2056            l.onStatusHintsChanged(this);
2057        }
2058    }
2059
2060    public boolean isUnknown() {
2061        return mCallDirection == CALL_DIRECTION_UNKNOWN;
2062    }
2063
2064    /**
2065     * Determines if this call is in a disconnecting state.
2066     *
2067     * @return {@code true} if this call is locally disconnecting.
2068     */
2069    public boolean isLocallyDisconnecting() {
2070        return mIsLocallyDisconnecting;
2071    }
2072
2073    /**
2074     * Sets whether this call is in a disconnecting state.
2075     *
2076     * @param isLocallyDisconnecting {@code true} if this call is locally disconnecting.
2077     */
2078    private void setLocallyDisconnecting(boolean isLocallyDisconnecting) {
2079        mIsLocallyDisconnecting = isLocallyDisconnecting;
2080    }
2081
2082    /**
2083     * @return user handle of user initiating the outgoing call.
2084     */
2085    public UserHandle getInitiatingUser() {
2086        return mInitiatingUser;
2087    }
2088
2089    /**
2090     * Set the user handle of user initiating the outgoing call.
2091     * @param initiatingUser
2092     */
2093    public void setInitiatingUser(UserHandle initiatingUser) {
2094        Preconditions.checkNotNull(initiatingUser);
2095        mInitiatingUser = initiatingUser;
2096    }
2097
2098    static int getStateFromConnectionState(int state) {
2099        switch (state) {
2100            case Connection.STATE_INITIALIZING:
2101                return CallState.CONNECTING;
2102            case Connection.STATE_ACTIVE:
2103                return CallState.ACTIVE;
2104            case Connection.STATE_DIALING:
2105                return CallState.DIALING;
2106            case Connection.STATE_PULLING_CALL:
2107                return CallState.PULLING;
2108            case Connection.STATE_DISCONNECTED:
2109                return CallState.DISCONNECTED;
2110            case Connection.STATE_HOLDING:
2111                return CallState.ON_HOLD;
2112            case Connection.STATE_NEW:
2113                return CallState.NEW;
2114            case Connection.STATE_RINGING:
2115                return CallState.RINGING;
2116        }
2117        return CallState.DISCONNECTED;
2118    }
2119
2120    /**
2121     * Determines if this call is in disconnected state and waiting to be destroyed.
2122     *
2123     * @return {@code true} if this call is disconected.
2124     */
2125    public boolean isDisconnected() {
2126        return (getState() == CallState.DISCONNECTED || getState() == CallState.ABORTED);
2127    }
2128
2129    /**
2130     * Determines if this call has just been created and has not been configured properly yet.
2131     *
2132     * @return {@code true} if this call is new.
2133     */
2134    public boolean isNew() {
2135        return getState() == CallState.NEW;
2136    }
2137
2138    /**
2139     * Sets the call data usage for the call.
2140     *
2141     * @param callDataUsage The new call data usage (in bytes).
2142     */
2143    public void setCallDataUsage(long callDataUsage) {
2144        mCallDataUsage = callDataUsage;
2145    }
2146
2147    /**
2148     * Returns the call data usage for the call.
2149     *
2150     * @return The call data usage (in bytes).
2151     */
2152    public long getCallDataUsage() {
2153        return mCallDataUsage;
2154    }
2155
2156    /**
2157     * Returns true if the call is outgoing and the NEW_OUTGOING_CALL ordered broadcast intent
2158     * has come back to telecom and was processed.
2159     */
2160    public boolean isNewOutgoingCallIntentBroadcastDone() {
2161        return mIsNewOutgoingCallIntentBroadcastDone;
2162    }
2163
2164    public void setNewOutgoingCallIntentBroadcastIsDone() {
2165        mIsNewOutgoingCallIntentBroadcastDone = true;
2166    }
2167
2168    /**
2169     * Determines if the call has been held by the remote party.
2170     *
2171     * @return {@code true} if the call is remotely held, {@code false} otherwise.
2172     */
2173    public boolean isRemotelyHeld() {
2174        return mIsRemotelyHeld;
2175    }
2176
2177    /**
2178     * Handles Connection events received from a {@link ConnectionService}.
2179     *
2180     * @param event The event.
2181     * @param extras The extras.
2182     */
2183    public void onConnectionEvent(String event, Bundle extras) {
2184        Log.addEvent(this, LogUtils.Events.CONNECTION_EVENT, event);
2185        if (Connection.EVENT_ON_HOLD_TONE_START.equals(event)) {
2186            mIsRemotelyHeld = true;
2187            Log.addEvent(this, LogUtils.Events.REMOTELY_HELD);
2188            // Inform listeners of the fact that a call hold tone was received.  This will trigger
2189            // the CallAudioManager to play a tone via the InCallTonePlayer.
2190            for (Listener l : mListeners) {
2191                l.onHoldToneRequested(this);
2192            }
2193        } else if (Connection.EVENT_ON_HOLD_TONE_END.equals(event)) {
2194            mIsRemotelyHeld = false;
2195            Log.addEvent(this, LogUtils.Events.REMOTELY_UNHELD);
2196            for (Listener l : mListeners) {
2197                l.onHoldToneRequested(this);
2198            }
2199        } else {
2200            for (Listener l : mListeners) {
2201                l.onConnectionEvent(this, event, extras);
2202            }
2203        }
2204    }
2205
2206    /**
2207     * Determines if a {@link Call}'s capabilities bitmask indicates that video is supported either
2208     * remotely or locally.
2209     *
2210     * @param capabilities The {@link Connection} capabilities for the call.
2211     * @return {@code true} if video is supported, {@code false} otherwise.
2212     */
2213    private boolean doesCallSupportVideo(int capabilities) {
2214        return (capabilities & Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL) != 0 ||
2215                (capabilities & Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL) != 0;
2216    }
2217
2218    /**
2219     * Remove any video capabilities set on a {@link Connection} capabilities bitmask.
2220     *
2221     * @param capabilities The capabilities.
2222     * @return The bitmask with video capabilities removed.
2223     */
2224    private int removeVideoCapabilities(int capabilities) {
2225        return capabilities & ~(Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL |
2226                Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
2227    }
2228}
2229