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