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