Call.java revision 96e662d3811673f284da0f9d2deabe939918a3b1
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 && !PhoneAccount.SCHEME_VOICEMAIL.equals(mHandle.getScheme())
427                        && TextUtils.isEmpty(mHandle.getSchemeSpecificPart())) {
428                    // If the number is actually empty, set it to null, unless this is a
429                    // SCHEME_VOICEMAIL uri which always has an empty number.
430                    mHandle = null;
431                }
432            }
433
434            mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(mContext,
435                    mHandle.getSchemeSpecificPart());
436            startCallerInfoLookup();
437            for (Listener l : mListeners) {
438                l.onHandleChanged(this);
439            }
440        }
441    }
442
443    String getCallerDisplayName() {
444        return mCallerDisplayName;
445    }
446
447    int getCallerDisplayNamePresentation() {
448        return mCallerDisplayNamePresentation;
449    }
450
451    void setCallerDisplayName(String callerDisplayName, int presentation) {
452        if (!TextUtils.equals(callerDisplayName, mCallerDisplayName) ||
453                presentation != mCallerDisplayNamePresentation) {
454            mCallerDisplayName = callerDisplayName;
455            mCallerDisplayNamePresentation = presentation;
456            for (Listener l : mListeners) {
457                l.onCallerDisplayNameChanged(this);
458            }
459        }
460    }
461
462    String getName() {
463        return mCallerInfo == null ? null : mCallerInfo.name;
464    }
465
466    Bitmap getPhotoIcon() {
467        return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
468    }
469
470    Drawable getPhoto() {
471        return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
472    }
473
474    /**
475     * @param disconnectCause The reason for the disconnection, represented by
476     *         {@link android.telecom.DisconnectCause}.
477     */
478    void setDisconnectCause(DisconnectCause disconnectCause) {
479        // TODO: Consider combining this method with a setDisconnected() method that is totally
480        // separate from setState.
481        mDisconnectCause = disconnectCause;
482    }
483
484    DisconnectCause getDisconnectCause() {
485        return mDisconnectCause;
486    }
487
488    boolean isEmergencyCall() {
489        return mIsEmergencyCall;
490    }
491
492    /**
493     * @return The original handle this call is associated with. In-call services should use this
494     * handle when indicating in their UI the handle that is being called.
495     */
496    public Uri getOriginalHandle() {
497        if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
498            return mGatewayInfo.getOriginalAddress();
499        }
500        return getHandle();
501    }
502
503    GatewayInfo getGatewayInfo() {
504        return mGatewayInfo;
505    }
506
507    void setGatewayInfo(GatewayInfo gatewayInfo) {
508        mGatewayInfo = gatewayInfo;
509    }
510
511    PhoneAccountHandle getConnectionManagerPhoneAccount() {
512        return mConnectionManagerPhoneAccountHandle;
513    }
514
515    void setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle) {
516        if (!Objects.equals(mConnectionManagerPhoneAccountHandle, accountHandle)) {
517            mConnectionManagerPhoneAccountHandle = accountHandle;
518            for (Listener l : mListeners) {
519                l.onConnectionManagerPhoneAccountChanged(this);
520            }
521        }
522
523    }
524
525    PhoneAccountHandle getTargetPhoneAccount() {
526        return mTargetPhoneAccountHandle;
527    }
528
529    void setTargetPhoneAccount(PhoneAccountHandle accountHandle) {
530        if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) {
531            mTargetPhoneAccountHandle = accountHandle;
532            for (Listener l : mListeners) {
533                l.onTargetPhoneAccountChanged(this);
534            }
535        }
536    }
537
538    boolean isIncoming() {
539        return mIsIncoming;
540    }
541
542    /**
543     * @return The "age" of this call object in milliseconds, which typically also represents the
544     *     period since this call was added to the set pending outgoing calls, see
545     *     mCreationTimeMillis.
546     */
547    long getAgeMillis() {
548        return System.currentTimeMillis() - mCreationTimeMillis;
549    }
550
551    /**
552     * @return The time when this call object was created and added to the set of pending outgoing
553     *     calls.
554     */
555    long getCreationTimeMillis() {
556        return mCreationTimeMillis;
557    }
558
559    void setCreationTimeMillis(long time) {
560        mCreationTimeMillis = time;
561    }
562
563    long getConnectTimeMillis() {
564        return mConnectTimeMillis;
565    }
566
567    void setConnectTimeMillis(long connectTimeMillis) {
568        mConnectTimeMillis = connectTimeMillis;
569    }
570
571    int getCallCapabilities() {
572        return mCallCapabilities;
573    }
574
575    void setCallCapabilities(int callCapabilities) {
576        setCallCapabilities(callCapabilities, false /* forceUpdate */);
577    }
578
579    void setCallCapabilities(int callCapabilities, boolean forceUpdate) {
580        Log.v(this, "setCallCapabilities: %s", PhoneCapabilities.toString(callCapabilities));
581        if (forceUpdate || mCallCapabilities != callCapabilities) {
582           mCallCapabilities = callCapabilities;
583            for (Listener l : mListeners) {
584                l.onCallCapabilitiesChanged(this);
585            }
586        }
587    }
588
589    Call getParentCall() {
590        return mParentCall;
591    }
592
593    List<Call> getChildCalls() {
594        return mChildCalls;
595    }
596
597    boolean wasConferencePreviouslyMerged() {
598        return mWasConferencePreviouslyMerged;
599    }
600
601    Call getConferenceLevelActiveCall() {
602        return mConferenceLevelActiveCall;
603    }
604
605    ConnectionServiceWrapper getConnectionService() {
606        return mConnectionService;
607    }
608
609    /**
610     * Retrieves the {@link Context} for the call.
611     *
612     * @return The {@link Context}.
613     */
614    Context getContext() {
615        return mContext;
616    }
617
618    void setConnectionService(ConnectionServiceWrapper service) {
619        Preconditions.checkNotNull(service);
620
621        clearConnectionService();
622
623        service.incrementAssociatedCallCount();
624        mConnectionService = service;
625        mConnectionService.addCall(this);
626    }
627
628    /**
629     * Clears the associated connection service.
630     */
631    void clearConnectionService() {
632        if (mConnectionService != null) {
633            ConnectionServiceWrapper serviceTemp = mConnectionService;
634            mConnectionService = null;
635            serviceTemp.removeCall(this);
636
637            // Decrementing the count can cause the service to unbind, which itself can trigger the
638            // service-death code.  Since the service death code tries to clean up any associated
639            // calls, we need to make sure to remove that information (e.g., removeCall()) before
640            // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
641            // necessary, but cleaning up mConnectionService prior to triggering an unbind is good
642            // to do.
643            decrementAssociatedCallCount(serviceTemp);
644        }
645    }
646
647    private void processDirectToVoicemail() {
648        if (mDirectToVoicemailQueryPending) {
649            if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
650                Log.i(this, "Directing call to voicemail: %s.", this);
651                // TODO: Once we move State handling from CallsManager to Call, we
652                // will not need to set STATE_RINGING state prior to calling reject.
653                setState(CallState.RINGING);
654                reject(false, null);
655            } else {
656                // TODO: Make this class (not CallsManager) responsible for changing
657                // the call state to STATE_RINGING.
658
659                // TODO: Replace this with state transition to STATE_RINGING.
660                for (Listener l : mListeners) {
661                    l.onSuccessfulIncomingCall(this);
662                }
663            }
664
665            mDirectToVoicemailQueryPending = false;
666        }
667    }
668
669    /**
670     * Starts the create connection sequence. Upon completion, there should exist an active
671     * connection through a connection service (or the call will have failed).
672     *
673     * @param phoneAccountRegistrar The phone account registrar.
674     */
675    void startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar) {
676        Preconditions.checkState(mCreateConnectionProcessor == null);
677        mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this,
678                phoneAccountRegistrar, mContext);
679        mCreateConnectionProcessor.process();
680    }
681
682    @Override
683    public void handleCreateConnectionSuccess(
684            CallIdMapper idMapper,
685            ParcelableConnection connection) {
686        Log.v(this, "handleCreateConnectionSuccessful %s", connection);
687        mCreateConnectionProcessor = null;
688        setTargetPhoneAccount(connection.getPhoneAccount());
689        setHandle(connection.getHandle(), connection.getHandlePresentation());
690        setCallerDisplayName(
691                connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());
692        setCallCapabilities(connection.getCapabilities());
693        setVideoProvider(connection.getVideoProvider());
694        setVideoState(connection.getVideoState());
695        setRingbackRequested(connection.isRingbackRequested());
696        setIsVoipAudioMode(connection.getIsVoipAudioMode());
697        setStatusHints(connection.getStatusHints());
698
699        mConferenceableCalls.clear();
700        for (String id : connection.getConferenceableConnectionIds()) {
701            mConferenceableCalls.add(idMapper.getCall(id));
702        }
703
704        if (mIsUnknown) {
705            for (Listener l : mListeners) {
706                l.onSuccessfulUnknownCall(this, getStateFromConnectionState(connection.getState()));
707            }
708        } else if (mIsIncoming) {
709            // We do not handle incoming calls immediately when they are verified by the connection
710            // service. We allow the caller-info-query code to execute first so that we can read the
711            // direct-to-voicemail property before deciding if we want to show the incoming call to
712            // the user or if we want to reject the call.
713            mDirectToVoicemailQueryPending = true;
714
715            // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
716            // showing the user the incoming call screen.
717            mHandler.postDelayed(mDirectToVoicemailRunnable, Timeouts.getDirectToVoicemailMillis(
718                    mContext.getContentResolver()));
719        } else {
720            for (Listener l : mListeners) {
721                l.onSuccessfulOutgoingCall(this,
722                        getStateFromConnectionState(connection.getState()));
723            }
724        }
725    }
726
727    @Override
728    public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
729        mCreateConnectionProcessor = null;
730        clearConnectionService();
731        setDisconnectCause(disconnectCause);
732        CallsManager.getInstance().markCallAsDisconnected(this, disconnectCause);
733
734        if (mIsUnknown) {
735            for (Listener listener : mListeners) {
736                listener.onFailedUnknownCall(this);
737            }
738        } else if (mIsIncoming) {
739            for (Listener listener : mListeners) {
740                listener.onFailedIncomingCall(this);
741            }
742        } else {
743            for (Listener listener : mListeners) {
744                listener.onFailedOutgoingCall(this, disconnectCause);
745            }
746        }
747    }
748
749    /**
750     * Plays the specified DTMF tone.
751     */
752    void playDtmfTone(char digit) {
753        if (mConnectionService == null) {
754            Log.w(this, "playDtmfTone() request on a call without a connection service.");
755        } else {
756            Log.i(this, "Send playDtmfTone to connection service for call %s", this);
757            mConnectionService.playDtmfTone(this, digit);
758        }
759    }
760
761    /**
762     * Stops playing any currently playing DTMF tone.
763     */
764    void stopDtmfTone() {
765        if (mConnectionService == null) {
766            Log.w(this, "stopDtmfTone() request on a call without a connection service.");
767        } else {
768            Log.i(this, "Send stopDtmfTone to connection service for call %s", this);
769            mConnectionService.stopDtmfTone(this);
770        }
771    }
772
773    /**
774     * Attempts to disconnect the call through the connection service.
775     */
776    void disconnect() {
777        // Track that the call is now locally disconnecting.
778        setLocallyDisconnecting(true);
779
780        if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT ||
781                mState == CallState.CONNECTING) {
782            Log.v(this, "Aborting call %s", this);
783            abort();
784        } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
785            if (mConnectionService == null) {
786                Log.e(this, new Exception(), "disconnect() request on a call without a"
787                        + " connection service.");
788            } else {
789                Log.i(this, "Send disconnect to connection service for call: %s", this);
790                // The call isn't officially disconnected until the connection service
791                // confirms that the call was actually disconnected. Only then is the
792                // association between call and connection service severed, see
793                // {@link CallsManager#markCallAsDisconnected}.
794                mConnectionService.disconnect(this);
795            }
796        }
797    }
798
799    void abort() {
800        if (mCreateConnectionProcessor != null) {
801            mCreateConnectionProcessor.abort();
802        } else if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT
803                || mState == CallState.CONNECTING) {
804            handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
805        } else {
806            Log.v(this, "Cannot abort a call which isn't either PRE_DIAL_WAIT or CONNECTING");
807        }
808    }
809
810    /**
811     * Answers the call if it is ringing.
812     *
813     * @param videoState The video state in which to answer the call.
814     */
815    void answer(int videoState) {
816        Preconditions.checkNotNull(mConnectionService);
817
818        // Check to verify that the call is still in the ringing state. A call can change states
819        // between the time the user hits 'answer' and Telecom receives the command.
820        if (isRinging("answer")) {
821            // At this point, we are asking the connection service to answer but we don't assume
822            // that it will work. Instead, we wait until confirmation from the connectino service
823            // that the call is in a non-STATE_RINGING state before changing the UI. See
824            // {@link ConnectionServiceAdapter#setActive} and other set* methods.
825            mConnectionService.answer(this, videoState);
826        }
827    }
828
829    /**
830     * Rejects the call if it is ringing.
831     *
832     * @param rejectWithMessage Whether to send a text message as part of the call rejection.
833     * @param textMessage An optional text message to send as part of the rejection.
834     */
835    void reject(boolean rejectWithMessage, String textMessage) {
836        Preconditions.checkNotNull(mConnectionService);
837
838        // Check to verify that the call is still in the ringing state. A call can change states
839        // between the time the user hits 'reject' and Telecomm receives the command.
840        if (isRinging("reject")) {
841            mConnectionService.reject(this);
842        }
843    }
844
845    /**
846     * Puts the call on hold if it is currently active.
847     */
848    void hold() {
849        Preconditions.checkNotNull(mConnectionService);
850
851        if (mState == CallState.ACTIVE) {
852            mConnectionService.hold(this);
853        }
854    }
855
856    /**
857     * Releases the call from hold if it is currently active.
858     */
859    void unhold() {
860        Preconditions.checkNotNull(mConnectionService);
861
862        if (mState == CallState.ON_HOLD) {
863            mConnectionService.unhold(this);
864        }
865    }
866
867    /** Checks if this is a live call or not. */
868    boolean isAlive() {
869        switch (mState) {
870            case CallState.NEW:
871            case CallState.RINGING:
872            case CallState.DISCONNECTED:
873            case CallState.ABORTED:
874                return false;
875            default:
876                return true;
877        }
878    }
879
880    boolean isActive() {
881        return mState == CallState.ACTIVE;
882    }
883
884    Bundle getExtras() {
885        return mExtras;
886    }
887
888    void setExtras(Bundle extras) {
889        mExtras = extras;
890    }
891
892    /**
893     * @return the uri of the contact associated with this call.
894     */
895    Uri getContactUri() {
896        if (mCallerInfo == null || !mCallerInfo.contactExists) {
897            return getHandle();
898        }
899        return Contacts.getLookupUri(mCallerInfo.contactIdOrZero, mCallerInfo.lookupKey);
900    }
901
902    Uri getRingtone() {
903        return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
904    }
905
906    void onPostDialWait(String remaining) {
907        for (Listener l : mListeners) {
908            l.onPostDialWait(this, remaining);
909        }
910    }
911
912    void postDialContinue(boolean proceed) {
913        mConnectionService.onPostDialContinue(this, proceed);
914    }
915
916    void conferenceWith(Call otherCall) {
917        if (mConnectionService == null) {
918            Log.w(this, "conference requested on a call without a connection service.");
919        } else {
920            mConnectionService.conference(this, otherCall);
921        }
922    }
923
924    void splitFromConference() {
925        if (mConnectionService == null) {
926            Log.w(this, "splitting from conference call without a connection service");
927        } else {
928            mConnectionService.splitFromConference(this);
929        }
930    }
931
932    void mergeConference() {
933        if (mConnectionService == null) {
934            Log.w(this, "merging conference calls without a connection service.");
935        } else if (can(PhoneCapabilities.MERGE_CONFERENCE)) {
936            mConnectionService.mergeConference(this);
937            mWasConferencePreviouslyMerged = true;
938        }
939    }
940
941    void swapConference() {
942        if (mConnectionService == null) {
943            Log.w(this, "swapping conference calls without a connection service.");
944        } else if (can(PhoneCapabilities.SWAP_CONFERENCE)) {
945            mConnectionService.swapConference(this);
946            switch (mChildCalls.size()) {
947                case 1:
948                    mConferenceLevelActiveCall = mChildCalls.get(0);
949                    break;
950                case 2:
951                    // swap
952                    mConferenceLevelActiveCall = mChildCalls.get(0) == mConferenceLevelActiveCall ?
953                            mChildCalls.get(1) : mChildCalls.get(0);
954                    break;
955                default:
956                    // For anything else 0, or 3+, set it to null since it is impossible to tell.
957                    mConferenceLevelActiveCall = null;
958                    break;
959            }
960        }
961    }
962
963    void setParentCall(Call parentCall) {
964        if (parentCall == this) {
965            Log.e(this, new Exception(), "setting the parent to self");
966            return;
967        }
968        if (parentCall == mParentCall) {
969            // nothing to do
970            return;
971        }
972        Preconditions.checkState(parentCall == null || mParentCall == null);
973
974        Call oldParent = mParentCall;
975        if (mParentCall != null) {
976            mParentCall.removeChildCall(this);
977        }
978        mParentCall = parentCall;
979        if (mParentCall != null) {
980            mParentCall.addChildCall(this);
981        }
982
983        for (Listener l : mListeners) {
984            l.onParentChanged(this);
985        }
986    }
987
988    void setConferenceableCalls(List<Call> conferenceableCalls) {
989        mConferenceableCalls.clear();
990        mConferenceableCalls.addAll(conferenceableCalls);
991
992        for (Listener l : mListeners) {
993            l.onConferenceableCallsChanged(this);
994        }
995    }
996
997    List<Call> getConferenceableCalls() {
998        return mConferenceableCalls;
999    }
1000
1001    boolean can(int capability) {
1002        return (mCallCapabilities & capability) == capability;
1003    }
1004
1005    private void addChildCall(Call call) {
1006        if (!mChildCalls.contains(call)) {
1007            // Set the pseudo-active call to the latest child added to the conference.
1008            // See definition of mConferenceLevelActiveCall for more detail.
1009            mConferenceLevelActiveCall = call;
1010            mChildCalls.add(call);
1011
1012            for (Listener l : mListeners) {
1013                l.onChildrenChanged(this);
1014            }
1015        }
1016    }
1017
1018    private void removeChildCall(Call call) {
1019        if (mChildCalls.remove(call)) {
1020            for (Listener l : mListeners) {
1021                l.onChildrenChanged(this);
1022            }
1023        }
1024    }
1025
1026    /**
1027     * Return whether the user can respond to this {@code Call} via an SMS message.
1028     *
1029     * @return true if the "Respond via SMS" feature should be enabled
1030     * for this incoming call.
1031     *
1032     * The general rule is that we *do* allow "Respond via SMS" except for
1033     * the few (relatively rare) cases where we know for sure it won't
1034     * work, namely:
1035     *   - a bogus or blank incoming number
1036     *   - a call from a SIP address
1037     *   - a "call presentation" that doesn't allow the number to be revealed
1038     *
1039     * In all other cases, we allow the user to respond via SMS.
1040     *
1041     * Note that this behavior isn't perfect; for example we have no way
1042     * to detect whether the incoming call is from a landline (with most
1043     * networks at least), so we still enable this feature even though
1044     * SMSes to that number will silently fail.
1045     */
1046    boolean isRespondViaSmsCapable() {
1047        if (mState != CallState.RINGING) {
1048            return false;
1049        }
1050
1051        if (getHandle() == null) {
1052            // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in
1053            // other words, the user should not be able to see the incoming phone number.
1054            return false;
1055        }
1056
1057        if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
1058            // The incoming number is actually a URI (i.e. a SIP address),
1059            // not a regular PSTN phone number, and we can't send SMSes to
1060            // SIP addresses.
1061            // (TODO: That might still be possible eventually, though. Is
1062            // there some SIP-specific equivalent to sending a text message?)
1063            return false;
1064        }
1065
1066        // Is there a valid SMS application on the phone?
1067        if (SmsApplication.getDefaultRespondViaMessageApplication(mContext,
1068                true /*updateIfNeeded*/) == null) {
1069            return false;
1070        }
1071
1072        // TODO: with some carriers (in certain countries) you *can* actually
1073        // tell whether a given number is a mobile phone or not. So in that
1074        // case we could potentially return false here if the incoming call is
1075        // from a land line.
1076
1077        // If none of the above special cases apply, it's OK to enable the
1078        // "Respond via SMS" feature.
1079        return true;
1080    }
1081
1082    List<String> getCannedSmsResponses() {
1083        return mCannedSmsResponses;
1084    }
1085
1086    /**
1087     * We need to make sure that before we move a call to the disconnected state, it no
1088     * longer has any parent/child relationships.  We want to do this to ensure that the InCall
1089     * Service always has the right data in the right order.  We also want to do it in telecom so
1090     * that the insurance policy lives in the framework side of things.
1091     */
1092    private void fixParentAfterDisconnect() {
1093        setParentCall(null);
1094    }
1095
1096    /**
1097     * @return True if the call is ringing, else logs the action name.
1098     */
1099    private boolean isRinging(String actionName) {
1100        if (mState == CallState.RINGING) {
1101            return true;
1102        }
1103
1104        Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
1105        return false;
1106    }
1107
1108    @SuppressWarnings("rawtypes")
1109    private void decrementAssociatedCallCount(ServiceBinder binder) {
1110        if (binder != null) {
1111            binder.decrementAssociatedCallCount();
1112        }
1113    }
1114
1115    /**
1116     * Looks up contact information based on the current handle.
1117     */
1118    private void startCallerInfoLookup() {
1119        String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
1120
1121        mQueryToken++;  // Updated so that previous queries can no longer set the information.
1122        mCallerInfo = null;
1123        if (!TextUtils.isEmpty(number)) {
1124            Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
1125            CallerInfoAsyncQuery.startQuery(
1126                    mQueryToken,
1127                    mContext,
1128                    number,
1129                    sCallerInfoQueryListener,
1130                    this);
1131        }
1132    }
1133
1134    /**
1135     * Saves the specified caller info if the specified token matches that of the last query
1136     * that was made.
1137     *
1138     * @param callerInfo The new caller information to set.
1139     * @param token The token used with this query.
1140     */
1141    private void setCallerInfo(CallerInfo callerInfo, int token) {
1142        Preconditions.checkNotNull(callerInfo);
1143
1144        if (mQueryToken == token) {
1145            mCallerInfo = callerInfo;
1146            Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
1147
1148            if (mCallerInfo.contactDisplayPhotoUri != null) {
1149                Log.d(this, "Searching person uri %s for call %s",
1150                        mCallerInfo.contactDisplayPhotoUri, this);
1151                ContactsAsyncHelper.startObtainPhotoAsync(
1152                        token,
1153                        mContext,
1154                        mCallerInfo.contactDisplayPhotoUri,
1155                        sPhotoLoadListener,
1156                        this);
1157                // Do not call onCallerInfoChanged yet in this case.  We call it in setPhoto().
1158            } else {
1159                for (Listener l : mListeners) {
1160                    l.onCallerInfoChanged(this);
1161                }
1162            }
1163
1164            processDirectToVoicemail();
1165        }
1166    }
1167
1168    CallerInfo getCallerInfo() {
1169        return mCallerInfo;
1170    }
1171
1172    /**
1173     * Saves the specified photo information if the specified token matches that of the last query.
1174     *
1175     * @param photo The photo as a drawable.
1176     * @param photoIcon The photo as a small icon.
1177     * @param token The token used with this query.
1178     */
1179    private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
1180        if (mQueryToken == token) {
1181            mCallerInfo.cachedPhoto = photo;
1182            mCallerInfo.cachedPhotoIcon = photoIcon;
1183
1184            for (Listener l : mListeners) {
1185                l.onCallerInfoChanged(this);
1186            }
1187        }
1188    }
1189
1190    private void maybeLoadCannedSmsResponses() {
1191        if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
1192            Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
1193            mCannedSmsResponsesLoadingStarted = true;
1194            RespondViaSmsManager.getInstance().loadCannedTextMessages(
1195                    new Response<Void, List<String>>() {
1196                        @Override
1197                        public void onResult(Void request, List<String>... result) {
1198                            if (result.length > 0) {
1199                                Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]);
1200                                mCannedSmsResponses = result[0];
1201                                for (Listener l : mListeners) {
1202                                    l.onCannedSmsResponsesLoaded(Call.this);
1203                                }
1204                            }
1205                        }
1206
1207                        @Override
1208                        public void onError(Void request, int code, String msg) {
1209                            Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code,
1210                                    msg);
1211                        }
1212                    },
1213                    mContext
1214            );
1215        } else {
1216            Log.d(this, "maybeLoadCannedSmsResponses: doing nothing");
1217        }
1218    }
1219
1220    /**
1221     * Sets speakerphone option on when call begins.
1222     */
1223    public void setStartWithSpeakerphoneOn(boolean startWithSpeakerphone) {
1224        mSpeakerphoneOn = startWithSpeakerphone;
1225    }
1226
1227    /**
1228     * Returns speakerphone option.
1229     *
1230     * @return Whether or not speakerphone should be set automatically when call begins.
1231     */
1232    public boolean getStartWithSpeakerphoneOn() {
1233        return mSpeakerphoneOn;
1234    }
1235
1236    /**
1237     * Sets a video call provider for the call.
1238     */
1239    public void setVideoProvider(IVideoProvider videoProvider) {
1240        mVideoProvider = videoProvider;
1241        for (Listener l : mListeners) {
1242            l.onVideoCallProviderChanged(Call.this);
1243        }
1244    }
1245
1246    /**
1247     * @return Return the {@link Connection.VideoProvider} binder.
1248     */
1249    public IVideoProvider getVideoProvider() {
1250        return mVideoProvider;
1251    }
1252
1253    /**
1254     * The current video state for the call.
1255     * Valid values: see {@link VideoProfile.VideoState}.
1256     */
1257    public int getVideoState() {
1258        return mVideoState;
1259    }
1260
1261    /**
1262     * Returns the video states which were applicable over the duration of a call.
1263     * See {@link VideoProfile} for a list of valid video states.
1264     *
1265     * @return The video states applicable over the duration of the call.
1266     */
1267    public int getVideoStateHistory() {
1268        return mVideoStateHistory;
1269    }
1270
1271    /**
1272     * Determines the current video state for the call.
1273     * For an outgoing call determines the desired video state for the call.
1274     * Valid values: see {@link VideoProfile.VideoState}
1275     *
1276     * @param videoState The video state for the call.
1277     */
1278    public void setVideoState(int videoState) {
1279        // Track which video states were applicable over the duration of the call.
1280        mVideoStateHistory = mVideoStateHistory | videoState;
1281
1282        mVideoState = videoState;
1283        for (Listener l : mListeners) {
1284            l.onVideoStateChanged(this);
1285        }
1286    }
1287
1288    public boolean getIsVoipAudioMode() {
1289        return mIsVoipAudioMode;
1290    }
1291
1292    public void setIsVoipAudioMode(boolean audioModeIsVoip) {
1293        mIsVoipAudioMode = audioModeIsVoip;
1294        for (Listener l : mListeners) {
1295            l.onIsVoipAudioModeChanged(this);
1296        }
1297    }
1298
1299    public StatusHints getStatusHints() {
1300        return mStatusHints;
1301    }
1302
1303    public void setStatusHints(StatusHints statusHints) {
1304        mStatusHints = statusHints;
1305        for (Listener l : mListeners) {
1306            l.onStatusHintsChanged(this);
1307        }
1308    }
1309
1310    public boolean isUnknown() {
1311        return mIsUnknown;
1312    }
1313
1314    public void setIsUnknown(boolean isUnknown) {
1315        mIsUnknown = isUnknown;
1316    }
1317
1318    /**
1319     * Determines if this call is in a disconnecting state.
1320     *
1321     * @return {@code true} if this call is locally disconnecting.
1322     */
1323    public boolean isLocallyDisconnecting() {
1324        return mIsLocallyDisconnecting;
1325    }
1326
1327    /**
1328     * Sets whether this call is in a disconnecting state.
1329     *
1330     * @param isLocallyDisconnecting {@code true} if this call is locally disconnecting.
1331     */
1332    private void setLocallyDisconnecting(boolean isLocallyDisconnecting) {
1333        mIsLocallyDisconnecting = isLocallyDisconnecting;
1334    }
1335
1336    static int getStateFromConnectionState(int state) {
1337        switch (state) {
1338            case Connection.STATE_INITIALIZING:
1339                return CallState.CONNECTING;
1340            case Connection.STATE_ACTIVE:
1341                return CallState.ACTIVE;
1342            case Connection.STATE_DIALING:
1343                return CallState.DIALING;
1344            case Connection.STATE_DISCONNECTED:
1345                return CallState.DISCONNECTED;
1346            case Connection.STATE_HOLDING:
1347                return CallState.ON_HOLD;
1348            case Connection.STATE_NEW:
1349                return CallState.NEW;
1350            case Connection.STATE_RINGING:
1351                return CallState.RINGING;
1352        }
1353        return CallState.DISCONNECTED;
1354    }
1355}
1356