CallsManager.java revision 4bc0245eb5c09ab48dcbb673eefdf7175ae8ac9c
1/*
2 * Copyright (C) 2013 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.content.Intent;
21import android.content.pm.PackageManager;
22import android.content.pm.ResolveInfo;
23import android.net.Uri;
24import android.os.Bundle;
25import android.os.Handler;
26import android.os.Trace;
27import android.provider.CallLog.Calls;
28import android.telecom.AudioState;
29import android.telecom.CallState;
30import android.telecom.DisconnectCause;
31import android.telecom.GatewayInfo;
32import android.telecom.ParcelableConference;
33import android.telecom.ParcelableConnection;
34import android.telecom.PhoneAccount;
35import android.telecom.PhoneAccountHandle;
36import android.telecom.PhoneCapabilities;
37import android.telecom.TelecomManager;
38import android.telecom.VideoProfile;
39import android.telephony.TelephonyManager;
40
41import com.android.internal.util.IndentingPrintWriter;
42
43import java.util.Collection;
44import java.util.Collections;
45import java.util.HashSet;
46import java.util.List;
47import java.util.Objects;
48import java.util.Set;
49import java.util.concurrent.ConcurrentHashMap;
50
51/**
52 * Singleton.
53 *
54 * NOTE: by design most APIs are package private, use the relevant adapter/s to allow
55 * access from other packages specifically refraining from passing the CallsManager instance
56 * beyond the com.android.server.telecom package boundary.
57 */
58public final class CallsManager extends Call.ListenerBase {
59
60    // TODO: Consider renaming this CallsManagerPlugin.
61    interface CallsManagerListener {
62        void onCallAdded(Call call);
63        void onCallRemoved(Call call);
64        void onCallStateChanged(Call call, int oldState, int newState);
65        void onConnectionServiceChanged(
66                Call call,
67                ConnectionServiceWrapper oldService,
68                ConnectionServiceWrapper newService);
69        void onIncomingCallAnswered(Call call);
70        void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage);
71        void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall);
72        void onAudioStateChanged(AudioState oldAudioState, AudioState newAudioState);
73        void onRingbackRequested(Call call, boolean ringback);
74        void onIsConferencedChanged(Call call);
75        void onIsVoipAudioModeChanged(Call call);
76        void onVideoStateChanged(Call call);
77        void onCanAddCallChanged(boolean canAddCall);
78    }
79
80    /**
81     * Singleton instance of the {@link CallsManager}, initialized from {@link TelecomService}.
82     */
83    private static CallsManager INSTANCE = null;
84
85    private static final String TAG = "CallsManager";
86
87    private static final int MAXIMUM_LIVE_CALLS = 1;
88    private static final int MAXIMUM_HOLD_CALLS = 1;
89    private static final int MAXIMUM_RINGING_CALLS = 1;
90    private static final int MAXIMUM_OUTGOING_CALLS = 1;
91    private static final int MAXIMUM_TOP_LEVEL_CALLS = 2;
92
93    private static final int[] OUTGOING_CALL_STATES =
94            {CallState.CONNECTING, CallState.PRE_DIAL_WAIT, CallState.DIALING};
95
96    private static final int[] LIVE_CALL_STATES =
97            {CallState.CONNECTING, CallState.PRE_DIAL_WAIT, CallState.DIALING, CallState.ACTIVE};
98
99    /**
100     * The main call repository. Keeps an instance of all live calls. New incoming and outgoing
101     * calls are added to the map and removed when the calls move to the disconnected state.
102     *
103     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
104     * load factor before resizing, 1 means we only expect a single thread to
105     * access the map so make only a single shard
106     */
107    private final Set<Call> mCalls = Collections.newSetFromMap(
108            new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
109
110    private final ConnectionServiceRepository mConnectionServiceRepository;
111    private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
112    private final InCallController mInCallController;
113    private final CallAudioManager mCallAudioManager;
114    private final Ringer mRinger;
115    // For this set initial table size to 16 because we add 13 listeners in
116    // the CallsManager constructor.
117    private final Set<CallsManagerListener> mListeners = Collections.newSetFromMap(
118            new ConcurrentHashMap<CallsManagerListener, Boolean>(16, 0.9f, 1));
119    private final HeadsetMediaButton mHeadsetMediaButton;
120    private final WiredHeadsetManager mWiredHeadsetManager;
121    private final TtyManager mTtyManager;
122    private final ProximitySensorManager mProximitySensorManager;
123    private final PhoneStateBroadcaster mPhoneStateBroadcaster;
124    private final CallLogManager mCallLogManager;
125    private final Context mContext;
126    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
127    private final MissedCallNotifier mMissedCallNotifier;
128    private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
129    private final Set<Call> mPendingCallsToDisconnect = new HashSet<>();
130    /* Handler tied to thread in which CallManager was initialized. */
131    private final Handler mHandler = new Handler();
132
133    private boolean mCanAddCall = true;
134
135    /**
136     * The call the user is currently interacting with. This is the call that should have audio
137     * focus and be visible in the in-call UI.
138     */
139    private Call mForegroundCall;
140
141    /** Singleton accessor. */
142    static CallsManager getInstance() {
143        return INSTANCE;
144    }
145
146    /**
147     * Sets the static singleton instance.
148     *
149     * @param instance The instance to set.
150     */
151    static void initialize(CallsManager instance) {
152        INSTANCE = instance;
153    }
154
155    /**
156     * Initializes the required Telecom components.
157     */
158     CallsManager(Context context, MissedCallNotifier missedCallNotifier,
159             PhoneAccountRegistrar phoneAccountRegistrar) {
160        mContext = context;
161        mPhoneAccountRegistrar = phoneAccountRegistrar;
162        mMissedCallNotifier = missedCallNotifier;
163        StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
164        mWiredHeadsetManager = new WiredHeadsetManager(context);
165        mCallAudioManager = new CallAudioManager(context, statusBarNotifier, mWiredHeadsetManager);
166        InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager);
167        mRinger = new Ringer(mCallAudioManager, this, playerFactory, context);
168        mHeadsetMediaButton = new HeadsetMediaButton(context, this);
169        mTtyManager = new TtyManager(context, mWiredHeadsetManager);
170        mProximitySensorManager = new ProximitySensorManager(context);
171        mPhoneStateBroadcaster = new PhoneStateBroadcaster();
172        mCallLogManager = new CallLogManager(context);
173        mInCallController = new InCallController(context);
174        mDtmfLocalTonePlayer = new DtmfLocalTonePlayer(context);
175        mConnectionServiceRepository = new ConnectionServiceRepository(mPhoneAccountRegistrar,
176                context);
177
178        mListeners.add(statusBarNotifier);
179        mListeners.add(mCallLogManager);
180        mListeners.add(mPhoneStateBroadcaster);
181        mListeners.add(mInCallController);
182        mListeners.add(mRinger);
183        mListeners.add(new RingbackPlayer(this, playerFactory));
184        mListeners.add(new InCallToneMonitor(playerFactory, this));
185        mListeners.add(mCallAudioManager);
186        mListeners.add(missedCallNotifier);
187        mListeners.add(mDtmfLocalTonePlayer);
188        mListeners.add(mHeadsetMediaButton);
189        mListeners.add(RespondViaSmsManager.getInstance());
190        mListeners.add(mProximitySensorManager);
191    }
192
193    @Override
194    public void onSuccessfulOutgoingCall(Call call, int callState) {
195        Log.v(this, "onSuccessfulOutgoingCall, %s", call);
196
197        setCallState(call, callState);
198        if (!mCalls.contains(call)) {
199            // Call was not added previously in startOutgoingCall due to it being a potential MMI
200            // code, so add it now.
201            addCall(call);
202        }
203
204        // The call's ConnectionService has been updated.
205        for (CallsManagerListener listener : mListeners) {
206            listener.onConnectionServiceChanged(call, null, call.getConnectionService());
207        }
208
209        markCallAsDialing(call);
210    }
211
212    @Override
213    public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {
214        Log.v(this, "onFailedOutgoingCall, call: %s", call);
215
216        markCallAsRemoved(call);
217    }
218
219    @Override
220    public void onSuccessfulIncomingCall(Call incomingCall) {
221        Log.d(this, "onSuccessfulIncomingCall");
222        setCallState(incomingCall, CallState.RINGING);
223
224        if (hasMaximumRingingCalls()) {
225            incomingCall.reject(false, null);
226            // since the call was not added to the list of calls, we have to call the missed
227            // call notifier and the call logger manually.
228            mMissedCallNotifier.showMissedCallNotification(incomingCall);
229            mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE);
230        } else {
231            addCall(incomingCall);
232        }
233    }
234
235    @Override
236    public void onFailedIncomingCall(Call call) {
237        setCallState(call, CallState.DISCONNECTED);
238        call.removeListener(this);
239    }
240
241    @Override
242    public void onSuccessfulUnknownCall(Call call, int callState) {
243        setCallState(call, callState);
244        Log.i(this, "onSuccessfulUnknownCall for call %s", call);
245        addCall(call);
246    }
247
248    @Override
249    public void onFailedUnknownCall(Call call) {
250        Log.i(this, "onFailedUnknownCall for call %s", call);
251        setCallState(call, CallState.DISCONNECTED);
252        call.removeListener(this);
253    }
254
255    @Override
256    public void onRingbackRequested(Call call, boolean ringback) {
257        for (CallsManagerListener listener : mListeners) {
258            listener.onRingbackRequested(call, ringback);
259        }
260    }
261
262    @Override
263    public void onPostDialWait(Call call, String remaining) {
264        mInCallController.onPostDialWait(call, remaining);
265    }
266
267    @Override
268    public void onParentChanged(Call call) {
269        // parent-child relationship affects which call should be foreground, so do an update.
270        updateCallsManagerState();
271        for (CallsManagerListener listener : mListeners) {
272            listener.onIsConferencedChanged(call);
273        }
274    }
275
276    @Override
277    public void onChildrenChanged(Call call) {
278        // parent-child relationship affects which call should be foreground, so do an update.
279        updateCallsManagerState();
280        for (CallsManagerListener listener : mListeners) {
281            listener.onIsConferencedChanged(call);
282        }
283    }
284
285    @Override
286    public void onIsVoipAudioModeChanged(Call call) {
287        for (CallsManagerListener listener : mListeners) {
288            listener.onIsVoipAudioModeChanged(call);
289        }
290    }
291
292    @Override
293    public void onVideoStateChanged(Call call) {
294        for (CallsManagerListener listener : mListeners) {
295            listener.onVideoStateChanged(call);
296        }
297    }
298
299    @Override
300    public boolean onCanceledViaNewOutgoingCallBroadcast(final Call call) {
301        mPendingCallsToDisconnect.add(call);
302        mHandler.postDelayed(new Runnable() {
303            @Override
304            public void run() {
305                if (mPendingCallsToDisconnect.remove(call)) {
306                    Log.i(this, "Delayed disconnection of call: %s", call);
307                    call.disconnect();
308                }
309            }
310        }, Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver()));
311
312        return true;
313    }
314
315    Collection<Call> getCalls() {
316        return Collections.unmodifiableCollection(mCalls);
317    }
318
319    Call getForegroundCall() {
320        return mForegroundCall;
321    }
322
323    Ringer getRinger() {
324        return mRinger;
325    }
326
327    InCallController getInCallController() {
328        return mInCallController;
329    }
330
331    boolean hasEmergencyCall() {
332        for (Call call : mCalls) {
333            if (call.isEmergencyCall()) {
334                return true;
335            }
336        }
337        return false;
338    }
339
340    boolean hasVideoCall() {
341        for (Call call : mCalls) {
342            if (call.getVideoState() != VideoProfile.VideoState.AUDIO_ONLY) {
343                return true;
344            }
345        }
346        return false;
347    }
348
349    AudioState getAudioState() {
350        return mCallAudioManager.getAudioState();
351    }
352
353    boolean isTtySupported() {
354        return mTtyManager.isTtySupported();
355    }
356
357    int getCurrentTtyMode() {
358        return mTtyManager.getCurrentTtyMode();
359    }
360
361    void addListener(CallsManagerListener listener) {
362        mListeners.add(listener);
363    }
364
365    void removeListener(CallsManagerListener listener) {
366        mListeners.remove(listener);
367    }
368
369    /**
370     * Starts the process to attach the call to a connection service.
371     *
372     * @param phoneAccountHandle The phone account which contains the component name of the
373     *        connection service to use for this call.
374     * @param extras The optional extras Bundle passed with the intent used for the incoming call.
375     */
376    void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
377        Log.d(this, "processIncomingCallIntent");
378        Uri handle = extras.getParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER);
379        Call call = new Call(
380                mContext,
381                mConnectionServiceRepository,
382                handle,
383                null /* gatewayInfo */,
384                null /* connectionManagerPhoneAccount */,
385                phoneAccountHandle,
386                true /* isIncoming */,
387                false /* isConference */);
388
389        call.setExtras(extras);
390        // TODO: Move this to be a part of addCall()
391        call.addListener(this);
392        call.startCreateConnection(mPhoneAccountRegistrar);
393    }
394
395    void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
396        Uri handle = extras.getParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE);
397        Log.i(this, "addNewUnknownCall with handle: %s", Log.pii(handle));
398        Call call = new Call(
399                mContext,
400                mConnectionServiceRepository,
401                handle,
402                null /* gatewayInfo */,
403                null /* connectionManagerPhoneAccount */,
404                phoneAccountHandle,
405                // Use onCreateIncomingConnection in TelephonyConnectionService, so that we attach
406                // to the existing connection instead of trying to create a new one.
407                true /* isIncoming */,
408                false /* isConference */);
409        call.setConnectTimeMillis(System.currentTimeMillis());
410        call.setIsUnknown(true);
411        call.setExtras(extras);
412        call.addListener(this);
413        call.startCreateConnection(mPhoneAccountRegistrar);
414    }
415
416    private Call getNewOutgoingCall(Uri handle) {
417        // First check to see if we can reuse any of the calls that are waiting to disconnect.
418        // See {@link Call#abort} and {@link #onCanceledViaNewOutgoingCall} for more information.
419        for (Call pendingCall : mPendingCallsToDisconnect) {
420            if (Objects.equals(pendingCall.getHandle(), handle)) {
421                mPendingCallsToDisconnect.remove(pendingCall);
422                Log.i(this, "Reusing disconnected call %s", pendingCall);
423                return pendingCall;
424            }
425        }
426
427        // Create a call with original handle. The handle may be changed when the call is attached
428        // to a connection service, but in most cases will remain the same.
429        return new Call(
430                mContext,
431                mConnectionServiceRepository,
432                handle,
433                null /* gatewayInfo */,
434                null /* connectionManagerPhoneAccount */,
435                null /* phoneAccountHandle */,
436                false /* isIncoming */,
437                false /* isConference */);
438    }
439
440    /**
441     * Kicks off the first steps to creating an outgoing call so that InCallUI can launch.
442     *
443     * @param handle Handle to connect the call with.
444     * @param phoneAccountHandle The phone account which contains the component name of the
445     *        connection service to use for this call.
446     * @param extras The optional extras Bundle passed with the intent used for the incoming call.
447     */
448    Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) {
449        Call call = getNewOutgoingCall(handle);
450
451        List<PhoneAccountHandle> accounts =
452                mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme());
453
454        Log.v(this, "startOutgoingCall found accounts = " + accounts);
455
456        // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call
457        // as if a phoneAccount was not specified (does the default behavior instead).
458        // Note: We will not attempt to dial with a requested phoneAccount if it is disabled.
459        if (phoneAccountHandle != null) {
460            if (!accounts.contains(phoneAccountHandle)) {
461                phoneAccountHandle = null;
462            }
463        }
464
465        if (phoneAccountHandle == null) {
466            // No preset account, check if default exists that supports the URI scheme for the
467            // handle.
468            PhoneAccountHandle defaultAccountHandle =
469                    mPhoneAccountRegistrar.getDefaultOutgoingPhoneAccount(
470                            handle.getScheme());
471            if (defaultAccountHandle != null) {
472                phoneAccountHandle = defaultAccountHandle;
473            }
474        }
475
476        call.setTargetPhoneAccount(phoneAccountHandle);
477
478        boolean isEmergencyCall = TelephonyUtil.shouldProcessAsEmergency(mContext,
479                call.getHandle());
480        boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle);
481
482        // Do not support any more live calls.  Our options are to move a call to hold, disconnect
483        // a call, or cancel this call altogether.
484        if (!isPotentialInCallMMICode && !makeRoomForOutgoingCall(call, isEmergencyCall)) {
485            // just cancel at this point.
486            if (mCalls.contains(call)) {
487                // This call can already exist if it is a reused call,
488                // See {@link #getNewOutgoingCall}.
489                call.disconnect();
490            }
491            return null;
492        }
493
494        boolean needsAccountSelection = phoneAccountHandle == null && accounts.size() > 1 &&
495                !isEmergencyCall;
496
497        if (needsAccountSelection) {
498            // This is the state where the user is expected to select an account
499            call.setState(CallState.PRE_DIAL_WAIT);
500            extras.putParcelableList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS, accounts);
501        } else {
502            call.setState(CallState.CONNECTING);
503        }
504
505        call.setExtras(extras);
506
507        // Do not add the call if it is a potential MMI code.
508        if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) {
509            call.addListener(this);
510        } else if (!mCalls.contains(call)) {
511            // We check if mCalls already contains the call because we could potentially be reusing
512            // a call which was previously added (See {@link #getNewOutgoingCall}).
513            addCall(call);
514        }
515
516        return call;
517    }
518
519    /**
520     * Attempts to issue/connect the specified call.
521     *
522     * @param handle Handle to connect the call with.
523     * @param gatewayInfo Optional gateway information that can be used to route the call to the
524     *        actual dialed handle via a gateway provider. May be null.
525     * @param speakerphoneOn Whether or not to turn the speakerphone on once the call connects.
526     * @param videoState The desired video state for the outgoing call.
527     */
528    void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn,
529            int videoState) {
530        if (call == null) {
531            // don't do anything if the call no longer exists
532            Log.i(this, "Canceling unknown call.");
533            return;
534        }
535
536        final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayAddress();
537
538        if (gatewayInfo == null) {
539            Log.i(this, "Creating a new outgoing call with handle: %s", Log.piiHandle(uriHandle));
540        } else {
541            Log.i(this, "Creating a new outgoing call with gateway handle: %s, original handle: %s",
542                    Log.pii(uriHandle), Log.pii(handle));
543        }
544
545        call.setHandle(uriHandle);
546        call.setGatewayInfo(gatewayInfo);
547        call.setStartWithSpeakerphoneOn(speakerphoneOn);
548        call.setVideoState(videoState);
549
550        boolean isEmergencyCall = TelephonyUtil.shouldProcessAsEmergency(mContext,
551                call.getHandle());
552        if (isEmergencyCall) {
553            // Emergency -- CreateConnectionProcessor will choose accounts automatically
554            call.setTargetPhoneAccount(null);
555        }
556
557        if (call.getTargetPhoneAccount() != null || isEmergencyCall) {
558            // If the account has been set, proceed to place the outgoing call.
559            // Otherwise the connection will be initiated when the account is set by the user.
560            call.startCreateConnection(mPhoneAccountRegistrar);
561        }
562    }
563
564    /**
565     * Attempts to start a conference call for the specified call.
566     *
567     * @param call The call to conference.
568     * @param otherCall The other call to conference with.
569     */
570    void conference(Call call, Call otherCall) {
571        call.conferenceWith(otherCall);
572    }
573
574    /**
575     * Instructs Telecom to answer the specified call. Intended to be invoked by the in-call
576     * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by
577     * the user opting to answer said call.
578     *
579     * @param call The call to answer.
580     * @param videoState The video state in which to answer the call.
581     */
582    void answerCall(Call call, int videoState) {
583        if (!mCalls.contains(call)) {
584            Log.i(this, "Request to answer a non-existent call %s", call);
585        } else {
586            // If the foreground call is not the ringing call and it is currently isActive() or
587            // STATE_DIALING, put it on hold before answering the call.
588            if (mForegroundCall != null && mForegroundCall != call &&
589                    (mForegroundCall.isActive() ||
590                     mForegroundCall.getState() == CallState.DIALING)) {
591                if (0 == (mForegroundCall.getCallCapabilities() & PhoneCapabilities.HOLD)) {
592                    // This call does not support hold.  If it is from a different connection
593                    // service, then disconnect it, otherwise allow the connection service to
594                    // figure out the right states.
595                    if (mForegroundCall.getConnectionService() != call.getConnectionService()) {
596                        mForegroundCall.disconnect();
597                    }
598                } else {
599                    Log.v(this, "Holding active/dialing call %s before answering incoming call %s.",
600                            mForegroundCall, call);
601                    mForegroundCall.hold();
602                }
603                // TODO: Wait until we get confirmation of the active call being
604                // on-hold before answering the new call.
605                // TODO: Import logic from CallManager.acceptCall()
606            }
607
608            for (CallsManagerListener listener : mListeners) {
609                listener.onIncomingCallAnswered(call);
610            }
611
612            // We do not update the UI until we get confirmation of the answer() through
613            // {@link #markCallAsActive}.
614            call.answer(videoState);
615        }
616    }
617
618    /**
619     * Instructs Telecom to reject the specified call. Intended to be invoked by the in-call
620     * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by
621     * the user opting to reject said call.
622     */
623    void rejectCall(Call call, boolean rejectWithMessage, String textMessage) {
624        if (!mCalls.contains(call)) {
625            Log.i(this, "Request to reject a non-existent call %s", call);
626        } else {
627            for (CallsManagerListener listener : mListeners) {
628                listener.onIncomingCallRejected(call, rejectWithMessage, textMessage);
629            }
630            call.reject(rejectWithMessage, textMessage);
631        }
632    }
633
634    /**
635     * Instructs Telecom to play the specified DTMF tone within the specified call.
636     *
637     * @param digit The DTMF digit to play.
638     */
639    void playDtmfTone(Call call, char digit) {
640        if (!mCalls.contains(call)) {
641            Log.i(this, "Request to play DTMF in a non-existent call %s", call);
642        } else {
643            call.playDtmfTone(digit);
644            mDtmfLocalTonePlayer.playTone(call, digit);
645        }
646    }
647
648    /**
649     * Instructs Telecom to stop the currently playing DTMF tone, if any.
650     */
651    void stopDtmfTone(Call call) {
652        if (!mCalls.contains(call)) {
653            Log.i(this, "Request to stop DTMF in a non-existent call %s", call);
654        } else {
655            call.stopDtmfTone();
656            mDtmfLocalTonePlayer.stopTone(call);
657        }
658    }
659
660    /**
661     * Instructs Telecom to continue (or not) the current post-dial DTMF string, if any.
662     */
663    void postDialContinue(Call call, boolean proceed) {
664        if (!mCalls.contains(call)) {
665            Log.i(this, "Request to continue post-dial string in a non-existent call %s", call);
666        } else {
667            call.postDialContinue(proceed);
668        }
669    }
670
671    /**
672     * Instructs Telecom to disconnect the specified call. Intended to be invoked by the
673     * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
674     * the user hitting the end-call button.
675     */
676    void disconnectCall(Call call) {
677        Log.v(this, "disconnectCall %s", call);
678
679        if (!mCalls.contains(call)) {
680            Log.w(this, "Unknown call (%s) asked to disconnect", call);
681        } else {
682            mLocallyDisconnectingCalls.add(call);
683            call.disconnect();
684        }
685    }
686
687    /**
688     * Instructs Telecom to disconnect all calls.
689     */
690    void disconnectAllCalls() {
691        Log.v(this, "disconnectAllCalls");
692
693        for (Call call : mCalls) {
694            disconnectCall(call);
695        }
696    }
697
698
699    /**
700     * Instructs Telecom to put the specified call on hold. Intended to be invoked by the
701     * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
702     * the user hitting the hold button during an active call.
703     */
704    void holdCall(Call call) {
705        if (!mCalls.contains(call)) {
706            Log.w(this, "Unknown call (%s) asked to be put on hold", call);
707        } else {
708            Log.d(this, "Putting call on hold: (%s)", call);
709            call.hold();
710        }
711    }
712
713    /**
714     * Instructs Telecom to release the specified call from hold. Intended to be invoked by
715     * the in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered
716     * by the user hitting the hold button during a held call.
717     */
718    void unholdCall(Call call) {
719        if (!mCalls.contains(call)) {
720            Log.w(this, "Unknown call (%s) asked to be removed from hold", call);
721        } else {
722            Log.d(this, "unholding call: (%s)", call);
723            for (Call c : mCalls) {
724                if (c != null && c.isAlive() && c != call) {
725                    c.hold();
726                }
727            }
728            call.unhold();
729        }
730    }
731
732    /** Called by the in-call UI to change the mute state. */
733    void mute(boolean shouldMute) {
734        mCallAudioManager.mute(shouldMute);
735    }
736
737    /**
738      * Called by the in-call UI to change the audio route, for example to change from earpiece to
739      * speaker phone.
740      */
741    void setAudioRoute(int route) {
742        mCallAudioManager.setAudioRoute(route);
743    }
744
745    /** Called by the in-call UI to turn the proximity sensor on. */
746    void turnOnProximitySensor() {
747        mProximitySensorManager.turnOn();
748    }
749
750    /**
751     * Called by the in-call UI to turn the proximity sensor off.
752     * @param screenOnImmediately If true, the screen will be turned on immediately. Otherwise,
753     *        the screen will be kept off until the proximity sensor goes negative.
754     */
755    void turnOffProximitySensor(boolean screenOnImmediately) {
756        mProximitySensorManager.turnOff(screenOnImmediately);
757    }
758
759    void phoneAccountSelected(Call call, PhoneAccountHandle account, boolean setDefault) {
760        if (!mCalls.contains(call)) {
761            Log.i(this, "Attempted to add account to unknown call %s", call);
762        } else {
763            // TODO: There is an odd race condition here. Since NewOutgoingCallIntentBroadcaster and
764            // the PRE_DIAL_WAIT sequence run in parallel, if the user selects an account before the
765            // NEW_OUTGOING_CALL sequence finishes, we'll start the call immediately without
766            // respecting a rewritten number or a canceled number. This is unlikely since
767            // NEW_OUTGOING_CALL sequence, in practice, runs a lot faster than the user selecting
768            // a phone account from the in-call UI.
769            call.setTargetPhoneAccount(account);
770
771            // Note: emergency calls never go through account selection dialog so they never
772            // arrive here.
773            if (makeRoomForOutgoingCall(call, false /* isEmergencyCall */)) {
774                call.startCreateConnection(mPhoneAccountRegistrar);
775            } else {
776                call.disconnect();
777            }
778
779            if (setDefault) {
780                mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(account);
781            }
782        }
783    }
784
785    /** Called when the audio state changes. */
786    void onAudioStateChanged(AudioState oldAudioState, AudioState newAudioState) {
787        Log.v(this, "onAudioStateChanged, audioState: %s -> %s", oldAudioState, newAudioState);
788        for (CallsManagerListener listener : mListeners) {
789            listener.onAudioStateChanged(oldAudioState, newAudioState);
790        }
791    }
792
793    void markCallAsRinging(Call call) {
794        setCallState(call, CallState.RINGING);
795    }
796
797    void markCallAsDialing(Call call) {
798        setCallState(call, CallState.DIALING);
799    }
800
801    void markCallAsActive(Call call) {
802        if (call.getConnectTimeMillis() == 0) {
803            call.setConnectTimeMillis(System.currentTimeMillis());
804        }
805        setCallState(call, CallState.ACTIVE);
806
807        if (call.getStartWithSpeakerphoneOn()) {
808            setAudioRoute(AudioState.ROUTE_SPEAKER);
809        }
810    }
811
812    void markCallAsOnHold(Call call) {
813        setCallState(call, CallState.ON_HOLD);
814    }
815
816    /**
817     * Marks the specified call as STATE_DISCONNECTED and notifies the in-call app. If this was the
818     * last live call, then also disconnect from the in-call controller.
819     *
820     * @param disconnectCause The disconnect cause, see {@link android.telecomm.DisconnectCause}.
821     */
822    void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
823        call.setDisconnectCause(disconnectCause);
824        setCallState(call, CallState.DISCONNECTED);
825    }
826
827    /**
828     * Removes an existing disconnected call, and notifies the in-call app.
829     */
830    void markCallAsRemoved(Call call) {
831        removeCall(call);
832        if (mLocallyDisconnectingCalls.contains(call)) {
833            mLocallyDisconnectingCalls.remove(call);
834            if (mForegroundCall != null && mForegroundCall.getState() == CallState.ON_HOLD) {
835                mForegroundCall.unhold();
836            }
837        }
838    }
839
840    /**
841     * Cleans up any calls currently associated with the specified connection service when the
842     * service binder disconnects unexpectedly.
843     *
844     * @param service The connection service that disconnected.
845     */
846    void handleConnectionServiceDeath(ConnectionServiceWrapper service) {
847        if (service != null) {
848            for (Call call : mCalls) {
849                if (call.getConnectionService() == service) {
850                    if (call.getState() != CallState.DISCONNECTED) {
851                        markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR));
852                    }
853                    markCallAsRemoved(call);
854                }
855            }
856        }
857    }
858
859    boolean hasAnyCalls() {
860        return !mCalls.isEmpty();
861    }
862
863    boolean hasActiveOrHoldingCall() {
864        return getFirstCallWithState(CallState.ACTIVE, CallState.ON_HOLD) != null;
865    }
866
867    boolean hasRingingCall() {
868        return getFirstCallWithState(CallState.RINGING) != null;
869    }
870
871    boolean onMediaButton(int type) {
872        if (hasAnyCalls()) {
873            if (HeadsetMediaButton.SHORT_PRESS == type) {
874                Call ringingCall = getFirstCallWithState(CallState.RINGING);
875                if (ringingCall == null) {
876                    mCallAudioManager.toggleMute();
877                    return true;
878                } else {
879                    ringingCall.answer(ringingCall.getVideoState());
880                    return true;
881                }
882            } else if (HeadsetMediaButton.LONG_PRESS == type) {
883                Log.d(this, "handleHeadsetHook: longpress -> hangup");
884                Call callToHangup = getFirstCallWithState(
885                        CallState.RINGING, CallState.DIALING, CallState.ACTIVE, CallState.ON_HOLD);
886                if (callToHangup != null) {
887                    callToHangup.disconnect();
888                    return true;
889                }
890            }
891        }
892        return false;
893    }
894
895    /**
896     * Returns true if telecom supports adding another top-level call.
897     */
898    boolean canAddCall() {
899        int count = 0;
900        for (Call call : mCalls) {
901            if (call.isEmergencyCall()) {
902                // We never support add call if one of the calls is an emergency call.
903                return false;
904            } else if (call.getParentCall() == null) {
905                count++;
906            }
907
908            // We do not check states for canAddCall. We treat disconnected calls the same
909            // and wait until they are removed instead. If we didn't count disconnected calls,
910            // we could put InCallServices into a state where they are showing two calls but
911            // also support add-call. Technically it's right, but overall looks better (UI-wise)
912            // and acts better if we wait until the call is removed.
913            if (count >= MAXIMUM_TOP_LEVEL_CALLS) {
914                return false;
915            }
916        }
917        return true;
918    }
919
920    Call getRingingCall() {
921        return getFirstCallWithState(CallState.RINGING);
922    }
923
924    Call getActiveCall() {
925        return getFirstCallWithState(CallState.ACTIVE);
926    }
927
928    Call getDialingCall() {
929        return getFirstCallWithState(CallState.DIALING);
930    }
931
932    Call getHeldCall() {
933        return getFirstCallWithState(CallState.ON_HOLD);
934    }
935
936    int getNumHeldCalls() {
937        int count = 0;
938        for (Call call : mCalls) {
939            if (call.getParentCall() == null && call.getState() == CallState.ON_HOLD) {
940                count++;
941            }
942        }
943        return count;
944    }
945
946    Call getFirstCallWithState(int... states) {
947        return getFirstCallWithState(null, states);
948    }
949
950    /**
951     * Returns the first call that it finds with the given states. The states are treated as having
952     * priority order so that any call with the first state will be returned before any call with
953     * states listed later in the parameter list.
954     *
955     * @param callToSkip Call that this method should skip while searching
956     */
957    Call getFirstCallWithState(Call callToSkip, int... states) {
958        for (int currentState : states) {
959            // check the foreground first
960            if (mForegroundCall != null && mForegroundCall.getState() == currentState) {
961                return mForegroundCall;
962            }
963
964            for (Call call : mCalls) {
965                if (Objects.equals(callToSkip, call)) {
966                    continue;
967                }
968
969                // Only operate on top-level calls
970                if (call.getParentCall() != null) {
971                    continue;
972                }
973
974                if (currentState == call.getState()) {
975                    return call;
976                }
977            }
978        }
979        return null;
980    }
981
982    Call createConferenceCall(
983            PhoneAccountHandle phoneAccount,
984            ParcelableConference parcelableConference) {
985        Call call = new Call(
986                mContext,
987                mConnectionServiceRepository,
988                null /* handle */,
989                null /* gatewayInfo */,
990                null /* connectionManagerPhoneAccount */,
991                phoneAccount,
992                false /* isIncoming */,
993                true /* isConference */);
994
995        setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()));
996        if (call.getState() == CallState.ACTIVE) {
997            call.setConnectTimeMillis(System.currentTimeMillis());
998        }
999        call.setCallCapabilities(parcelableConference.getCapabilities());
1000
1001        // TODO: Move this to be a part of addCall()
1002        call.addListener(this);
1003        addCall(call);
1004        return call;
1005    }
1006
1007    /**
1008     * @return the call state currently tracked by {@link PhoneStateBroadcaster}
1009     */
1010    int getCallState() {
1011        return mPhoneStateBroadcaster.getCallState();
1012    }
1013
1014    /**
1015     * Retrieves the {@link PhoneAccountRegistrar}.
1016     *
1017     * @return The {@link PhoneAccountRegistrar}.
1018     */
1019    PhoneAccountRegistrar getPhoneAccountRegistrar() {
1020        return mPhoneAccountRegistrar;
1021    }
1022
1023    /**
1024     * Retrieves the {@link MissedCallNotifier}
1025     * @return The {@link MissedCallNotifier}.
1026     */
1027    MissedCallNotifier getMissedCallNotifier() {
1028        return mMissedCallNotifier;
1029    }
1030
1031    /**
1032     * Adds the specified call to the main list of live calls.
1033     *
1034     * @param call The call to add.
1035     */
1036    private void addCall(Call call) {
1037        Trace.beginSection("addCall");
1038        Log.v(this, "addCall(%s)", call);
1039        call.addListener(this);
1040        mCalls.add(call);
1041
1042        // TODO: Update mForegroundCall prior to invoking
1043        // onCallAdded for calls which immediately take the foreground (like the first call).
1044        for (CallsManagerListener listener : mListeners) {
1045            if (Log.SYSTRACE_DEBUG) {
1046                Trace.beginSection(listener.getClass().toString() + " addCall");
1047            }
1048            listener.onCallAdded(call);
1049            if (Log.SYSTRACE_DEBUG) {
1050                Trace.endSection();
1051            }
1052        }
1053        updateCallsManagerState();
1054        Trace.endSection();
1055    }
1056
1057    private void removeCall(Call call) {
1058        Trace.beginSection("removeCall");
1059        Log.v(this, "removeCall(%s)", call);
1060
1061        call.setParentCall(null);  // need to clean up parent relationship before destroying.
1062        call.removeListener(this);
1063        call.clearConnectionService();
1064
1065        boolean shouldNotify = false;
1066        if (mCalls.contains(call)) {
1067            mCalls.remove(call);
1068            shouldNotify = true;
1069        }
1070
1071        // Only broadcast changes for calls that are being tracked.
1072        if (shouldNotify) {
1073            for (CallsManagerListener listener : mListeners) {
1074                if (Log.SYSTRACE_DEBUG) {
1075                    Trace.beginSection(listener.getClass().toString() + " onCallRemoved");
1076                }
1077                listener.onCallRemoved(call);
1078                if (Log.SYSTRACE_DEBUG) {
1079                    Trace.endSection();
1080                }
1081            }
1082            updateCallsManagerState();
1083        }
1084        Trace.endSection();
1085    }
1086
1087    /**
1088     * Sets the specified state on the specified call.
1089     *
1090     * @param call The call.
1091     * @param newState The new state of the call.
1092     */
1093    private void setCallState(Call call, int newState) {
1094        if (call == null) {
1095            return;
1096        }
1097        int oldState = call.getState();
1098        Log.i(this, "setCallState %s -> %s, call: %s", CallState.toString(oldState),
1099                CallState.toString(newState), call);
1100        if (newState != oldState) {
1101            // Unfortunately, in the telephony world the radio is king. So if the call notifies
1102            // us that the call is in a particular state, we allow it even if it doesn't make
1103            // sense (e.g., STATE_ACTIVE -> STATE_RINGING).
1104            // TODO: Consider putting a stop to the above and turning CallState
1105            // into a well-defined state machine.
1106            // TODO: Define expected state transitions here, and log when an
1107            // unexpected transition occurs.
1108            call.setState(newState);
1109
1110            Trace.beginSection("onCallStateChanged");
1111            // Only broadcast state change for calls that are being tracked.
1112            if (mCalls.contains(call)) {
1113                for (CallsManagerListener listener : mListeners) {
1114                    if (Log.SYSTRACE_DEBUG) {
1115                        Trace.beginSection(listener.getClass().toString() + " onCallStateChanged");
1116                    }
1117                    listener.onCallStateChanged(call, oldState, newState);
1118                    if (Log.SYSTRACE_DEBUG) {
1119                        Trace.endSection();
1120                    }
1121                }
1122                updateCallsManagerState();
1123            }
1124            Trace.endSection();
1125        }
1126    }
1127
1128    /**
1129     * Checks which call should be visible to the user and have audio focus.
1130     */
1131    private void updateForegroundCall() {
1132        Trace.beginSection("updateForegroundCall");
1133        Call newForegroundCall = null;
1134        for (Call call : mCalls) {
1135            // TODO: Foreground-ness needs to be explicitly set. No call, regardless
1136            // of its state will be foreground by default and instead the connection service should
1137            // be notified when its calls enter and exit foreground state. Foreground will mean that
1138            // the call should play audio and listen to microphone if it wants.
1139
1140            // Only top-level calls can be in foreground
1141            if (call.getParentCall() != null) {
1142                continue;
1143            }
1144
1145            // Active calls have priority.
1146            if (call.isActive()) {
1147                newForegroundCall = call;
1148                break;
1149            }
1150
1151            if (call.isAlive() || call.getState() == CallState.RINGING) {
1152                newForegroundCall = call;
1153                // Don't break in case there's an active call that has priority.
1154            }
1155        }
1156
1157        if (newForegroundCall != mForegroundCall) {
1158            Log.v(this, "Updating foreground call, %s -> %s.", mForegroundCall, newForegroundCall);
1159            Call oldForegroundCall = mForegroundCall;
1160            mForegroundCall = newForegroundCall;
1161
1162            for (CallsManagerListener listener : mListeners) {
1163                if (Log.SYSTRACE_DEBUG) {
1164                    Trace.beginSection(listener.getClass().toString() + " updateForegroundCall");
1165                }
1166                listener.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
1167                if (Log.SYSTRACE_DEBUG) {
1168                    Trace.endSection();
1169                }
1170            }
1171        }
1172        Trace.endSection();
1173    }
1174
1175    private void updateCanAddCall() {
1176        boolean newCanAddCall = canAddCall();
1177        if (newCanAddCall != mCanAddCall) {
1178            mCanAddCall = newCanAddCall;
1179            for (CallsManagerListener listener : mListeners) {
1180                if (Log.SYSTRACE_DEBUG) {
1181                    Trace.beginSection(listener.getClass().toString() + " updateCanAddCall");
1182                }
1183                listener.onCanAddCallChanged(mCanAddCall);
1184                if (Log.SYSTRACE_DEBUG) {
1185                    Trace.endSection();
1186                }
1187            }
1188        }
1189    }
1190
1191    private void updateCallsManagerState() {
1192        updateForegroundCall();
1193        updateCanAddCall();
1194    }
1195
1196    private boolean isPotentialMMICode(Uri handle) {
1197        return (handle != null && handle.getSchemeSpecificPart() != null
1198                && handle.getSchemeSpecificPart().contains("#"));
1199    }
1200
1201    /**
1202     * Determines if a dialed number is potentially an In-Call MMI code.  In-Call MMI codes are
1203     * MMI codes which can be dialed when one or more calls are in progress.
1204     * <P>
1205     * Checks for numbers formatted similar to the MMI codes defined in:
1206     * {@link com.android.internal.telephony.gsm.GSMPhone#handleInCallMmiCommands(String)}
1207     * and
1208     * {@link com.android.internal.telephony.imsphone.ImsPhone#handleInCallMmiCommands(String)}
1209     *
1210     * @param handle The URI to call.
1211     * @return {@code True} if the URI represents a number which could be an in-call MMI code.
1212     */
1213    private boolean isPotentialInCallMMICode(Uri handle) {
1214        if (handle != null && handle.getSchemeSpecificPart() != null &&
1215                handle.getScheme().equals(PhoneAccount.SCHEME_TEL)) {
1216
1217            String dialedNumber = handle.getSchemeSpecificPart();
1218            return (dialedNumber.equals("0") ||
1219                    (dialedNumber.startsWith("1") && dialedNumber.length() <= 2) ||
1220                    (dialedNumber.startsWith("2") && dialedNumber.length() <= 2) ||
1221                    dialedNumber.equals("3") ||
1222                    dialedNumber.equals("4") ||
1223                    dialedNumber.equals("5"));
1224        }
1225        return false;
1226    }
1227
1228    private int getNumCallsWithState(int... states) {
1229        int count = 0;
1230        for (int state : states) {
1231            for (Call call : mCalls) {
1232                if (call.getParentCall() == null && call.getState() == state) {
1233                    count++;
1234                }
1235            }
1236        }
1237        return count;
1238    }
1239
1240    private boolean hasMaximumLiveCalls() {
1241        return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(LIVE_CALL_STATES);
1242    }
1243
1244    private boolean hasMaximumHoldingCalls() {
1245        return MAXIMUM_HOLD_CALLS <= getNumCallsWithState(CallState.ON_HOLD);
1246    }
1247
1248    private boolean hasMaximumRingingCalls() {
1249        return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(CallState.RINGING);
1250    }
1251
1252    private boolean hasMaximumOutgoingCalls() {
1253        return MAXIMUM_OUTGOING_CALLS <= getNumCallsWithState(OUTGOING_CALL_STATES);
1254    }
1255
1256    private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
1257        if (hasMaximumLiveCalls()) {
1258            // NOTE: If the amount of live calls changes beyond 1, this logic will probably
1259            // have to change.
1260            Call liveCall = getFirstCallWithState(call, LIVE_CALL_STATES);
1261
1262            if (call == liveCall) {
1263                // If the call is already the foreground call, then we are golden.
1264                // This can happen after the user selects an account in the PRE_DIAL_WAIT
1265                // state since the call was already populated into the list.
1266                return true;
1267            }
1268
1269            if (hasMaximumOutgoingCalls()) {
1270                // Disconnect the current outgoing call if it's not an emergency call. If the user
1271                // tries to make two outgoing calls to different emergency call numbers, we will try
1272                // to connect the first outgoing call.
1273                if (isEmergency) {
1274                    Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES);
1275                    if (!outgoingCall.isEmergencyCall()) {
1276                        outgoingCall.disconnect();
1277                        return true;
1278                    }
1279                }
1280                return false;
1281            }
1282
1283            if (hasMaximumHoldingCalls()) {
1284                // There is no more room for any more calls, unless it's an emergency.
1285                if (isEmergency) {
1286                    // Kill the current active call, this is easier then trying to disconnect a
1287                    // holding call and hold an active call.
1288                    liveCall.disconnect();
1289                    return true;
1290                }
1291                return false;  // No more room!
1292            }
1293
1294            // We have room for at least one more holding call at this point.
1295
1296            // First thing, if we are trying to make a call with the same phone account as the live
1297            // call, then allow it so that the connection service can make its own decision about
1298            // how to handle the new call relative to the current one.
1299            if (Objects.equals(liveCall.getTargetPhoneAccount(), call.getTargetPhoneAccount())) {
1300                return true;
1301            } else if (call.getTargetPhoneAccount() == null) {
1302                // Without a phone account, we can't say reliably that the call will fail.
1303                // If the user chooses the same phone account as the live call, then it's
1304                // still possible that the call can be made (like with CDMA calls not supporting
1305                // hold but they still support adding a call by going immediately into conference
1306                // mode). Return true here and we'll run this code again after user chooses an
1307                // account.
1308                return true;
1309            }
1310
1311            // Try to hold the live call before attempting the new outgoing call.
1312            if (liveCall.can(PhoneCapabilities.HOLD)) {
1313                liveCall.hold();
1314                return true;
1315            }
1316
1317            // The live call cannot be held so we're out of luck here.  There's no room.
1318            return false;
1319        }
1320        return true;
1321    }
1322
1323    /**
1324     * Creates a new call for an existing connection.
1325     *
1326     * @param callId The id of the new call.
1327     * @param connection The connection information.
1328     * @return The new call.
1329     */
1330    Call createCallForExistingConnection(String callId, ParcelableConnection connection) {
1331        Call call = new Call(
1332                mContext,
1333                mConnectionServiceRepository,
1334                connection.getHandle() /* handle */,
1335                null /* gatewayInfo */,
1336                null /* connectionManagerPhoneAccount */,
1337                connection.getPhoneAccount(), /* targetPhoneAccountHandle */
1338                false /* isIncoming */,
1339                false /* isConference */);
1340
1341        setCallState(call, Call.getStateFromConnectionState(connection.getState()));
1342        call.setConnectTimeMillis(System.currentTimeMillis());
1343        call.setCallCapabilities(connection.getCapabilities());
1344        call.setCallerDisplayName(connection.getCallerDisplayName(),
1345                connection.getCallerDisplayNamePresentation());
1346
1347        call.addListener(this);
1348        addCall(call);
1349
1350        return call;
1351    }
1352
1353    /**
1354     * Dumps the state of the {@link CallsManager}.
1355     *
1356     * @param pw The {@code IndentingPrintWriter} to write the state to.
1357     */
1358    public void dump(IndentingPrintWriter pw) {
1359        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1360        if (mCalls != null) {
1361            pw.println("mCalls: ");
1362            pw.increaseIndent();
1363            for (Call call : mCalls) {
1364                pw.println(call);
1365            }
1366            pw.decreaseIndent();
1367        }
1368        pw.println("mForegroundCall: " + (mForegroundCall == null ? "none" : mForegroundCall));
1369
1370        if (mCallAudioManager != null) {
1371            pw.println("mCallAudioManager:");
1372            pw.increaseIndent();
1373            mCallAudioManager.dump(pw);
1374            pw.decreaseIndent();
1375        }
1376
1377        if (mTtyManager != null) {
1378            pw.println("mTtyManager:");
1379            pw.increaseIndent();
1380            mTtyManager.dump(pw);
1381            pw.decreaseIndent();
1382        }
1383
1384        if (mInCallController != null) {
1385            pw.println("mInCallController:");
1386            pw.increaseIndent();
1387            mInCallController.dump(pw);
1388            pw.decreaseIndent();
1389        }
1390
1391        if (mConnectionServiceRepository != null) {
1392            pw.println("mConnectionServiceRepository:");
1393            pw.increaseIndent();
1394            mConnectionServiceRepository.dump(pw);
1395            pw.decreaseIndent();
1396        }
1397    }
1398}
1399