Call.java revision 587fc27fafcab87738a3be1c9a74b435ad0d97b4
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.content.Intent;
21import android.graphics.Bitmap;
22import android.graphics.drawable.Drawable;
23import android.net.Uri;
24import android.os.Build;
25import android.os.Bundle;
26import android.os.Handler;
27import android.os.Looper;
28import android.os.ParcelFileDescriptor;
29import android.os.Parcelable;
30import android.os.RemoteException;
31import android.os.SystemClock;
32import android.os.Trace;
33import android.provider.ContactsContract.Contacts;
34import android.telecom.CallAudioState;
35import android.telecom.Conference;
36import android.telecom.ConnectionService;
37import android.telecom.DisconnectCause;
38import android.telecom.Connection;
39import android.telecom.GatewayInfo;
40import android.telecom.Log;
41import android.telecom.Logging.EventManager;
42import android.telecom.ParcelableConnection;
43import android.telecom.PhoneAccount;
44import android.telecom.PhoneAccountHandle;
45import android.telecom.Response;
46import android.telecom.StatusHints;
47import android.telecom.TelecomManager;
48import android.telecom.VideoProfile;
49import android.telephony.PhoneNumberUtils;
50import android.text.TextUtils;
51import android.os.UserHandle;
52
53import com.android.internal.annotations.VisibleForTesting;
54import com.android.internal.telecom.IVideoProvider;
55import com.android.internal.telephony.CallerInfo;
56import com.android.internal.telephony.SmsApplication;
57import com.android.internal.util.Preconditions;
58
59import java.io.IOException;
60import java.lang.String;
61import java.text.SimpleDateFormat;
62import java.util.ArrayList;
63import java.util.Collections;
64import java.util.Date;
65import java.util.LinkedList;
66import java.util.List;
67import java.util.Locale;
68import java.util.Objects;
69import java.util.Set;
70import java.util.concurrent.ConcurrentHashMap;
71
72/**
73 *  Encapsulates all aspects of a given phone call throughout its lifecycle, starting
74 *  from the time the call intent was received by Telecom (vs. the time the call was
75 *  connected etc).
76 */
77@VisibleForTesting
78public class Call implements CreateConnectionResponse, EventManager.Loggable,
79        ConnectionServiceFocusManager.CallFocus {
80    public final static String CALL_ID_UNKNOWN = "-1";
81    public final static long DATA_USAGE_NOT_SET = -1;
82
83    public static final int CALL_DIRECTION_UNDEFINED = 0;
84    public static final int CALL_DIRECTION_OUTGOING = 1;
85    public static final int CALL_DIRECTION_INCOMING = 2;
86    public static final int CALL_DIRECTION_UNKNOWN = 3;
87
88    /** Identifies extras changes which originated from a connection service. */
89    public static final int SOURCE_CONNECTION_SERVICE = 1;
90    /** Identifies extras changes which originated from an incall service. */
91    public static final int SOURCE_INCALL_SERVICE = 2;
92
93    private static final int RTT_PIPE_READ_SIDE_INDEX = 0;
94    private static final int RTT_PIPE_WRITE_SIDE_INDEX = 1;
95
96    private static final int INVALID_RTT_REQUEST_ID = -1;
97
98    private static final char NO_DTMF_TONE = '\0';
99
100    /**
101     * Listener for events on the call.
102     */
103    @VisibleForTesting
104    public interface Listener {
105        void onSuccessfulOutgoingCall(Call call, int callState);
106        void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause);
107        void onSuccessfulIncomingCall(Call call);
108        void onFailedIncomingCall(Call call);
109        void onSuccessfulUnknownCall(Call call, int callState);
110        void onFailedUnknownCall(Call call);
111        void onRingbackRequested(Call call, boolean ringbackRequested);
112        void onPostDialWait(Call call, String remaining);
113        void onPostDialChar(Call call, char nextChar);
114        void onConnectionCapabilitiesChanged(Call call);
115        void onConnectionPropertiesChanged(Call call, boolean didRttChange);
116        void onParentChanged(Call call);
117        void onChildrenChanged(Call call);
118        void onCannedSmsResponsesLoaded(Call call);
119        void onVideoCallProviderChanged(Call call);
120        void onCallerInfoChanged(Call call);
121        void onIsVoipAudioModeChanged(Call call);
122        void onStatusHintsChanged(Call call);
123        void onExtrasChanged(Call c, int source, Bundle extras);
124        void onExtrasRemoved(Call c, int source, List<String> keys);
125        void onHandleChanged(Call call);
126        void onCallerDisplayNameChanged(Call call);
127        void onVideoStateChanged(Call call, int previousVideoState, int newVideoState);
128        void onTargetPhoneAccountChanged(Call call);
129        void onConnectionManagerPhoneAccountChanged(Call call);
130        void onPhoneAccountChanged(Call call);
131        void onConferenceableCallsChanged(Call call);
132        boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout);
133        void onHoldToneRequested(Call call);
134        void onConnectionEvent(Call call, String event, Bundle extras);
135        void onExternalCallChanged(Call call, boolean isExternalCall);
136        void onRttInitiationFailure(Call call, int reason);
137        void onRemoteRttRequest(Call call, int requestId);
138        void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState,
139                                 Bundle extras, boolean isLegacy);
140        void onHandoverFailed(Call call, int error);
141        void onHandoverComplete(Call call);
142    }
143
144    public abstract static class ListenerBase implements Listener {
145        @Override
146        public void onSuccessfulOutgoingCall(Call call, int callState) {}
147        @Override
148        public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {}
149        @Override
150        public void onSuccessfulIncomingCall(Call call) {}
151        @Override
152        public void onFailedIncomingCall(Call call) {}
153        @Override
154        public void onSuccessfulUnknownCall(Call call, int callState) {}
155        @Override
156        public void onFailedUnknownCall(Call call) {}
157        @Override
158        public void onRingbackRequested(Call call, boolean ringbackRequested) {}
159        @Override
160        public void onPostDialWait(Call call, String remaining) {}
161        @Override
162        public void onPostDialChar(Call call, char nextChar) {}
163        @Override
164        public void onConnectionCapabilitiesChanged(Call call) {}
165        @Override
166        public void onConnectionPropertiesChanged(Call call, boolean didRttChange) {}
167        @Override
168        public void onParentChanged(Call call) {}
169        @Override
170        public void onChildrenChanged(Call call) {}
171        @Override
172        public void onCannedSmsResponsesLoaded(Call call) {}
173        @Override
174        public void onVideoCallProviderChanged(Call call) {}
175        @Override
176        public void onCallerInfoChanged(Call call) {}
177        @Override
178        public void onIsVoipAudioModeChanged(Call call) {}
179        @Override
180        public void onStatusHintsChanged(Call call) {}
181        @Override
182        public void onExtrasChanged(Call c, int source, Bundle extras) {}
183        @Override
184        public void onExtrasRemoved(Call c, int source, List<String> keys) {}
185        @Override
186        public void onHandleChanged(Call call) {}
187        @Override
188        public void onCallerDisplayNameChanged(Call call) {}
189        @Override
190        public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {}
191        @Override
192        public void onTargetPhoneAccountChanged(Call call) {}
193        @Override
194        public void onConnectionManagerPhoneAccountChanged(Call call) {}
195        @Override
196        public void onPhoneAccountChanged(Call call) {}
197        @Override
198        public void onConferenceableCallsChanged(Call call) {}
199        @Override
200        public boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout) {
201            return false;
202        }
203        @Override
204        public void onHoldToneRequested(Call call) {}
205        @Override
206        public void onConnectionEvent(Call call, String event, Bundle extras) {}
207        @Override
208        public void onExternalCallChanged(Call call, boolean isExternalCall) {}
209        @Override
210        public void onRttInitiationFailure(Call call, int reason) {}
211        @Override
212        public void onRemoteRttRequest(Call call, int requestId) {}
213        @Override
214        public void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState,
215                                        Bundle extras, boolean isLegacy) {}
216        @Override
217        public void onHandoverFailed(Call call, int error) {}
218        @Override
219        public void onHandoverComplete(Call call) {}
220    }
221
222    private final CallerInfoLookupHelper.OnQueryCompleteListener mCallerInfoQueryListener =
223            new CallerInfoLookupHelper.OnQueryCompleteListener() {
224                /** ${inheritDoc} */
225                @Override
226                public void onCallerInfoQueryComplete(Uri handle, CallerInfo callerInfo) {
227                    synchronized (mLock) {
228                        Call.this.setCallerInfo(handle, callerInfo);
229                    }
230                }
231
232                @Override
233                public void onContactPhotoQueryComplete(Uri handle, CallerInfo callerInfo) {
234                    synchronized (mLock) {
235                        Call.this.setCallerInfo(handle, callerInfo);
236                    }
237                }
238            };
239
240    /**
241     * One of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING, or CALL_DIRECTION_UNKNOWN
242     */
243    private final int mCallDirection;
244
245    /**
246     * The post-dial digits that were dialed after the network portion of the number
247     */
248    private final String mPostDialDigits;
249
250    /**
251     * The secondary line number that an incoming call has been received on if the SIM subscription
252     * has multiple associated numbers.
253     */
254    private String mViaNumber = "";
255
256    /**
257     * The wall clock time this call was created. Beyond logging and such, may also be used for
258     * bookkeeping and specifically for marking certain call attempts as failed attempts.
259     * Note: This timestamp should NOT be used for calculating call duration.
260     */
261    private long mCreationTimeMillis;
262
263    /** The time this call was made active. */
264    private long mConnectTimeMillis = 0;
265
266    /**
267     * The time, in millis, since boot when this call was connected.  This should ONLY be used when
268     * calculating the duration of the call.
269     *
270     * The reason for this is that the {@link SystemClock#elapsedRealtime()} is based on the
271     * elapsed time since the device was booted.  Changes to the system clock (e.g. due to NITZ
272     * time sync, time zone changes user initiated clock changes) would cause a duration calculated
273     * based on {@link #mConnectTimeMillis} to change based on the delta in the time.
274     * Using the {@link SystemClock#elapsedRealtime()} ensures that changes to the wall clock do
275     * not impact the call duration.
276     */
277    private long mConnectElapsedTimeMillis = 0;
278
279    /** The wall clock time this call was disconnected. */
280    private long mDisconnectTimeMillis = 0;
281
282    /**
283     * The elapsed time since boot when this call was disconnected.  Recorded as the
284     * {@link SystemClock#elapsedRealtime()}.  This ensures that the call duration is not impacted
285     * by changes in the wall time clock.
286     */
287    private long mDisconnectElapsedTimeMillis = 0;
288
289    /** The gateway information associated with this call. This stores the original call handle
290     * that the user is attempting to connect to via the gateway, the actual handle to dial in
291     * order to connect the call via the gateway, as well as the package name of the gateway
292     * service. */
293    private GatewayInfo mGatewayInfo;
294
295    private PhoneAccountHandle mConnectionManagerPhoneAccountHandle;
296
297    private PhoneAccountHandle mTargetPhoneAccountHandle;
298
299    private UserHandle mInitiatingUser;
300
301    private final Handler mHandler = new Handler(Looper.getMainLooper());
302
303    private final List<Call> mConferenceableCalls = new ArrayList<>();
304
305    /** The state of the call. */
306    private int mState;
307
308    /** The handle with which to establish this call. */
309    private Uri mHandle;
310
311    /**
312     * The presentation requirements for the handle. See {@link TelecomManager} for valid values.
313     */
314    private int mHandlePresentation;
315
316    /** The caller display name (CNAP) set by the connection service. */
317    private String mCallerDisplayName;
318
319    /**
320     * The presentation requirements for the handle. See {@link TelecomManager} for valid values.
321     */
322    private int mCallerDisplayNamePresentation;
323
324    /**
325     * The connection service which is attempted or already connecting this call.
326     */
327    private ConnectionServiceWrapper mConnectionService;
328
329    private boolean mIsEmergencyCall;
330
331    private boolean mSpeakerphoneOn;
332
333    private boolean mIsDisconnectingChildCall = false;
334
335    /**
336     * Tracks the video states which were applicable over the duration of a call.
337     * See {@link VideoProfile} for a list of valid video states.
338     * <p>
339     * Video state history is tracked when the call is active, and when a call is rejected or
340     * missed.
341     */
342    private int mVideoStateHistory;
343
344    private int mVideoState;
345
346    /**
347     * Disconnect cause for the call. Only valid if the state of the call is STATE_DISCONNECTED.
348     * See {@link android.telecom.DisconnectCause}.
349     */
350    private DisconnectCause mDisconnectCause = new DisconnectCause(DisconnectCause.UNKNOWN);
351
352    private Bundle mIntentExtras = new Bundle();
353
354    /**
355     * The {@link Intent} which originally created this call.  Only populated when we are putting a
356     * call into a pending state and need to pick up initiation of the call later.
357     */
358    private Intent mOriginalCallIntent = null;
359
360    /** Set of listeners on this call.
361     *
362     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
363     * load factor before resizing, 1 means we only expect a single thread to
364     * access the map so make only a single shard
365     */
366    private final Set<Listener> mListeners = Collections.newSetFromMap(
367            new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
368
369    private CreateConnectionProcessor mCreateConnectionProcessor;
370
371    /** Caller information retrieved from the latest contact query. */
372    private CallerInfo mCallerInfo;
373
374    /** The latest token used with a contact info query. */
375    private int mQueryToken = 0;
376
377    /** Whether this call is requesting that Telecom play the ringback tone on its behalf. */
378    private boolean mRingbackRequested = false;
379
380    /** Whether direct-to-voicemail query is pending. */
381    private boolean mDirectToVoicemailQueryPending;
382
383    private int mConnectionCapabilities;
384
385    private int mConnectionProperties;
386
387    private int mSupportedAudioRoutes = CallAudioState.ROUTE_ALL;
388
389    private boolean mIsConference = false;
390
391    private final boolean mShouldAttachToExistingConnection;
392
393    private Call mParentCall = null;
394
395    private List<Call> mChildCalls = new LinkedList<>();
396
397    /** Set of text message responses allowed for this call, if applicable. */
398    private List<String> mCannedSmsResponses = Collections.EMPTY_LIST;
399
400    /** Whether an attempt has been made to load the text message responses. */
401    private boolean mCannedSmsResponsesLoadingStarted = false;
402
403    private IVideoProvider mVideoProvider;
404    private VideoProviderProxy mVideoProviderProxy;
405
406    private boolean mIsVoipAudioMode;
407    private StatusHints mStatusHints;
408    private Bundle mExtras;
409    private final ConnectionServiceRepository mRepository;
410    private final Context mContext;
411    private final CallsManager mCallsManager;
412    private final ClockProxy mClockProxy;
413    private final TelecomSystem.SyncRoot mLock;
414    private final String mId;
415    private String mConnectionId;
416    private Analytics.CallInfo mAnalytics;
417    private char mPlayingDtmfTone;
418
419    private boolean mWasConferencePreviouslyMerged = false;
420    private boolean mWasHighDefAudio = false;
421
422    // For conferences which support merge/swap at their level, we retain a notion of an active
423    // call. This is used for BluetoothPhoneService.  In order to support hold/merge, it must have
424    // the notion of the current "active" call within the conference call. This maintains the
425    // "active" call and switches every time the user hits "swap".
426    private Call mConferenceLevelActiveCall = null;
427
428    private boolean mIsLocallyDisconnecting = false;
429
430    /**
431     * Tracks the current call data usage as reported by the video provider.
432     */
433    private long mCallDataUsage = DATA_USAGE_NOT_SET;
434
435    private boolean mIsWorkCall;
436
437    /**
438     * Tracks whether this {@link Call}'s {@link #getTargetPhoneAccount()} has
439     * {@link PhoneAccount#EXTRA_PLAY_CALL_RECORDING_TONE} set.
440     */
441    private boolean mUseCallRecordingTone;
442
443    // Set to true once the NewOutgoingCallIntentBroadcast comes back and is processed.
444    private boolean mIsNewOutgoingCallIntentBroadcastDone = false;
445
446    /**
447     * Indicates whether the call is remotely held.  A call is considered remotely held when
448     * {@link #onConnectionEvent(String)} receives the {@link Connection#EVENT_ON_HOLD_TONE_START}
449     * event.
450     */
451    private boolean mIsRemotelyHeld = false;
452
453    /**
454     * Indicates whether the {@link PhoneAccount} associated with this call is self-managed.
455     * See {@link PhoneAccount#CAPABILITY_SELF_MANAGED} for more information.
456     */
457    private boolean mIsSelfManaged = false;
458
459    /**
460     * Indicates whether the {@link PhoneAccount} associated with this call supports video calling.
461     * {@code True} if the phone account supports video calling, {@code false} otherwise.
462     */
463    private boolean mIsVideoCallingSupported = false;
464
465    private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
466
467    /**
468     * For {@link Connection}s or {@link android.telecom.Conference}s added via a ConnectionManager
469     * using the {@link android.telecom.ConnectionService#addExistingConnection(PhoneAccountHandle,
470     * Connection)} or {@link android.telecom.ConnectionService#addConference(Conference)},
471     * indicates the ID of this call as it was referred to by the {@code ConnectionService} which
472     * originally created it.
473     *
474     * See {@link Connection#EXTRA_ORIGINAL_CONNECTION_ID} for more information.
475     */
476    private String mOriginalConnectionId;
477
478    /**
479     * Two pairs of {@link android.os.ParcelFileDescriptor}s that handle RTT text communication
480     * between the in-call app and the connection service. If both non-null, this call should be
481     * treated as an RTT call.
482     * Each array should be of size 2. First one is the read side and the second one is the write
483     * side.
484     */
485    private ParcelFileDescriptor[] mInCallToConnectionServiceStreams;
486    private ParcelFileDescriptor[] mConnectionServiceToInCallStreams;
487    /**
488     * Integer constant from {@link android.telecom.Call.RttCall}. Describes the current RTT mode.
489     */
490    private int mRttMode;
491    /**
492     * True if the call was ever an RTT call.
493     */
494    private boolean mWasEverRtt = false;
495
496    /**
497     * Integer indicating the remote RTT request ID that is pending a response from the user.
498     */
499    private int mPendingRttRequestId = INVALID_RTT_REQUEST_ID;
500
501    /**
502     * When a call handover has been initiated via {@link #requestHandover(PhoneAccountHandle,
503     * int, Bundle, boolean)}, contains the call which this call is being handed over to.
504     */
505    private Call mHandoverDestinationCall = null;
506
507    /**
508     * When a call handover has been initiated via {@link #requestHandover(PhoneAccountHandle,
509     * int, Bundle, boolean)}, contains the call which this call is being handed over from.
510     */
511    private Call mHandoverSourceCall = null;
512
513    /**
514     * Indicates the current state of this call if it is in the process of a handover.
515     */
516    private int mHandoverState = HandoverState.HANDOVER_NONE;
517
518    /**
519     * Persists the specified parameters and initializes the new instance.
520     *  @param context The context.
521     * @param repository The connection service repository.
522     * @param handle The handle to dial.
523     * @param gatewayInfo Gateway information to use for the call.
524     * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
525*         This account must be one that was registered with the
526*         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
527     * @param targetPhoneAccountHandle Account information to use for the call. This account must be
528*         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
529     * @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING,
530*         or CALL_DIRECTION_UNKNOWN.
531     * @param shouldAttachToExistingConnection Set to true to attach the call to an existing
532     * @param clockProxy
533     */
534    public Call(
535            String callId,
536            Context context,
537            CallsManager callsManager,
538            TelecomSystem.SyncRoot lock,
539            ConnectionServiceRepository repository,
540            ContactsAsyncHelper contactsAsyncHelper,
541            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
542            PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
543            Uri handle,
544            GatewayInfo gatewayInfo,
545            PhoneAccountHandle connectionManagerPhoneAccountHandle,
546            PhoneAccountHandle targetPhoneAccountHandle,
547            int callDirection,
548            boolean shouldAttachToExistingConnection,
549            boolean isConference,
550            ClockProxy clockProxy) {
551        mId = callId;
552        mConnectionId = callId;
553        mState = isConference ? CallState.ACTIVE : CallState.NEW;
554        mContext = context;
555        mCallsManager = callsManager;
556        mLock = lock;
557        mRepository = repository;
558        mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
559        setHandle(handle);
560        mPostDialDigits = handle != null
561                ? PhoneNumberUtils.extractPostDialPortion(handle.getSchemeSpecificPart()) : "";
562        mGatewayInfo = gatewayInfo;
563        setConnectionManagerPhoneAccount(connectionManagerPhoneAccountHandle);
564        setTargetPhoneAccount(targetPhoneAccountHandle);
565        mCallDirection = callDirection;
566        mIsConference = isConference;
567        mShouldAttachToExistingConnection = shouldAttachToExistingConnection
568                || callDirection == CALL_DIRECTION_INCOMING;
569        maybeLoadCannedSmsResponses();
570        mAnalytics = new Analytics.CallInfo();
571        mClockProxy = clockProxy;
572        mCreationTimeMillis = mClockProxy.currentTimeMillis();
573    }
574
575    /**
576     * Persists the specified parameters and initializes the new instance.
577     *  @param context The context.
578     * @param repository The connection service repository.
579     * @param handle The handle to dial.
580     * @param gatewayInfo Gateway information to use for the call.
581     * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
582*         This account must be one that was registered with the
583*         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
584     * @param targetPhoneAccountHandle Account information to use for the call. This account must be
585*         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
586     * @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING,
587*         or CALL_DIRECTION_UNKNOWN
588     * @param shouldAttachToExistingConnection Set to true to attach the call to an existing
589*         connection, regardless of whether it's incoming or outgoing.
590     * @param connectTimeMillis The connection time of the call.
591     * @param clockProxy
592     */
593    Call(
594            String callId,
595            Context context,
596            CallsManager callsManager,
597            TelecomSystem.SyncRoot lock,
598            ConnectionServiceRepository repository,
599            ContactsAsyncHelper contactsAsyncHelper,
600            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
601            PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
602            Uri handle,
603            GatewayInfo gatewayInfo,
604            PhoneAccountHandle connectionManagerPhoneAccountHandle,
605            PhoneAccountHandle targetPhoneAccountHandle,
606            int callDirection,
607            boolean shouldAttachToExistingConnection,
608            boolean isConference,
609            long connectTimeMillis,
610            long connectElapsedTimeMillis,
611            ClockProxy clockProxy) {
612        this(callId, context, callsManager, lock, repository, contactsAsyncHelper,
613                callerInfoAsyncQueryFactory, phoneNumberUtilsAdapter, handle, gatewayInfo,
614                connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
615                shouldAttachToExistingConnection, isConference, clockProxy);
616
617        mConnectTimeMillis = connectTimeMillis;
618        mConnectElapsedTimeMillis = connectElapsedTimeMillis;
619        mAnalytics.setCallStartTime(connectTimeMillis);
620    }
621
622    public void addListener(Listener listener) {
623        mListeners.add(listener);
624    }
625
626    public void removeListener(Listener listener) {
627        if (listener != null) {
628            mListeners.remove(listener);
629        }
630    }
631
632    public void initAnalytics() {
633        int analyticsDirection;
634        switch (mCallDirection) {
635            case CALL_DIRECTION_OUTGOING:
636                analyticsDirection = Analytics.OUTGOING_DIRECTION;
637                break;
638            case CALL_DIRECTION_INCOMING:
639                analyticsDirection = Analytics.INCOMING_DIRECTION;
640                break;
641            case CALL_DIRECTION_UNKNOWN:
642            case CALL_DIRECTION_UNDEFINED:
643            default:
644                analyticsDirection = Analytics.UNKNOWN_DIRECTION;
645        }
646        mAnalytics = Analytics.initiateCallAnalytics(mId, analyticsDirection);
647        Log.addEvent(this, LogUtils.Events.CREATED);
648    }
649
650    public Analytics.CallInfo getAnalytics() {
651        return mAnalytics;
652    }
653
654    public void destroy() {
655        // We should not keep these bitmaps around because the Call objects may be held for logging
656        // purposes.
657        // TODO: Make a container object that only stores the information we care about for Logging.
658        if (mCallerInfo != null) {
659            mCallerInfo.cachedPhotoIcon = null;
660            mCallerInfo.cachedPhoto = null;
661        }
662        Log.addEvent(this, LogUtils.Events.DESTROYED);
663    }
664
665    /** {@inheritDoc} */
666    @Override
667    public String toString() {
668        String component = null;
669        if (mConnectionService != null && mConnectionService.getComponentName() != null) {
670            component = mConnectionService.getComponentName().flattenToShortString();
671        }
672
673        return String.format(Locale.US, "[%s, %s, %s, %s, %s, childs(%d), has_parent(%b), %s, %s]",
674                mId,
675                CallState.toString(mState),
676                component,
677                Log.piiHandle(mHandle),
678                getVideoStateDescription(getVideoState()),
679                getChildCalls().size(),
680                getParentCall() != null,
681                Connection.capabilitiesToString(getConnectionCapabilities()),
682                Connection.propertiesToString(getConnectionProperties()));
683    }
684
685    @Override
686    public String getDescription() {
687        StringBuilder s = new StringBuilder();
688        if (isSelfManaged()) {
689            s.append("SelfMgd Call");
690        } else if (isExternalCall()) {
691            s.append("External Call");
692        } else {
693            s.append("Call");
694        }
695        s.append(getId());
696        s.append(" [");
697        s.append(SimpleDateFormat.getDateTimeInstance().format(new Date(getCreationTimeMillis())));
698        s.append("]");
699        s.append(isIncoming() ? "(MT - incoming)" : "(MO - outgoing)");
700        s.append("\n\tVia PhoneAccount: ");
701        PhoneAccountHandle targetPhoneAccountHandle = getTargetPhoneAccount();
702        if (targetPhoneAccountHandle != null) {
703            s.append(targetPhoneAccountHandle);
704            s.append(" (");
705            s.append(getTargetPhoneAccountLabel());
706            s.append(")");
707        } else {
708            s.append("not set");
709        }
710
711        s.append("\n\tTo address: ");
712        s.append(Log.piiHandle(getHandle()));
713        s.append(" Presentation: ");
714        switch (getHandlePresentation()) {
715            case TelecomManager.PRESENTATION_ALLOWED:
716                s.append("Allowed");
717                break;
718            case TelecomManager.PRESENTATION_PAYPHONE:
719                s.append("Payphone");
720                break;
721            case TelecomManager.PRESENTATION_RESTRICTED:
722                s.append("Restricted");
723                break;
724            case TelecomManager.PRESENTATION_UNKNOWN:
725                s.append("Unknown");
726                break;
727            default:
728                s.append("<undefined>");
729        }
730        s.append("\n");
731        return s.toString();
732    }
733
734    /**
735     * Builds a debug-friendly description string for a video state.
736     * <p>
737     * A = audio active, T = video transmission active, R = video reception active, P = video
738     * paused.
739     *
740     * @param videoState The video state.
741     * @return A string indicating which bits are set in the video state.
742     */
743    private String getVideoStateDescription(int videoState) {
744        StringBuilder sb = new StringBuilder();
745        sb.append("A");
746
747        if (VideoProfile.isTransmissionEnabled(videoState)) {
748            sb.append("T");
749        }
750
751        if (VideoProfile.isReceptionEnabled(videoState)) {
752            sb.append("R");
753        }
754
755        if (VideoProfile.isPaused(videoState)) {
756            sb.append("P");
757        }
758
759        return sb.toString();
760    }
761
762    @Override
763    public ConnectionServiceFocusManager.ConnectionServiceFocus getConnectionServiceWrapper() {
764        return mConnectionService;
765    }
766
767    @VisibleForTesting
768    public int getState() {
769        return mState;
770    }
771
772    private boolean shouldContinueProcessingAfterDisconnect() {
773        // Stop processing once the call is active.
774        if (!CreateConnectionTimeout.isCallBeingPlaced(this)) {
775            return false;
776        }
777
778        // Only Redial a Call in the case of it being an Emergency Call.
779        if(!isEmergencyCall()) {
780            return false;
781        }
782
783        // Make sure that there are additional connection services to process.
784        if (mCreateConnectionProcessor == null
785            || !mCreateConnectionProcessor.isProcessingComplete()
786            || !mCreateConnectionProcessor.hasMorePhoneAccounts()) {
787            return false;
788        }
789
790        if (mDisconnectCause == null) {
791            return false;
792        }
793
794        // Continue processing if the current attempt failed or timed out.
795        return mDisconnectCause.getCode() == DisconnectCause.ERROR ||
796            mCreateConnectionProcessor.isCallTimedOut();
797    }
798
799    /**
800     * Returns the unique ID for this call as it exists in Telecom.
801     * @return The call ID.
802     */
803    public String getId() {
804        return mId;
805    }
806
807    /**
808     * Returns the unique ID for this call (see {@link #getId}) along with an attempt indicator that
809     * iterates based on attempts to establish a {@link Connection} using createConnectionProcessor.
810     * @return The call ID with an appended attempt id.
811     */
812    public String getConnectionId() {
813        if(mCreateConnectionProcessor != null) {
814            mConnectionId = mId + "_" +
815                    String.valueOf(mCreateConnectionProcessor.getConnectionAttempt());
816            return mConnectionId;
817        } else {
818            return mConnectionId;
819        }
820    }
821
822    /**
823     * Sets the call state. Although there exists the notion of appropriate state transitions
824     * (see {@link CallState}), in practice those expectations break down when cellular systems
825     * misbehave and they do this very often. The result is that we do not enforce state transitions
826     * and instead keep the code resilient to unexpected state changes.
827     */
828    public void setState(int newState, String tag) {
829        if (mState != newState) {
830            Log.v(this, "setState %s -> %s", mState, newState);
831
832            if (newState == CallState.DISCONNECTED && shouldContinueProcessingAfterDisconnect()) {
833                Log.w(this, "continuing processing disconnected call with another service");
834                mCreateConnectionProcessor.continueProcessingIfPossible(this, mDisconnectCause);
835                return;
836            }
837
838            updateVideoHistoryViaState(mState, newState);
839
840            mState = newState;
841            maybeLoadCannedSmsResponses();
842
843            if (mState == CallState.ACTIVE || mState == CallState.ON_HOLD) {
844                if (mConnectTimeMillis == 0) {
845                    // We check to see if mConnectTime is already set to prevent the
846                    // call from resetting active time when it goes in and out of
847                    // ACTIVE/ON_HOLD
848                    mConnectTimeMillis = mClockProxy.currentTimeMillis();
849                    mConnectElapsedTimeMillis = mClockProxy.elapsedRealtime();
850                    mAnalytics.setCallStartTime(mConnectTimeMillis);
851                }
852
853                // We're clearly not disconnected, so reset the disconnected time.
854                mDisconnectTimeMillis = 0;
855                mDisconnectElapsedTimeMillis = 0;
856            } else if (mState == CallState.DISCONNECTED) {
857                mDisconnectTimeMillis = mClockProxy.currentTimeMillis();
858                mDisconnectElapsedTimeMillis = mClockProxy.elapsedRealtime();
859                mAnalytics.setCallEndTime(mDisconnectTimeMillis);
860                setLocallyDisconnecting(false);
861                fixParentAfterDisconnect();
862            }
863
864            // Log the state transition event
865            String event = null;
866            Object data = null;
867            switch (newState) {
868                case CallState.ACTIVE:
869                    event = LogUtils.Events.SET_ACTIVE;
870                    break;
871                case CallState.CONNECTING:
872                    event = LogUtils.Events.SET_CONNECTING;
873                    break;
874                case CallState.DIALING:
875                    event = LogUtils.Events.SET_DIALING;
876                    break;
877                case CallState.PULLING:
878                    event = LogUtils.Events.SET_PULLING;
879                    break;
880                case CallState.DISCONNECTED:
881                    event = LogUtils.Events.SET_DISCONNECTED;
882                    data = getDisconnectCause();
883                    break;
884                case CallState.DISCONNECTING:
885                    event = LogUtils.Events.SET_DISCONNECTING;
886                    break;
887                case CallState.ON_HOLD:
888                    event = LogUtils.Events.SET_HOLD;
889                    break;
890                case CallState.SELECT_PHONE_ACCOUNT:
891                    event = LogUtils.Events.SET_SELECT_PHONE_ACCOUNT;
892                    break;
893                case CallState.RINGING:
894                    event = LogUtils.Events.SET_RINGING;
895                    break;
896            }
897            if (event != null) {
898                // The string data should be just the tag.
899                String stringData = tag;
900                if (data != null) {
901                    // If data exists, add it to tag.  If no tag, just use data.toString().
902                    stringData = stringData == null ? data.toString() : stringData + "> " + data;
903                }
904                Log.addEvent(this, event, stringData);
905            }
906        }
907    }
908
909    void setRingbackRequested(boolean ringbackRequested) {
910        mRingbackRequested = ringbackRequested;
911        for (Listener l : mListeners) {
912            l.onRingbackRequested(this, mRingbackRequested);
913        }
914    }
915
916    boolean isRingbackRequested() {
917        return mRingbackRequested;
918    }
919
920    @VisibleForTesting
921    public boolean isConference() {
922        return mIsConference;
923    }
924
925    public Uri getHandle() {
926        return mHandle;
927    }
928
929    public String getPostDialDigits() {
930        return mPostDialDigits;
931    }
932
933    public String getViaNumber() {
934        return mViaNumber;
935    }
936
937    public void setViaNumber(String viaNumber) {
938        // If at any point the via number is not empty throughout the call, save that via number.
939        if (!TextUtils.isEmpty(viaNumber)) {
940            mViaNumber = viaNumber;
941        }
942    }
943
944    int getHandlePresentation() {
945        return mHandlePresentation;
946    }
947
948
949    void setHandle(Uri handle) {
950        setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
951    }
952
953    public void setHandle(Uri handle, int presentation) {
954        if (!Objects.equals(handle, mHandle) || presentation != mHandlePresentation) {
955            mHandlePresentation = presentation;
956            if (mHandlePresentation == TelecomManager.PRESENTATION_RESTRICTED ||
957                    mHandlePresentation == TelecomManager.PRESENTATION_UNKNOWN) {
958                mHandle = null;
959            } else {
960                mHandle = handle;
961                if (mHandle != null && !PhoneAccount.SCHEME_VOICEMAIL.equals(mHandle.getScheme())
962                        && TextUtils.isEmpty(mHandle.getSchemeSpecificPart())) {
963                    // If the number is actually empty, set it to null, unless this is a
964                    // SCHEME_VOICEMAIL uri which always has an empty number.
965                    mHandle = null;
966                }
967            }
968
969            // Let's not allow resetting of the emergency flag. Once a call becomes an emergency
970            // call, it will remain so for the rest of it's lifetime.
971            if (!mIsEmergencyCall) {
972                mIsEmergencyCall = mHandle != null &&
973                        mPhoneNumberUtilsAdapter.isLocalEmergencyNumber(mContext,
974                                mHandle.getSchemeSpecificPart());
975            }
976            startCallerInfoLookup();
977            for (Listener l : mListeners) {
978                l.onHandleChanged(this);
979            }
980        }
981    }
982
983    public String getCallerDisplayName() {
984        return mCallerDisplayName;
985    }
986
987    public int getCallerDisplayNamePresentation() {
988        return mCallerDisplayNamePresentation;
989    }
990
991    void setCallerDisplayName(String callerDisplayName, int presentation) {
992        if (!TextUtils.equals(callerDisplayName, mCallerDisplayName) ||
993                presentation != mCallerDisplayNamePresentation) {
994            mCallerDisplayName = callerDisplayName;
995            mCallerDisplayNamePresentation = presentation;
996            for (Listener l : mListeners) {
997                l.onCallerDisplayNameChanged(this);
998            }
999        }
1000    }
1001
1002    public String getName() {
1003        return mCallerInfo == null ? null : mCallerInfo.name;
1004    }
1005
1006    public String getPhoneNumber() {
1007        return mCallerInfo == null ? null : mCallerInfo.phoneNumber;
1008    }
1009
1010    public Bitmap getPhotoIcon() {
1011        return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
1012    }
1013
1014    public Drawable getPhoto() {
1015        return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
1016    }
1017
1018    /**
1019     * @param disconnectCause The reason for the disconnection, represented by
1020     *         {@link android.telecom.DisconnectCause}.
1021     */
1022    public void setDisconnectCause(DisconnectCause disconnectCause) {
1023        // TODO: Consider combining this method with a setDisconnected() method that is totally
1024        // separate from setState.
1025        mAnalytics.setCallDisconnectCause(disconnectCause);
1026        mDisconnectCause = disconnectCause;
1027    }
1028
1029    public DisconnectCause getDisconnectCause() {
1030        return mDisconnectCause;
1031    }
1032
1033    @VisibleForTesting
1034    public boolean isEmergencyCall() {
1035        return mIsEmergencyCall;
1036    }
1037
1038    /**
1039     * @return The original handle this call is associated with. In-call services should use this
1040     * handle when indicating in their UI the handle that is being called.
1041     */
1042    public Uri getOriginalHandle() {
1043        if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
1044            return mGatewayInfo.getOriginalAddress();
1045        }
1046        return getHandle();
1047    }
1048
1049    @VisibleForTesting
1050    public GatewayInfo getGatewayInfo() {
1051        return mGatewayInfo;
1052    }
1053
1054    void setGatewayInfo(GatewayInfo gatewayInfo) {
1055        mGatewayInfo = gatewayInfo;
1056    }
1057
1058    @VisibleForTesting
1059    public PhoneAccountHandle getConnectionManagerPhoneAccount() {
1060        return mConnectionManagerPhoneAccountHandle;
1061    }
1062
1063    @VisibleForTesting
1064    public void setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle) {
1065        if (!Objects.equals(mConnectionManagerPhoneAccountHandle, accountHandle)) {
1066            mConnectionManagerPhoneAccountHandle = accountHandle;
1067            for (Listener l : mListeners) {
1068                l.onConnectionManagerPhoneAccountChanged(this);
1069            }
1070        }
1071
1072    }
1073
1074    @VisibleForTesting
1075    public PhoneAccountHandle getTargetPhoneAccount() {
1076        return mTargetPhoneAccountHandle;
1077    }
1078
1079    @VisibleForTesting
1080    public void setTargetPhoneAccount(PhoneAccountHandle accountHandle) {
1081        if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) {
1082            mTargetPhoneAccountHandle = accountHandle;
1083            for (Listener l : mListeners) {
1084                l.onTargetPhoneAccountChanged(this);
1085            }
1086            configureCallAttributes();
1087        }
1088        checkIfVideoCapable();
1089    }
1090
1091    public CharSequence getTargetPhoneAccountLabel() {
1092        if (getTargetPhoneAccount() == null) {
1093            return null;
1094        }
1095        PhoneAccount phoneAccount = mCallsManager.getPhoneAccountRegistrar()
1096                .getPhoneAccountUnchecked(getTargetPhoneAccount());
1097
1098        if (phoneAccount == null) {
1099            return null;
1100        }
1101
1102        return phoneAccount.getLabel();
1103    }
1104
1105    /**
1106     * Determines if this Call should be written to the call log.
1107     * @return {@code true} for managed calls or for self-managed calls which have the
1108     * {@link PhoneAccount#EXTRA_LOG_SELF_MANAGED_CALLS} extra set.
1109     */
1110    public boolean isLoggedSelfManaged() {
1111        if (!isSelfManaged()) {
1112            // Managed calls are always logged.
1113            return true;
1114        }
1115        if (getTargetPhoneAccount() == null) {
1116            return false;
1117        }
1118        PhoneAccount phoneAccount = mCallsManager.getPhoneAccountRegistrar()
1119                .getPhoneAccountUnchecked(getTargetPhoneAccount());
1120
1121        if (phoneAccount == null) {
1122            return false;
1123        }
1124
1125        return phoneAccount.getExtras() != null && phoneAccount.getExtras().getBoolean(
1126                PhoneAccount.EXTRA_LOG_SELF_MANAGED_CALLS, false);
1127    }
1128
1129    @VisibleForTesting
1130    public boolean isIncoming() {
1131        return mCallDirection == CALL_DIRECTION_INCOMING;
1132    }
1133
1134    public boolean isExternalCall() {
1135        return (getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) ==
1136                Connection.PROPERTY_IS_EXTERNAL_CALL;
1137    }
1138
1139    public boolean isWorkCall() {
1140        return mIsWorkCall;
1141    }
1142
1143    public boolean isUsingCallRecordingTone() {
1144        return mUseCallRecordingTone;
1145    }
1146
1147    public boolean isVideoCallingSupported() {
1148        return mIsVideoCallingSupported;
1149    }
1150
1151    public boolean isSelfManaged() {
1152        return mIsSelfManaged;
1153    }
1154
1155    public void setIsSelfManaged(boolean isSelfManaged) {
1156        mIsSelfManaged = isSelfManaged;
1157
1158        // Connection properties will add/remove the PROPERTY_SELF_MANAGED.
1159        setConnectionProperties(getConnectionProperties());
1160    }
1161
1162    public void markFinishedHandoverStateAndCleanup(int handoverState) {
1163        if (mHandoverSourceCall != null) {
1164            mHandoverSourceCall.setHandoverState(handoverState);
1165        } else if (mHandoverDestinationCall != null) {
1166            mHandoverDestinationCall.setHandoverState(handoverState);
1167        }
1168        setHandoverState(handoverState);
1169        maybeCleanupHandover();
1170    }
1171
1172    public void maybeCleanupHandover() {
1173        if (mHandoverSourceCall != null) {
1174            mHandoverSourceCall.setHandoverSourceCall(null);
1175            mHandoverSourceCall.setHandoverDestinationCall(null);
1176            mHandoverSourceCall = null;
1177        } else if (mHandoverDestinationCall != null) {
1178            mHandoverDestinationCall.setHandoverSourceCall(null);
1179            mHandoverDestinationCall.setHandoverDestinationCall(null);
1180            mHandoverDestinationCall = null;
1181        }
1182    }
1183
1184    public boolean isHandoverInProgress() {
1185        return mHandoverSourceCall != null || mHandoverDestinationCall != null;
1186    }
1187
1188    public Call getHandoverDestinationCall() {
1189        return mHandoverDestinationCall;
1190    }
1191
1192    public void setHandoverDestinationCall(Call call) {
1193        mHandoverDestinationCall = call;
1194    }
1195
1196    public Call getHandoverSourceCall() {
1197        return mHandoverSourceCall;
1198    }
1199
1200    public void setHandoverSourceCall(Call call) {
1201        mHandoverSourceCall = call;
1202    }
1203
1204    public void setHandoverState(int handoverState) {
1205        Log.d(this, "setHandoverState: callId=%s, handoverState=%s", getId(),
1206                HandoverState.stateToString(handoverState));
1207        mHandoverState = handoverState;
1208    }
1209
1210    public int getHandoverState() {
1211        return mHandoverState;
1212    }
1213
1214    private void configureCallAttributes() {
1215        PhoneAccountRegistrar phoneAccountRegistrar = mCallsManager.getPhoneAccountRegistrar();
1216        boolean isWorkCall = false;
1217        boolean isCallRecordingToneSupported = false;
1218        PhoneAccount phoneAccount =
1219                phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle);
1220        if (phoneAccount != null) {
1221            final UserHandle userHandle;
1222            if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
1223                userHandle = mInitiatingUser;
1224            } else {
1225                userHandle = mTargetPhoneAccountHandle.getUserHandle();
1226            }
1227            if (userHandle != null) {
1228                isWorkCall = UserUtil.isManagedProfile(mContext, userHandle);
1229            }
1230
1231            isCallRecordingToneSupported = (phoneAccount.hasCapabilities(
1232                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) && phoneAccount.getExtras() != null
1233                    && phoneAccount.getExtras().getBoolean(
1234                    PhoneAccount.EXTRA_PLAY_CALL_RECORDING_TONE, false));
1235        }
1236        mIsWorkCall = isWorkCall;
1237        mUseCallRecordingTone = isCallRecordingToneSupported;
1238    }
1239
1240    /**
1241     * Caches the state of the {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} {@link PhoneAccount}
1242     * capability and ensures that the video state is updated if the phone account does not support
1243     * video calling.
1244     */
1245    private void checkIfVideoCapable() {
1246        PhoneAccountRegistrar phoneAccountRegistrar = mCallsManager.getPhoneAccountRegistrar();
1247        if (mTargetPhoneAccountHandle == null) {
1248            // If no target phone account handle is specified, assume we can potentially perform a
1249            // video call; once the phone account is set, we can confirm that it is video capable.
1250            mIsVideoCallingSupported = true;
1251            Log.d(this, "checkIfVideoCapable: no phone account selected; assume video capable.");
1252            return;
1253        }
1254        PhoneAccount phoneAccount =
1255                phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle);
1256        mIsVideoCallingSupported = phoneAccount != null && phoneAccount.hasCapabilities(
1257                    PhoneAccount.CAPABILITY_VIDEO_CALLING);
1258
1259        if (!mIsVideoCallingSupported && VideoProfile.isVideo(getVideoState())) {
1260            // The PhoneAccount for the Call was set to one which does not support video calling,
1261            // and the current call is configured to be a video call; downgrade to audio-only.
1262            setVideoState(VideoProfile.STATE_AUDIO_ONLY);
1263            Log.d(this, "checkIfVideoCapable: selected phone account doesn't support video.");
1264        }
1265    }
1266
1267    boolean shouldAttachToExistingConnection() {
1268        return mShouldAttachToExistingConnection;
1269    }
1270
1271    /**
1272     * Note: This method relies on {@link #mConnectElapsedTimeMillis} and
1273     * {@link #mDisconnectElapsedTimeMillis} which are independent of the wall clock (which could
1274     * change due to clock changes).
1275     * @return The "age" of this call object in milliseconds, which typically also represents the
1276     *     period since this call was added to the set pending outgoing calls.
1277     */
1278    @VisibleForTesting
1279    public long getAgeMillis() {
1280        if (mState == CallState.DISCONNECTED &&
1281                (mDisconnectCause.getCode() == DisconnectCause.REJECTED ||
1282                 mDisconnectCause.getCode() == DisconnectCause.MISSED)) {
1283            // Rejected and missed calls have no age. They're immortal!!
1284            return 0;
1285        } else if (mConnectElapsedTimeMillis == 0) {
1286            // Age is measured in the amount of time the call was active. A zero connect time
1287            // indicates that we never went active, so return 0 for the age.
1288            return 0;
1289        } else if (mDisconnectElapsedTimeMillis == 0) {
1290            // We connected, but have not yet disconnected
1291            return mClockProxy.elapsedRealtime() - mConnectElapsedTimeMillis;
1292        }
1293
1294        return mDisconnectElapsedTimeMillis - mConnectElapsedTimeMillis;
1295    }
1296
1297    /**
1298     * @return The time when this call object was created and added to the set of pending outgoing
1299     *     calls.
1300     */
1301    public long getCreationTimeMillis() {
1302        return mCreationTimeMillis;
1303    }
1304
1305    public void setCreationTimeMillis(long time) {
1306        mCreationTimeMillis = time;
1307    }
1308
1309    long getConnectTimeMillis() {
1310        return mConnectTimeMillis;
1311    }
1312
1313    int getConnectionCapabilities() {
1314        return mConnectionCapabilities;
1315    }
1316
1317    int getConnectionProperties() {
1318        return mConnectionProperties;
1319    }
1320
1321    void setConnectionCapabilities(int connectionCapabilities) {
1322        setConnectionCapabilities(connectionCapabilities, false /* forceUpdate */);
1323    }
1324
1325    void setConnectionCapabilities(int connectionCapabilities, boolean forceUpdate) {
1326        Log.v(this, "setConnectionCapabilities: %s", Connection.capabilitiesToString(
1327                connectionCapabilities));
1328        if (forceUpdate || mConnectionCapabilities != connectionCapabilities) {
1329            // If the phone account does not support video calling, and the connection capabilities
1330            // passed in indicate that the call supports video, remove those video capabilities.
1331            if (!isVideoCallingSupported() && doesCallSupportVideo(connectionCapabilities)) {
1332                Log.w(this, "setConnectionCapabilities: attempt to set connection as video " +
1333                        "capable when not supported by the phone account.");
1334                connectionCapabilities = removeVideoCapabilities(connectionCapabilities);
1335            }
1336
1337            int previousCapabilities = mConnectionCapabilities;
1338            mConnectionCapabilities = connectionCapabilities;
1339            for (Listener l : mListeners) {
1340                l.onConnectionCapabilitiesChanged(this);
1341            }
1342
1343            int xorCaps = previousCapabilities ^ mConnectionCapabilities;
1344            Log.addEvent(this, LogUtils.Events.CAPABILITY_CHANGE,
1345                    "Current: [%s], Removed [%s], Added [%s]",
1346                    Connection.capabilitiesToStringShort(mConnectionCapabilities),
1347                    Connection.capabilitiesToStringShort(previousCapabilities & xorCaps),
1348                    Connection.capabilitiesToStringShort(mConnectionCapabilities & xorCaps));
1349        }
1350    }
1351
1352    void setConnectionProperties(int connectionProperties) {
1353        Log.v(this, "setConnectionProperties: %s", Connection.propertiesToString(
1354                connectionProperties));
1355
1356        // Ensure the ConnectionService can't change the state of the self-managed property.
1357        if (isSelfManaged()) {
1358            connectionProperties |= Connection.PROPERTY_SELF_MANAGED;
1359        } else {
1360            connectionProperties &= ~Connection.PROPERTY_SELF_MANAGED;
1361        }
1362
1363        int changedProperties = mConnectionProperties ^ connectionProperties;
1364
1365        if (changedProperties != 0) {
1366            int previousProperties = mConnectionProperties;
1367            mConnectionProperties = connectionProperties;
1368            setRttStreams((mConnectionProperties & Connection.PROPERTY_IS_RTT) ==
1369                    Connection.PROPERTY_IS_RTT);
1370            mWasHighDefAudio = (connectionProperties & Connection.PROPERTY_HIGH_DEF_AUDIO) ==
1371                    Connection.PROPERTY_HIGH_DEF_AUDIO;
1372            boolean didRttChange =
1373                    (changedProperties & Connection.PROPERTY_IS_RTT) == Connection.PROPERTY_IS_RTT;
1374            for (Listener l : mListeners) {
1375                l.onConnectionPropertiesChanged(this, didRttChange);
1376            }
1377
1378            boolean wasExternal = (previousProperties & Connection.PROPERTY_IS_EXTERNAL_CALL)
1379                    == Connection.PROPERTY_IS_EXTERNAL_CALL;
1380            boolean isExternal = (connectionProperties & Connection.PROPERTY_IS_EXTERNAL_CALL)
1381                    == Connection.PROPERTY_IS_EXTERNAL_CALL;
1382            if (wasExternal != isExternal) {
1383                Log.v(this, "setConnectionProperties: external call changed isExternal = %b",
1384                        isExternal);
1385                Log.addEvent(this, LogUtils.Events.IS_EXTERNAL, isExternal);
1386                for (Listener l : mListeners) {
1387                    l.onExternalCallChanged(this, isExternal);
1388                }
1389            }
1390
1391            mAnalytics.addCallProperties(mConnectionProperties);
1392
1393            int xorProps = previousProperties ^ mConnectionProperties;
1394            Log.addEvent(this, LogUtils.Events.PROPERTY_CHANGE,
1395                    "Current: [%s], Removed [%s], Added [%s]",
1396                    Connection.propertiesToStringShort(mConnectionProperties),
1397                    Connection.propertiesToStringShort(previousProperties & xorProps),
1398                    Connection.propertiesToStringShort(mConnectionProperties & xorProps));
1399        }
1400    }
1401
1402    public int getSupportedAudioRoutes() {
1403        return mSupportedAudioRoutes;
1404    }
1405
1406    void setSupportedAudioRoutes(int audioRoutes) {
1407        if (mSupportedAudioRoutes != audioRoutes) {
1408            mSupportedAudioRoutes = audioRoutes;
1409        }
1410    }
1411
1412    @VisibleForTesting
1413    public Call getParentCall() {
1414        return mParentCall;
1415    }
1416
1417    @VisibleForTesting
1418    public List<Call> getChildCalls() {
1419        return mChildCalls;
1420    }
1421
1422    @VisibleForTesting
1423    public boolean wasConferencePreviouslyMerged() {
1424        return mWasConferencePreviouslyMerged;
1425    }
1426
1427    public boolean isDisconnectingChildCall() {
1428        return mIsDisconnectingChildCall;
1429    }
1430
1431    /**
1432     * Sets whether this call is a child call.
1433     */
1434    private void maybeSetCallAsDisconnectingChild() {
1435        if (mParentCall != null) {
1436            mIsDisconnectingChildCall = true;
1437        }
1438    }
1439
1440    @VisibleForTesting
1441    public Call getConferenceLevelActiveCall() {
1442        return mConferenceLevelActiveCall;
1443    }
1444
1445    @VisibleForTesting
1446    public ConnectionServiceWrapper getConnectionService() {
1447        return mConnectionService;
1448    }
1449
1450    /**
1451     * Retrieves the {@link Context} for the call.
1452     *
1453     * @return The {@link Context}.
1454     */
1455    public Context getContext() {
1456        return mContext;
1457    }
1458
1459    @VisibleForTesting
1460    public void setConnectionService(ConnectionServiceWrapper service) {
1461        Preconditions.checkNotNull(service);
1462
1463        clearConnectionService();
1464
1465        service.incrementAssociatedCallCount();
1466        mConnectionService = service;
1467        mAnalytics.setCallConnectionService(service.getComponentName().flattenToShortString());
1468        mConnectionService.addCall(this);
1469    }
1470
1471    /**
1472     * Perform an in-place replacement of the {@link ConnectionServiceWrapper} for this Call.
1473     * Removes the call from its former {@link ConnectionServiceWrapper}, ensuring that the
1474     * ConnectionService is NOT unbound if the call count hits zero.
1475     * This is used by the {@link ConnectionServiceWrapper} when handling {@link Connection} and
1476     * {@link Conference} additions via a ConnectionManager.
1477     * The original {@link android.telecom.ConnectionService} will directly add external calls and
1478     * conferences to Telecom as well as the ConnectionManager, which will add to Telecom.  In these
1479     * cases since its first added to via the original CS, we want to change the CS responsible for
1480     * the call to the ConnectionManager rather than adding it again as another call/conference.
1481     *
1482     * @param service The new {@link ConnectionServiceWrapper}.
1483     */
1484    public void replaceConnectionService(ConnectionServiceWrapper service) {
1485        Preconditions.checkNotNull(service);
1486
1487        if (mConnectionService != null) {
1488            ConnectionServiceWrapper serviceTemp = mConnectionService;
1489            mConnectionService = null;
1490            serviceTemp.removeCall(this);
1491            serviceTemp.decrementAssociatedCallCount(true /*isSuppressingUnbind*/);
1492        }
1493
1494        service.incrementAssociatedCallCount();
1495        mConnectionService = service;
1496        mAnalytics.setCallConnectionService(service.getComponentName().flattenToShortString());
1497    }
1498
1499    /**
1500     * Clears the associated connection service.
1501     */
1502    void clearConnectionService() {
1503        if (mConnectionService != null) {
1504            ConnectionServiceWrapper serviceTemp = mConnectionService;
1505            mConnectionService = null;
1506            serviceTemp.removeCall(this);
1507
1508            // Decrementing the count can cause the service to unbind, which itself can trigger the
1509            // service-death code.  Since the service death code tries to clean up any associated
1510            // calls, we need to make sure to remove that information (e.g., removeCall()) before
1511            // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
1512            // necessary, but cleaning up mConnectionService prior to triggering an unbind is good
1513            // to do.
1514            decrementAssociatedCallCount(serviceTemp);
1515        }
1516    }
1517
1518    /**
1519     * Starts the create connection sequence. Upon completion, there should exist an active
1520     * connection through a connection service (or the call will have failed).
1521     *
1522     * @param phoneAccountRegistrar The phone account registrar.
1523     */
1524    void startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar) {
1525        if (mCreateConnectionProcessor != null) {
1526            Log.w(this, "mCreateConnectionProcessor in startCreateConnection is not null. This is" +
1527                    " due to a race between NewOutgoingCallIntentBroadcaster and " +
1528                    "phoneAccountSelected, but is harmlessly resolved by ignoring the second " +
1529                    "invocation.");
1530            return;
1531        }
1532        mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this,
1533                phoneAccountRegistrar, mContext);
1534        mCreateConnectionProcessor.process();
1535    }
1536
1537    @Override
1538    public void handleCreateConnectionSuccess(
1539            CallIdMapper idMapper,
1540            ParcelableConnection connection) {
1541        Log.v(this, "handleCreateConnectionSuccessful %s", connection);
1542        setTargetPhoneAccount(connection.getPhoneAccount());
1543        setHandle(connection.getHandle(), connection.getHandlePresentation());
1544        setCallerDisplayName(
1545                connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());
1546
1547        setConnectionCapabilities(connection.getConnectionCapabilities());
1548        setConnectionProperties(connection.getConnectionProperties());
1549        setSupportedAudioRoutes(connection.getSupportedAudioRoutes());
1550        setVideoProvider(connection.getVideoProvider());
1551        setVideoState(connection.getVideoState());
1552        setRingbackRequested(connection.isRingbackRequested());
1553        setStatusHints(connection.getStatusHints());
1554        putExtras(SOURCE_CONNECTION_SERVICE, connection.getExtras());
1555
1556        mConferenceableCalls.clear();
1557        for (String id : connection.getConferenceableConnectionIds()) {
1558            mConferenceableCalls.add(idMapper.getCall(id));
1559        }
1560
1561        switch (mCallDirection) {
1562            case CALL_DIRECTION_INCOMING:
1563                // Listeners (just CallsManager for now) will be responsible for checking whether
1564                // the call should be blocked.
1565                for (Listener l : mListeners) {
1566                    l.onSuccessfulIncomingCall(this);
1567                }
1568                break;
1569            case CALL_DIRECTION_OUTGOING:
1570                for (Listener l : mListeners) {
1571                    l.onSuccessfulOutgoingCall(this,
1572                            getStateFromConnectionState(connection.getState()));
1573                }
1574                break;
1575            case CALL_DIRECTION_UNKNOWN:
1576                for (Listener l : mListeners) {
1577                    l.onSuccessfulUnknownCall(this, getStateFromConnectionState(connection
1578                            .getState()));
1579                }
1580                break;
1581        }
1582    }
1583
1584    @Override
1585    public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
1586        clearConnectionService();
1587        setDisconnectCause(disconnectCause);
1588        mCallsManager.markCallAsDisconnected(this, disconnectCause);
1589
1590        switch (mCallDirection) {
1591            case CALL_DIRECTION_INCOMING:
1592                for (Listener listener : mListeners) {
1593                    listener.onFailedIncomingCall(this);
1594                }
1595                break;
1596            case CALL_DIRECTION_OUTGOING:
1597                for (Listener listener : mListeners) {
1598                    listener.onFailedOutgoingCall(this, disconnectCause);
1599                }
1600                break;
1601            case CALL_DIRECTION_UNKNOWN:
1602                for (Listener listener : mListeners) {
1603                    listener.onFailedUnknownCall(this);
1604                }
1605                break;
1606        }
1607    }
1608
1609    /**
1610     * Plays the specified DTMF tone.
1611     */
1612    @VisibleForTesting
1613    public void playDtmfTone(char digit) {
1614        if (mConnectionService == null) {
1615            Log.w(this, "playDtmfTone() request on a call without a connection service.");
1616        } else {
1617            Log.i(this, "Send playDtmfTone to connection service for call %s", this);
1618            mConnectionService.playDtmfTone(this, digit);
1619            Log.addEvent(this, LogUtils.Events.START_DTMF, Log.pii(digit));
1620        }
1621        mPlayingDtmfTone = digit;
1622    }
1623
1624    /**
1625     * Stops playing any currently playing DTMF tone.
1626     */
1627    @VisibleForTesting
1628    public void stopDtmfTone() {
1629        if (mConnectionService == null) {
1630            Log.w(this, "stopDtmfTone() request on a call without a connection service.");
1631        } else {
1632            Log.i(this, "Send stopDtmfTone to connection service for call %s", this);
1633            Log.addEvent(this, LogUtils.Events.STOP_DTMF);
1634            mConnectionService.stopDtmfTone(this);
1635        }
1636        mPlayingDtmfTone = NO_DTMF_TONE;
1637    }
1638
1639    /**
1640     * @return {@code true} if a DTMF tone has been started via {@link #playDtmfTone(char)} but has
1641     * not been stopped via {@link #stopDtmfTone()}, {@code false} otherwise.
1642     */
1643    boolean isDtmfTonePlaying() {
1644        return mPlayingDtmfTone != NO_DTMF_TONE;
1645    }
1646
1647    /**
1648     * Silences the ringer.
1649     */
1650    void silence() {
1651        if (mConnectionService == null) {
1652            Log.w(this, "silence() request on a call without a connection service.");
1653        } else {
1654            Log.i(this, "Send silence to connection service for call %s", this);
1655            Log.addEvent(this, LogUtils.Events.SILENCE);
1656            mConnectionService.silence(this);
1657        }
1658    }
1659
1660    @VisibleForTesting
1661    public void disconnect() {
1662        disconnect(0);
1663    }
1664
1665    /**
1666     * Attempts to disconnect the call through the connection service.
1667     */
1668    @VisibleForTesting
1669    public void disconnect(long disconnectionTimeout) {
1670        disconnect(disconnectionTimeout, "internal" /** callingPackage */);
1671    }
1672
1673    /**
1674     * Attempts to disconnect the call through the connection service.
1675     */
1676    @VisibleForTesting
1677    public void disconnect(long disconnectionTimeout, String callingPackage) {
1678        Log.addEvent(this, LogUtils.Events.REQUEST_DISCONNECT, callingPackage);
1679
1680        // Track that the call is now locally disconnecting.
1681        setLocallyDisconnecting(true);
1682        maybeSetCallAsDisconnectingChild();
1683
1684        if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT ||
1685                mState == CallState.CONNECTING) {
1686            Log.v(this, "Aborting call %s", this);
1687            abort(disconnectionTimeout);
1688        } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
1689            if (mConnectionService == null) {
1690                Log.e(this, new Exception(), "disconnect() request on a call without a"
1691                        + " connection service.");
1692            } else {
1693                Log.i(this, "Send disconnect to connection service for call: %s", this);
1694                // The call isn't officially disconnected until the connection service
1695                // confirms that the call was actually disconnected. Only then is the
1696                // association between call and connection service severed, see
1697                // {@link CallsManager#markCallAsDisconnected}.
1698                mConnectionService.disconnect(this);
1699            }
1700        }
1701    }
1702
1703    void abort(long disconnectionTimeout) {
1704        if (mCreateConnectionProcessor != null &&
1705                !mCreateConnectionProcessor.isProcessingComplete()) {
1706            mCreateConnectionProcessor.abort();
1707        } else if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT
1708                || mState == CallState.CONNECTING) {
1709            if (disconnectionTimeout > 0) {
1710                // If the cancelation was from NEW_OUTGOING_CALL with a timeout of > 0
1711                // milliseconds, do not destroy the call.
1712                // Instead, we announce the cancellation and CallsManager handles
1713                // it through a timer. Since apps often cancel calls through NEW_OUTGOING_CALL and
1714                // then re-dial them quickly using a gateway, allowing the first call to end
1715                // causes jank. This timeout allows CallsManager to transition the first call into
1716                // the second call so that in-call only ever sees a single call...eliminating the
1717                // jank altogether. The app will also be able to set the timeout via an extra on
1718                // the ordered broadcast.
1719                for (Listener listener : mListeners) {
1720                    if (listener.onCanceledViaNewOutgoingCallBroadcast(
1721                            this, disconnectionTimeout)) {
1722                        // The first listener to handle this wins. A return value of true means that
1723                        // the listener will handle the disconnection process later and so we
1724                        // should not continue it here.
1725                        setLocallyDisconnecting(false);
1726                        return;
1727                    }
1728                }
1729            }
1730
1731            handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
1732        } else {
1733            Log.v(this, "Cannot abort a call which is neither SELECT_PHONE_ACCOUNT or CONNECTING");
1734        }
1735    }
1736
1737    /**
1738     * Answers the call if it is ringing.
1739     *
1740     * @param videoState The video state in which to answer the call.
1741     */
1742    @VisibleForTesting
1743    public void answer(int videoState) {
1744        // Check to verify that the call is still in the ringing state. A call can change states
1745        // between the time the user hits 'answer' and Telecom receives the command.
1746        if (isRinging("answer")) {
1747            if (!isVideoCallingSupported() && VideoProfile.isVideo(videoState)) {
1748                // Video calling is not supported, yet the InCallService is attempting to answer as
1749                // video.  We will simply answer as audio-only.
1750                videoState = VideoProfile.STATE_AUDIO_ONLY;
1751            }
1752            // At this point, we are asking the connection service to answer but we don't assume
1753            // that it will work. Instead, we wait until confirmation from the connectino service
1754            // that the call is in a non-STATE_RINGING state before changing the UI. See
1755            // {@link ConnectionServiceAdapter#setActive} and other set* methods.
1756            if (mConnectionService != null) {
1757                mConnectionService.answer(this, videoState);
1758            } else {
1759                Log.e(this, new NullPointerException(),
1760                        "answer call failed due to null CS callId=%s", getId());
1761            }
1762            Log.addEvent(this, LogUtils.Events.REQUEST_ACCEPT);
1763        }
1764    }
1765
1766    /**
1767     * Deflects the call if it is ringing.
1768     *
1769     * @param address address to be deflected to.
1770     */
1771    @VisibleForTesting
1772    public void deflect(Uri address) {
1773        // Check to verify that the call is still in the ringing state. A call can change states
1774        // between the time the user hits 'deflect' and Telecomm receives the command.
1775        if (isRinging("deflect")) {
1776            // At this point, we are asking the connection service to deflect but we don't assume
1777            // that it will work. Instead, we wait until confirmation from the connection service
1778            // that the call is in a non-STATE_RINGING state before changing the UI. See
1779            // {@link ConnectionServiceAdapter#setActive} and other set* methods.
1780            mVideoStateHistory |= mVideoState;
1781            if (mConnectionService != null) {
1782                mConnectionService.deflect(this, address);
1783            } else {
1784                Log.e(this, new NullPointerException(),
1785                        "deflect call failed due to null CS callId=%s", getId());
1786            }
1787            Log.addEvent(this, LogUtils.Events.REQUEST_DEFLECT, Log.pii(address));
1788        }
1789    }
1790
1791    /**
1792     * Rejects the call if it is ringing.
1793     *
1794     * @param rejectWithMessage Whether to send a text message as part of the call rejection.
1795     * @param textMessage An optional text message to send as part of the rejection.
1796     */
1797    @VisibleForTesting
1798    public void reject(boolean rejectWithMessage, String textMessage) {
1799        reject(rejectWithMessage, textMessage, "internal" /** callingPackage */);
1800    }
1801
1802    /**
1803     * Rejects the call if it is ringing.
1804     *
1805     * @param rejectWithMessage Whether to send a text message as part of the call rejection.
1806     * @param textMessage An optional text message to send as part of the rejection.
1807     */
1808    @VisibleForTesting
1809    public void reject(boolean rejectWithMessage, String textMessage, String callingPackage) {
1810        // Check to verify that the call is still in the ringing state. A call can change states
1811        // between the time the user hits 'reject' and Telecomm receives the command.
1812        if (isRinging("reject")) {
1813            // Ensure video state history tracks video state at time of rejection.
1814            mVideoStateHistory |= mVideoState;
1815
1816            if (mConnectionService != null) {
1817                mConnectionService.reject(this, rejectWithMessage, textMessage);
1818            } else {
1819                Log.e(this, new NullPointerException(),
1820                        "reject call failed due to null CS callId=%s", getId());
1821            }
1822            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, callingPackage);
1823        }
1824    }
1825
1826    /**
1827     * Puts the call on hold if it is currently active.
1828     */
1829    @VisibleForTesting
1830    public void hold() {
1831        if (mState == CallState.ACTIVE) {
1832            if (mConnectionService != null) {
1833                mConnectionService.hold(this);
1834            } else {
1835                Log.e(this, new NullPointerException(),
1836                        "hold call failed due to null CS callId=%s", getId());
1837            }
1838            Log.addEvent(this, LogUtils.Events.REQUEST_HOLD);
1839        }
1840    }
1841
1842    /**
1843     * Releases the call from hold if it is currently active.
1844     */
1845    @VisibleForTesting
1846    public void unhold() {
1847        if (mState == CallState.ON_HOLD) {
1848            if (mConnectionService != null) {
1849                mConnectionService.unhold(this);
1850            } else {
1851                Log.e(this, new NullPointerException(),
1852                        "unhold call failed due to null CS callId=%s", getId());
1853            }
1854            Log.addEvent(this, LogUtils.Events.REQUEST_UNHOLD);
1855        }
1856    }
1857
1858    /** Checks if this is a live call or not. */
1859    @VisibleForTesting
1860    public boolean isAlive() {
1861        switch (mState) {
1862            case CallState.NEW:
1863            case CallState.RINGING:
1864            case CallState.DISCONNECTED:
1865            case CallState.ABORTED:
1866                return false;
1867            default:
1868                return true;
1869        }
1870    }
1871
1872    boolean isActive() {
1873        return mState == CallState.ACTIVE;
1874    }
1875
1876    Bundle getExtras() {
1877        return mExtras;
1878    }
1879
1880    /**
1881     * Adds extras to the extras bundle associated with this {@link Call}.
1882     *
1883     * Note: this method needs to know the source of the extras change (see
1884     * {@link #SOURCE_CONNECTION_SERVICE}, {@link #SOURCE_INCALL_SERVICE}).  Extras changes which
1885     * originate from a connection service will only be notified to incall services.  Likewise,
1886     * changes originating from the incall services will only notify the connection service of the
1887     * change.
1888     *
1889     * @param source The source of the extras addition.
1890     * @param extras The extras.
1891     */
1892    void putExtras(int source, Bundle extras) {
1893        if (extras == null) {
1894            return;
1895        }
1896        if (mExtras == null) {
1897            mExtras = new Bundle();
1898        }
1899        mExtras.putAll(extras);
1900
1901        for (Listener l : mListeners) {
1902            l.onExtrasChanged(this, source, extras);
1903        }
1904
1905        // If the change originated from an InCallService, notify the connection service.
1906        if (source == SOURCE_INCALL_SERVICE) {
1907            if (mConnectionService != null) {
1908                mConnectionService.onExtrasChanged(this, mExtras);
1909            } else {
1910                Log.e(this, new NullPointerException(),
1911                        "putExtras failed due to null CS callId=%s", getId());
1912            }
1913        }
1914    }
1915
1916    /**
1917     * Removes extras from the extras bundle associated with this {@link Call}.
1918     *
1919     * Note: this method needs to know the source of the extras change (see
1920     * {@link #SOURCE_CONNECTION_SERVICE}, {@link #SOURCE_INCALL_SERVICE}).  Extras changes which
1921     * originate from a connection service will only be notified to incall services.  Likewise,
1922     * changes originating from the incall services will only notify the connection service of the
1923     * change.
1924     *
1925     * @param source The source of the extras removal.
1926     * @param keys The extra keys to remove.
1927     */
1928    void removeExtras(int source, List<String> keys) {
1929        if (mExtras == null) {
1930            return;
1931        }
1932        for (String key : keys) {
1933            mExtras.remove(key);
1934        }
1935
1936        for (Listener l : mListeners) {
1937            l.onExtrasRemoved(this, source, keys);
1938        }
1939
1940        // If the change originated from an InCallService, notify the connection service.
1941        if (source == SOURCE_INCALL_SERVICE) {
1942            if (mConnectionService != null) {
1943                mConnectionService.onExtrasChanged(this, mExtras);
1944            } else {
1945                Log.e(this, new NullPointerException(),
1946                        "removeExtras failed due to null CS callId=%s", getId());
1947            }
1948        }
1949    }
1950
1951    @VisibleForTesting
1952    public Bundle getIntentExtras() {
1953        return mIntentExtras;
1954    }
1955
1956    void setIntentExtras(Bundle extras) {
1957        mIntentExtras = extras;
1958    }
1959
1960    public Intent getOriginalCallIntent() {
1961        return mOriginalCallIntent;
1962    }
1963
1964    public void setOriginalCallIntent(Intent intent) {
1965        mOriginalCallIntent = intent;
1966    }
1967
1968    /**
1969     * @return the uri of the contact associated with this call.
1970     */
1971    @VisibleForTesting
1972    public Uri getContactUri() {
1973        if (mCallerInfo == null || !mCallerInfo.contactExists) {
1974            return getHandle();
1975        }
1976        return Contacts.getLookupUri(mCallerInfo.contactIdOrZero, mCallerInfo.lookupKey);
1977    }
1978
1979    Uri getRingtone() {
1980        return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
1981    }
1982
1983    void onPostDialWait(String remaining) {
1984        for (Listener l : mListeners) {
1985            l.onPostDialWait(this, remaining);
1986        }
1987    }
1988
1989    void onPostDialChar(char nextChar) {
1990        for (Listener l : mListeners) {
1991            l.onPostDialChar(this, nextChar);
1992        }
1993    }
1994
1995    void postDialContinue(boolean proceed) {
1996        if (mConnectionService != null) {
1997            mConnectionService.onPostDialContinue(this, proceed);
1998        } else {
1999            Log.e(this, new NullPointerException(),
2000                    "postDialContinue failed due to null CS callId=%s", getId());
2001        }
2002    }
2003
2004    void conferenceWith(Call otherCall) {
2005        if (mConnectionService == null) {
2006            Log.w(this, "conference requested on a call without a connection service.");
2007        } else {
2008            Log.addEvent(this, LogUtils.Events.CONFERENCE_WITH, otherCall);
2009            mConnectionService.conference(this, otherCall);
2010        }
2011    }
2012
2013    void splitFromConference() {
2014        if (mConnectionService == null) {
2015            Log.w(this, "splitting from conference call without a connection service");
2016        } else {
2017            Log.addEvent(this, LogUtils.Events.SPLIT_FROM_CONFERENCE);
2018            mConnectionService.splitFromConference(this);
2019        }
2020    }
2021
2022    @VisibleForTesting
2023    public void mergeConference() {
2024        if (mConnectionService == null) {
2025            Log.w(this, "merging conference calls without a connection service.");
2026        } else if (can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
2027            Log.addEvent(this, LogUtils.Events.CONFERENCE_WITH);
2028            mConnectionService.mergeConference(this);
2029            mWasConferencePreviouslyMerged = true;
2030        }
2031    }
2032
2033    @VisibleForTesting
2034    public void swapConference() {
2035        if (mConnectionService == null) {
2036            Log.w(this, "swapping conference calls without a connection service.");
2037        } else if (can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
2038            Log.addEvent(this, LogUtils.Events.SWAP);
2039            mConnectionService.swapConference(this);
2040            switch (mChildCalls.size()) {
2041                case 1:
2042                    mConferenceLevelActiveCall = mChildCalls.get(0);
2043                    break;
2044                case 2:
2045                    // swap
2046                    mConferenceLevelActiveCall = mChildCalls.get(0) == mConferenceLevelActiveCall ?
2047                            mChildCalls.get(1) : mChildCalls.get(0);
2048                    break;
2049                default:
2050                    // For anything else 0, or 3+, set it to null since it is impossible to tell.
2051                    mConferenceLevelActiveCall = null;
2052                    break;
2053            }
2054        }
2055    }
2056
2057    /**
2058     * Initiates a request to the connection service to pull this call.
2059     * <p>
2060     * This method can only be used for calls that have the
2061     * {@link android.telecom.Connection#CAPABILITY_CAN_PULL_CALL} capability and
2062     * {@link android.telecom.Connection#PROPERTY_IS_EXTERNAL_CALL} property set.
2063     * <p>
2064     * An external call is a representation of a call which is taking place on another device
2065     * associated with a PhoneAccount on this device.  Issuing a request to pull the external call
2066     * tells the {@link android.telecom.ConnectionService} that it should move the call from the
2067     * other device to this one.  An example of this is the IMS multi-endpoint functionality.  A
2068     * user may have two phones with the same phone number.  If the user is engaged in an active
2069     * call on their first device, the network will inform the second device of that ongoing call in
2070     * the form of an external call.  The user may wish to continue their conversation on the second
2071     * device, so will issue a request to pull the call to the second device.
2072     * <p>
2073     * Requests to pull a call which is not external, or a call which is not pullable are ignored.
2074     */
2075    public void pullExternalCall() {
2076        if (mConnectionService == null) {
2077            Log.w(this, "pulling a call without a connection service.");
2078        }
2079
2080        if (!hasProperty(Connection.PROPERTY_IS_EXTERNAL_CALL)) {
2081            Log.w(this, "pullExternalCall - call %s is not an external call.", mId);
2082            return;
2083        }
2084
2085        if (!can(Connection.CAPABILITY_CAN_PULL_CALL)) {
2086            Log.w(this, "pullExternalCall - call %s is external but cannot be pulled.", mId);
2087            return;
2088        }
2089        Log.addEvent(this, LogUtils.Events.REQUEST_PULL);
2090        mConnectionService.pullExternalCall(this);
2091    }
2092
2093    /**
2094     * Sends a call event to the {@link ConnectionService} for this call. This function is
2095     * called for event other than {@link Call#EVENT_REQUEST_HANDOVER}
2096     *
2097     * @param event The call event.
2098     * @param extras Associated extras.
2099     */
2100    public void sendCallEvent(String event, Bundle extras) {
2101        sendCallEvent(event, 0/*For Event != EVENT_REQUEST_HANDOVER*/, extras);
2102    }
2103
2104    /**
2105     * Sends a call event to the {@link ConnectionService} for this call.
2106     *
2107     * See {@link Call#sendCallEvent(String, Bundle)}.
2108     *
2109     * @param event The call event.
2110     * @param targetSdkVer SDK version of the app calling this api
2111     * @param extras Associated extras.
2112     */
2113    public void sendCallEvent(String event, int targetSdkVer, Bundle extras) {
2114        if (mConnectionService != null) {
2115            if (android.telecom.Call.EVENT_REQUEST_HANDOVER.equals(event)) {
2116                if (targetSdkVer > Build.VERSION_CODES.O_MR1) {
2117                    Log.e(this, new Exception(), "sendCallEvent failed. Use public api handoverTo" +
2118                            " for API > 27(O-MR1)");
2119                    // TODO: Add "return" after DUO team adds new API support for handover
2120                }
2121
2122                // Handover requests are targeted at Telecom, not the ConnectionService.
2123                if (extras == null) {
2124                    Log.w(this, "sendCallEvent: %s event received with null extras.",
2125                            android.telecom.Call.EVENT_REQUEST_HANDOVER);
2126                    mConnectionService.sendCallEvent(this,
2127                            android.telecom.Call.EVENT_HANDOVER_FAILED, null);
2128                    return;
2129                }
2130                Parcelable parcelable = extras.getParcelable(
2131                        android.telecom.Call.EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE);
2132                if (!(parcelable instanceof PhoneAccountHandle) || parcelable == null) {
2133                    Log.w(this, "sendCallEvent: %s event received with invalid handover acct.",
2134                            android.telecom.Call.EVENT_REQUEST_HANDOVER);
2135                    mConnectionService.sendCallEvent(this,
2136                            android.telecom.Call.EVENT_HANDOVER_FAILED, null);
2137                    return;
2138                }
2139                PhoneAccountHandle phoneAccountHandle = (PhoneAccountHandle) parcelable;
2140                int videoState = extras.getInt(android.telecom.Call.EXTRA_HANDOVER_VIDEO_STATE,
2141                        VideoProfile.STATE_AUDIO_ONLY);
2142                Parcelable handoverExtras = extras.getParcelable(
2143                        android.telecom.Call.EXTRA_HANDOVER_EXTRAS);
2144                Bundle handoverExtrasBundle = null;
2145                if (handoverExtras instanceof Bundle) {
2146                    handoverExtrasBundle = (Bundle) handoverExtras;
2147                }
2148                requestHandover(phoneAccountHandle, videoState, handoverExtrasBundle, true);
2149            } else {
2150                Log.addEvent(this, LogUtils.Events.CALL_EVENT, event);
2151                mConnectionService.sendCallEvent(this, event, extras);
2152            }
2153        } else {
2154            Log.e(this, new NullPointerException(),
2155                    "sendCallEvent failed due to null CS callId=%s", getId());
2156        }
2157    }
2158
2159    /**
2160     * Initiates a handover of this Call to the {@link ConnectionService} identified
2161     * by destAcct.
2162     * @param destAcct ConnectionService to which the call should be handed over.
2163     * @param videoState The video state desired after the handover.
2164     * @param extras Extra information to be passed to ConnectionService
2165     */
2166    public void handoverTo(PhoneAccountHandle destAcct, int videoState, Bundle extras) {
2167        requestHandover(destAcct, videoState, extras, false);
2168    }
2169
2170    /**
2171     * Sets this {@link Call} to has the specified {@code parentCall}.  Also sets the parent to
2172     * have this call as a child.
2173     * @param parentCall
2174     */
2175    void setParentAndChildCall(Call parentCall) {
2176        boolean isParentChanging = (mParentCall != parentCall);
2177        setParentCall(parentCall);
2178        setChildOf(parentCall);
2179        if (isParentChanging) {
2180            notifyParentChanged(parentCall);
2181        }
2182    }
2183
2184    /**
2185     * Notifies listeners when the parent call changes.
2186     * Used by {@link #setParentAndChildCall(Call)}, and in {@link CallsManager}.
2187     * @param parentCall The new parent call for this call.
2188     */
2189    void notifyParentChanged(Call parentCall) {
2190        Log.addEvent(this, LogUtils.Events.SET_PARENT, parentCall);
2191        for (Listener l : mListeners) {
2192            l.onParentChanged(this);
2193        }
2194    }
2195
2196    /**
2197     * Unlike {@link #setParentAndChildCall(Call)}, only sets the parent call but does NOT set
2198     * the child.
2199     * TODO: This is only required when adding existing connections as a workaround so that we
2200     * can avoid sending the "onParentChanged" callback until later.
2201     * @param parentCall The new parent call.
2202     */
2203    void setParentCall(Call parentCall) {
2204        if (parentCall == this) {
2205            Log.e(this, new Exception(), "setting the parent to self");
2206            return;
2207        }
2208        if (parentCall == mParentCall) {
2209            // nothing to do
2210            return;
2211        }
2212        if (mParentCall != null) {
2213            mParentCall.removeChildCall(this);
2214        }
2215        mParentCall = parentCall;
2216    }
2217
2218    /**
2219     * To be called after {@link #setParentCall(Call)} to complete setting the parent by adding
2220     * this call as a child of another call.
2221     * <p>
2222     * Note: if using this method alone, the caller must call {@link #notifyParentChanged(Call)} to
2223     * ensure the InCall UI is updated with the change in parent.
2224     * @param parentCall The new parent for this call.
2225     */
2226    void setChildOf(Call parentCall) {
2227        if (parentCall != null && !parentCall.getChildCalls().contains(this)) {
2228            parentCall.addChildCall(this);
2229        }
2230    }
2231
2232    void setConferenceableCalls(List<Call> conferenceableCalls) {
2233        mConferenceableCalls.clear();
2234        mConferenceableCalls.addAll(conferenceableCalls);
2235
2236        for (Listener l : mListeners) {
2237            l.onConferenceableCallsChanged(this);
2238        }
2239    }
2240
2241    @VisibleForTesting
2242    public List<Call> getConferenceableCalls() {
2243        return mConferenceableCalls;
2244    }
2245
2246    @VisibleForTesting
2247    public boolean can(int capability) {
2248        return (mConnectionCapabilities & capability) == capability;
2249    }
2250
2251    @VisibleForTesting
2252    public boolean hasProperty(int property) {
2253        return (mConnectionProperties & property) == property;
2254    }
2255
2256    private void addChildCall(Call call) {
2257        if (!mChildCalls.contains(call)) {
2258            // Set the pseudo-active call to the latest child added to the conference.
2259            // See definition of mConferenceLevelActiveCall for more detail.
2260            mConferenceLevelActiveCall = call;
2261            mChildCalls.add(call);
2262
2263            Log.addEvent(this, LogUtils.Events.ADD_CHILD, call);
2264
2265            for (Listener l : mListeners) {
2266                l.onChildrenChanged(this);
2267            }
2268        }
2269    }
2270
2271    private void removeChildCall(Call call) {
2272        if (mChildCalls.remove(call)) {
2273            Log.addEvent(this, LogUtils.Events.REMOVE_CHILD, call);
2274            for (Listener l : mListeners) {
2275                l.onChildrenChanged(this);
2276            }
2277        }
2278    }
2279
2280    /**
2281     * Return whether the user can respond to this {@code Call} via an SMS message.
2282     *
2283     * @return true if the "Respond via SMS" feature should be enabled
2284     * for this incoming call.
2285     *
2286     * The general rule is that we *do* allow "Respond via SMS" except for
2287     * the few (relatively rare) cases where we know for sure it won't
2288     * work, namely:
2289     *   - a bogus or blank incoming number
2290     *   - a call from a SIP address
2291     *   - a "call presentation" that doesn't allow the number to be revealed
2292     *
2293     * In all other cases, we allow the user to respond via SMS.
2294     *
2295     * Note that this behavior isn't perfect; for example we have no way
2296     * to detect whether the incoming call is from a landline (with most
2297     * networks at least), so we still enable this feature even though
2298     * SMSes to that number will silently fail.
2299     */
2300    boolean isRespondViaSmsCapable() {
2301        if (mState != CallState.RINGING) {
2302            return false;
2303        }
2304
2305        if (getHandle() == null) {
2306            // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in
2307            // other words, the user should not be able to see the incoming phone number.
2308            return false;
2309        }
2310
2311        if (mPhoneNumberUtilsAdapter.isUriNumber(getHandle().toString())) {
2312            // The incoming number is actually a URI (i.e. a SIP address),
2313            // not a regular PSTN phone number, and we can't send SMSes to
2314            // SIP addresses.
2315            // (TODO: That might still be possible eventually, though. Is
2316            // there some SIP-specific equivalent to sending a text message?)
2317            return false;
2318        }
2319
2320        // Is there a valid SMS application on the phone?
2321        if (SmsApplication.getDefaultRespondViaMessageApplication(mContext,
2322                true /*updateIfNeeded*/) == null) {
2323            return false;
2324        }
2325
2326        // TODO: with some carriers (in certain countries) you *can* actually
2327        // tell whether a given number is a mobile phone or not. So in that
2328        // case we could potentially return false here if the incoming call is
2329        // from a land line.
2330
2331        // If none of the above special cases apply, it's OK to enable the
2332        // "Respond via SMS" feature.
2333        return true;
2334    }
2335
2336    List<String> getCannedSmsResponses() {
2337        return mCannedSmsResponses;
2338    }
2339
2340    /**
2341     * We need to make sure that before we move a call to the disconnected state, it no
2342     * longer has any parent/child relationships.  We want to do this to ensure that the InCall
2343     * Service always has the right data in the right order.  We also want to do it in telecom so
2344     * that the insurance policy lives in the framework side of things.
2345     */
2346    private void fixParentAfterDisconnect() {
2347        setParentAndChildCall(null);
2348    }
2349
2350    /**
2351     * @return True if the call is ringing, else logs the action name.
2352     */
2353    private boolean isRinging(String actionName) {
2354        if (mState == CallState.RINGING) {
2355            return true;
2356        }
2357
2358        Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
2359        return false;
2360    }
2361
2362    @SuppressWarnings("rawtypes")
2363    private void decrementAssociatedCallCount(ServiceBinder binder) {
2364        if (binder != null) {
2365            binder.decrementAssociatedCallCount();
2366        }
2367    }
2368
2369    /**
2370     * Looks up contact information based on the current handle.
2371     */
2372    private void startCallerInfoLookup() {
2373        mCallerInfo = null;
2374        mCallsManager.getCallerInfoLookupHelper().startLookup(mHandle, mCallerInfoQueryListener);
2375    }
2376
2377    /**
2378     * Saves the specified caller info if the specified token matches that of the last query
2379     * that was made.
2380     *
2381     * @param callerInfo The new caller information to set.
2382     */
2383    private void setCallerInfo(Uri handle, CallerInfo callerInfo) {
2384        Trace.beginSection("setCallerInfo");
2385        if (callerInfo == null) {
2386            Log.i(this, "CallerInfo lookup returned null, skipping update");
2387            return;
2388        }
2389
2390        if ((handle != null) && !handle.equals(mHandle)) {
2391            Log.i(this, "setCallerInfo received stale caller info for an old handle. Ignoring.");
2392            return;
2393        }
2394
2395        mCallerInfo = callerInfo;
2396        Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
2397
2398        if (mCallerInfo.contactDisplayPhotoUri == null ||
2399                mCallerInfo.cachedPhotoIcon != null || mCallerInfo.cachedPhoto != null) {
2400            for (Listener l : mListeners) {
2401                l.onCallerInfoChanged(this);
2402            }
2403        }
2404
2405        Trace.endSection();
2406    }
2407
2408    public CallerInfo getCallerInfo() {
2409        return mCallerInfo;
2410    }
2411
2412    private void maybeLoadCannedSmsResponses() {
2413        if (mCallDirection == CALL_DIRECTION_INCOMING
2414                && isRespondViaSmsCapable()
2415                && !mCannedSmsResponsesLoadingStarted) {
2416            Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
2417            mCannedSmsResponsesLoadingStarted = true;
2418            mCallsManager.getRespondViaSmsManager().loadCannedTextMessages(
2419                    new Response<Void, List<String>>() {
2420                        @Override
2421                        public void onResult(Void request, List<String>... result) {
2422                            if (result.length > 0) {
2423                                Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]);
2424                                mCannedSmsResponses = result[0];
2425                                for (Listener l : mListeners) {
2426                                    l.onCannedSmsResponsesLoaded(Call.this);
2427                                }
2428                            }
2429                        }
2430
2431                        @Override
2432                        public void onError(Void request, int code, String msg) {
2433                            Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code,
2434                                    msg);
2435                        }
2436                    },
2437                    mContext
2438            );
2439        } else {
2440            Log.d(this, "maybeLoadCannedSmsResponses: doing nothing");
2441        }
2442    }
2443
2444    /**
2445     * Sets speakerphone option on when call begins.
2446     */
2447    public void setStartWithSpeakerphoneOn(boolean startWithSpeakerphone) {
2448        mSpeakerphoneOn = startWithSpeakerphone;
2449    }
2450
2451    /**
2452     * Returns speakerphone option.
2453     *
2454     * @return Whether or not speakerphone should be set automatically when call begins.
2455     */
2456    public boolean getStartWithSpeakerphoneOn() {
2457        return mSpeakerphoneOn;
2458    }
2459
2460    public void stopRtt() {
2461        if (mConnectionService != null) {
2462            mConnectionService.stopRtt(this);
2463        } else {
2464            // If this gets called by the in-call app before the connection service is set, we'll
2465            // just ignore it since it's really not supposed to happen.
2466            Log.w(this, "stopRtt() called before connection service is set.");
2467        }
2468    }
2469
2470    public void sendRttRequest() {
2471        setRttStreams(true);
2472        mConnectionService.startRtt(this, getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs());
2473    }
2474
2475    public void setRttStreams(boolean shouldBeRtt) {
2476        boolean areStreamsInitialized = mInCallToConnectionServiceStreams != null
2477                && mConnectionServiceToInCallStreams != null;
2478        if (shouldBeRtt && !areStreamsInitialized) {
2479            try {
2480                mWasEverRtt = true;
2481                mInCallToConnectionServiceStreams = ParcelFileDescriptor.createReliablePipe();
2482                mConnectionServiceToInCallStreams = ParcelFileDescriptor.createReliablePipe();
2483            } catch (IOException e) {
2484                Log.e(this, e, "Failed to create pipes for RTT call.");
2485            }
2486        } else if (!shouldBeRtt && areStreamsInitialized) {
2487            closeRttPipes();
2488            mInCallToConnectionServiceStreams = null;
2489            mConnectionServiceToInCallStreams = null;
2490        }
2491    }
2492
2493    public void onRttConnectionFailure(int reason) {
2494        setRttStreams(false);
2495        for (Listener l : mListeners) {
2496            l.onRttInitiationFailure(this, reason);
2497        }
2498    }
2499
2500    public void onRemoteRttRequest() {
2501        if (isRttCall()) {
2502            Log.w(this, "Remote RTT request on a call that's already RTT");
2503            return;
2504        }
2505
2506        mPendingRttRequestId = mCallsManager.getNextRttRequestId();
2507        for (Listener l : mListeners) {
2508            l.onRemoteRttRequest(this, mPendingRttRequestId);
2509        }
2510    }
2511
2512    public void handleRttRequestResponse(int id, boolean accept) {
2513        if (mPendingRttRequestId == INVALID_RTT_REQUEST_ID) {
2514            Log.w(this, "Response received to a nonexistent RTT request: %d", id);
2515            return;
2516        }
2517        if (id != mPendingRttRequestId) {
2518            Log.w(this, "Response ID %d does not match expected %d", id, mPendingRttRequestId);
2519            return;
2520        }
2521        setRttStreams(accept);
2522        if (accept) {
2523            Log.i(this, "RTT request %d accepted.", id);
2524            mConnectionService.respondToRttRequest(
2525                    this, getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs());
2526        } else {
2527            Log.i(this, "RTT request %d rejected.", id);
2528            mConnectionService.respondToRttRequest(this, null, null);
2529        }
2530    }
2531
2532    public void closeRttPipes() {
2533        // TODO: may defer this until call is removed?
2534    }
2535
2536    public boolean isRttCall() {
2537        return (mConnectionProperties & Connection.PROPERTY_IS_RTT) == Connection.PROPERTY_IS_RTT;
2538    }
2539
2540    public boolean wasEverRttCall() {
2541        return mWasEverRtt;
2542    }
2543
2544    public ParcelFileDescriptor getCsToInCallRttPipeForCs() {
2545        return mConnectionServiceToInCallStreams == null ? null
2546                : mConnectionServiceToInCallStreams[RTT_PIPE_WRITE_SIDE_INDEX];
2547    }
2548
2549    public ParcelFileDescriptor getInCallToCsRttPipeForCs() {
2550        return mInCallToConnectionServiceStreams == null ? null
2551                : mInCallToConnectionServiceStreams[RTT_PIPE_READ_SIDE_INDEX];
2552    }
2553
2554    public ParcelFileDescriptor getCsToInCallRttPipeForInCall() {
2555        return mConnectionServiceToInCallStreams == null ? null
2556                : mConnectionServiceToInCallStreams[RTT_PIPE_READ_SIDE_INDEX];
2557    }
2558
2559    public ParcelFileDescriptor getInCallToCsRttPipeForInCall() {
2560        return mInCallToConnectionServiceStreams == null ? null
2561                : mInCallToConnectionServiceStreams[RTT_PIPE_WRITE_SIDE_INDEX];
2562    }
2563
2564    public int getRttMode() {
2565        return mRttMode;
2566    }
2567
2568    /**
2569     * Sets a video call provider for the call.
2570     */
2571    public void setVideoProvider(IVideoProvider videoProvider) {
2572        Log.v(this, "setVideoProvider");
2573
2574        if (videoProvider != null ) {
2575            try {
2576                mVideoProviderProxy = new VideoProviderProxy(mLock, videoProvider, this,
2577                        mCallsManager);
2578            } catch (RemoteException ignored) {
2579                // Ignore RemoteException.
2580            }
2581        } else {
2582            mVideoProviderProxy = null;
2583        }
2584
2585        mVideoProvider = videoProvider;
2586
2587        for (Listener l : mListeners) {
2588            l.onVideoCallProviderChanged(Call.this);
2589        }
2590    }
2591
2592    /**
2593     * @return The {@link Connection.VideoProvider} binder.
2594     */
2595    public IVideoProvider getVideoProvider() {
2596        if (mVideoProviderProxy == null) {
2597            return null;
2598        }
2599
2600        return mVideoProviderProxy.getInterface();
2601    }
2602
2603    /**
2604     * @return The {@link VideoProviderProxy} for this call.
2605     */
2606    public VideoProviderProxy getVideoProviderProxy() {
2607        return mVideoProviderProxy;
2608    }
2609
2610    /**
2611     * The current video state for the call.
2612     * See {@link VideoProfile} for a list of valid video states.
2613     */
2614    public int getVideoState() {
2615        return mVideoState;
2616    }
2617
2618    /**
2619     * Returns the video states which were applicable over the duration of a call.
2620     * See {@link VideoProfile} for a list of valid video states.
2621     *
2622     * @return The video states applicable over the duration of the call.
2623     */
2624    public int getVideoStateHistory() {
2625        return mVideoStateHistory;
2626    }
2627
2628    /**
2629     * Determines the current video state for the call.
2630     * For an outgoing call determines the desired video state for the call.
2631     * Valid values: see {@link VideoProfile}
2632     *
2633     * @param videoState The video state for the call.
2634     */
2635    public void setVideoState(int videoState) {
2636        // If the phone account associated with this call does not support video calling, then we
2637        // will automatically set the video state to audio-only.
2638        if (!isVideoCallingSupported()) {
2639            Log.d(this, "setVideoState: videoState=%s defaulted to audio (video not supported)",
2640                    VideoProfile.videoStateToString(videoState));
2641            videoState = VideoProfile.STATE_AUDIO_ONLY;
2642        }
2643
2644        // Track Video State history during the duration of the call.
2645        // Only update the history when the call is active or disconnected. This ensures we do
2646        // not include the video state history when:
2647        // - Call is incoming (but not answered).
2648        // - Call it outgoing (but not answered).
2649        // We include the video state when disconnected to ensure that rejected calls reflect the
2650        // appropriate video state.
2651        // For all other times we add to the video state history, see #setState.
2652        if (isActive() || getState() == CallState.DISCONNECTED) {
2653            mVideoStateHistory = mVideoStateHistory | videoState;
2654        }
2655
2656        int previousVideoState = mVideoState;
2657        mVideoState = videoState;
2658        if (mVideoState != previousVideoState) {
2659            Log.addEvent(this, LogUtils.Events.VIDEO_STATE_CHANGED,
2660                    VideoProfile.videoStateToString(videoState));
2661            for (Listener l : mListeners) {
2662                l.onVideoStateChanged(this, previousVideoState, mVideoState);
2663            }
2664        }
2665
2666        if (VideoProfile.isVideo(videoState)) {
2667            mAnalytics.setCallIsVideo(true);
2668        }
2669    }
2670
2671    public boolean getIsVoipAudioMode() {
2672        return mIsVoipAudioMode;
2673    }
2674
2675    public void setIsVoipAudioMode(boolean audioModeIsVoip) {
2676        mIsVoipAudioMode = audioModeIsVoip;
2677        for (Listener l : mListeners) {
2678            l.onIsVoipAudioModeChanged(this);
2679        }
2680    }
2681
2682    public StatusHints getStatusHints() {
2683        return mStatusHints;
2684    }
2685
2686    public void setStatusHints(StatusHints statusHints) {
2687        mStatusHints = statusHints;
2688        for (Listener l : mListeners) {
2689            l.onStatusHintsChanged(this);
2690        }
2691    }
2692
2693    public boolean isUnknown() {
2694        return mCallDirection == CALL_DIRECTION_UNKNOWN;
2695    }
2696
2697    /**
2698     * Determines if this call is in a disconnecting state.
2699     *
2700     * @return {@code true} if this call is locally disconnecting.
2701     */
2702    public boolean isLocallyDisconnecting() {
2703        return mIsLocallyDisconnecting;
2704    }
2705
2706    /**
2707     * Sets whether this call is in a disconnecting state.
2708     *
2709     * @param isLocallyDisconnecting {@code true} if this call is locally disconnecting.
2710     */
2711    private void setLocallyDisconnecting(boolean isLocallyDisconnecting) {
2712        mIsLocallyDisconnecting = isLocallyDisconnecting;
2713    }
2714
2715    /**
2716     * @return user handle of user initiating the outgoing call.
2717     */
2718    public UserHandle getInitiatingUser() {
2719        return mInitiatingUser;
2720    }
2721
2722    /**
2723     * Set the user handle of user initiating the outgoing call.
2724     * @param initiatingUser
2725     */
2726    public void setInitiatingUser(UserHandle initiatingUser) {
2727        Preconditions.checkNotNull(initiatingUser);
2728        mInitiatingUser = initiatingUser;
2729    }
2730
2731    static int getStateFromConnectionState(int state) {
2732        switch (state) {
2733            case Connection.STATE_INITIALIZING:
2734                return CallState.CONNECTING;
2735            case Connection.STATE_ACTIVE:
2736                return CallState.ACTIVE;
2737            case Connection.STATE_DIALING:
2738                return CallState.DIALING;
2739            case Connection.STATE_PULLING_CALL:
2740                return CallState.PULLING;
2741            case Connection.STATE_DISCONNECTED:
2742                return CallState.DISCONNECTED;
2743            case Connection.STATE_HOLDING:
2744                return CallState.ON_HOLD;
2745            case Connection.STATE_NEW:
2746                return CallState.NEW;
2747            case Connection.STATE_RINGING:
2748                return CallState.RINGING;
2749        }
2750        return CallState.DISCONNECTED;
2751    }
2752
2753    /**
2754     * Determines if this call is in disconnected state and waiting to be destroyed.
2755     *
2756     * @return {@code true} if this call is disconected.
2757     */
2758    public boolean isDisconnected() {
2759        return (getState() == CallState.DISCONNECTED || getState() == CallState.ABORTED);
2760    }
2761
2762    /**
2763     * Determines if this call has just been created and has not been configured properly yet.
2764     *
2765     * @return {@code true} if this call is new.
2766     */
2767    public boolean isNew() {
2768        return getState() == CallState.NEW;
2769    }
2770
2771    /**
2772     * Sets the call data usage for the call.
2773     *
2774     * @param callDataUsage The new call data usage (in bytes).
2775     */
2776    public void setCallDataUsage(long callDataUsage) {
2777        mCallDataUsage = callDataUsage;
2778    }
2779
2780    /**
2781     * Returns the call data usage for the call.
2782     *
2783     * @return The call data usage (in bytes).
2784     */
2785    public long getCallDataUsage() {
2786        return mCallDataUsage;
2787    }
2788
2789    public void setRttMode(int mode) {
2790        mRttMode = mode;
2791        // TODO: hook this up to CallAudioManager
2792    }
2793
2794    /**
2795     * Returns true if the call is outgoing and the NEW_OUTGOING_CALL ordered broadcast intent
2796     * has come back to telecom and was processed.
2797     */
2798    public boolean isNewOutgoingCallIntentBroadcastDone() {
2799        return mIsNewOutgoingCallIntentBroadcastDone;
2800    }
2801
2802    public void setNewOutgoingCallIntentBroadcastIsDone() {
2803        mIsNewOutgoingCallIntentBroadcastDone = true;
2804    }
2805
2806    /**
2807     * Determines if the call has been held by the remote party.
2808     *
2809     * @return {@code true} if the call is remotely held, {@code false} otherwise.
2810     */
2811    public boolean isRemotelyHeld() {
2812        return mIsRemotelyHeld;
2813    }
2814
2815    /**
2816     * Handles Connection events received from a {@link ConnectionService}.
2817     *
2818     * @param event The event.
2819     * @param extras The extras.
2820     */
2821    public void onConnectionEvent(String event, Bundle extras) {
2822        Log.addEvent(this, LogUtils.Events.CONNECTION_EVENT, event);
2823        if (Connection.EVENT_ON_HOLD_TONE_START.equals(event)) {
2824            mIsRemotelyHeld = true;
2825            Log.addEvent(this, LogUtils.Events.REMOTELY_HELD);
2826            // Inform listeners of the fact that a call hold tone was received.  This will trigger
2827            // the CallAudioManager to play a tone via the InCallTonePlayer.
2828            for (Listener l : mListeners) {
2829                l.onHoldToneRequested(this);
2830            }
2831        } else if (Connection.EVENT_ON_HOLD_TONE_END.equals(event)) {
2832            mIsRemotelyHeld = false;
2833            Log.addEvent(this, LogUtils.Events.REMOTELY_UNHELD);
2834            for (Listener l : mListeners) {
2835                l.onHoldToneRequested(this);
2836            }
2837        } else {
2838            for (Listener l : mListeners) {
2839                l.onConnectionEvent(this, event, extras);
2840            }
2841        }
2842    }
2843
2844    /**
2845     * Notifies interested parties that the handover has completed.
2846     * Notifies:
2847     * 1. {@link InCallController} which communicates this to the
2848     * {@link android.telecom.InCallService} via {@link Listener#onHandoverComplete()}.
2849     * 2. {@link ConnectionServiceWrapper} which informs the {@link android.telecom.Connection} of
2850     * the successful handover.
2851     */
2852    public void onHandoverComplete() {
2853        Log.i(this, "onHandoverComplete; callId=%s", getId());
2854        if (mConnectionService != null) {
2855            mConnectionService.handoverComplete(this);
2856        }
2857        for (Listener l : mListeners) {
2858            l.onHandoverComplete(this);
2859        }
2860    }
2861
2862    public void onHandoverFailed(int handoverError) {
2863        Log.i(this, "onHandoverFailed; callId=%s, handoverError=%d", getId(), handoverError);
2864        for (Listener l : mListeners) {
2865            l.onHandoverFailed(this, handoverError);
2866        }
2867    }
2868
2869    public void setOriginalConnectionId(String originalConnectionId) {
2870        mOriginalConnectionId = originalConnectionId;
2871    }
2872
2873    /**
2874     * For calls added via a ConnectionManager using the
2875     * {@link android.telecom.ConnectionService#addExistingConnection(PhoneAccountHandle,
2876     * Connection)}, or {@link android.telecom.ConnectionService#addConference(Conference)} APIS,
2877     * indicates the ID of this call as it was referred to by the {@code ConnectionService} which
2878     * originally created it.
2879     *
2880     * See {@link Connection#EXTRA_ORIGINAL_CONNECTION_ID}.
2881     * @return The original connection ID.
2882     */
2883    public String getOriginalConnectionId() {
2884        return mOriginalConnectionId;
2885    }
2886
2887    ConnectionServiceFocusManager getConnectionServiceFocusManager() {
2888        return mCallsManager.getConnectionServiceFocusManager();
2889    }
2890
2891    /**
2892     * Determines if a {@link Call}'s capabilities bitmask indicates that video is supported either
2893     * remotely or locally.
2894     *
2895     * @param capabilities The {@link Connection} capabilities for the call.
2896     * @return {@code true} if video is supported, {@code false} otherwise.
2897     */
2898    private boolean doesCallSupportVideo(int capabilities) {
2899        return (capabilities & Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL) != 0 ||
2900                (capabilities & Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL) != 0;
2901    }
2902
2903    /**
2904     * Remove any video capabilities set on a {@link Connection} capabilities bitmask.
2905     *
2906     * @param capabilities The capabilities.
2907     * @return The bitmask with video capabilities removed.
2908     */
2909    private int removeVideoCapabilities(int capabilities) {
2910        return capabilities & ~(Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL |
2911                Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
2912    }
2913
2914    /**
2915     * Initiates a handover of this {@link Call} to another {@link PhoneAccount}.
2916     * @param handoverToHandle The {@link PhoneAccountHandle} to handover to.
2917     * @param videoState The video state of the call when handed over.
2918     * @param extras Optional extras {@link Bundle} provided by the initiating
2919     *      {@link android.telecom.InCallService}.
2920     */
2921    private void requestHandover(PhoneAccountHandle handoverToHandle, int videoState,
2922                                 Bundle extras, boolean isLegacy) {
2923        for (Listener l : mListeners) {
2924            l.onHandoverRequested(this, handoverToHandle, videoState, extras, isLegacy);
2925        }
2926    }
2927
2928    /**
2929     * Sets the video history based on the state and state transitions of the call. Always add the
2930     * current video state to the video state history during a call transition except for the
2931     * transitions DIALING->ACTIVE and RINGING->ACTIVE. In these cases, clear the history. If a
2932     * call starts dialing/ringing as a VT call and gets downgraded to audio, we need to record
2933     * the history as an audio call.
2934     */
2935    private void updateVideoHistoryViaState(int oldState, int newState) {
2936        if ((oldState == CallState.DIALING || oldState == CallState.RINGING)
2937                && newState == CallState.ACTIVE) {
2938            mVideoStateHistory = mVideoState;
2939        }
2940
2941        mVideoStateHistory |= mVideoState;
2942    }
2943
2944    /**
2945     * Returns whether or not high definition audio was used.
2946     *
2947     * @return true if high definition audio was used during this call.
2948     */
2949    boolean wasHighDefAudio() {
2950        return mWasHighDefAudio;
2951    }
2952}
2953