InCallController.java revision b3907b3e79864a3a42d5e7105e5051f7e364fdba
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.CallAudioState; 35import android.telecom.Connection; 36import android.telecom.DefaultDialerManager; 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 onExtrasChanged(Call call) { 101 updateCall(call); 102 } 103 104 @Override 105 public void onHandleChanged(Call call) { 106 updateCall(call); 107 } 108 109 @Override 110 public void onCallerDisplayNameChanged(Call call) { 111 updateCall(call); 112 } 113 114 @Override 115 public void onVideoStateChanged(Call call) { 116 updateCall(call); 117 } 118 119 @Override 120 public void onTargetPhoneAccountChanged(Call call) { 121 updateCall(call); 122 } 123 124 @Override 125 public void onConferenceableCallsChanged(Call call) { 126 updateCall(call); 127 } 128 }; 129 130 /** 131 * Maintains a binding connection to the in-call app(s). 132 * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is 133 * load factor before resizing, 1 means we only expect a single thread to 134 * access the map so make only a single shard 135 */ 136 private final Map<ComponentName, InCallServiceConnection> mServiceConnections = 137 new ConcurrentHashMap<ComponentName, InCallServiceConnection>(8, 0.9f, 1); 138 139 /** The in-call app implementations, see {@link IInCallService}. */ 140 private final Map<ComponentName, IInCallService> mInCallServices = new ArrayMap<>(); 141 142 private final CallIdMapper mCallIdMapper = new CallIdMapper("InCall"); 143 144 /** The {@link ComponentName} of the default InCall UI. */ 145 private final ComponentName mInCallComponentName; 146 147 private final Context mContext; 148 private final TelecomSystem.SyncRoot mLock; 149 private final CallsManager mCallsManager; 150 151 public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager) { 152 mContext = context; 153 mLock = lock; 154 mCallsManager = callsManager; 155 Resources resources = mContext.getResources(); 156 157 mInCallComponentName = new ComponentName( 158 resources.getString(R.string.ui_default_package), 159 resources.getString(R.string.incall_default_class)); 160 } 161 162 @Override 163 public void onCallAdded(Call call) { 164 if (mInCallServices.isEmpty()) { 165 bind(call); 166 } else { 167 Log.i(this, "onCallAdded: %s", call); 168 // Track the call if we don't already know about it. 169 addCall(call); 170 171 for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) { 172 ComponentName componentName = entry.getKey(); 173 IInCallService inCallService = entry.getValue(); 174 ParcelableCall parcelableCall = toParcelableCall(call, 175 true /* includeVideoProvider */); 176 try { 177 inCallService.addCall(parcelableCall); 178 } catch (RemoteException ignored) { 179 } 180 } 181 } 182 } 183 184 @Override 185 public void onCallRemoved(Call call) { 186 Log.i(this, "onCallRemoved: %s", call); 187 if (mCallsManager.getCalls().isEmpty()) { 188 // TODO: Wait for all messages to be delivered to the service before unbinding. 189 unbind(); 190 } 191 call.removeListener(mCallListener); 192 mCallIdMapper.removeCall(call); 193 } 194 195 @Override 196 public void onCallStateChanged(Call call, int oldState, int newState) { 197 updateCall(call); 198 } 199 200 @Override 201 public void onConnectionServiceChanged( 202 Call call, 203 ConnectionServiceWrapper oldService, 204 ConnectionServiceWrapper newService) { 205 updateCall(call); 206 } 207 208 @Override 209 public void onCallAudioStateChanged(CallAudioState oldCallAudioState, 210 CallAudioState newCallAudioState) { 211 if (!mInCallServices.isEmpty()) { 212 Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState, 213 newCallAudioState); 214 for (IInCallService inCallService : mInCallServices.values()) { 215 try { 216 inCallService.onCallAudioStateChanged(newCallAudioState); 217 } catch (RemoteException ignored) { 218 } 219 } 220 } 221 } 222 223 @Override 224 public void onCanAddCallChanged(boolean canAddCall) { 225 if (!mInCallServices.isEmpty()) { 226 Log.i(this, "onCanAddCallChanged : %b", canAddCall); 227 for (IInCallService inCallService : mInCallServices.values()) { 228 try { 229 inCallService.onCanAddCallChanged(canAddCall); 230 } catch (RemoteException ignored) { 231 } 232 } 233 } 234 } 235 236 void onPostDialWait(Call call, String remaining) { 237 if (!mInCallServices.isEmpty()) { 238 Log.i(this, "Calling onPostDialWait, remaining = %s", remaining); 239 for (IInCallService inCallService : mInCallServices.values()) { 240 try { 241 inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining); 242 } catch (RemoteException ignored) { 243 } 244 } 245 } 246 } 247 248 @Override 249 public void onIsConferencedChanged(Call call) { 250 Log.d(this, "onIsConferencedChanged %s", call); 251 updateCall(call); 252 } 253 254 void bringToForeground(boolean showDialpad) { 255 if (!mInCallServices.isEmpty()) { 256 for (IInCallService inCallService : mInCallServices.values()) { 257 try { 258 inCallService.bringToForeground(showDialpad); 259 } catch (RemoteException ignored) { 260 } 261 } 262 } else { 263 Log.w(this, "Asking to bring unbound in-call UI to foreground."); 264 } 265 } 266 267 /** 268 * Unbinds an existing bound connection to the in-call app. 269 */ 270 private void unbind() { 271 Iterator<Map.Entry<ComponentName, InCallServiceConnection>> iterator = 272 mServiceConnections.entrySet().iterator(); 273 while (iterator.hasNext()) { 274 Log.i(this, "Unbinding from InCallService %s"); 275 mContext.unbindService(iterator.next().getValue()); 276 iterator.remove(); 277 } 278 mInCallServices.clear(); 279 } 280 281 /** 282 * Binds to the in-call app if not already connected by binding directly to the saved 283 * component name of the {@link IInCallService} implementation. 284 * 285 * @param call The newly added call that triggered the binding to the in-call services. 286 */ 287 private void bind(Call call) { 288 if (mInCallServices.isEmpty()) { 289 PackageManager packageManager = mContext.getPackageManager(); 290 Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE); 291 292 for (ResolveInfo entry : packageManager.queryIntentServices(serviceIntent, 0)) { 293 ServiceInfo serviceInfo = entry.serviceInfo; 294 if (serviceInfo != null) { 295 boolean hasServiceBindPermission = serviceInfo.permission != null && 296 serviceInfo.permission.equals( 297 Manifest.permission.BIND_INCALL_SERVICE); 298 boolean hasControlInCallPermission = packageManager.checkPermission( 299 Manifest.permission.CONTROL_INCALL_EXPERIENCE, 300 serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED; 301 302 if (!hasServiceBindPermission) { 303 Log.w(this, "InCallService does not have BIND_INCALL_SERVICE permission: " + 304 serviceInfo.packageName); 305 continue; 306 } 307 308 if (!hasControlInCallPermission 309 && !DefaultDialerManager.isDefaultOrSystemDialer(mContext, 310 serviceInfo.packageName)) { 311 Log.w(this, "Service does not have CONTROL_INCALL_EXPERIENCE permission: %s" 312 + " and is not system or default dialer.", serviceInfo.packageName); 313 continue; 314 } 315 316 InCallServiceConnection inCallServiceConnection = new InCallServiceConnection(); 317 ComponentName componentName = new ComponentName(serviceInfo.packageName, 318 serviceInfo.name); 319 320 Log.i(this, "Attempting to bind to InCall %s, is dupe? %b ", 321 serviceInfo.packageName, 322 mServiceConnections.containsKey(componentName)); 323 324 if (!mServiceConnections.containsKey(componentName)) { 325 Intent intent = new Intent(InCallService.SERVICE_INTERFACE); 326 intent.setComponent(componentName); 327 328 final int bindFlags; 329 if (mInCallComponentName.equals(componentName)) { 330 bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT; 331 if (!call.isIncoming()) { 332 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, 333 call.getIntentExtras()); 334 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, 335 call.getTargetPhoneAccount()); 336 } 337 } else { 338 bindFlags = Context.BIND_AUTO_CREATE; 339 } 340 341 if (mContext.bindServiceAsUser(intent, inCallServiceConnection, bindFlags, 342 UserHandle.CURRENT)) { 343 mServiceConnections.put(componentName, inCallServiceConnection); 344 } 345 } 346 } 347 } 348 } 349 } 350 351 /** 352 * Persists the {@link IInCallService} instance and starts the communication between 353 * this class and in-call app by sending the first update to in-call app. This method is 354 * called after a successful binding connection is established. 355 * 356 * @param componentName The service {@link ComponentName}. 357 * @param service The {@link IInCallService} implementation. 358 */ 359 private void onConnected(ComponentName componentName, IBinder service) { 360 Trace.beginSection("onConnected: " + componentName); 361 Log.i(this, "onConnected to %s", componentName); 362 363 IInCallService inCallService = IInCallService.Stub.asInterface(service); 364 365 try { 366 inCallService.setInCallAdapter( 367 new InCallAdapter( 368 mCallsManager, 369 mCallIdMapper, 370 mLock)); 371 mInCallServices.put(componentName, inCallService); 372 } catch (RemoteException e) { 373 Log.e(this, e, "Failed to set the in-call adapter."); 374 Trace.endSection(); 375 return; 376 } 377 378 // Upon successful connection, send the state of the world to the service. 379 Collection<Call> calls = mCallsManager.getCalls(); 380 if (!calls.isEmpty()) { 381 Log.i(this, "Adding %s calls to InCallService after onConnected: %s", calls.size(), 382 componentName); 383 for (Call call : calls) { 384 try { 385 // Track the call if we don't already know about it. 386 addCall(call); 387 inCallService.addCall(toParcelableCall(call, true /* includeVideoProvider */)); 388 } catch (RemoteException ignored) { 389 } 390 } 391 onCallAudioStateChanged( 392 null, 393 mCallsManager.getAudioState()); 394 onCanAddCallChanged(mCallsManager.canAddCall()); 395 } else { 396 unbind(); 397 } 398 Trace.endSection(); 399 } 400 401 /** 402 * Cleans up an instance of in-call app after the service has been unbound. 403 * 404 * @param disconnectedComponent The {@link ComponentName} of the service which disconnected. 405 */ 406 private void onDisconnected(ComponentName disconnectedComponent) { 407 Log.i(this, "onDisconnected from %s", disconnectedComponent); 408 409 if (mInCallServices.containsKey(disconnectedComponent)) { 410 mInCallServices.remove(disconnectedComponent); 411 } 412 413 if (mServiceConnections.containsKey(disconnectedComponent)) { 414 // One of the services that we were bound to has disconnected. If the default in-call UI 415 // has disconnected, disconnect all calls and un-bind all other InCallService 416 // implementations. 417 if (disconnectedComponent.equals(mInCallComponentName)) { 418 Log.i(this, "In-call UI %s disconnected.", disconnectedComponent); 419 mCallsManager.disconnectAllCalls(); 420 unbind(); 421 } else { 422 Log.i(this, "In-Call Service %s suddenly disconnected", disconnectedComponent); 423 // Else, if it wasn't the default in-call UI, then one of the other in-call services 424 // disconnected and, well, that's probably their fault. Clear their state and 425 // ignore. 426 InCallServiceConnection serviceConnection = 427 mServiceConnections.get(disconnectedComponent); 428 429 // We still need to call unbind even though it disconnected. 430 mContext.unbindService(serviceConnection); 431 432 mServiceConnections.remove(disconnectedComponent); 433 mInCallServices.remove(disconnectedComponent); 434 } 435 } 436 } 437 438 /** 439 * Informs all {@link InCallService} instances of the updated call information. 440 * 441 * @param call The {@link Call}. 442 */ 443 private void updateCall(Call call) { 444 updateCall(call, false /* videoProviderChanged */); 445 } 446 447 /** 448 * Informs all {@link InCallService} instances of the updated call information. 449 * 450 * @param call The {@link Call}. 451 * @param videoProviderChanged {@code true} if the video provider changed, {@code false} 452 * otherwise. 453 */ 454 private void updateCall(Call call, boolean videoProviderChanged) { 455 if (!mInCallServices.isEmpty()) { 456 for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) { 457 ComponentName componentName = entry.getKey(); 458 IInCallService inCallService = entry.getValue(); 459 ParcelableCall parcelableCall = toParcelableCall(call, 460 videoProviderChanged /* includeVideoProvider */); 461 Log.v(this, "updateCall %s ==> %s", call, parcelableCall); 462 try { 463 inCallService.updateCall(parcelableCall); 464 } catch (RemoteException ignored) { 465 } 466 } 467 } 468 } 469 470 /** 471 * Parcels all information for a {@link Call} into a new {@link ParcelableCall} instance. 472 * 473 * @param call The {@link Call} to parcel. 474 * @param includeVideoProvider {@code true} if the video provider should be parcelled with the 475 * {@link Call}, {@code false} otherwise. Since the {@link ParcelableCall#getVideoCall()} 476 * method creates a {@link VideoCallImpl} instance on access it is important for the 477 * recipient of the {@link ParcelableCall} to know if the video provider changed. 478 * @return The {@link ParcelableCall} containing all call information from the {@link Call}. 479 */ 480 private ParcelableCall toParcelableCall(Call call, boolean includeVideoProvider) { 481 String callId = mCallIdMapper.getCallId(call); 482 483 int state = getParcelableState(call); 484 int capabilities = convertConnectionToCallCapabilities(call.getConnectionCapabilities()); 485 int properties = convertConnectionToCallProperties(call.getConnectionCapabilities()); 486 if (call.isConference()) { 487 properties |= android.telecom.Call.Details.PROPERTY_CONFERENCE; 488 } 489 490 // If this is a single-SIM device, the "default SIM" will always be the only SIM. 491 boolean isDefaultSmsAccount = 492 mCallsManager.getPhoneAccountRegistrar() 493 .isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount()); 494 if (call.isRespondViaSmsCapable() && isDefaultSmsAccount) { 495 capabilities |= android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT; 496 } 497 498 if (call.isEmergencyCall()) { 499 capabilities = removeCapability( 500 capabilities, android.telecom.Call.Details.CAPABILITY_MUTE); 501 } 502 503 if (state == android.telecom.Call.STATE_DIALING) { 504 capabilities = removeCapability(capabilities, 505 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL); 506 capabilities = removeCapability(capabilities, 507 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL); 508 } 509 510 String parentCallId = null; 511 Call parentCall = call.getParentCall(); 512 if (parentCall != null) { 513 parentCallId = mCallIdMapper.getCallId(parentCall); 514 } 515 516 long connectTimeMillis = call.getConnectTimeMillis(); 517 List<Call> childCalls = call.getChildCalls(); 518 List<String> childCallIds = new ArrayList<>(); 519 if (!childCalls.isEmpty()) { 520 long childConnectTimeMillis = Long.MAX_VALUE; 521 for (Call child : childCalls) { 522 if (child.getConnectTimeMillis() > 0) { 523 childConnectTimeMillis = Math.min(child.getConnectTimeMillis(), 524 childConnectTimeMillis); 525 } 526 childCallIds.add(mCallIdMapper.getCallId(child)); 527 } 528 529 if (childConnectTimeMillis != Long.MAX_VALUE) { 530 connectTimeMillis = childConnectTimeMillis; 531 } 532 } 533 534 Uri handle = call.getHandlePresentation() == TelecomManager.PRESENTATION_ALLOWED ? 535 call.getHandle() : null; 536 String callerDisplayName = call.getCallerDisplayNamePresentation() == 537 TelecomManager.PRESENTATION_ALLOWED ? call.getCallerDisplayName() : null; 538 539 List<Call> conferenceableCalls = call.getConferenceableCalls(); 540 List<String> conferenceableCallIds = new ArrayList<String>(conferenceableCalls.size()); 541 for (Call otherCall : conferenceableCalls) { 542 String otherId = mCallIdMapper.getCallId(otherCall); 543 if (otherId != null) { 544 conferenceableCallIds.add(otherId); 545 } 546 } 547 548 return new ParcelableCall( 549 callId, 550 state, 551 call.getDisconnectCause(), 552 call.getCannedSmsResponses(), 553 capabilities, 554 properties, 555 connectTimeMillis, 556 handle, 557 call.getHandlePresentation(), 558 callerDisplayName, 559 call.getCallerDisplayNamePresentation(), 560 call.getGatewayInfo(), 561 call.getTargetPhoneAccount(), 562 includeVideoProvider, 563 includeVideoProvider ? call.getVideoProvider() : null, 564 parentCallId, 565 childCallIds, 566 call.getStatusHints(), 567 call.getVideoState(), 568 conferenceableCallIds, 569 call.getIntentExtras(), 570 call.getExtras()); 571 } 572 573 private static int getParcelableState(Call call) { 574 int state = CallState.NEW; 575 switch (call.getState()) { 576 case CallState.ABORTED: 577 case CallState.DISCONNECTED: 578 state = android.telecom.Call.STATE_DISCONNECTED; 579 break; 580 case CallState.ACTIVE: 581 state = android.telecom.Call.STATE_ACTIVE; 582 break; 583 case CallState.CONNECTING: 584 state = android.telecom.Call.STATE_CONNECTING; 585 break; 586 case CallState.DIALING: 587 state = android.telecom.Call.STATE_DIALING; 588 break; 589 case CallState.DISCONNECTING: 590 state = android.telecom.Call.STATE_DISCONNECTING; 591 break; 592 case CallState.NEW: 593 state = android.telecom.Call.STATE_NEW; 594 break; 595 case CallState.ON_HOLD: 596 state = android.telecom.Call.STATE_HOLDING; 597 break; 598 case CallState.RINGING: 599 state = android.telecom.Call.STATE_RINGING; 600 break; 601 case CallState.SELECT_PHONE_ACCOUNT: 602 state = android.telecom.Call.STATE_SELECT_PHONE_ACCOUNT; 603 break; 604 } 605 606 // If we are marked as 'locally disconnecting' then mark ourselves as disconnecting instead. 607 // Unless we're disconnect*ED*, in which case leave it at that. 608 if (call.isLocallyDisconnecting() && 609 (state != android.telecom.Call.STATE_DISCONNECTED)) { 610 state = android.telecom.Call.STATE_DISCONNECTING; 611 } 612 return state; 613 } 614 615 private static final int[] CONNECTION_TO_CALL_CAPABILITY = new int[] { 616 Connection.CAPABILITY_HOLD, 617 android.telecom.Call.Details.CAPABILITY_HOLD, 618 619 Connection.CAPABILITY_SUPPORT_HOLD, 620 android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD, 621 622 Connection.CAPABILITY_MERGE_CONFERENCE, 623 android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE, 624 625 Connection.CAPABILITY_SWAP_CONFERENCE, 626 android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE, 627 628 Connection.CAPABILITY_RESPOND_VIA_TEXT, 629 android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT, 630 631 Connection.CAPABILITY_MUTE, 632 android.telecom.Call.Details.CAPABILITY_MUTE, 633 634 Connection.CAPABILITY_MANAGE_CONFERENCE, 635 android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE, 636 637 Connection.CAPABILITY_SUPPORTS_VT_LOCAL_RX, 638 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_RX, 639 640 Connection.CAPABILITY_SUPPORTS_VT_LOCAL_TX, 641 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX, 642 643 Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL, 644 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL, 645 646 Connection.CAPABILITY_SUPPORTS_VT_REMOTE_RX, 647 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX, 648 649 Connection.CAPABILITY_SUPPORTS_VT_REMOTE_TX, 650 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_TX, 651 652 Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, 653 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, 654 655 Connection.CAPABILITY_SEPARATE_FROM_CONFERENCE, 656 android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE, 657 658 Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE, 659 android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE, 660 661 Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO, 662 android.telecom.Call.Details.CAPABILITY_CAN_UPGRADE_TO_VIDEO, 663 664 Connection.CAPABILITY_CAN_PAUSE_VIDEO, 665 android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO 666 }; 667 668 private static int convertConnectionToCallCapabilities(int connectionCapabilities) { 669 int callCapabilities = 0; 670 for (int i = 0; i < CONNECTION_TO_CALL_CAPABILITY.length; i += 2) { 671 if ((CONNECTION_TO_CALL_CAPABILITY[i] & connectionCapabilities) != 0) { 672 callCapabilities |= CONNECTION_TO_CALL_CAPABILITY[i + 1]; 673 } 674 } 675 return callCapabilities; 676 } 677 678 private static final int[] CONNECTION_TO_CALL_PROPERTIES = new int[] { 679 Connection.CAPABILITY_HIGH_DEF_AUDIO, 680 android.telecom.Call.Details.PROPERTY_HIGH_DEF_AUDIO, 681 682 Connection.CAPABILITY_WIFI, 683 android.telecom.Call.Details.PROPERTY_WIFI, 684 685 Connection.CAPABILITY_GENERIC_CONFERENCE, 686 android.telecom.Call.Details.PROPERTY_GENERIC_CONFERENCE, 687 688 Connection.CAPABILITY_SHOW_CALLBACK_NUMBER, 689 android.telecom.Call.Details.PROPERTY_EMERGENCY_CALLBACK_MODE, 690 }; 691 692 private static int convertConnectionToCallProperties(int connectionCapabilities) { 693 int callProperties = 0; 694 for (int i = 0; i < CONNECTION_TO_CALL_PROPERTIES.length; i += 2) { 695 if ((CONNECTION_TO_CALL_PROPERTIES[i] & connectionCapabilities) != 0) { 696 callProperties |= CONNECTION_TO_CALL_PROPERTIES[i + 1]; 697 } 698 } 699 return callProperties; 700 } 701 702 /** 703 * Adds the call to the list of calls tracked by the {@link InCallController}. 704 * @param call The call to add. 705 */ 706 private void addCall(Call call) { 707 if (mCallIdMapper.getCallId(call) == null) { 708 mCallIdMapper.addCall(call); 709 call.addListener(mCallListener); 710 } 711 } 712 713 /** 714 * Removes the specified capability from the set of capabilities bits and returns the new set. 715 */ 716 private static int removeCapability(int capabilities, int capability) { 717 return capabilities & ~capability; 718 } 719 720 /** 721 * Dumps the state of the {@link InCallController}. 722 * 723 * @param pw The {@code IndentingPrintWriter} to write the state to. 724 */ 725 public void dump(IndentingPrintWriter pw) { 726 pw.println("mInCallServices (InCalls registered):"); 727 pw.increaseIndent(); 728 for (ComponentName componentName : mInCallServices.keySet()) { 729 pw.println(componentName); 730 } 731 pw.decreaseIndent(); 732 733 pw.println("mServiceConnections (InCalls bound):"); 734 pw.increaseIndent(); 735 for (ComponentName componentName : mServiceConnections.keySet()) { 736 pw.println(componentName); 737 } 738 pw.decreaseIndent(); 739 } 740} 741