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