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