InCallController.java revision fc0e227881bbb1c0795e5223d0594f13e1b2de12
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.telecom.VideoCallImpl;
41import android.util.ArrayMap;
42
43// TODO: Needed for move to system service: import com.android.internal.R;
44import com.android.internal.telecom.IInCallService;
45import com.android.internal.util.IndentingPrintWriter;
46
47import java.util.ArrayList;
48import java.util.Collection;
49import java.util.Iterator;
50import java.util.List;
51import java.util.Map;
52import java.util.concurrent.ConcurrentHashMap;
53
54/**
55 * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
56 * can send updates to the in-call app. This class is created and owned by CallsManager and retains
57 * a binding to the {@link IInCallService} (implemented by the in-call app).
58 */
59public final class InCallController extends CallsManagerListenerBase {
60    /**
61     * Used to bind to the in-call app and triggers the start of communication between
62     * this class and in-call app.
63     */
64    private class InCallServiceConnection implements ServiceConnection {
65        /** {@inheritDoc} */
66        @Override public void onServiceConnected(ComponentName name, IBinder service) {
67            Log.d(this, "onServiceConnected: %s", name);
68            onConnected(name, service);
69        }
70
71        /** {@inheritDoc} */
72        @Override public void onServiceDisconnected(ComponentName name) {
73            Log.d(this, "onDisconnected: %s", name);
74            onDisconnected(name);
75        }
76    }
77
78    private final Call.Listener mCallListener = new Call.ListenerBase() {
79        @Override
80        public void onConnectionCapabilitiesChanged(Call call) {
81            updateCall(call);
82        }
83
84        @Override
85        public void onCannedSmsResponsesLoaded(Call call) {
86            updateCall(call);
87        }
88
89        @Override
90        public void onVideoCallProviderChanged(Call call) {
91            updateCall(call, true /* videoProviderChanged */);
92        }
93
94        @Override
95        public void onStatusHintsChanged(Call call) {
96            updateCall(call);
97        }
98
99        @Override
100        public void onHandleChanged(Call call) {
101            updateCall(call);
102        }
103
104        @Override
105        public void onCallerDisplayNameChanged(Call call) {
106            updateCall(call);
107        }
108
109        @Override
110        public void onVideoStateChanged(Call call) {
111            updateCall(call);
112        }
113
114        @Override
115        public void onTargetPhoneAccountChanged(Call call) {
116            updateCall(call);
117        }
118
119        @Override
120        public void onConferenceableCallsChanged(Call call) {
121            updateCall(call);
122        }
123    };
124
125    /**
126     * Maintains a binding connection to the in-call app(s).
127     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
128     * load factor before resizing, 1 means we only expect a single thread to
129     * access the map so make only a single shard
130     */
131    private final Map<ComponentName, InCallServiceConnection> mServiceConnections =
132            new ConcurrentHashMap<ComponentName, InCallServiceConnection>(8, 0.9f, 1);
133
134    /** The in-call app implementations, see {@link IInCallService}. */
135    private final Map<ComponentName, IInCallService> mInCallServices = new ArrayMap<>();
136
137    private final CallIdMapper mCallIdMapper = new CallIdMapper("InCall");
138
139    /** The {@link ComponentName} of the default InCall UI. */
140    private final ComponentName mInCallComponentName;
141
142    private final Context mContext;
143    private final TelecomSystem.SyncRoot mLock;
144    private final CallsManager mCallsManager;
145
146    public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager) {
147        mContext = context;
148        mLock = lock;
149        mCallsManager = callsManager;
150        Resources resources = mContext.getResources();
151
152        mInCallComponentName = new ComponentName(
153                resources.getString(R.string.ui_default_package),
154                resources.getString(R.string.incall_default_class));
155    }
156
157    @Override
158    public void onCallAdded(Call call) {
159        if (mInCallServices.isEmpty()) {
160            bind(call);
161        } else {
162            Log.i(this, "onCallAdded: %s", call);
163            // Track the call if we don't already know about it.
164            addCall(call);
165
166            for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) {
167                ComponentName componentName = entry.getKey();
168                IInCallService inCallService = entry.getValue();
169                ParcelableCall parcelableCall = toParcelableCall(call,
170                        true /* 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(call, true /* includeVideoProvider */));
381                } catch (RemoteException ignored) {
382                }
383            }
384            onAudioStateChanged(
385                    null,
386                    mCallsManager.getAudioState());
387            onCanAddCallChanged(mCallsManager.canAddCall());
388        } else {
389            unbind();
390        }
391        Trace.endSection();
392    }
393
394    /**
395     * Cleans up an instance of in-call app after the service has been unbound.
396     *
397     * @param disconnectedComponent The {@link ComponentName} of the service which disconnected.
398     */
399    private void onDisconnected(ComponentName disconnectedComponent) {
400        Log.i(this, "onDisconnected from %s", disconnectedComponent);
401
402        if (mInCallServices.containsKey(disconnectedComponent)) {
403            mInCallServices.remove(disconnectedComponent);
404        }
405
406        if (mServiceConnections.containsKey(disconnectedComponent)) {
407            // One of the services that we were bound to has disconnected. If the default in-call UI
408            // has disconnected, disconnect all calls and un-bind all other InCallService
409            // implementations.
410            if (disconnectedComponent.equals(mInCallComponentName)) {
411                Log.i(this, "In-call UI %s disconnected.", disconnectedComponent);
412                mCallsManager.disconnectAllCalls();
413                unbind();
414            } else {
415                Log.i(this, "In-Call Service %s suddenly disconnected", disconnectedComponent);
416                // Else, if it wasn't the default in-call UI, then one of the other in-call services
417                // disconnected and, well, that's probably their fault.  Clear their state and
418                // ignore.
419                InCallServiceConnection serviceConnection =
420                        mServiceConnections.get(disconnectedComponent);
421
422                // We still need to call unbind even though it disconnected.
423                mContext.unbindService(serviceConnection);
424
425                mServiceConnections.remove(disconnectedComponent);
426                mInCallServices.remove(disconnectedComponent);
427            }
428        }
429    }
430
431    /**
432     * Informs all {@link InCallService} instances of the updated call information.
433     *
434     * @param call The {@link Call}.
435     */
436    private void updateCall(Call call) {
437        updateCall(call, false /* videoProviderChanged */);
438    }
439
440    /**
441     * Informs all {@link InCallService} instances of the updated call information.
442     *
443     * @param call The {@link Call}.
444     * @param videoProviderChanged {@code true} if the video provider changed, {@code false}
445     *      otherwise.
446     */
447    private void updateCall(Call call, boolean videoProviderChanged) {
448        if (!mInCallServices.isEmpty()) {
449            for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) {
450                ComponentName componentName = entry.getKey();
451                IInCallService inCallService = entry.getValue();
452                ParcelableCall parcelableCall = toParcelableCall(call,
453                        videoProviderChanged /* includeVideoProvider */);
454                Log.v(this, "updateCall %s ==> %s", call, parcelableCall);
455                try {
456                    inCallService.updateCall(parcelableCall);
457                } catch (RemoteException ignored) {
458                }
459            }
460        }
461    }
462
463    /**
464     * Parcels all information for a {@link Call} into a new {@link ParcelableCall} instance.
465     *
466     * @param call The {@link Call} to parcel.
467     * @param includeVideoProvider {@code true} if the video provider should be parcelled with the
468     *      {@link Call}, {@code false} otherwise.  Since the {@link ParcelableCall#getVideoCall()}
469     *      method creates a {@link VideoCallImpl} instance on access it is important for the
470     *      recipient of the {@link ParcelableCall} to know if the video provider changed.
471     * @return The {@link ParcelableCall} containing all call information from the {@link Call}.
472     */
473    private ParcelableCall toParcelableCall(Call call, boolean includeVideoProvider) {
474        String callId = mCallIdMapper.getCallId(call);
475
476        int state = call.getState();
477        int capabilities = convertConnectionToCallCapabilities(call.getConnectionCapabilities());
478
479        // If this is a single-SIM device, the "default SIM" will always be the only SIM.
480        boolean isDefaultSmsAccount =
481                mCallsManager.getPhoneAccountRegistrar()
482                        .isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
483        if (call.isRespondViaSmsCapable() && isDefaultSmsAccount) {
484            capabilities |= android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT;
485        }
486
487        if (call.isEmergencyCall()) {
488            capabilities = removeCapability(
489                    capabilities, android.telecom.Call.Details.CAPABILITY_MUTE);
490        }
491
492        if (state == CallState.DIALING) {
493            capabilities = removeCapability(capabilities,
494                    android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
495            capabilities = removeCapability(capabilities,
496                    android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
497        }
498
499        if (state == CallState.ABORTED) {
500            state = CallState.DISCONNECTED;
501        }
502
503        if (call.isLocallyDisconnecting() && state != CallState.DISCONNECTED) {
504            state = CallState.DISCONNECTING;
505        }
506
507        String parentCallId = null;
508        Call parentCall = call.getParentCall();
509        if (parentCall != null) {
510            parentCallId = mCallIdMapper.getCallId(parentCall);
511        }
512
513        long connectTimeMillis = call.getConnectTimeMillis();
514        List<Call> childCalls = call.getChildCalls();
515        List<String> childCallIds = new ArrayList<>();
516        if (!childCalls.isEmpty()) {
517            long childConnectTimeMillis = Long.MAX_VALUE;
518            for (Call child : childCalls) {
519                if (child.getConnectTimeMillis() > 0) {
520                    childConnectTimeMillis = Math.min(child.getConnectTimeMillis(),
521                            childConnectTimeMillis);
522                }
523                childCallIds.add(mCallIdMapper.getCallId(child));
524            }
525
526            if (childConnectTimeMillis != Long.MAX_VALUE) {
527                connectTimeMillis = childConnectTimeMillis;
528            }
529        }
530
531        Uri handle = call.getHandlePresentation() == TelecomManager.PRESENTATION_ALLOWED ?
532                call.getHandle() : null;
533        String callerDisplayName = call.getCallerDisplayNamePresentation() ==
534                TelecomManager.PRESENTATION_ALLOWED ?  call.getCallerDisplayName() : null;
535
536        List<Call> conferenceableCalls = call.getConferenceableCalls();
537        List<String> conferenceableCallIds = new ArrayList<String>(conferenceableCalls.size());
538        for (Call otherCall : conferenceableCalls) {
539            String otherId = mCallIdMapper.getCallId(otherCall);
540            if (otherId != null) {
541                conferenceableCallIds.add(otherId);
542            }
543        }
544
545        int properties = call.isConference() ? CallProperties.CONFERENCE : 0;
546        return new ParcelableCall(
547                callId,
548                state,
549                call.getDisconnectCause(),
550                call.getCannedSmsResponses(),
551                capabilities,
552                properties,
553                connectTimeMillis,
554                handle,
555                call.getHandlePresentation(),
556                callerDisplayName,
557                call.getCallerDisplayNamePresentation(),
558                call.getGatewayInfo(),
559                call.getTargetPhoneAccount(),
560                includeVideoProvider,
561                includeVideoProvider ? call.getVideoProvider() : null,
562                parentCallId,
563                childCallIds,
564                call.getStatusHints(),
565                call.getVideoState(),
566                conferenceableCallIds,
567                call.getExtras());
568    }
569
570    private static final int[] CONNECTION_TO_CALL_CAPABILITY = new int[] {
571        Connection.CAPABILITY_HOLD,
572        android.telecom.Call.Details.CAPABILITY_HOLD,
573
574        Connection.CAPABILITY_SUPPORT_HOLD,
575        android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD,
576
577        Connection.CAPABILITY_MERGE_CONFERENCE,
578        android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE,
579
580        Connection.CAPABILITY_SWAP_CONFERENCE,
581        android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE,
582
583        Connection.CAPABILITY_UNUSED,
584        android.telecom.Call.Details.CAPABILITY_UNUSED,
585
586        Connection.CAPABILITY_RESPOND_VIA_TEXT,
587        android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT,
588
589        Connection.CAPABILITY_MUTE,
590        android.telecom.Call.Details.CAPABILITY_MUTE,
591
592        Connection.CAPABILITY_MANAGE_CONFERENCE,
593        android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE,
594
595        Connection.CAPABILITY_SUPPORTS_VT_LOCAL_RX,
596        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_RX,
597
598        Connection.CAPABILITY_SUPPORTS_VT_LOCAL_TX,
599        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX,
600
601        Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
602        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
603
604        Connection.CAPABILITY_SUPPORTS_VT_REMOTE_RX,
605        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX,
606
607        Connection.CAPABILITY_SUPPORTS_VT_REMOTE_TX,
608        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_TX,
609
610        Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
611        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
612
613        Connection.CAPABILITY_HIGH_DEF_AUDIO,
614        android.telecom.Call.Details.CAPABILITY_HIGH_DEF_AUDIO,
615
616        Connection.CAPABILITY_WIFI,
617        android.telecom.Call.Details.CAPABILITY_WIFI,
618
619        Connection.CAPABILITY_SEPARATE_FROM_CONFERENCE,
620        android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE,
621
622        Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE,
623        android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE,
624
625        Connection.CAPABILITY_GENERIC_CONFERENCE,
626        android.telecom.Call.Details.CAPABILITY_GENERIC_CONFERENCE,
627
628        Connection.CAPABILITY_SHOW_CALLBACK_NUMBER,
629        android.telecom.Call.Details.CAPABILITY_SHOW_CALLBACK_NUMBER,
630
631        Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
632        android.telecom.Call.Details.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
633
634        Connection.CAPABILITY_CAN_PAUSE_VIDEO,
635        android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO
636    };
637
638    private static int convertConnectionToCallCapabilities(int connectionCapabilities) {
639        int callCapabilities = 0;
640        for (int i = 0; i < CONNECTION_TO_CALL_CAPABILITY.length; i += 2) {
641            if ((CONNECTION_TO_CALL_CAPABILITY[i] & connectionCapabilities) != 0) {
642                callCapabilities |= CONNECTION_TO_CALL_CAPABILITY[i + 1];
643            }
644        }
645        return callCapabilities;
646    }
647
648    /**
649     * Adds the call to the list of calls tracked by the {@link InCallController}.
650     * @param call The call to add.
651     */
652    private void addCall(Call call) {
653        if (mCallIdMapper.getCallId(call) == null) {
654            mCallIdMapper.addCall(call);
655            call.addListener(mCallListener);
656        }
657    }
658
659    /**
660     * Removes the specified capability from the set of capabilities bits and returns the new set.
661     */
662    private static int removeCapability(int capabilities, int capability) {
663        return capabilities & ~capability;
664    }
665
666    /**
667     * Dumps the state of the {@link InCallController}.
668     *
669     * @param pw The {@code IndentingPrintWriter} to write the state to.
670     */
671    public void dump(IndentingPrintWriter pw) {
672        pw.println("mInCallServices (InCalls registered):");
673        pw.increaseIndent();
674        for (ComponentName componentName : mInCallServices.keySet()) {
675            pw.println(componentName);
676        }
677        pw.decreaseIndent();
678
679        pw.println("mServiceConnections (InCalls bound):");
680        pw.increaseIndent();
681        for (ComponentName componentName : mServiceConnections.keySet()) {
682            pw.println(componentName);
683        }
684        pw.decreaseIndent();
685    }
686}
687