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