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