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