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