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