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