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