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