Call.java revision fa3b7b6ac2e81a989850f0d5d5bc4fba6fd67fff
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    void disconnect() {
997        disconnect(false);
998    }
999
1000    /**
1001     * Attempts to disconnect the call through the connection service.
1002     */
1003    void disconnect(boolean wasViaNewOutgoingCallBroadcaster) {
1004        Log.event(this, Log.Events.REQUEST_DISCONNECT);
1005
1006        // Track that the call is now locally disconnecting.
1007        setLocallyDisconnecting(true);
1008
1009        if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT ||
1010                mState == CallState.CONNECTING) {
1011            Log.v(this, "Aborting call %s", this);
1012            abort(wasViaNewOutgoingCallBroadcaster);
1013        } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
1014            if (mConnectionService == null) {
1015                Log.e(this, new Exception(), "disconnect() request on a call without a"
1016                        + " connection service.");
1017            } else {
1018                Log.i(this, "Send disconnect to connection service for call: %s", this);
1019                // The call isn't officially disconnected until the connection service
1020                // confirms that the call was actually disconnected. Only then is the
1021                // association between call and connection service severed, see
1022                // {@link CallsManager#markCallAsDisconnected}.
1023                mConnectionService.disconnect(this);
1024            }
1025        }
1026    }
1027
1028    void abort(boolean wasViaNewOutgoingCallBroadcaster) {
1029        if (mCreateConnectionProcessor != null &&
1030                !mCreateConnectionProcessor.isProcessingComplete()) {
1031            mCreateConnectionProcessor.abort();
1032        } else if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT
1033                || mState == CallState.CONNECTING) {
1034            if (wasViaNewOutgoingCallBroadcaster) {
1035                // If the cancelation was from NEW_OUTGOING_CALL, then we do not automatically
1036                // destroy the call.  Instead, we announce the cancelation and CallsManager handles
1037                // it through a timer. Since apps often cancel calls through NEW_OUTGOING_CALL and
1038                // then re-dial them quickly using a gateway, allowing the first call to end
1039                // causes jank. This timeout allows CallsManager to transition the first call into
1040                // the second call so that in-call only ever sees a single call...eliminating the
1041                // jank altogether.
1042                for (Listener listener : mListeners) {
1043                    if (listener.onCanceledViaNewOutgoingCallBroadcast(this)) {
1044                        // The first listener to handle this wins. A return value of true means that
1045                        // the listener will handle the disconnection process later and so we
1046                        // should not continue it here.
1047                        setLocallyDisconnecting(false);
1048                        return;
1049                    }
1050                }
1051            }
1052
1053            handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
1054        } else {
1055            Log.v(this, "Cannot abort a call which is neither SELECT_PHONE_ACCOUNT or CONNECTING");
1056        }
1057    }
1058
1059    /**
1060     * Answers the call if it is ringing.
1061     *
1062     * @param videoState The video state in which to answer the call.
1063     */
1064    void answer(int videoState) {
1065        Preconditions.checkNotNull(mConnectionService);
1066
1067        // Check to verify that the call is still in the ringing state. A call can change states
1068        // between the time the user hits 'answer' and Telecom receives the command.
1069        if (isRinging("answer")) {
1070            // At this point, we are asking the connection service to answer but we don't assume
1071            // that it will work. Instead, we wait until confirmation from the connectino service
1072            // that the call is in a non-STATE_RINGING state before changing the UI. See
1073            // {@link ConnectionServiceAdapter#setActive} and other set* methods.
1074            mConnectionService.answer(this, videoState);
1075            Log.event(this, Log.Events.REQUEST_ACCEPT);
1076        }
1077    }
1078
1079    /**
1080     * Rejects the call if it is ringing.
1081     *
1082     * @param rejectWithMessage Whether to send a text message as part of the call rejection.
1083     * @param textMessage An optional text message to send as part of the rejection.
1084     */
1085    void reject(boolean rejectWithMessage, String textMessage) {
1086        Preconditions.checkNotNull(mConnectionService);
1087
1088        // Check to verify that the call is still in the ringing state. A call can change states
1089        // between the time the user hits 'reject' and Telecomm receives the command.
1090        if (isRinging("reject")) {
1091            // Ensure video state history tracks video state at time of rejection.
1092            mVideoStateHistory |= mVideoState;
1093
1094            mConnectionService.reject(this);
1095            Log.event(this, Log.Events.REQUEST_REJECT);
1096        }
1097    }
1098
1099    /**
1100     * Puts the call on hold if it is currently active.
1101     */
1102    void hold() {
1103        Preconditions.checkNotNull(mConnectionService);
1104
1105        if (mState == CallState.ACTIVE) {
1106            mConnectionService.hold(this);
1107            Log.event(this, Log.Events.REQUEST_HOLD);
1108        }
1109    }
1110
1111    /**
1112     * Releases the call from hold if it is currently active.
1113     */
1114    void unhold() {
1115        Preconditions.checkNotNull(mConnectionService);
1116
1117        if (mState == CallState.ON_HOLD) {
1118            mConnectionService.unhold(this);
1119            Log.event(this, Log.Events.REQUEST_UNHOLD);
1120        }
1121    }
1122
1123    /** Checks if this is a live call or not. */
1124    boolean isAlive() {
1125        switch (mState) {
1126            case CallState.NEW:
1127            case CallState.RINGING:
1128            case CallState.DISCONNECTED:
1129            case CallState.ABORTED:
1130                return false;
1131            default:
1132                return true;
1133        }
1134    }
1135
1136    boolean isActive() {
1137        return mState == CallState.ACTIVE;
1138    }
1139
1140    Bundle getExtras() {
1141        return mExtras;
1142    }
1143
1144    void setExtras(Bundle extras) {
1145        mExtras = extras;
1146        for (Listener l : mListeners) {
1147            l.onExtrasChanged(this);
1148        }
1149    }
1150
1151    Bundle getIntentExtras() {
1152        return mIntentExtras;
1153    }
1154
1155    void setIntentExtras(Bundle extras) {
1156        mIntentExtras = extras;
1157    }
1158
1159    /**
1160     * @return the uri of the contact associated with this call.
1161     */
1162    Uri getContactUri() {
1163        if (mCallerInfo == null || !mCallerInfo.contactExists) {
1164            return getHandle();
1165        }
1166        return Contacts.getLookupUri(mCallerInfo.contactIdOrZero, mCallerInfo.lookupKey);
1167    }
1168
1169    Uri getRingtone() {
1170        return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
1171    }
1172
1173    void onPostDialWait(String remaining) {
1174        for (Listener l : mListeners) {
1175            l.onPostDialWait(this, remaining);
1176        }
1177    }
1178
1179    void onPostDialChar(char nextChar) {
1180        for (Listener l : mListeners) {
1181            l.onPostDialChar(this, nextChar);
1182        }
1183    }
1184
1185    void postDialContinue(boolean proceed) {
1186        mConnectionService.onPostDialContinue(this, proceed);
1187    }
1188
1189    void conferenceWith(Call otherCall) {
1190        if (mConnectionService == null) {
1191            Log.w(this, "conference requested on a call without a connection service.");
1192        } else {
1193            Log.event(this, Log.Events.CONFERENCE_WITH, otherCall);
1194            mConnectionService.conference(this, otherCall);
1195        }
1196    }
1197
1198    void splitFromConference() {
1199        if (mConnectionService == null) {
1200            Log.w(this, "splitting from conference call without a connection service");
1201        } else {
1202            Log.event(this, Log.Events.SPLIT_CONFERENCE);
1203            mConnectionService.splitFromConference(this);
1204        }
1205    }
1206
1207    void mergeConference() {
1208        if (mConnectionService == null) {
1209            Log.w(this, "merging conference calls without a connection service.");
1210        } else if (can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
1211            Log.event(this, Log.Events.CONFERENCE_WITH);
1212            mConnectionService.mergeConference(this);
1213            mWasConferencePreviouslyMerged = true;
1214        }
1215    }
1216
1217    void swapConference() {
1218        if (mConnectionService == null) {
1219            Log.w(this, "swapping conference calls without a connection service.");
1220        } else if (can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
1221            Log.event(this, Log.Events.SWAP);
1222            mConnectionService.swapConference(this);
1223            switch (mChildCalls.size()) {
1224                case 1:
1225                    mConferenceLevelActiveCall = mChildCalls.get(0);
1226                    break;
1227                case 2:
1228                    // swap
1229                    mConferenceLevelActiveCall = mChildCalls.get(0) == mConferenceLevelActiveCall ?
1230                            mChildCalls.get(1) : mChildCalls.get(0);
1231                    break;
1232                default:
1233                    // For anything else 0, or 3+, set it to null since it is impossible to tell.
1234                    mConferenceLevelActiveCall = null;
1235                    break;
1236            }
1237        }
1238    }
1239
1240    void setParentCall(Call parentCall) {
1241        if (parentCall == this) {
1242            Log.e(this, new Exception(), "setting the parent to self");
1243            return;
1244        }
1245        if (parentCall == mParentCall) {
1246            // nothing to do
1247            return;
1248        }
1249        Preconditions.checkState(parentCall == null || mParentCall == null);
1250
1251        Call oldParent = mParentCall;
1252        if (mParentCall != null) {
1253            mParentCall.removeChildCall(this);
1254        }
1255        mParentCall = parentCall;
1256        if (mParentCall != null) {
1257            mParentCall.addChildCall(this);
1258        }
1259
1260        Log.event(this, Log.Events.SET_PARENT, mParentCall);
1261        for (Listener l : mListeners) {
1262            l.onParentChanged(this);
1263        }
1264    }
1265
1266    void setConferenceableCalls(List<Call> conferenceableCalls) {
1267        mConferenceableCalls.clear();
1268        mConferenceableCalls.addAll(conferenceableCalls);
1269
1270        for (Listener l : mListeners) {
1271            l.onConferenceableCallsChanged(this);
1272        }
1273    }
1274
1275    List<Call> getConferenceableCalls() {
1276        return mConferenceableCalls;
1277    }
1278
1279    boolean can(int capability) {
1280        return (mConnectionCapabilities & capability) == capability;
1281    }
1282
1283    private void addChildCall(Call call) {
1284        if (!mChildCalls.contains(call)) {
1285            // Set the pseudo-active call to the latest child added to the conference.
1286            // See definition of mConferenceLevelActiveCall for more detail.
1287            mConferenceLevelActiveCall = call;
1288            mChildCalls.add(call);
1289
1290            Log.event(this, Log.Events.ADD_CHILD, call);
1291
1292            for (Listener l : mListeners) {
1293                l.onChildrenChanged(this);
1294            }
1295        }
1296    }
1297
1298    private void removeChildCall(Call call) {
1299        if (mChildCalls.remove(call)) {
1300            Log.event(this, Log.Events.REMOVE_CHILD, call);
1301            for (Listener l : mListeners) {
1302                l.onChildrenChanged(this);
1303            }
1304        }
1305    }
1306
1307    /**
1308     * Return whether the user can respond to this {@code Call} via an SMS message.
1309     *
1310     * @return true if the "Respond via SMS" feature should be enabled
1311     * for this incoming call.
1312     *
1313     * The general rule is that we *do* allow "Respond via SMS" except for
1314     * the few (relatively rare) cases where we know for sure it won't
1315     * work, namely:
1316     *   - a bogus or blank incoming number
1317     *   - a call from a SIP address
1318     *   - a "call presentation" that doesn't allow the number to be revealed
1319     *
1320     * In all other cases, we allow the user to respond via SMS.
1321     *
1322     * Note that this behavior isn't perfect; for example we have no way
1323     * to detect whether the incoming call is from a landline (with most
1324     * networks at least), so we still enable this feature even though
1325     * SMSes to that number will silently fail.
1326     */
1327    boolean isRespondViaSmsCapable() {
1328        if (mState != CallState.RINGING) {
1329            return false;
1330        }
1331
1332        if (getHandle() == null) {
1333            // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in
1334            // other words, the user should not be able to see the incoming phone number.
1335            return false;
1336        }
1337
1338        if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
1339            // The incoming number is actually a URI (i.e. a SIP address),
1340            // not a regular PSTN phone number, and we can't send SMSes to
1341            // SIP addresses.
1342            // (TODO: That might still be possible eventually, though. Is
1343            // there some SIP-specific equivalent to sending a text message?)
1344            return false;
1345        }
1346
1347        // Is there a valid SMS application on the phone?
1348        if (SmsApplication.getDefaultRespondViaMessageApplication(mContext,
1349                true /*updateIfNeeded*/) == null) {
1350            return false;
1351        }
1352
1353        // TODO: with some carriers (in certain countries) you *can* actually
1354        // tell whether a given number is a mobile phone or not. So in that
1355        // case we could potentially return false here if the incoming call is
1356        // from a land line.
1357
1358        // If none of the above special cases apply, it's OK to enable the
1359        // "Respond via SMS" feature.
1360        return true;
1361    }
1362
1363    List<String> getCannedSmsResponses() {
1364        return mCannedSmsResponses;
1365    }
1366
1367    /**
1368     * We need to make sure that before we move a call to the disconnected state, it no
1369     * longer has any parent/child relationships.  We want to do this to ensure that the InCall
1370     * Service always has the right data in the right order.  We also want to do it in telecom so
1371     * that the insurance policy lives in the framework side of things.
1372     */
1373    private void fixParentAfterDisconnect() {
1374        setParentCall(null);
1375    }
1376
1377    /**
1378     * @return True if the call is ringing, else logs the action name.
1379     */
1380    private boolean isRinging(String actionName) {
1381        if (mState == CallState.RINGING) {
1382            return true;
1383        }
1384
1385        Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
1386        return false;
1387    }
1388
1389    @SuppressWarnings("rawtypes")
1390    private void decrementAssociatedCallCount(ServiceBinder binder) {
1391        if (binder != null) {
1392            binder.decrementAssociatedCallCount();
1393        }
1394    }
1395
1396    /**
1397     * Looks up contact information based on the current handle.
1398     */
1399    private void startCallerInfoLookup() {
1400        final String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
1401
1402        mQueryToken++;  // Updated so that previous queries can no longer set the information.
1403        mCallerInfo = null;
1404        if (!TextUtils.isEmpty(number)) {
1405            Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
1406            mHandler.post(new Runnable() {
1407                @Override
1408                public void run() {
1409                    mCallerInfoAsyncQueryFactory.startQuery(
1410                            mQueryToken,
1411                            mContext,
1412                            number,
1413                            mCallerInfoQueryListener,
1414                            Call.this);
1415                }
1416            });
1417        }
1418    }
1419
1420    /**
1421     * Saves the specified caller info if the specified token matches that of the last query
1422     * that was made.
1423     *
1424     * @param callerInfo The new caller information to set.
1425     * @param token The token used with this query.
1426     */
1427    private void setCallerInfo(CallerInfo callerInfo, int token) {
1428        Trace.beginSection("setCallerInfo");
1429        Preconditions.checkNotNull(callerInfo);
1430
1431        if (mQueryToken == token) {
1432            mCallerInfo = callerInfo;
1433            Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
1434
1435            if (mCallerInfo.contactDisplayPhotoUri != null) {
1436                Log.d(this, "Searching person uri %s for call %s",
1437                        mCallerInfo.contactDisplayPhotoUri, this);
1438                mContactsAsyncHelper.startObtainPhotoAsync(
1439                        token,
1440                        mContext,
1441                        mCallerInfo.contactDisplayPhotoUri,
1442                        mPhotoLoadListener,
1443                        this);
1444                // Do not call onCallerInfoChanged yet in this case.  We call it in setPhoto().
1445            } else {
1446                for (Listener l : mListeners) {
1447                    l.onCallerInfoChanged(this);
1448                }
1449            }
1450
1451            processDirectToVoicemail();
1452        }
1453        Trace.endSection();
1454    }
1455
1456    CallerInfo getCallerInfo() {
1457        return mCallerInfo;
1458    }
1459
1460    /**
1461     * Saves the specified photo information if the specified token matches that of the last query.
1462     *
1463     * @param photo The photo as a drawable.
1464     * @param photoIcon The photo as a small icon.
1465     * @param token The token used with this query.
1466     */
1467    private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
1468        if (mQueryToken == token) {
1469            mCallerInfo.cachedPhoto = photo;
1470            mCallerInfo.cachedPhotoIcon = photoIcon;
1471
1472            for (Listener l : mListeners) {
1473                l.onCallerInfoChanged(this);
1474            }
1475        }
1476    }
1477
1478    private void maybeLoadCannedSmsResponses() {
1479        if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
1480            Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
1481            mCannedSmsResponsesLoadingStarted = true;
1482            mCallsManager.getRespondViaSmsManager().loadCannedTextMessages(
1483                    new Response<Void, List<String>>() {
1484                        @Override
1485                        public void onResult(Void request, List<String>... result) {
1486                            if (result.length > 0) {
1487                                Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]);
1488                                mCannedSmsResponses = result[0];
1489                                for (Listener l : mListeners) {
1490                                    l.onCannedSmsResponsesLoaded(Call.this);
1491                                }
1492                            }
1493                        }
1494
1495                        @Override
1496                        public void onError(Void request, int code, String msg) {
1497                            Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code,
1498                                    msg);
1499                        }
1500                    },
1501                    mContext
1502            );
1503        } else {
1504            Log.d(this, "maybeLoadCannedSmsResponses: doing nothing");
1505        }
1506    }
1507
1508    /**
1509     * Sets speakerphone option on when call begins.
1510     */
1511    public void setStartWithSpeakerphoneOn(boolean startWithSpeakerphone) {
1512        mSpeakerphoneOn = startWithSpeakerphone;
1513    }
1514
1515    /**
1516     * Returns speakerphone option.
1517     *
1518     * @return Whether or not speakerphone should be set automatically when call begins.
1519     */
1520    public boolean getStartWithSpeakerphoneOn() {
1521        return mSpeakerphoneOn;
1522    }
1523
1524    /**
1525     * Sets a video call provider for the call.
1526     */
1527    public void setVideoProvider(IVideoProvider videoProvider) {
1528        Log.v(this, "setVideoProvider");
1529
1530        if (videoProvider != null ) {
1531            try {
1532                mVideoProviderProxy = new VideoProviderProxy(mLock, videoProvider, this);
1533            } catch (RemoteException ignored) {
1534                // Ignore RemoteException.
1535            }
1536        } else {
1537            mVideoProviderProxy = null;
1538        }
1539
1540        mVideoProvider = videoProvider;
1541
1542        for (Listener l : mListeners) {
1543            l.onVideoCallProviderChanged(Call.this);
1544        }
1545    }
1546
1547    /**
1548     * @return The {@link Connection.VideoProvider} binder.
1549     */
1550    public IVideoProvider getVideoProvider() {
1551        if (mVideoProviderProxy == null) {
1552            return null;
1553        }
1554
1555        return mVideoProviderProxy.getInterface();
1556    }
1557
1558    /**
1559     * @return The {@link VideoProviderProxy} for this call.
1560     */
1561    public VideoProviderProxy getVideoProviderProxy() {
1562        return mVideoProviderProxy;
1563    }
1564
1565    /**
1566     * The current video state for the call.
1567     * See {@link VideoProfile} for a list of valid video states.
1568     */
1569    public int getVideoState() {
1570        return mVideoState;
1571    }
1572
1573    /**
1574     * Returns the video states which were applicable over the duration of a call.
1575     * See {@link VideoProfile} for a list of valid video states.
1576     *
1577     * @return The video states applicable over the duration of the call.
1578     */
1579    public int getVideoStateHistory() {
1580        return mVideoStateHistory;
1581    }
1582
1583    /**
1584     * Determines the current video state for the call.
1585     * For an outgoing call determines the desired video state for the call.
1586     * Valid values: see {@link VideoProfile}
1587     *
1588     * @param videoState The video state for the call.
1589     */
1590    public void setVideoState(int videoState) {
1591        // Track which video states were applicable over the duration of the call.
1592        // Only track the call state when the call is active or disconnected.  This ensures we do
1593        // not include the video state when:
1594        // - Call is incoming (but not answered).
1595        // - Call it outgoing (but not answered).
1596        // We include the video state when disconnected to ensure that rejected calls reflect the
1597        // appropriate video state.
1598        if (isActive() || getState() == CallState.DISCONNECTED) {
1599            mVideoStateHistory = mVideoStateHistory | videoState;
1600        }
1601
1602        mVideoState = videoState;
1603        for (Listener l : mListeners) {
1604            l.onVideoStateChanged(this);
1605        }
1606    }
1607
1608    public boolean getIsVoipAudioMode() {
1609        return mIsVoipAudioMode;
1610    }
1611
1612    public void setIsVoipAudioMode(boolean audioModeIsVoip) {
1613        mIsVoipAudioMode = audioModeIsVoip;
1614        for (Listener l : mListeners) {
1615            l.onIsVoipAudioModeChanged(this);
1616        }
1617    }
1618
1619    public StatusHints getStatusHints() {
1620        return mStatusHints;
1621    }
1622
1623    public void setStatusHints(StatusHints statusHints) {
1624        mStatusHints = statusHints;
1625        for (Listener l : mListeners) {
1626            l.onStatusHintsChanged(this);
1627        }
1628    }
1629
1630    public boolean isUnknown() {
1631        return mIsUnknown;
1632    }
1633
1634    public void setIsUnknown(boolean isUnknown) {
1635        mIsUnknown = isUnknown;
1636    }
1637
1638    /**
1639     * Determines if this call is in a disconnecting state.
1640     *
1641     * @return {@code true} if this call is locally disconnecting.
1642     */
1643    public boolean isLocallyDisconnecting() {
1644        return mIsLocallyDisconnecting;
1645    }
1646
1647    /**
1648     * Sets whether this call is in a disconnecting state.
1649     *
1650     * @param isLocallyDisconnecting {@code true} if this call is locally disconnecting.
1651     */
1652    private void setLocallyDisconnecting(boolean isLocallyDisconnecting) {
1653        mIsLocallyDisconnecting = isLocallyDisconnecting;
1654    }
1655
1656    static int getStateFromConnectionState(int state) {
1657        switch (state) {
1658            case Connection.STATE_INITIALIZING:
1659                return CallState.CONNECTING;
1660            case Connection.STATE_ACTIVE:
1661                return CallState.ACTIVE;
1662            case Connection.STATE_DIALING:
1663                return CallState.DIALING;
1664            case Connection.STATE_DISCONNECTED:
1665                return CallState.DISCONNECTED;
1666            case Connection.STATE_HOLDING:
1667                return CallState.ON_HOLD;
1668            case Connection.STATE_NEW:
1669                return CallState.NEW;
1670            case Connection.STATE_RINGING:
1671                return CallState.RINGING;
1672        }
1673        return CallState.DISCONNECTED;
1674    }
1675}
1676