InCallController.java revision 8d5d9ddc66b55b6906364ab3c0e244dab4d58f13
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.Manifest;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.ServiceConnection;
24import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
26import android.content.pm.ServiceInfo;
27import android.content.res.Resources;
28import android.net.Uri;
29import android.os.IBinder;
30import android.os.RemoteException;
31import android.os.Trace;
32import android.os.UserHandle;
33import android.telecom.AudioState;
34import android.telecom.CallProperties;
35import android.telecom.CallState;
36import android.telecom.Connection;
37import android.telecom.InCallService;
38import android.telecom.ParcelableCall;
39import android.telecom.TelecomManager;
40import android.util.ArrayMap;
41
42// TODO: Needed for move to system service: import com.android.internal.R;
43import com.android.internal.telecom.IInCallService;
44import com.android.internal.util.IndentingPrintWriter;
45
46import java.util.ArrayList;
47import java.util.Collection;
48import java.util.Iterator;
49import java.util.List;
50import java.util.Map;
51import java.util.concurrent.ConcurrentHashMap;
52
53/**
54 * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
55 * can send updates to the in-call app. This class is created and owned by CallsManager and retains
56 * a binding to the {@link IInCallService} (implemented by the in-call app).
57 */
58public final class InCallController extends CallsManagerListenerBase {
59    /**
60     * Used to bind to the in-call app and triggers the start of communication between
61     * this class and in-call app.
62     */
63    private class InCallServiceConnection implements ServiceConnection {
64        /** {@inheritDoc} */
65        @Override public void onServiceConnected(ComponentName name, IBinder service) {
66            Log.d(this, "onServiceConnected: %s", name);
67            onConnected(name, service);
68        }
69
70        /** {@inheritDoc} */
71        @Override public void onServiceDisconnected(ComponentName name) {
72            Log.d(this, "onDisconnected: %s", name);
73            onDisconnected(name);
74        }
75    }
76
77    private final Call.Listener mCallListener = new Call.ListenerBase() {
78        @Override
79        public void onConnectionCapabilitiesChanged(Call call) {
80            updateCall(call);
81        }
82
83        @Override
84        public void onCannedSmsResponsesLoaded(Call call) {
85            updateCall(call);
86        }
87
88        @Override
89        public void onVideoCallProviderChanged(Call call) {
90            updateCall(call);
91        }
92
93        @Override
94        public void onStatusHintsChanged(Call call) {
95            updateCall(call);
96        }
97
98        @Override
99        public void onHandleChanged(Call call) {
100            updateCall(call);
101        }
102
103        @Override
104        public void onCallerDisplayNameChanged(Call call) {
105            updateCall(call);
106        }
107
108        @Override
109        public void onVideoStateChanged(Call call) {
110            updateCall(call);
111        }
112
113        @Override
114        public void onTargetPhoneAccountChanged(Call call) {
115            updateCall(call);
116        }
117
118        @Override
119        public void onConferenceableCallsChanged(Call call) {
120            updateCall(call);
121        }
122    };
123
124    /**
125     * Maintains a binding connection to the in-call app(s).
126     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
127     * load factor before resizing, 1 means we only expect a single thread to
128     * access the map so make only a single shard
129     */
130    private final Map<ComponentName, InCallServiceConnection> mServiceConnections =
131            new ConcurrentHashMap<ComponentName, InCallServiceConnection>(8, 0.9f, 1);
132
133    /** The in-call app implementations, see {@link IInCallService}. */
134    private final Map<ComponentName, IInCallService> mInCallServices = new ArrayMap<>();
135
136    private final CallIdMapper mCallIdMapper = new CallIdMapper("InCall");
137
138    /** The {@link ComponentName} of the default InCall UI. */
139    private final ComponentName mInCallComponentName;
140
141    private final Context mContext;
142    private final TelecomSystem.SyncRoot mLock;
143    private final CallsManager mCallsManager;
144
145    public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager) {
146        mContext = context;
147        mLock = lock;
148        mCallsManager = callsManager;
149        Resources resources = mContext.getResources();
150
151        mInCallComponentName = new ComponentName(
152                resources.getString(R.string.ui_default_package),
153                resources.getString(R.string.incall_default_class));
154    }
155
156    @Override
157    public void onCallAdded(Call call) {
158        if (mInCallServices.isEmpty()) {
159            bind(call);
160        } else {
161            Log.i(this, "onCallAdded: %s", call);
162            // Track the call if we don't already know about it.
163            addCall(call);
164
165            for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) {
166                ComponentName componentName = entry.getKey();
167                IInCallService inCallService = entry.getValue();
168
169                ParcelableCall parcelableCall = toParcelableCall(call,
170                        componentName.equals(mInCallComponentName) /* includeVideoProvider */);
171                try {
172                    inCallService.addCall(parcelableCall);
173                } catch (RemoteException ignored) {
174                }
175            }
176        }
177    }
178
179    @Override
180    public void onCallRemoved(Call call) {
181        Log.i(this, "onCallRemoved: %s", call);
182        if (mCallsManager.getCalls().isEmpty()) {
183            // TODO: Wait for all messages to be delivered to the service before unbinding.
184            unbind();
185        }
186        call.removeListener(mCallListener);
187        mCallIdMapper.removeCall(call);
188    }
189
190    @Override
191    public void onCallStateChanged(Call call, int oldState, int newState) {
192        updateCall(call);
193    }
194
195    @Override
196    public void onConnectionServiceChanged(
197            Call call,
198            ConnectionServiceWrapper oldService,
199            ConnectionServiceWrapper newService) {
200        updateCall(call);
201    }
202
203    @Override
204    public void onAudioStateChanged(AudioState oldAudioState, AudioState newAudioState) {
205        if (!mInCallServices.isEmpty()) {
206            Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldAudioState,
207                    newAudioState);
208            for (IInCallService inCallService : mInCallServices.values()) {
209                try {
210                    inCallService.onAudioStateChanged(newAudioState);
211                } catch (RemoteException ignored) {
212                }
213            }
214        }
215    }
216
217    @Override
218    public void onCanAddCallChanged(boolean canAddCall) {
219        if (!mInCallServices.isEmpty()) {
220            Log.i(this, "onCanAddCallChanged : %b", canAddCall);
221            for (IInCallService inCallService : mInCallServices.values()) {
222                try {
223                    inCallService.onCanAddCallChanged(canAddCall);
224                } catch (RemoteException ignored) {
225                }
226            }
227        }
228    }
229
230    void onPostDialWait(Call call, String remaining) {
231        if (!mInCallServices.isEmpty()) {
232            Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
233            for (IInCallService inCallService : mInCallServices.values()) {
234                try {
235                    inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
236                } catch (RemoteException ignored) {
237                }
238            }
239        }
240    }
241
242    @Override
243    public void onIsConferencedChanged(Call call) {
244        Log.d(this, "onIsConferencedChanged %s", call);
245        updateCall(call);
246    }
247
248    void bringToForeground(boolean showDialpad) {
249        if (!mInCallServices.isEmpty()) {
250            for (IInCallService inCallService : mInCallServices.values()) {
251                try {
252                    inCallService.bringToForeground(showDialpad);
253                } catch (RemoteException ignored) {
254                }
255            }
256        } else {
257            Log.w(this, "Asking to bring unbound in-call UI to foreground.");
258        }
259    }
260
261    /**
262     * Unbinds an existing bound connection to the in-call app.
263     */
264    private void unbind() {
265        Iterator<Map.Entry<ComponentName, InCallServiceConnection>> iterator =
266            mServiceConnections.entrySet().iterator();
267        while (iterator.hasNext()) {
268            Log.i(this, "Unbinding from InCallService %s");
269            mContext.unbindService(iterator.next().getValue());
270            iterator.remove();
271        }
272        mInCallServices.clear();
273    }
274
275    /**
276     * Binds to the in-call app if not already connected by binding directly to the saved
277     * component name of the {@link IInCallService} implementation.
278     *
279     * @param call The newly added call that triggered the binding to the in-call services.
280     */
281    private void bind(Call call) {
282        if (mInCallServices.isEmpty()) {
283            PackageManager packageManager = mContext.getPackageManager();
284            Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
285
286            for (ResolveInfo entry : packageManager.queryIntentServices(serviceIntent, 0)) {
287                ServiceInfo serviceInfo = entry.serviceInfo;
288                if (serviceInfo != null) {
289                    boolean hasServiceBindPermission = serviceInfo.permission != null &&
290                            serviceInfo.permission.equals(
291                                    Manifest.permission.BIND_INCALL_SERVICE);
292                    boolean hasControlInCallPermission = packageManager.checkPermission(
293                            Manifest.permission.CONTROL_INCALL_EXPERIENCE,
294                            serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;
295
296                    if (!hasServiceBindPermission) {
297                        Log.w(this, "InCallService does not have BIND_INCALL_SERVICE permission: " +
298                                serviceInfo.packageName);
299                        continue;
300                    }
301
302                    if (!hasControlInCallPermission) {
303                        Log.w(this,
304                                "InCall UI does not have CONTROL_INCALL_EXPERIENCE permission: " +
305                                        serviceInfo.packageName);
306                        continue;
307                    }
308
309                    InCallServiceConnection inCallServiceConnection = new InCallServiceConnection();
310                    ComponentName componentName = new ComponentName(serviceInfo.packageName,
311                            serviceInfo.name);
312
313                    Log.i(this, "Attempting to bind to InCall %s, is dupe? %b ",
314                            serviceInfo.packageName,
315                            mServiceConnections.containsKey(componentName));
316
317                    if (!mServiceConnections.containsKey(componentName)) {
318                        Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
319                        intent.setComponent(componentName);
320
321                        final int bindFlags;
322                        if (mInCallComponentName.equals(componentName)) {
323                            bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT;
324                            if (!call.isIncoming()) {
325                                intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,
326                                        call.getExtras());
327                                intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
328                                        call.getTargetPhoneAccount());
329                            }
330                        } else {
331                            bindFlags = Context.BIND_AUTO_CREATE;
332                        }
333
334                        if (mContext.bindServiceAsUser(intent, inCallServiceConnection, bindFlags,
335                                UserHandle.CURRENT)) {
336                            mServiceConnections.put(componentName, inCallServiceConnection);
337                        }
338                    }
339                }
340            }
341        }
342    }
343
344    /**
345     * Persists the {@link IInCallService} instance and starts the communication between
346     * this class and in-call app by sending the first update to in-call app. This method is
347     * called after a successful binding connection is established.
348     *
349     * @param componentName The service {@link ComponentName}.
350     * @param service The {@link IInCallService} implementation.
351     */
352    private void onConnected(ComponentName componentName, IBinder service) {
353        Trace.beginSection("onConnected: " + componentName);
354        Log.i(this, "onConnected to %s", componentName);
355
356        IInCallService inCallService = IInCallService.Stub.asInterface(service);
357
358        try {
359            inCallService.setInCallAdapter(
360                    new InCallAdapter(
361                            mCallsManager,
362                            mCallIdMapper,
363                            mLock));
364            mInCallServices.put(componentName, inCallService);
365        } catch (RemoteException e) {
366            Log.e(this, e, "Failed to set the in-call adapter.");
367            Trace.endSection();
368            return;
369        }
370
371        // Upon successful connection, send the state of the world to the service.
372        Collection<Call> calls = mCallsManager.getCalls();
373        if (!calls.isEmpty()) {
374            Log.i(this, "Adding %s calls to InCallService after onConnected: %s", calls.size(),
375                    componentName);
376            for (Call call : calls) {
377                try {
378                    // Track the call if we don't already know about it.
379                    addCall(call);
380                    inCallService.addCall(toParcelableCall(
381                            call,
382                            componentName.equals(mInCallComponentName) /* includeVideoProvider */));
383                } catch (RemoteException ignored) {
384                }
385            }
386            onAudioStateChanged(
387                    null,
388                    mCallsManager.getAudioState());
389            onCanAddCallChanged(mCallsManager.canAddCall());
390        } else {
391            unbind();
392        }
393        Trace.endSection();
394    }
395
396    /**
397     * Cleans up an instance of in-call app after the service has been unbound.
398     *
399     * @param disconnectedComponent The {@link ComponentName} of the service which disconnected.
400     */
401    private void onDisconnected(ComponentName disconnectedComponent) {
402        Log.i(this, "onDisconnected from %s", disconnectedComponent);
403
404        if (mInCallServices.containsKey(disconnectedComponent)) {
405            mInCallServices.remove(disconnectedComponent);
406        }
407
408        if (mServiceConnections.containsKey(disconnectedComponent)) {
409            // One of the services that we were bound to has disconnected. If the default in-call UI
410            // has disconnected, disconnect all calls and un-bind all other InCallService
411            // implementations.
412            if (disconnectedComponent.equals(mInCallComponentName)) {
413                Log.i(this, "In-call UI %s disconnected.", disconnectedComponent);
414                mCallsManager.disconnectAllCalls();
415                unbind();
416            } else {
417                Log.i(this, "In-Call Service %s suddenly disconnected", disconnectedComponent);
418                // Else, if it wasn't the default in-call UI, then one of the other in-call services
419                // disconnected and, well, that's probably their fault.  Clear their state and
420                // ignore.
421                InCallServiceConnection serviceConnection =
422                        mServiceConnections.get(disconnectedComponent);
423
424                // We still need to call unbind even though it disconnected.
425                mContext.unbindService(serviceConnection);
426
427                mServiceConnections.remove(disconnectedComponent);
428                mInCallServices.remove(disconnectedComponent);
429            }
430        }
431    }
432
433    /**
434     * Informs all {@link InCallService} instances of the updated call information.  Changes to the
435     * video provider are only communicated to the default in-call UI.
436     *
437     * @param call The {@link Call}.
438     */
439    private void updateCall(Call call) {
440        if (!mInCallServices.isEmpty()) {
441            for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) {
442                ComponentName componentName = entry.getKey();
443                IInCallService inCallService = entry.getValue();
444                ParcelableCall parcelableCall = toParcelableCall(call,
445                        componentName.equals(mInCallComponentName) /* includeVideoProvider */);
446                Log.v(this, "updateCall %s ==> %s", call, parcelableCall);
447                try {
448                    inCallService.updateCall(parcelableCall);
449                } catch (RemoteException ignored) {
450                }
451            }
452        }
453    }
454
455    /**
456     * Parcels all information for a {@link Call} into a new {@link ParcelableCall} instance.
457     *
458     * @param call The {@link Call} to parcel.
459     * @param includeVideoProvider When {@code true}, the {@link IVideoProvider} is included in the
460     *      parceled call.  When {@code false}, the {@link IVideoProvider} is not included.
461     * @return The {@link ParcelableCall} containing all call information from the {@link Call}.
462     */
463    private ParcelableCall toParcelableCall(Call call, boolean includeVideoProvider) {
464        String callId = mCallIdMapper.getCallId(call);
465
466        int state = call.getState();
467        int capabilities = convertConnectionToCallCapabilities(call.getConnectionCapabilities());
468
469        // If this is a single-SIM device, the "default SIM" will always be the only SIM.
470        boolean isDefaultSmsAccount =
471                mCallsManager.getPhoneAccountRegistrar()
472                        .isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
473        if (call.isRespondViaSmsCapable() && isDefaultSmsAccount) {
474            capabilities |= android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT;
475        }
476
477        if (call.isEmergencyCall()) {
478            capabilities = removeCapability(
479                    capabilities, android.telecom.Call.Details.CAPABILITY_MUTE);
480        }
481
482        if (state == CallState.DIALING) {
483            capabilities = removeCapability(
484                    capabilities, android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL);
485            capabilities = removeCapability(
486                    capabilities, android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE);
487        }
488
489        if (state == CallState.ABORTED) {
490            state = CallState.DISCONNECTED;
491        }
492
493        if (call.isLocallyDisconnecting() && state != CallState.DISCONNECTED) {
494            state = CallState.DISCONNECTING;
495        }
496
497        String parentCallId = null;
498        Call parentCall = call.getParentCall();
499        if (parentCall != null) {
500            parentCallId = mCallIdMapper.getCallId(parentCall);
501        }
502
503        long connectTimeMillis = call.getConnectTimeMillis();
504        List<Call> childCalls = call.getChildCalls();
505        List<String> childCallIds = new ArrayList<>();
506        if (!childCalls.isEmpty()) {
507            long childConnectTimeMillis = Long.MAX_VALUE;
508            for (Call child : childCalls) {
509                if (child.getConnectTimeMillis() > 0) {
510                    childConnectTimeMillis = Math.min(child.getConnectTimeMillis(),
511                            childConnectTimeMillis);
512                }
513                childCallIds.add(mCallIdMapper.getCallId(child));
514            }
515
516            if (childConnectTimeMillis != Long.MAX_VALUE) {
517                connectTimeMillis = childConnectTimeMillis;
518            }
519        }
520
521        Uri handle = call.getHandlePresentation() == TelecomManager.PRESENTATION_ALLOWED ?
522                call.getHandle() : null;
523        String callerDisplayName = call.getCallerDisplayNamePresentation() ==
524                TelecomManager.PRESENTATION_ALLOWED ?  call.getCallerDisplayName() : null;
525
526        List<Call> conferenceableCalls = call.getConferenceableCalls();
527        List<String> conferenceableCallIds = new ArrayList<String>(conferenceableCalls.size());
528        for (Call otherCall : conferenceableCalls) {
529            String otherId = mCallIdMapper.getCallId(otherCall);
530            if (otherId != null) {
531                conferenceableCallIds.add(otherId);
532            }
533        }
534
535        int properties = call.isConference() ? CallProperties.CONFERENCE : 0;
536        return new ParcelableCall(
537                callId,
538                state,
539                call.getDisconnectCause(),
540                call.getCannedSmsResponses(),
541                capabilities,
542                properties,
543                connectTimeMillis,
544                handle,
545                call.getHandlePresentation(),
546                callerDisplayName,
547                call.getCallerDisplayNamePresentation(),
548                call.getGatewayInfo(),
549                call.getTargetPhoneAccount(),
550                includeVideoProvider ? call.getVideoProvider() : null,
551                parentCallId,
552                childCallIds,
553                call.getStatusHints(),
554                call.getVideoState(),
555                conferenceableCallIds,
556                call.getExtras());
557    }
558
559    private static final int[] CONNECTION_TO_CALL_CAPABILITY = new int[] {
560        Connection.CAPABILITY_HOLD,
561        android.telecom.Call.Details.CAPABILITY_HOLD,
562
563        Connection.CAPABILITY_SUPPORT_HOLD,
564        android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD,
565
566        Connection.CAPABILITY_MERGE_CONFERENCE,
567        android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE,
568
569        Connection.CAPABILITY_SWAP_CONFERENCE,
570        android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE,
571
572        Connection.CAPABILITY_UNUSED,
573        android.telecom.Call.Details.CAPABILITY_UNUSED,
574
575        Connection.CAPABILITY_RESPOND_VIA_TEXT,
576        android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT,
577
578        Connection.CAPABILITY_MUTE,
579        android.telecom.Call.Details.CAPABILITY_MUTE,
580
581        Connection.CAPABILITY_MANAGE_CONFERENCE,
582        android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE,
583
584        Connection.CAPABILITY_SUPPORTS_VT_LOCAL_RX,
585        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_RX,
586
587        Connection.CAPABILITY_SUPPORTS_VT_LOCAL_TX,
588        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX,
589
590        Connection.CAPABILITY_SUPPORTS_VT_LOCAL,
591        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL,
592
593        Connection.CAPABILITY_SUPPORTS_VT_REMOTE_RX,
594        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX,
595
596        Connection.CAPABILITY_SUPPORTS_VT_REMOTE_TX,
597        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_TX,
598
599        Connection.CAPABILITY_SUPPORTS_VT_REMOTE,
600        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE,
601
602        Connection.CAPABILITY_HIGH_DEF_AUDIO,
603        android.telecom.Call.Details.CAPABILITY_HIGH_DEF_AUDIO,
604
605        Connection.CAPABILITY_WIFI,
606        android.telecom.Call.Details.CAPABILITY_WIFI,
607
608        Connection.CAPABILITY_SEPARATE_FROM_CONFERENCE,
609        android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE,
610
611        Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE,
612        android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE,
613
614        Connection.CAPABILITY_GENERIC_CONFERENCE,
615        android.telecom.Call.Details.CAPABILITY_GENERIC_CONFERENCE,
616
617        Connection.CAPABILITY_SHOW_CALLBACK_NUMBER,
618        android.telecom.Call.Details.CAPABILITY_SHOW_CALLBACK_NUMBER
619    };
620
621    private static int convertConnectionToCallCapabilities(int connectionCapabilities) {
622        int callCapabilities = 0;
623        for (int i = 0; i < CONNECTION_TO_CALL_CAPABILITY.length; i += 2) {
624            if ((CONNECTION_TO_CALL_CAPABILITY[i] & connectionCapabilities) != 0) {
625                callCapabilities |= CONNECTION_TO_CALL_CAPABILITY[i + 1];
626            }
627        }
628        return callCapabilities;
629    }
630
631    /**
632     * Adds the call to the list of calls tracked by the {@link InCallController}.
633     * @param call The call to add.
634     */
635    private void addCall(Call call) {
636        if (mCallIdMapper.getCallId(call) == null) {
637            mCallIdMapper.addCall(call);
638            call.addListener(mCallListener);
639        }
640    }
641
642    /**
643     * Removes the specified capability from the set of capabilities bits and returns the new set.
644     */
645    private static int removeCapability(int capabilities, int capability) {
646        return capabilities & ~capability;
647    }
648
649    /**
650     * Dumps the state of the {@link InCallController}.
651     *
652     * @param pw The {@code IndentingPrintWriter} to write the state to.
653     */
654    public void dump(IndentingPrintWriter pw) {
655        pw.println("mInCallServices (InCalls registered):");
656        pw.increaseIndent();
657        for (ComponentName componentName : mInCallServices.keySet()) {
658            pw.println(componentName);
659        }
660        pw.decreaseIndent();
661
662        pw.println("mServiceConnections (InCalls bound):");
663        pw.increaseIndent();
664        for (ComponentName componentName : mServiceConnections.keySet()) {
665            pw.println(componentName);
666        }
667        pw.decreaseIndent();
668    }
669}
670