Call.java revision b71c70b5c7ff858ac4f358b240efa09f8b8d3958
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.Looper;
26import android.os.RemoteException;
27import android.os.Trace;
28import android.provider.ContactsContract.Contacts;
29import android.telecom.DisconnectCause;
30import android.telecom.Connection;
31import android.telecom.GatewayInfo;
32import android.telecom.ParcelableConnection;
33import android.telecom.PhoneAccount;
34import android.telecom.PhoneAccountHandle;
35import android.telecom.Response;
36import android.telecom.StatusHints;
37import android.telecom.TelecomManager;
38import android.telecom.VideoProfile;
39import android.telephony.PhoneNumberUtils;
40import android.text.TextUtils;
41import android.os.UserHandle;
42
43import com.android.internal.annotations.VisibleForTesting;
44import com.android.internal.telecom.IVideoProvider;
45import com.android.internal.telephony.CallerInfo;
46import com.android.internal.telephony.SmsApplication;
47import com.android.internal.util.Preconditions;
48
49import java.lang.String;
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 */
64@VisibleForTesting
65public class Call implements CreateConnectionResponse {
66    public final static String CALL_ID_UNKNOWN = "-1";
67    public final static long DATA_USAGE_NOT_SET = -1;
68
69    public static final int CALL_DIRECTION_UNDEFINED = 0;
70    public static final int CALL_DIRECTION_OUTGOING = 1;
71    public static final int CALL_DIRECTION_INCOMING = 2;
72    public static final int CALL_DIRECTION_UNKNOWN = 3;
73
74    /** Identifies extras changes which originated from a connection service. */
75    public static final int SOURCE_CONNECTION_SERVICE = 1;
76    /** Identifies extras changes which originated from an incall service. */
77    public static final int SOURCE_INCALL_SERVICE = 2;
78
79    /**
80     * Listener for events on the call.
81     */
82    @VisibleForTesting
83    public interface Listener {
84        void onSuccessfulOutgoingCall(Call call, int callState);
85        void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause);
86        void onSuccessfulIncomingCall(Call call);
87        void onFailedIncomingCall(Call call);
88        void onSuccessfulUnknownCall(Call call, int callState);
89        void onFailedUnknownCall(Call call);
90        void onRingbackRequested(Call call, boolean ringbackRequested);
91        void onPostDialWait(Call call, String remaining);
92        void onPostDialChar(Call call, char nextChar);
93        void onConnectionCapabilitiesChanged(Call call);
94        void onConnectionPropertiesChanged(Call call);
95        void onParentChanged(Call call);
96        void onChildrenChanged(Call call);
97        void onCannedSmsResponsesLoaded(Call call);
98        void onVideoCallProviderChanged(Call call);
99        void onCallerInfoChanged(Call call);
100        void onIsVoipAudioModeChanged(Call call);
101        void onStatusHintsChanged(Call call);
102        void onExtrasChanged(Call c, int source, Bundle extras);
103        void onExtrasRemoved(Call c, int source, List<String> keys);
104        void onHandleChanged(Call call);
105        void onCallerDisplayNameChanged(Call call);
106        void onVideoStateChanged(Call call);
107        void onTargetPhoneAccountChanged(Call call);
108        void onConnectionManagerPhoneAccountChanged(Call call);
109        void onPhoneAccountChanged(Call call);
110        void onConferenceableCallsChanged(Call call);
111        boolean onCanceledViaNewOutgoingCallBroadcast(Call call);
112        void onHoldToneRequested(Call call);
113        void onConnectionEvent(Call call, String event, Bundle extras);
114        void onExternalCallChanged(Call call, boolean isExternalCall);
115    }
116
117    public abstract static class ListenerBase implements Listener {
118        @Override
119        public void onSuccessfulOutgoingCall(Call call, int callState) {}
120        @Override
121        public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {}
122        @Override
123        public void onSuccessfulIncomingCall(Call call) {}
124        @Override
125        public void onFailedIncomingCall(Call call) {}
126        @Override
127        public void onSuccessfulUnknownCall(Call call, int callState) {}
128        @Override
129        public void onFailedUnknownCall(Call call) {}
130        @Override
131        public void onRingbackRequested(Call call, boolean ringbackRequested) {}
132        @Override
133        public void onPostDialWait(Call call, String remaining) {}
134        @Override
135        public void onPostDialChar(Call call, char nextChar) {}
136        @Override
137        public void onConnectionCapabilitiesChanged(Call call) {}
138        @Override
139        public void onConnectionPropertiesChanged(Call call) {}
140        @Override
141        public void onParentChanged(Call call) {}
142        @Override
143        public void onChildrenChanged(Call call) {}
144        @Override
145        public void onCannedSmsResponsesLoaded(Call call) {}
146        @Override
147        public void onVideoCallProviderChanged(Call call) {}
148        @Override
149        public void onCallerInfoChanged(Call call) {}
150        @Override
151        public void onIsVoipAudioModeChanged(Call call) {}
152        @Override
153        public void onStatusHintsChanged(Call call) {}
154        @Override
155        public void onExtrasChanged(Call c, int source, Bundle extras) {}
156        @Override
157        public void onExtrasRemoved(Call c, int source, List<String> keys) {}
158        @Override
159        public void onHandleChanged(Call call) {}
160        @Override
161        public void onCallerDisplayNameChanged(Call call) {}
162        @Override
163        public void onVideoStateChanged(Call call) {}
164        @Override
165        public void onTargetPhoneAccountChanged(Call call) {}
166        @Override
167        public void onConnectionManagerPhoneAccountChanged(Call call) {}
168        @Override
169        public void onPhoneAccountChanged(Call call) {}
170        @Override
171        public void onConferenceableCallsChanged(Call call) {}
172        @Override
173        public boolean onCanceledViaNewOutgoingCallBroadcast(Call call) {
174            return false;
175        }
176
177        @Override
178        public void onHoldToneRequested(Call call) {}
179        @Override
180        public void onConnectionEvent(Call call, String event, Bundle extras) {}
181        @Override
182        public void onExternalCallChanged(Call call, boolean isExternalCall) {}
183    }
184
185    private final CallerInfoLookupHelper.OnQueryCompleteListener mCallerInfoQueryListener =
186            new CallerInfoLookupHelper.OnQueryCompleteListener() {
187                /** ${inheritDoc} */
188                @Override
189                public void onCallerInfoQueryComplete(Uri handle, CallerInfo callerInfo) {
190                    synchronized (mLock) {
191                        Call.this.setCallerInfo(handle, callerInfo);
192                    }
193                }
194
195                @Override
196                public void onContactPhotoQueryComplete(Uri handle, CallerInfo callerInfo) {
197                    synchronized (mLock) {
198                        Call.this.setCallerInfo(handle, callerInfo);
199                    }
200                }
201            };
202
203    /**
204     * One of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING, or CALL_DIRECTION_UNKNOWN
205     */
206    private final int mCallDirection;
207
208    /**
209     * The post-dial digits that were dialed after the network portion of the number
210     */
211    private final String mPostDialDigits;
212
213    /**
214     * The secondary line number that an incoming call has been received on if the SIM subscription
215     * has multiple associated numbers.
216     */
217    private String mViaNumber = "";
218
219    /**
220     * The time this call was created. Beyond logging and such, may also be used for bookkeeping
221     * and specifically for marking certain call attempts as failed attempts.
222     */
223    private long mCreationTimeMillis = System.currentTimeMillis();
224
225    /** The time this call was made active. */
226    private long mConnectTimeMillis = 0;
227
228    /** The time this call was disconnected. */
229    private long mDisconnectTimeMillis = 0;
230
231    /** The gateway information associated with this call. This stores the original call handle
232     * that the user is attempting to connect to via the gateway, the actual handle to dial in
233     * order to connect the call via the gateway, as well as the package name of the gateway
234     * service. */
235    private GatewayInfo mGatewayInfo;
236
237    private PhoneAccountHandle mConnectionManagerPhoneAccountHandle;
238
239    private PhoneAccountHandle mTargetPhoneAccountHandle;
240
241    private UserHandle mInitiatingUser;
242
243    private final Handler mHandler = new Handler(Looper.getMainLooper());
244
245    private final List<Call> mConferenceableCalls = new ArrayList<>();
246
247    /** The state of the call. */
248    private int mState;
249
250    /** The handle with which to establish this call. */
251    private Uri mHandle;
252
253    /**
254     * The presentation requirements for the handle. See {@link TelecomManager} for valid values.
255     */
256    private int mHandlePresentation;
257
258    /** The caller display name (CNAP) set by the connection service. */
259    private String mCallerDisplayName;
260
261    /**
262     * The presentation requirements for the handle. See {@link TelecomManager} for valid values.
263     */
264    private int mCallerDisplayNamePresentation;
265
266    /**
267     * The connection service which is attempted or already connecting this call.
268     */
269    private ConnectionServiceWrapper mConnectionService;
270
271    private boolean mIsEmergencyCall;
272
273    private boolean mSpeakerphoneOn;
274
275    /**
276     * Tracks the video states which were applicable over the duration of a call.
277     * See {@link VideoProfile} for a list of valid video states.
278     * <p>
279     * Video state history is tracked when the call is active, and when a call is rejected or
280     * missed.
281     */
282    private int mVideoStateHistory;
283
284    private int mVideoState;
285
286    /**
287     * Disconnect cause for the call. Only valid if the state of the call is STATE_DISCONNECTED.
288     * See {@link android.telecom.DisconnectCause}.
289     */
290    private DisconnectCause mDisconnectCause = new DisconnectCause(DisconnectCause.UNKNOWN);
291
292    private Bundle mIntentExtras = new Bundle();
293
294    /** Set of listeners on this call.
295     *
296     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
297     * load factor before resizing, 1 means we only expect a single thread to
298     * access the map so make only a single shard
299     */
300    private final Set<Listener> mListeners = Collections.newSetFromMap(
301            new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
302
303    private CreateConnectionProcessor mCreateConnectionProcessor;
304
305    /** Caller information retrieved from the latest contact query. */
306    private CallerInfo mCallerInfo;
307
308    /** The latest token used with a contact info query. */
309    private int mQueryToken = 0;
310
311    /** Whether this call is requesting that Telecom play the ringback tone on its behalf. */
312    private boolean mRingbackRequested = false;
313
314    /** Whether direct-to-voicemail query is pending. */
315    private boolean mDirectToVoicemailQueryPending;
316
317    private int mConnectionCapabilities;
318
319    private int mConnectionProperties;
320
321    private boolean mIsConference = false;
322
323    private final boolean mShouldAttachToExistingConnection;
324
325    private Call mParentCall = null;
326
327    private List<Call> mChildCalls = new LinkedList<>();
328
329    /** Set of text message responses allowed for this call, if applicable. */
330    private List<String> mCannedSmsResponses = Collections.EMPTY_LIST;
331
332    /** Whether an attempt has been made to load the text message responses. */
333    private boolean mCannedSmsResponsesLoadingStarted = false;
334
335    private IVideoProvider mVideoProvider;
336    private VideoProviderProxy mVideoProviderProxy;
337
338    private boolean mIsVoipAudioMode;
339    private StatusHints mStatusHints;
340    private Bundle mExtras;
341    private final ConnectionServiceRepository mRepository;
342    private final Context mContext;
343    private final CallsManager mCallsManager;
344    private final TelecomSystem.SyncRoot mLock;
345    private final String mId;
346    private Analytics.CallInfo mAnalytics;
347
348    private boolean mWasConferencePreviouslyMerged = false;
349
350    // For conferences which support merge/swap at their level, we retain a notion of an active
351    // call. This is used for BluetoothPhoneService.  In order to support hold/merge, it must have
352    // the notion of the current "active" call within the conference call. This maintains the
353    // "active" call and switches every time the user hits "swap".
354    private Call mConferenceLevelActiveCall = null;
355
356    private boolean mIsLocallyDisconnecting = false;
357
358    /**
359     * Tracks the current call data usage as reported by the video provider.
360     */
361    private long mCallDataUsage = DATA_USAGE_NOT_SET;
362
363    private boolean mIsWorkCall;
364
365    // Set to true once the NewOutgoingCallIntentBroadcast comes back and is processed.
366    private boolean mIsNewOutgoingCallIntentBroadcastDone = false;
367
368
369    /**
370     * Indicates whether the call is remotely held.  A call is considered remotely held when
371     * {@link #onConnectionEvent(String)} receives the {@link Connection#EVENT_ON_HOLD_TONE_START}
372     * event.
373     */
374    private boolean mIsRemotelyHeld = false;
375
376    /**
377     * Indicates whether the {@link PhoneAccount} associated with this call supports video calling.
378     * {@code True} if the phone account supports video calling, {@code false} otherwise.
379     */
380    private boolean mIsVideoCallingSupported = false;
381
382    /**
383     * Persists the specified parameters and initializes the new instance.
384     *
385     * @param context The context.
386     * @param repository The connection service repository.
387     * @param handle The handle to dial.
388     * @param gatewayInfo Gateway information to use for the call.
389     * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
390     *         This account must be one that was registered with the
391     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
392     * @param targetPhoneAccountHandle Account information to use for the call. This account must be
393     *         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
394     * @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING,
395     *         or CALL_DIRECTION_UNKNOWN.
396     * @param shouldAttachToExistingConnection Set to true to attach the call to an existing
397     *         connection, regardless of whether it's incoming or outgoing.
398     */
399    public Call(
400            String callId,
401            Context context,
402            CallsManager callsManager,
403            TelecomSystem.SyncRoot lock,
404            ConnectionServiceRepository repository,
405            ContactsAsyncHelper contactsAsyncHelper,
406            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
407            Uri handle,
408            GatewayInfo gatewayInfo,
409            PhoneAccountHandle connectionManagerPhoneAccountHandle,
410            PhoneAccountHandle targetPhoneAccountHandle,
411            int callDirection,
412            boolean shouldAttachToExistingConnection,
413            boolean isConference) {
414        mId = callId;
415        mState = isConference ? CallState.ACTIVE : CallState.NEW;
416        mContext = context;
417        mCallsManager = callsManager;
418        mLock = lock;
419        mRepository = repository;
420        setHandle(handle);
421        mPostDialDigits = handle != null
422                ? PhoneNumberUtils.extractPostDialPortion(handle.getSchemeSpecificPart()) : "";
423        mGatewayInfo = gatewayInfo;
424        setConnectionManagerPhoneAccount(connectionManagerPhoneAccountHandle);
425        setTargetPhoneAccount(targetPhoneAccountHandle);
426        mCallDirection = callDirection;
427        mIsConference = isConference;
428        mShouldAttachToExistingConnection = shouldAttachToExistingConnection
429                || callDirection == CALL_DIRECTION_INCOMING;
430        maybeLoadCannedSmsResponses();
431        mAnalytics = new Analytics.CallInfo();
432
433        Log.event(this, Log.Events.CREATED);
434    }
435
436    /**
437     * Persists the specified parameters and initializes the new instance.
438     *
439     * @param context The context.
440     * @param repository The connection service repository.
441     * @param handle The handle to dial.
442     * @param gatewayInfo Gateway information to use for the call.
443     * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
444     *         This account must be one that was registered with the
445     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
446     * @param targetPhoneAccountHandle Account information to use for the call. This account must be
447     *         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
448     * @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING,
449     *         or CALL_DIRECTION_UNKNOWN
450     * @param shouldAttachToExistingConnection Set to true to attach the call to an existing
451     *         connection, regardless of whether it's incoming or outgoing.
452     * @param connectTimeMillis The connection time of the call.
453     */
454    Call(
455            String callId,
456            Context context,
457            CallsManager callsManager,
458            TelecomSystem.SyncRoot lock,
459            ConnectionServiceRepository repository,
460            ContactsAsyncHelper contactsAsyncHelper,
461            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
462            Uri handle,
463            GatewayInfo gatewayInfo,
464            PhoneAccountHandle connectionManagerPhoneAccountHandle,
465            PhoneAccountHandle targetPhoneAccountHandle,
466            int callDirection,
467            boolean shouldAttachToExistingConnection,
468            boolean isConference,
469            long connectTimeMillis) {
470        this(callId, context, callsManager, lock, repository, contactsAsyncHelper,
471                callerInfoAsyncQueryFactory, handle, gatewayInfo,
472                connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
473                shouldAttachToExistingConnection, isConference);
474
475        mConnectTimeMillis = connectTimeMillis;
476        mAnalytics.setCallStartTime(connectTimeMillis);
477    }
478
479    public void addListener(Listener listener) {
480        mListeners.add(listener);
481    }
482
483    public void removeListener(Listener listener) {
484        if (listener != null) {
485            mListeners.remove(listener);
486        }
487    }
488
489    public void initAnalytics() {
490        int analyticsDirection;
491        switch (mCallDirection) {
492            case CALL_DIRECTION_OUTGOING:
493                analyticsDirection = Analytics.OUTGOING_DIRECTION;
494                break;
495            case CALL_DIRECTION_INCOMING:
496                analyticsDirection = Analytics.INCOMING_DIRECTION;
497                break;
498            case CALL_DIRECTION_UNKNOWN:
499            case CALL_DIRECTION_UNDEFINED:
500            default:
501                analyticsDirection = Analytics.UNKNOWN_DIRECTION;
502        }
503        mAnalytics = Analytics.initiateCallAnalytics(mId, analyticsDirection);
504    }
505
506    public Analytics.CallInfo getAnalytics() {
507        return mAnalytics;
508    }
509
510    public void destroy() {
511        Log.event(this, Log.Events.DESTROYED);
512    }
513
514    /** {@inheritDoc} */
515    @Override
516    public String toString() {
517        String component = null;
518        if (mConnectionService != null && mConnectionService.getComponentName() != null) {
519            component = mConnectionService.getComponentName().flattenToShortString();
520        }
521
522        return String.format(Locale.US, "[%s, %s, %s, %s, %s, childs(%d), has_parent(%b), %s, %s]",
523                mId,
524                CallState.toString(mState),
525                component,
526                Log.piiHandle(mHandle),
527                getVideoStateDescription(getVideoState()),
528                getChildCalls().size(),
529                getParentCall() != null,
530                Connection.capabilitiesToString(getConnectionCapabilities()),
531                Connection.propertiesToString(getConnectionProperties()));
532    }
533
534    /**
535     * Builds a debug-friendly description string for a video state.
536     * <p>
537     * A = audio active, T = video transmission active, R = video reception active, P = video
538     * paused.
539     *
540     * @param videoState The video state.
541     * @return A string indicating which bits are set in the video state.
542     */
543    private String getVideoStateDescription(int videoState) {
544        StringBuilder sb = new StringBuilder();
545        sb.append("A");
546
547        if (VideoProfile.isTransmissionEnabled(videoState)) {
548            sb.append("T");
549        }
550
551        if (VideoProfile.isReceptionEnabled(videoState)) {
552            sb.append("R");
553        }
554
555        if (VideoProfile.isPaused(videoState)) {
556            sb.append("P");
557        }
558
559        return sb.toString();
560    }
561
562    @VisibleForTesting
563    public int getState() {
564        return mState;
565    }
566
567    private boolean shouldContinueProcessingAfterDisconnect() {
568        // Stop processing once the call is active.
569        if (!CreateConnectionTimeout.isCallBeingPlaced(this)) {
570            return false;
571        }
572
573        // Make sure that there are additional connection services to process.
574        if (mCreateConnectionProcessor == null
575            || !mCreateConnectionProcessor.isProcessingComplete()
576            || !mCreateConnectionProcessor.hasMorePhoneAccounts()) {
577            return false;
578        }
579
580        if (mDisconnectCause == null) {
581            return false;
582        }
583
584        // Continue processing if the current attempt failed or timed out.
585        return mDisconnectCause.getCode() == DisconnectCause.ERROR ||
586            mCreateConnectionProcessor.isCallTimedOut();
587    }
588
589    /**
590     * Returns the unique ID for this call as it exists in Telecom.
591     * @return The call ID.
592     */
593    public String getId() {
594        return mId;
595    }
596
597    /**
598     * Sets the call state. Although there exists the notion of appropriate state transitions
599     * (see {@link CallState}), in practice those expectations break down when cellular systems
600     * misbehave and they do this very often. The result is that we do not enforce state transitions
601     * and instead keep the code resilient to unexpected state changes.
602     */
603    public void setState(int newState, String tag) {
604        if (mState != newState) {
605            Log.v(this, "setState %s -> %s", mState, newState);
606
607            if (newState == CallState.DISCONNECTED && shouldContinueProcessingAfterDisconnect()) {
608                Log.w(this, "continuing processing disconnected call with another service");
609                mCreateConnectionProcessor.continueProcessingIfPossible(this, mDisconnectCause);
610                return;
611            }
612
613            mState = newState;
614            maybeLoadCannedSmsResponses();
615
616            if (mState == CallState.ACTIVE || mState == CallState.ON_HOLD) {
617                if (mConnectTimeMillis == 0) {
618                    // We check to see if mConnectTime is already set to prevent the
619                    // call from resetting active time when it goes in and out of
620                    // ACTIVE/ON_HOLD
621                    mConnectTimeMillis = System.currentTimeMillis();
622                    mAnalytics.setCallStartTime(mConnectTimeMillis);
623                }
624
625                // Video state changes are normally tracked against history when a call is active.
626                // When the call goes active we need to be sure we track the history in case the
627                // state never changes during the duration of the call -- we want to ensure we
628                // always know the state at the start of the call.
629                mVideoStateHistory = mVideoStateHistory | mVideoState;
630
631                // We're clearly not disconnected, so reset the disconnected time.
632                mDisconnectTimeMillis = 0;
633            } else if (mState == CallState.DISCONNECTED) {
634                mDisconnectTimeMillis = System.currentTimeMillis();
635                mAnalytics.setCallEndTime(mDisconnectTimeMillis);
636                setLocallyDisconnecting(false);
637                fixParentAfterDisconnect();
638            }
639            if (mState == CallState.DISCONNECTED &&
640                    mDisconnectCause.getCode() == DisconnectCause.MISSED) {
641                // Ensure when an incoming call is missed that the video state history is updated.
642                mVideoStateHistory |= mVideoState;
643            }
644
645            // Log the state transition event
646            String event = null;
647            Object data = null;
648            switch (newState) {
649                case CallState.ACTIVE:
650                    event = Log.Events.SET_ACTIVE;
651                    break;
652                case CallState.CONNECTING:
653                    event = Log.Events.SET_CONNECTING;
654                    break;
655                case CallState.DIALING:
656                    event = Log.Events.SET_DIALING;
657                    break;
658                case CallState.DISCONNECTED:
659                    event = Log.Events.SET_DISCONNECTED;
660                    data = getDisconnectCause();
661                    break;
662                case CallState.DISCONNECTING:
663                    event = Log.Events.SET_DISCONNECTING;
664                    break;
665                case CallState.ON_HOLD:
666                    event = Log.Events.SET_HOLD;
667                    break;
668                case CallState.SELECT_PHONE_ACCOUNT:
669                    event = Log.Events.SET_SELECT_PHONE_ACCOUNT;
670                    break;
671                case CallState.RINGING:
672                    event = Log.Events.SET_RINGING;
673                    break;
674            }
675            if (event != null) {
676                // The string data should be just the tag.
677                String stringData = tag;
678                if (data != null) {
679                    // If data exists, add it to tag.  If no tag, just use data.toString().
680                    stringData = stringData == null ? data.toString() : stringData + "> " + data;
681                }
682                Log.event(this, event, stringData);
683            }
684        }
685    }
686
687    void setRingbackRequested(boolean ringbackRequested) {
688        mRingbackRequested = ringbackRequested;
689        for (Listener l : mListeners) {
690            l.onRingbackRequested(this, mRingbackRequested);
691        }
692    }
693
694    boolean isRingbackRequested() {
695        return mRingbackRequested;
696    }
697
698    @VisibleForTesting
699    public boolean isConference() {
700        return mIsConference;
701    }
702
703    public Uri getHandle() {
704        return mHandle;
705    }
706
707    public String getPostDialDigits() {
708        return mPostDialDigits;
709    }
710
711    public String getViaNumber() {
712        return mViaNumber;
713    }
714
715    public void setViaNumber(String viaNumber) {
716        // If at any point the via number is not empty throughout the call, save that via number.
717        if (!TextUtils.isEmpty(viaNumber)) {
718            mViaNumber = viaNumber;
719        }
720    }
721
722    int getHandlePresentation() {
723        return mHandlePresentation;
724    }
725
726
727    void setHandle(Uri handle) {
728        setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
729    }
730
731    public void setHandle(Uri handle, int presentation) {
732        if (!Objects.equals(handle, mHandle) || presentation != mHandlePresentation) {
733            mHandlePresentation = presentation;
734            if (mHandlePresentation == TelecomManager.PRESENTATION_RESTRICTED ||
735                    mHandlePresentation == TelecomManager.PRESENTATION_UNKNOWN) {
736                mHandle = null;
737            } else {
738                mHandle = handle;
739                if (mHandle != null && !PhoneAccount.SCHEME_VOICEMAIL.equals(mHandle.getScheme())
740                        && TextUtils.isEmpty(mHandle.getSchemeSpecificPart())) {
741                    // If the number is actually empty, set it to null, unless this is a
742                    // SCHEME_VOICEMAIL uri which always has an empty number.
743                    mHandle = null;
744                }
745            }
746
747            // Let's not allow resetting of the emergency flag. Once a call becomes an emergency
748            // call, it will remain so for the rest of it's lifetime.
749            if (!mIsEmergencyCall) {
750                mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
751                        mContext, mHandle.getSchemeSpecificPart());
752            }
753            startCallerInfoLookup();
754            for (Listener l : mListeners) {
755                l.onHandleChanged(this);
756            }
757        }
758    }
759
760    String getCallerDisplayName() {
761        return mCallerDisplayName;
762    }
763
764    int getCallerDisplayNamePresentation() {
765        return mCallerDisplayNamePresentation;
766    }
767
768    void setCallerDisplayName(String callerDisplayName, int presentation) {
769        if (!TextUtils.equals(callerDisplayName, mCallerDisplayName) ||
770                presentation != mCallerDisplayNamePresentation) {
771            mCallerDisplayName = callerDisplayName;
772            mCallerDisplayNamePresentation = presentation;
773            for (Listener l : mListeners) {
774                l.onCallerDisplayNameChanged(this);
775            }
776        }
777    }
778
779    public String getName() {
780        return mCallerInfo == null ? null : mCallerInfo.name;
781    }
782
783    public String getPhoneNumber() {
784        return mCallerInfo == null ? null : mCallerInfo.phoneNumber;
785    }
786
787    public Bitmap getPhotoIcon() {
788        return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
789    }
790
791    public Drawable getPhoto() {
792        return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
793    }
794
795    /**
796     * @param disconnectCause The reason for the disconnection, represented by
797     *         {@link android.telecom.DisconnectCause}.
798     */
799    public void setDisconnectCause(DisconnectCause disconnectCause) {
800        // TODO: Consider combining this method with a setDisconnected() method that is totally
801        // separate from setState.
802        mAnalytics.setCallDisconnectCause(disconnectCause);
803        mDisconnectCause = disconnectCause;
804    }
805
806    public DisconnectCause getDisconnectCause() {
807        return mDisconnectCause;
808    }
809
810    @VisibleForTesting
811    public boolean isEmergencyCall() {
812        return mIsEmergencyCall;
813    }
814
815    /**
816     * @return The original handle this call is associated with. In-call services should use this
817     * handle when indicating in their UI the handle that is being called.
818     */
819    public Uri getOriginalHandle() {
820        if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
821            return mGatewayInfo.getOriginalAddress();
822        }
823        return getHandle();
824    }
825
826    @VisibleForTesting
827    public GatewayInfo getGatewayInfo() {
828        return mGatewayInfo;
829    }
830
831    void setGatewayInfo(GatewayInfo gatewayInfo) {
832        mGatewayInfo = gatewayInfo;
833    }
834
835    @VisibleForTesting
836    public PhoneAccountHandle getConnectionManagerPhoneAccount() {
837        return mConnectionManagerPhoneAccountHandle;
838    }
839
840    @VisibleForTesting
841    public void setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle) {
842        if (!Objects.equals(mConnectionManagerPhoneAccountHandle, accountHandle)) {
843            mConnectionManagerPhoneAccountHandle = accountHandle;
844            for (Listener l : mListeners) {
845                l.onConnectionManagerPhoneAccountChanged(this);
846            }
847        }
848
849    }
850
851    @VisibleForTesting
852    public PhoneAccountHandle getTargetPhoneAccount() {
853        return mTargetPhoneAccountHandle;
854    }
855
856    @VisibleForTesting
857    public void setTargetPhoneAccount(PhoneAccountHandle accountHandle) {
858        if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) {
859            mTargetPhoneAccountHandle = accountHandle;
860            for (Listener l : mListeners) {
861                l.onTargetPhoneAccountChanged(this);
862            }
863            configureIsWorkCall();
864            checkIfVideoCapable();
865        }
866    }
867
868    @VisibleForTesting
869    public boolean isIncoming() {
870        return mCallDirection == CALL_DIRECTION_INCOMING;
871    }
872
873    public boolean isExternalCall() {
874        return (getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) ==
875                Connection.PROPERTY_IS_EXTERNAL_CALL;
876    }
877
878    public boolean isWorkCall() {
879        return mIsWorkCall;
880    }
881
882    public boolean isVideoCallingSupported() {
883        return mIsVideoCallingSupported;
884    }
885
886    private void configureIsWorkCall() {
887        PhoneAccountRegistrar phoneAccountRegistrar = mCallsManager.getPhoneAccountRegistrar();
888        boolean isWorkCall = false;
889        PhoneAccount phoneAccount =
890                phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle);
891        if (phoneAccount != null) {
892            final UserHandle userHandle;
893            if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
894                userHandle = mInitiatingUser;
895            } else {
896                userHandle = mTargetPhoneAccountHandle.getUserHandle();
897            }
898            if (userHandle != null) {
899                isWorkCall = UserUtil.isManagedProfile(mContext, userHandle);
900            }
901        }
902        mIsWorkCall = isWorkCall;
903    }
904
905    /**
906     * Caches the state of the {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} {@link PhoneAccount}
907     * capability.
908     */
909    private void checkIfVideoCapable() {
910        PhoneAccountRegistrar phoneAccountRegistrar = mCallsManager.getPhoneAccountRegistrar();
911        PhoneAccount phoneAccount =
912                phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle);
913        mIsVideoCallingSupported = phoneAccount != null && phoneAccount.hasCapabilities(
914                    PhoneAccount.CAPABILITY_VIDEO_CALLING);
915    }
916
917    boolean shouldAttachToExistingConnection() {
918        return mShouldAttachToExistingConnection;
919    }
920
921    /**
922     * @return The "age" of this call object in milliseconds, which typically also represents the
923     *     period since this call was added to the set pending outgoing calls, see
924     *     mCreationTimeMillis.
925     */
926    @VisibleForTesting
927    public long getAgeMillis() {
928        if (mState == CallState.DISCONNECTED &&
929                (mDisconnectCause.getCode() == DisconnectCause.REJECTED ||
930                 mDisconnectCause.getCode() == DisconnectCause.MISSED)) {
931            // Rejected and missed calls have no age. They're immortal!!
932            return 0;
933        } else if (mConnectTimeMillis == 0) {
934            // Age is measured in the amount of time the call was active. A zero connect time
935            // indicates that we never went active, so return 0 for the age.
936            return 0;
937        } else if (mDisconnectTimeMillis == 0) {
938            // We connected, but have not yet disconnected
939            return System.currentTimeMillis() - mConnectTimeMillis;
940        }
941
942        return mDisconnectTimeMillis - mConnectTimeMillis;
943    }
944
945    /**
946     * @return The time when this call object was created and added to the set of pending outgoing
947     *     calls.
948     */
949    public long getCreationTimeMillis() {
950        return mCreationTimeMillis;
951    }
952
953    public void setCreationTimeMillis(long time) {
954        mCreationTimeMillis = time;
955    }
956
957    long getConnectTimeMillis() {
958        return mConnectTimeMillis;
959    }
960
961    int getConnectionCapabilities() {
962        return mConnectionCapabilities;
963    }
964
965    int getConnectionProperties() {
966        return mConnectionProperties;
967    }
968
969    void setConnectionCapabilities(int connectionCapabilities) {
970        setConnectionCapabilities(connectionCapabilities, false /* forceUpdate */);
971    }
972
973    void setConnectionCapabilities(int connectionCapabilities, boolean forceUpdate) {
974        Log.v(this, "setConnectionCapabilities: %s", Connection.capabilitiesToString(
975                connectionCapabilities));
976        if (forceUpdate || mConnectionCapabilities != connectionCapabilities) {
977            // If the phone account does not support video calling, and the connection capabilities
978            // passed in indicate that the call supports video, remove those video capabilities.
979            if (!isVideoCallingSupported() && doesCallSupportVideo(connectionCapabilities)) {
980                Log.w(this, "setConnectionCapabilities: attempt to set connection as video " +
981                        "capable when not supported by the phone account.");
982                connectionCapabilities = removeVideoCapabilities(connectionCapabilities);
983            }
984
985            mConnectionCapabilities = connectionCapabilities;
986            for (Listener l : mListeners) {
987                l.onConnectionCapabilitiesChanged(this);
988            }
989        }
990    }
991
992    void setConnectionProperties(int connectionProperties) {
993        Log.v(this, "setConnectionProperties: %s", Connection.propertiesToString(
994                connectionProperties));
995        if (mConnectionProperties != connectionProperties) {
996            int previousProperties = mConnectionProperties;
997            mConnectionProperties = connectionProperties;
998            for (Listener l : mListeners) {
999                l.onConnectionPropertiesChanged(this);
1000            }
1001
1002            boolean wasExternal = (previousProperties & Connection.PROPERTY_IS_EXTERNAL_CALL)
1003                    == Connection.PROPERTY_IS_EXTERNAL_CALL;
1004            boolean isExternal = (connectionProperties & Connection.PROPERTY_IS_EXTERNAL_CALL)
1005                    == Connection.PROPERTY_IS_EXTERNAL_CALL;
1006            if (wasExternal != isExternal) {
1007                Log.v(this, "setConnectionProperties: external call changed isExternal = %b",
1008                        isExternal);
1009
1010                for (Listener l : mListeners) {
1011                    l.onExternalCallChanged(this, isExternal);
1012                }
1013
1014            }
1015        }
1016    }
1017
1018    @VisibleForTesting
1019    public Call getParentCall() {
1020        return mParentCall;
1021    }
1022
1023    @VisibleForTesting
1024    public List<Call> getChildCalls() {
1025        return mChildCalls;
1026    }
1027
1028    @VisibleForTesting
1029    public boolean wasConferencePreviouslyMerged() {
1030        return mWasConferencePreviouslyMerged;
1031    }
1032
1033    @VisibleForTesting
1034    public Call getConferenceLevelActiveCall() {
1035        return mConferenceLevelActiveCall;
1036    }
1037
1038    @VisibleForTesting
1039    public ConnectionServiceWrapper getConnectionService() {
1040        return mConnectionService;
1041    }
1042
1043    /**
1044     * Retrieves the {@link Context} for the call.
1045     *
1046     * @return The {@link Context}.
1047     */
1048    Context getContext() {
1049        return mContext;
1050    }
1051
1052    @VisibleForTesting
1053    public void setConnectionService(ConnectionServiceWrapper service) {
1054        Preconditions.checkNotNull(service);
1055
1056        clearConnectionService();
1057
1058        service.incrementAssociatedCallCount();
1059        mConnectionService = service;
1060        mAnalytics.setCallConnectionService(service.getComponentName().flattenToShortString());
1061        mConnectionService.addCall(this);
1062    }
1063
1064    /**
1065     * Clears the associated connection service.
1066     */
1067    void clearConnectionService() {
1068        if (mConnectionService != null) {
1069            ConnectionServiceWrapper serviceTemp = mConnectionService;
1070            mConnectionService = null;
1071            serviceTemp.removeCall(this);
1072
1073            // Decrementing the count can cause the service to unbind, which itself can trigger the
1074            // service-death code.  Since the service death code tries to clean up any associated
1075            // calls, we need to make sure to remove that information (e.g., removeCall()) before
1076            // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
1077            // necessary, but cleaning up mConnectionService prior to triggering an unbind is good
1078            // to do.
1079            decrementAssociatedCallCount(serviceTemp);
1080        }
1081    }
1082
1083    /**
1084     * Starts the create connection sequence. Upon completion, there should exist an active
1085     * connection through a connection service (or the call will have failed).
1086     *
1087     * @param phoneAccountRegistrar The phone account registrar.
1088     */
1089    void startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar) {
1090        if (mCreateConnectionProcessor != null) {
1091            Log.w(this, "mCreateConnectionProcessor in startCreateConnection is not null. This is" +
1092                    " due to a race between NewOutgoingCallIntentBroadcaster and " +
1093                    "phoneAccountSelected, but is harmlessly resolved by ignoring the second " +
1094                    "invocation.");
1095            return;
1096        }
1097        mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this,
1098                phoneAccountRegistrar, mContext);
1099        mCreateConnectionProcessor.process();
1100    }
1101
1102    @Override
1103    public void handleCreateConnectionSuccess(
1104            CallIdMapper idMapper,
1105            ParcelableConnection connection) {
1106        Log.v(this, "handleCreateConnectionSuccessful %s", connection);
1107        setTargetPhoneAccount(connection.getPhoneAccount());
1108        setHandle(connection.getHandle(), connection.getHandlePresentation());
1109        setCallerDisplayName(
1110                connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());
1111
1112        setConnectionCapabilities(connection.getConnectionCapabilities());
1113        setConnectionProperties(connection.getConnectionProperties());
1114        setVideoProvider(connection.getVideoProvider());
1115        setVideoState(connection.getVideoState());
1116        setRingbackRequested(connection.isRingbackRequested());
1117        setIsVoipAudioMode(connection.getIsVoipAudioMode());
1118        setStatusHints(connection.getStatusHints());
1119        putExtras(SOURCE_CONNECTION_SERVICE, connection.getExtras());
1120
1121        mConferenceableCalls.clear();
1122        for (String id : connection.getConferenceableConnectionIds()) {
1123            mConferenceableCalls.add(idMapper.getCall(id));
1124        }
1125
1126        switch (mCallDirection) {
1127            case CALL_DIRECTION_INCOMING:
1128                // Listeners (just CallsManager for now) will be responsible for checking whether
1129                // the call should be blocked.
1130                for (Listener l : mListeners) {
1131                    l.onSuccessfulIncomingCall(this);
1132                }
1133                break;
1134            case CALL_DIRECTION_OUTGOING:
1135                for (Listener l : mListeners) {
1136                    l.onSuccessfulOutgoingCall(this,
1137                            getStateFromConnectionState(connection.getState()));
1138                }
1139                break;
1140            case CALL_DIRECTION_UNKNOWN:
1141                for (Listener l : mListeners) {
1142                    l.onSuccessfulUnknownCall(this, getStateFromConnectionState(connection
1143                            .getState()));
1144                }
1145                break;
1146        }
1147    }
1148
1149    @Override
1150    public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
1151        clearConnectionService();
1152        setDisconnectCause(disconnectCause);
1153        mCallsManager.markCallAsDisconnected(this, disconnectCause);
1154
1155        switch (mCallDirection) {
1156            case CALL_DIRECTION_INCOMING:
1157                for (Listener listener : mListeners) {
1158                    listener.onFailedIncomingCall(this);
1159                }
1160                break;
1161            case CALL_DIRECTION_OUTGOING:
1162                for (Listener listener : mListeners) {
1163                    listener.onFailedOutgoingCall(this, disconnectCause);
1164                }
1165                break;
1166            case CALL_DIRECTION_UNKNOWN:
1167                for (Listener listener : mListeners) {
1168                    listener.onFailedUnknownCall(this);
1169                }
1170                break;
1171        }
1172    }
1173
1174    /**
1175     * Plays the specified DTMF tone.
1176     */
1177    void playDtmfTone(char digit) {
1178        if (mConnectionService == null) {
1179            Log.w(this, "playDtmfTone() request on a call without a connection service.");
1180        } else {
1181            Log.i(this, "Send playDtmfTone to connection service for call %s", this);
1182            mConnectionService.playDtmfTone(this, digit);
1183            Log.event(this, Log.Events.START_DTMF, Log.pii(digit));
1184        }
1185    }
1186
1187    /**
1188     * Stops playing any currently playing DTMF tone.
1189     */
1190    void stopDtmfTone() {
1191        if (mConnectionService == null) {
1192            Log.w(this, "stopDtmfTone() request on a call without a connection service.");
1193        } else {
1194            Log.i(this, "Send stopDtmfTone to connection service for call %s", this);
1195            Log.event(this, Log.Events.STOP_DTMF);
1196            mConnectionService.stopDtmfTone(this);
1197        }
1198    }
1199
1200    /**
1201     * Silences the ringer.
1202     */
1203    void silence() {
1204        if (mConnectionService == null) {
1205            Log.w(this, "silence() request on a call without a connection service.");
1206        } else {
1207            Log.i(this, "Send silence to connection service for call %s", this);
1208            Log.event(this, Log.Events.SILENCE);
1209            mConnectionService.silence(this);
1210        }
1211    }
1212
1213    @VisibleForTesting
1214    public void disconnect() {
1215        disconnect(false);
1216    }
1217
1218    /**
1219     * Attempts to disconnect the call through the connection service.
1220     */
1221    @VisibleForTesting
1222    public void disconnect(boolean wasViaNewOutgoingCallBroadcaster) {
1223        Log.event(this, Log.Events.REQUEST_DISCONNECT);
1224
1225        // Track that the call is now locally disconnecting.
1226        setLocallyDisconnecting(true);
1227
1228        if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT ||
1229                mState == CallState.CONNECTING) {
1230            Log.v(this, "Aborting call %s", this);
1231            abort(wasViaNewOutgoingCallBroadcaster);
1232        } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
1233            if (mConnectionService == null) {
1234                Log.e(this, new Exception(), "disconnect() request on a call without a"
1235                        + " connection service.");
1236            } else {
1237                Log.i(this, "Send disconnect to connection service for call: %s", this);
1238                // The call isn't officially disconnected until the connection service
1239                // confirms that the call was actually disconnected. Only then is the
1240                // association between call and connection service severed, see
1241                // {@link CallsManager#markCallAsDisconnected}.
1242                mConnectionService.disconnect(this);
1243            }
1244        }
1245    }
1246
1247    void abort(boolean wasViaNewOutgoingCallBroadcaster) {
1248        if (mCreateConnectionProcessor != null &&
1249                !mCreateConnectionProcessor.isProcessingComplete()) {
1250            mCreateConnectionProcessor.abort();
1251        } else if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT
1252                || mState == CallState.CONNECTING) {
1253            if (wasViaNewOutgoingCallBroadcaster) {
1254                // If the cancelation was from NEW_OUTGOING_CALL, then we do not automatically
1255                // destroy the call.  Instead, we announce the cancelation and CallsManager handles
1256                // it through a timer. Since apps often cancel calls through NEW_OUTGOING_CALL and
1257                // then re-dial them quickly using a gateway, allowing the first call to end
1258                // causes jank. This timeout allows CallsManager to transition the first call into
1259                // the second call so that in-call only ever sees a single call...eliminating the
1260                // jank altogether.
1261                for (Listener listener : mListeners) {
1262                    if (listener.onCanceledViaNewOutgoingCallBroadcast(this)) {
1263                        // The first listener to handle this wins. A return value of true means that
1264                        // the listener will handle the disconnection process later and so we
1265                        // should not continue it here.
1266                        setLocallyDisconnecting(false);
1267                        return;
1268                    }
1269                }
1270            }
1271
1272            handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
1273        } else {
1274            Log.v(this, "Cannot abort a call which is neither SELECT_PHONE_ACCOUNT or CONNECTING");
1275        }
1276    }
1277
1278    /**
1279     * Answers the call if it is ringing.
1280     *
1281     * @param videoState The video state in which to answer the call.
1282     */
1283    @VisibleForTesting
1284    public void answer(int videoState) {
1285        Preconditions.checkNotNull(mConnectionService);
1286
1287        // Check to verify that the call is still in the ringing state. A call can change states
1288        // between the time the user hits 'answer' and Telecom receives the command.
1289        if (isRinging("answer")) {
1290            // At this point, we are asking the connection service to answer but we don't assume
1291            // that it will work. Instead, we wait until confirmation from the connectino service
1292            // that the call is in a non-STATE_RINGING state before changing the UI. See
1293            // {@link ConnectionServiceAdapter#setActive} and other set* methods.
1294            mConnectionService.answer(this, videoState);
1295            Log.event(this, Log.Events.REQUEST_ACCEPT);
1296        }
1297    }
1298
1299    /**
1300     * Rejects the call if it is ringing.
1301     *
1302     * @param rejectWithMessage Whether to send a text message as part of the call rejection.
1303     * @param textMessage An optional text message to send as part of the rejection.
1304     */
1305    @VisibleForTesting
1306    public void reject(boolean rejectWithMessage, String textMessage) {
1307        Preconditions.checkNotNull(mConnectionService);
1308
1309        // Check to verify that the call is still in the ringing state. A call can change states
1310        // between the time the user hits 'reject' and Telecomm receives the command.
1311        if (isRinging("reject")) {
1312            // Ensure video state history tracks video state at time of rejection.
1313            mVideoStateHistory |= mVideoState;
1314
1315            mConnectionService.reject(this, rejectWithMessage, textMessage);
1316            Log.event(this, Log.Events.REQUEST_REJECT);
1317        }
1318    }
1319
1320    /**
1321     * Puts the call on hold if it is currently active.
1322     */
1323    void hold() {
1324        Preconditions.checkNotNull(mConnectionService);
1325
1326        if (mState == CallState.ACTIVE) {
1327            mConnectionService.hold(this);
1328            Log.event(this, Log.Events.REQUEST_HOLD);
1329        }
1330    }
1331
1332    /**
1333     * Releases the call from hold if it is currently active.
1334     */
1335    void unhold() {
1336        Preconditions.checkNotNull(mConnectionService);
1337
1338        if (mState == CallState.ON_HOLD) {
1339            mConnectionService.unhold(this);
1340            Log.event(this, Log.Events.REQUEST_UNHOLD);
1341        }
1342    }
1343
1344    /** Checks if this is a live call or not. */
1345    @VisibleForTesting
1346    public boolean isAlive() {
1347        switch (mState) {
1348            case CallState.NEW:
1349            case CallState.RINGING:
1350            case CallState.DISCONNECTED:
1351            case CallState.ABORTED:
1352                return false;
1353            default:
1354                return true;
1355        }
1356    }
1357
1358    boolean isActive() {
1359        return mState == CallState.ACTIVE;
1360    }
1361
1362    Bundle getExtras() {
1363        return mExtras;
1364    }
1365
1366    /**
1367     * Adds extras to the extras bundle associated with this {@link Call}.
1368     *
1369     * Note: this method needs to know the source of the extras change (see
1370     * {@link #SOURCE_CONNECTION_SERVICE}, {@link #SOURCE_INCALL_SERVICE}).  Extras changes which
1371     * originate from a connection service will only be notified to incall services.  Likewise,
1372     * changes originating from the incall services will only notify the connection service of the
1373     * change.
1374     *
1375     * @param source The source of the extras addition.
1376     * @param extras The extras.
1377     */
1378    void putExtras(int source, Bundle extras) {
1379        if (extras == null) {
1380            return;
1381        }
1382        if (mExtras == null) {
1383            mExtras = new Bundle();
1384        }
1385        mExtras.putAll(extras);
1386
1387        for (Listener l : mListeners) {
1388            l.onExtrasChanged(this, source, extras);
1389        }
1390
1391        // If the change originated from an InCallService, notify the connection service.
1392        if (source == SOURCE_INCALL_SERVICE) {
1393            mConnectionService.onExtrasChanged(this, mExtras);
1394        }
1395    }
1396
1397    /**
1398     * Removes extras from the extras bundle associated with this {@link Call}.
1399     *
1400     * Note: this method needs to know the source of the extras change (see
1401     * {@link #SOURCE_CONNECTION_SERVICE}, {@link #SOURCE_INCALL_SERVICE}).  Extras changes which
1402     * originate from a connection service will only be notified to incall services.  Likewise,
1403     * changes originating from the incall services will only notify the connection service of the
1404     * change.
1405     *
1406     * @param source The source of the extras removal.
1407     * @param keys The extra keys to remove.
1408     */
1409    void removeExtras(int source, List<String> keys) {
1410        if (mExtras == null) {
1411            return;
1412        }
1413        for (String key : keys) {
1414            mExtras.remove(key);
1415        }
1416
1417        for (Listener l : mListeners) {
1418            l.onExtrasRemoved(this, source, keys);
1419        }
1420
1421        // If the change originated from an InCallService, notify the connection service.
1422        if (source == SOURCE_INCALL_SERVICE) {
1423            mConnectionService.onExtrasChanged(this, mExtras);
1424        }
1425    }
1426
1427    @VisibleForTesting
1428    public Bundle getIntentExtras() {
1429        return mIntentExtras;
1430    }
1431
1432    void setIntentExtras(Bundle extras) {
1433        mIntentExtras = extras;
1434    }
1435
1436    /**
1437     * @return the uri of the contact associated with this call.
1438     */
1439    @VisibleForTesting
1440    public Uri getContactUri() {
1441        if (mCallerInfo == null || !mCallerInfo.contactExists) {
1442            return getHandle();
1443        }
1444        return Contacts.getLookupUri(mCallerInfo.contactIdOrZero, mCallerInfo.lookupKey);
1445    }
1446
1447    Uri getRingtone() {
1448        return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
1449    }
1450
1451    void onPostDialWait(String remaining) {
1452        for (Listener l : mListeners) {
1453            l.onPostDialWait(this, remaining);
1454        }
1455    }
1456
1457    void onPostDialChar(char nextChar) {
1458        for (Listener l : mListeners) {
1459            l.onPostDialChar(this, nextChar);
1460        }
1461    }
1462
1463    void postDialContinue(boolean proceed) {
1464        mConnectionService.onPostDialContinue(this, proceed);
1465    }
1466
1467    void conferenceWith(Call otherCall) {
1468        if (mConnectionService == null) {
1469            Log.w(this, "conference requested on a call without a connection service.");
1470        } else {
1471            Log.event(this, Log.Events.CONFERENCE_WITH, otherCall);
1472            mConnectionService.conference(this, otherCall);
1473        }
1474    }
1475
1476    void splitFromConference() {
1477        if (mConnectionService == null) {
1478            Log.w(this, "splitting from conference call without a connection service");
1479        } else {
1480            Log.event(this, Log.Events.SPLIT_CONFERENCE);
1481            mConnectionService.splitFromConference(this);
1482        }
1483    }
1484
1485    @VisibleForTesting
1486    public void mergeConference() {
1487        if (mConnectionService == null) {
1488            Log.w(this, "merging conference calls without a connection service.");
1489        } else if (can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
1490            Log.event(this, Log.Events.CONFERENCE_WITH);
1491            mConnectionService.mergeConference(this);
1492            mWasConferencePreviouslyMerged = true;
1493        }
1494    }
1495
1496    @VisibleForTesting
1497    public void swapConference() {
1498        if (mConnectionService == null) {
1499            Log.w(this, "swapping conference calls without a connection service.");
1500        } else if (can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
1501            Log.event(this, Log.Events.SWAP);
1502            mConnectionService.swapConference(this);
1503            switch (mChildCalls.size()) {
1504                case 1:
1505                    mConferenceLevelActiveCall = mChildCalls.get(0);
1506                    break;
1507                case 2:
1508                    // swap
1509                    mConferenceLevelActiveCall = mChildCalls.get(0) == mConferenceLevelActiveCall ?
1510                            mChildCalls.get(1) : mChildCalls.get(0);
1511                    break;
1512                default:
1513                    // For anything else 0, or 3+, set it to null since it is impossible to tell.
1514                    mConferenceLevelActiveCall = null;
1515                    break;
1516            }
1517        }
1518    }
1519
1520    /**
1521     * Initiates a request to the connection service to pull this call.
1522     * <p>
1523     * This method can only be used for calls that have the
1524     * {@link android.telecom.Connection#CAPABILITY_CAN_PULL_CALL} capability and
1525     * {@link android.telecom.Connection#PROPERTY_IS_EXTERNAL_CALL} property set.
1526     * <p>
1527     * An external call is a representation of a call which is taking place on another device
1528     * associated with a PhoneAccount on this device.  Issuing a request to pull the external call
1529     * tells the {@link android.telecom.ConnectionService} that it should move the call from the
1530     * other device to this one.  An example of this is the IMS multi-endpoint functionality.  A
1531     * user may have two phones with the same phone number.  If the user is engaged in an active
1532     * call on their first device, the network will inform the second device of that ongoing call in
1533     * the form of an external call.  The user may wish to continue their conversation on the second
1534     * device, so will issue a request to pull the call to the second device.
1535     * <p>
1536     * Requests to pull a call which is not external, or a call which is not pullable are ignored.
1537     */
1538    public void pullExternalCall() {
1539        if (mConnectionService == null) {
1540            Log.w(this, "pulling a call without a connection service.");
1541        }
1542
1543        if (!hasProperty(Connection.PROPERTY_IS_EXTERNAL_CALL)) {
1544            Log.w(this, "pullExternalCall - call %s is not an external call.", mId);
1545            return;
1546        }
1547
1548        if (!can(Connection.CAPABILITY_CAN_PULL_CALL)) {
1549            Log.w(this, "pullExternalCall - call %s is external but cannot be pulled.", mId);
1550            return;
1551        }
1552
1553        Log.event(this, Log.Events.PULL);
1554        mConnectionService.pullExternalCall(this);
1555    }
1556
1557    /**
1558     * Sends a call event to the {@link ConnectionService} for this call.
1559     *
1560     * See {@link Call#sendCallEvent(String, Bundle)}.
1561     *
1562     * @param event The call event.
1563     * @param extras Associated extras.
1564     */
1565    public void sendCallEvent(String event, Bundle extras) {
1566        mConnectionService.sendCallEvent(this, event, extras);
1567    }
1568
1569    void setParentCall(Call parentCall) {
1570        if (parentCall == this) {
1571            Log.e(this, new Exception(), "setting the parent to self");
1572            return;
1573        }
1574        if (parentCall == mParentCall) {
1575            // nothing to do
1576            return;
1577        }
1578        Preconditions.checkState(parentCall == null || mParentCall == null);
1579
1580        Call oldParent = mParentCall;
1581        if (mParentCall != null) {
1582            mParentCall.removeChildCall(this);
1583        }
1584        mParentCall = parentCall;
1585        if (mParentCall != null) {
1586            mParentCall.addChildCall(this);
1587        }
1588
1589        Log.event(this, Log.Events.SET_PARENT, mParentCall);
1590        for (Listener l : mListeners) {
1591            l.onParentChanged(this);
1592        }
1593    }
1594
1595    void setConferenceableCalls(List<Call> conferenceableCalls) {
1596        mConferenceableCalls.clear();
1597        mConferenceableCalls.addAll(conferenceableCalls);
1598
1599        for (Listener l : mListeners) {
1600            l.onConferenceableCallsChanged(this);
1601        }
1602    }
1603
1604    @VisibleForTesting
1605    public List<Call> getConferenceableCalls() {
1606        return mConferenceableCalls;
1607    }
1608
1609    @VisibleForTesting
1610    public boolean can(int capability) {
1611        return (mConnectionCapabilities & capability) == capability;
1612    }
1613
1614    @VisibleForTesting
1615    public boolean hasProperty(int property) {
1616        return (mConnectionProperties & property) == property;
1617    }
1618
1619    private void addChildCall(Call call) {
1620        if (!mChildCalls.contains(call)) {
1621            // Set the pseudo-active call to the latest child added to the conference.
1622            // See definition of mConferenceLevelActiveCall for more detail.
1623            mConferenceLevelActiveCall = call;
1624            mChildCalls.add(call);
1625
1626            Log.event(this, Log.Events.ADD_CHILD, call);
1627
1628            for (Listener l : mListeners) {
1629                l.onChildrenChanged(this);
1630            }
1631        }
1632    }
1633
1634    private void removeChildCall(Call call) {
1635        if (mChildCalls.remove(call)) {
1636            Log.event(this, Log.Events.REMOVE_CHILD, call);
1637            for (Listener l : mListeners) {
1638                l.onChildrenChanged(this);
1639            }
1640        }
1641    }
1642
1643    /**
1644     * Return whether the user can respond to this {@code Call} via an SMS message.
1645     *
1646     * @return true if the "Respond via SMS" feature should be enabled
1647     * for this incoming call.
1648     *
1649     * The general rule is that we *do* allow "Respond via SMS" except for
1650     * the few (relatively rare) cases where we know for sure it won't
1651     * work, namely:
1652     *   - a bogus or blank incoming number
1653     *   - a call from a SIP address
1654     *   - a "call presentation" that doesn't allow the number to be revealed
1655     *
1656     * In all other cases, we allow the user to respond via SMS.
1657     *
1658     * Note that this behavior isn't perfect; for example we have no way
1659     * to detect whether the incoming call is from a landline (with most
1660     * networks at least), so we still enable this feature even though
1661     * SMSes to that number will silently fail.
1662     */
1663    boolean isRespondViaSmsCapable() {
1664        if (mState != CallState.RINGING) {
1665            return false;
1666        }
1667
1668        if (getHandle() == null) {
1669            // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in
1670            // other words, the user should not be able to see the incoming phone number.
1671            return false;
1672        }
1673
1674        if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
1675            // The incoming number is actually a URI (i.e. a SIP address),
1676            // not a regular PSTN phone number, and we can't send SMSes to
1677            // SIP addresses.
1678            // (TODO: That might still be possible eventually, though. Is
1679            // there some SIP-specific equivalent to sending a text message?)
1680            return false;
1681        }
1682
1683        // Is there a valid SMS application on the phone?
1684        if (SmsApplication.getDefaultRespondViaMessageApplication(mContext,
1685                true /*updateIfNeeded*/) == null) {
1686            return false;
1687        }
1688
1689        // TODO: with some carriers (in certain countries) you *can* actually
1690        // tell whether a given number is a mobile phone or not. So in that
1691        // case we could potentially return false here if the incoming call is
1692        // from a land line.
1693
1694        // If none of the above special cases apply, it's OK to enable the
1695        // "Respond via SMS" feature.
1696        return true;
1697    }
1698
1699    List<String> getCannedSmsResponses() {
1700        return mCannedSmsResponses;
1701    }
1702
1703    /**
1704     * We need to make sure that before we move a call to the disconnected state, it no
1705     * longer has any parent/child relationships.  We want to do this to ensure that the InCall
1706     * Service always has the right data in the right order.  We also want to do it in telecom so
1707     * that the insurance policy lives in the framework side of things.
1708     */
1709    private void fixParentAfterDisconnect() {
1710        setParentCall(null);
1711    }
1712
1713    /**
1714     * @return True if the call is ringing, else logs the action name.
1715     */
1716    private boolean isRinging(String actionName) {
1717        if (mState == CallState.RINGING) {
1718            return true;
1719        }
1720
1721        Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
1722        return false;
1723    }
1724
1725    @SuppressWarnings("rawtypes")
1726    private void decrementAssociatedCallCount(ServiceBinder binder) {
1727        if (binder != null) {
1728            binder.decrementAssociatedCallCount();
1729        }
1730    }
1731
1732    /**
1733     * Looks up contact information based on the current handle.
1734     */
1735    private void startCallerInfoLookup() {
1736        mCallerInfo = null;
1737        mCallsManager.getCallerInfoLookupHelper().startLookup(mHandle, mCallerInfoQueryListener);
1738    }
1739
1740    /**
1741     * Saves the specified caller info if the specified token matches that of the last query
1742     * that was made.
1743     *
1744     * @param callerInfo The new caller information to set.
1745     */
1746    private void setCallerInfo(Uri handle, CallerInfo callerInfo) {
1747        Trace.beginSection("setCallerInfo");
1748        Preconditions.checkNotNull(callerInfo);
1749
1750        if (!handle.equals(mHandle)) {
1751            Log.i(this, "setCallerInfo received stale caller info for an old handle. Ignoring.");
1752            return;
1753        }
1754
1755        mCallerInfo = callerInfo;
1756        Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
1757
1758        if (mCallerInfo.contactDisplayPhotoUri == null ||
1759                mCallerInfo.cachedPhotoIcon != null || mCallerInfo.cachedPhoto != null) {
1760            for (Listener l : mListeners) {
1761                l.onCallerInfoChanged(this);
1762            }
1763        }
1764
1765        Trace.endSection();
1766    }
1767
1768    public CallerInfo getCallerInfo() {
1769        return mCallerInfo;
1770    }
1771
1772    private void maybeLoadCannedSmsResponses() {
1773        if (mCallDirection == CALL_DIRECTION_INCOMING
1774                && isRespondViaSmsCapable()
1775                && !mCannedSmsResponsesLoadingStarted) {
1776            Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
1777            mCannedSmsResponsesLoadingStarted = true;
1778            mCallsManager.getRespondViaSmsManager().loadCannedTextMessages(
1779                    new Response<Void, List<String>>() {
1780                        @Override
1781                        public void onResult(Void request, List<String>... result) {
1782                            if (result.length > 0) {
1783                                Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]);
1784                                mCannedSmsResponses = result[0];
1785                                for (Listener l : mListeners) {
1786                                    l.onCannedSmsResponsesLoaded(Call.this);
1787                                }
1788                            }
1789                        }
1790
1791                        @Override
1792                        public void onError(Void request, int code, String msg) {
1793                            Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code,
1794                                    msg);
1795                        }
1796                    },
1797                    mContext
1798            );
1799        } else {
1800            Log.d(this, "maybeLoadCannedSmsResponses: doing nothing");
1801        }
1802    }
1803
1804    /**
1805     * Sets speakerphone option on when call begins.
1806     */
1807    public void setStartWithSpeakerphoneOn(boolean startWithSpeakerphone) {
1808        mSpeakerphoneOn = startWithSpeakerphone;
1809    }
1810
1811    /**
1812     * Returns speakerphone option.
1813     *
1814     * @return Whether or not speakerphone should be set automatically when call begins.
1815     */
1816    public boolean getStartWithSpeakerphoneOn() {
1817        return mSpeakerphoneOn;
1818    }
1819
1820    /**
1821     * Sets a video call provider for the call.
1822     */
1823    public void setVideoProvider(IVideoProvider videoProvider) {
1824        Log.v(this, "setVideoProvider");
1825
1826        if (videoProvider != null ) {
1827            try {
1828                mVideoProviderProxy = new VideoProviderProxy(mLock, videoProvider, this);
1829            } catch (RemoteException ignored) {
1830                // Ignore RemoteException.
1831            }
1832        } else {
1833            mVideoProviderProxy = null;
1834        }
1835
1836        mVideoProvider = videoProvider;
1837
1838        for (Listener l : mListeners) {
1839            l.onVideoCallProviderChanged(Call.this);
1840        }
1841    }
1842
1843    /**
1844     * @return The {@link Connection.VideoProvider} binder.
1845     */
1846    public IVideoProvider getVideoProvider() {
1847        if (mVideoProviderProxy == null) {
1848            return null;
1849        }
1850
1851        return mVideoProviderProxy.getInterface();
1852    }
1853
1854    /**
1855     * @return The {@link VideoProviderProxy} for this call.
1856     */
1857    public VideoProviderProxy getVideoProviderProxy() {
1858        return mVideoProviderProxy;
1859    }
1860
1861    /**
1862     * The current video state for the call.
1863     * See {@link VideoProfile} for a list of valid video states.
1864     */
1865    public int getVideoState() {
1866        return mVideoState;
1867    }
1868
1869    /**
1870     * Returns the video states which were applicable over the duration of a call.
1871     * See {@link VideoProfile} for a list of valid video states.
1872     *
1873     * @return The video states applicable over the duration of the call.
1874     */
1875    public int getVideoStateHistory() {
1876        return mVideoStateHistory;
1877    }
1878
1879    /**
1880     * Determines the current video state for the call.
1881     * For an outgoing call determines the desired video state for the call.
1882     * Valid values: see {@link VideoProfile}
1883     *
1884     * @param videoState The video state for the call.
1885     */
1886    public void setVideoState(int videoState) {
1887        // Track which video states were applicable over the duration of the call.
1888        // Only track the call state when the call is active or disconnected.  This ensures we do
1889        // not include the video state when:
1890        // - Call is incoming (but not answered).
1891        // - Call it outgoing (but not answered).
1892        // We include the video state when disconnected to ensure that rejected calls reflect the
1893        // appropriate video state.
1894        if (isActive() || getState() == CallState.DISCONNECTED) {
1895            mVideoStateHistory = mVideoStateHistory | videoState;
1896        }
1897
1898        mVideoState = videoState;
1899        for (Listener l : mListeners) {
1900            l.onVideoStateChanged(this);
1901        }
1902    }
1903
1904    public boolean getIsVoipAudioMode() {
1905        return mIsVoipAudioMode;
1906    }
1907
1908    public void setIsVoipAudioMode(boolean audioModeIsVoip) {
1909        mIsVoipAudioMode = audioModeIsVoip;
1910        for (Listener l : mListeners) {
1911            l.onIsVoipAudioModeChanged(this);
1912        }
1913    }
1914
1915    public StatusHints getStatusHints() {
1916        return mStatusHints;
1917    }
1918
1919    public void setStatusHints(StatusHints statusHints) {
1920        mStatusHints = statusHints;
1921        for (Listener l : mListeners) {
1922            l.onStatusHintsChanged(this);
1923        }
1924    }
1925
1926    public boolean isUnknown() {
1927        return mCallDirection == CALL_DIRECTION_UNKNOWN;
1928    }
1929
1930    /**
1931     * Determines if this call is in a disconnecting state.
1932     *
1933     * @return {@code true} if this call is locally disconnecting.
1934     */
1935    public boolean isLocallyDisconnecting() {
1936        return mIsLocallyDisconnecting;
1937    }
1938
1939    /**
1940     * Sets whether this call is in a disconnecting state.
1941     *
1942     * @param isLocallyDisconnecting {@code true} if this call is locally disconnecting.
1943     */
1944    private void setLocallyDisconnecting(boolean isLocallyDisconnecting) {
1945        mIsLocallyDisconnecting = isLocallyDisconnecting;
1946    }
1947
1948    /**
1949     * @return user handle of user initiating the outgoing call.
1950     */
1951    public UserHandle getInitiatingUser() {
1952        return mInitiatingUser;
1953    }
1954
1955    /**
1956     * Set the user handle of user initiating the outgoing call.
1957     * @param initiatingUser
1958     */
1959    public void setInitiatingUser(UserHandle initiatingUser) {
1960        Preconditions.checkNotNull(initiatingUser);
1961        mInitiatingUser = initiatingUser;
1962    }
1963
1964    static int getStateFromConnectionState(int state) {
1965        switch (state) {
1966            case Connection.STATE_INITIALIZING:
1967                return CallState.CONNECTING;
1968            case Connection.STATE_ACTIVE:
1969                return CallState.ACTIVE;
1970            case Connection.STATE_DIALING:
1971                return CallState.DIALING;
1972            case Connection.STATE_DISCONNECTED:
1973                return CallState.DISCONNECTED;
1974            case Connection.STATE_HOLDING:
1975                return CallState.ON_HOLD;
1976            case Connection.STATE_NEW:
1977                return CallState.NEW;
1978            case Connection.STATE_RINGING:
1979                return CallState.RINGING;
1980        }
1981        return CallState.DISCONNECTED;
1982    }
1983
1984    /**
1985     * Determines if this call is in disconnected state and waiting to be destroyed.
1986     *
1987     * @return {@code true} if this call is disconected.
1988     */
1989    public boolean isDisconnected() {
1990        return (getState() == CallState.DISCONNECTED || getState() == CallState.ABORTED);
1991    }
1992
1993    /**
1994     * Determines if this call has just been created and has not been configured properly yet.
1995     *
1996     * @return {@code true} if this call is new.
1997     */
1998    public boolean isNew() {
1999        return getState() == CallState.NEW;
2000    }
2001
2002    /**
2003     * Sets the call data usage for the call.
2004     *
2005     * @param callDataUsage The new call data usage (in bytes).
2006     */
2007    public void setCallDataUsage(long callDataUsage) {
2008        mCallDataUsage = callDataUsage;
2009    }
2010
2011    /**
2012     * Returns the call data usage for the call.
2013     *
2014     * @return The call data usage (in bytes).
2015     */
2016    public long getCallDataUsage() {
2017        return mCallDataUsage;
2018    }
2019
2020    /**
2021     * Returns true if the call is outgoing and the NEW_OUTGOING_CALL ordered broadcast intent
2022     * has come back to telecom and was processed.
2023     */
2024    public boolean isNewOutgoingCallIntentBroadcastDone() {
2025        return mIsNewOutgoingCallIntentBroadcastDone;
2026    }
2027
2028    public void setNewOutgoingCallIntentBroadcastIsDone() {
2029        mIsNewOutgoingCallIntentBroadcastDone = true;
2030    }
2031
2032    /**
2033     * Determines if the call has been held by the remote party.
2034     *
2035     * @return {@code true} if the call is remotely held, {@code false} otherwise.
2036     */
2037    public boolean isRemotelyHeld() {
2038        return mIsRemotelyHeld;
2039    }
2040
2041    /**
2042     * Handles Connection events received from a {@link ConnectionService}.
2043     *
2044     * @param event The event.
2045     * @param extras The extras.
2046     */
2047    public void onConnectionEvent(String event, Bundle extras) {
2048        if (Connection.EVENT_ON_HOLD_TONE_START.equals(event)) {
2049            mIsRemotelyHeld = true;
2050            Log.event(this, Log.Events.REMOTELY_HELD);
2051            // Inform listeners of the fact that a call hold tone was received.  This will trigger
2052            // the CallAudioManager to play a tone via the InCallTonePlayer.
2053            for (Listener l : mListeners) {
2054                l.onHoldToneRequested(this);
2055            }
2056        } else if (Connection.EVENT_ON_HOLD_TONE_END.equals(event)) {
2057            mIsRemotelyHeld = false;
2058            Log.event(this, Log.Events.REMOTELY_UNHELD);
2059            for (Listener l : mListeners) {
2060                l.onHoldToneRequested(this);
2061            }
2062        } else {
2063            for (Listener l : mListeners) {
2064                l.onConnectionEvent(this, event, extras);
2065            }
2066        }
2067    }
2068
2069    /**
2070     * Determines if a {@link Call}'s capabilities bitmask indicates that video is supported either
2071     * remotely or locally.
2072     *
2073     * @param capabilities The {@link Connection} capabilities for the call.
2074     * @return {@code true} if video is supported, {@code false} otherwise.
2075     */
2076    private boolean doesCallSupportVideo(int capabilities) {
2077        return (capabilities & Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL) != 0 ||
2078                (capabilities & Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL) != 0;
2079    }
2080
2081    /**
2082     * Remove any video capabilities set on a {@link Connection} capabilities bitmask.
2083     *
2084     * @param capabilities The capabilities.
2085     * @return The bitmask with video capabilities removed.
2086     */
2087    private int removeVideoCapabilities(int capabilities) {
2088        return capabilities & ~(Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL |
2089                Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
2090    }
2091}
2092