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