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