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