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