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