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