InCallController.java revision f78a72f8a1054726aa6f85a21372d917576906dd
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.Handler; 30import android.os.IBinder; 31import android.os.Looper; 32import android.os.RemoteException; 33import android.os.Trace; 34import android.os.UserHandle; 35import android.telecom.Call.Details; 36import android.telecom.CallAudioState; 37import android.telecom.Connection; 38import android.telecom.DefaultDialerManager; 39import android.telecom.InCallService; 40import android.telecom.ParcelableCall; 41import android.telecom.PhoneAccount; 42import android.telecom.PhoneAccountHandle; 43import android.telecom.TelecomManager; 44import android.telecom.VideoCallImpl; 45import android.util.ArrayMap; 46 47// TODO: Needed for move to system service: import com.android.internal.R; 48import com.android.internal.telecom.IInCallService; 49import com.android.internal.util.IndentingPrintWriter; 50import com.android.server.telecom.SystemStateProvider.SystemStateListener; 51 52import java.util.ArrayList; 53import java.util.Collection; 54import java.util.Iterator; 55import java.util.LinkedList; 56import java.util.List; 57import java.util.Map; 58import java.util.Objects; 59import java.util.concurrent.ConcurrentHashMap; 60 61/** 62 * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it 63 * can send updates to the in-call app. This class is created and owned by CallsManager and retains 64 * a binding to the {@link IInCallService} (implemented by the in-call app). 65 */ 66public final class InCallController extends CallsManagerListenerBase { 67 /** 68 * Used to bind to the in-call app and triggers the start of communication between 69 * this class and in-call app. 70 */ 71 private class InCallServiceConnection implements ServiceConnection { 72 /** {@inheritDoc} */ 73 @Override public void onServiceConnected(ComponentName name, IBinder service) { 74 Log.startSession("ICSC.oSC"); 75 Log.d(this, "onServiceConnected: %s", name); 76 onConnected(name, service); 77 Log.endSession(); 78 } 79 80 /** {@inheritDoc} */ 81 @Override public void onServiceDisconnected(ComponentName name) { 82 Log.startSession("ICSC.oSD"); 83 Log.d(this, "onDisconnected: %s", name); 84 onDisconnected(name); 85 Log.endSession(); 86 } 87 } 88 89 private final Call.Listener mCallListener = new Call.ListenerBase() { 90 @Override 91 public void onConnectionCapabilitiesChanged(Call call) { 92 updateCall(call); 93 } 94 95 @Override 96 public void onCannedSmsResponsesLoaded(Call call) { 97 updateCall(call); 98 } 99 100 @Override 101 public void onVideoCallProviderChanged(Call call) { 102 updateCall(call, true /* videoProviderChanged */); 103 } 104 105 @Override 106 public void onStatusHintsChanged(Call call) { 107 updateCall(call); 108 } 109 110 @Override 111 public void onExtrasChanged(Call call) { 112 updateCall(call); 113 } 114 115 @Override 116 public void onHandleChanged(Call call) { 117 updateCall(call); 118 } 119 120 @Override 121 public void onCallerDisplayNameChanged(Call call) { 122 updateCall(call); 123 } 124 125 @Override 126 public void onVideoStateChanged(Call call) { 127 updateCall(call); 128 } 129 130 @Override 131 public void onTargetPhoneAccountChanged(Call call) { 132 updateCall(call); 133 } 134 135 @Override 136 public void onConferenceableCallsChanged(Call call) { 137 updateCall(call); 138 } 139 }; 140 141 private final SystemStateListener mSystemStateListener = new SystemStateListener() { 142 @Override 143 public void onCarModeChanged(boolean isCarMode) { 144 // Do something when the car mode changes. 145 } 146 }; 147 148 private static final int IN_CALL_SERVICE_TYPE_INVALID = 0; 149 private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1; 150 private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2; 151 private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3; 152 private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4; 153 154 /** 155 * Maintains a binding connection to the in-call app(s). 156 * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is 157 * load factor before resizing, 1 means we only expect a single thread to 158 * access the map so make only a single shard 159 */ 160 private final Map<ComponentName, InCallServiceConnection> mServiceConnections = 161 new ConcurrentHashMap<ComponentName, InCallServiceConnection>(8, 0.9f, 1); 162 163 /** The in-call app implementations, see {@link IInCallService}. */ 164 private final Map<ComponentName, IInCallService> mInCallServices = new ArrayMap<>(); 165 166 /** 167 * The {@link ComponentName} of the bound In-Call UI Service. 168 */ 169 private ComponentName mInCallUIComponentName; 170 171 private final CallIdMapper mCallIdMapper = new CallIdMapper(); 172 173 /** The {@link ComponentName} of the default InCall UI. */ 174 private final ComponentName mSystemInCallComponentName; 175 176 private final Context mContext; 177 private final TelecomSystem.SyncRoot mLock; 178 private final CallsManager mCallsManager; 179 private final SystemStateProvider mSystemStateProvider; 180 181 public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, 182 SystemStateProvider systemStateProvider) { 183 mContext = context; 184 mLock = lock; 185 mCallsManager = callsManager; 186 mSystemStateProvider = systemStateProvider; 187 188 Resources resources = mContext.getResources(); 189 mSystemInCallComponentName = new ComponentName( 190 resources.getString(R.string.ui_default_package), 191 resources.getString(R.string.incall_default_class)); 192 193 mSystemStateProvider.addListener(mSystemStateListener); 194 } 195 196 @Override 197 public void onCallAdded(Call call) { 198 if (!isBoundToServices()) { 199 bindToServices(call); 200 } else { 201 adjustServiceBindingsForEmergency(); 202 203 Log.i(this, "onCallAdded: %s", call); 204 // Track the call if we don't already know about it. 205 addCall(call); 206 207 for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) { 208 ComponentName componentName = entry.getKey(); 209 IInCallService inCallService = entry.getValue(); 210 ParcelableCall parcelableCall = toParcelableCall(call, 211 true /* includeVideoProvider */); 212 try { 213 inCallService.addCall(parcelableCall); 214 } catch (RemoteException ignored) { 215 } 216 } 217 } 218 } 219 220 @Override 221 public void onCallRemoved(Call call) { 222 Log.i(this, "onCallRemoved: %s", call); 223 if (mCallsManager.getCalls().isEmpty()) { 224 /** Let's add a 2 second delay before we send unbind to the services to hopefully 225 * give them enough time to process all the pending messages. 226 */ 227 final Session subsession = Log.createSubsession(); 228 Handler handler = new Handler(Looper.getMainLooper()); 229 final Runnable runnableUnbind = new Runnable() { 230 @Override 231 public void run() { 232 try { 233 Log.continueSession(subsession, "ICC.oCR"); 234 synchronized (mLock) { 235 // Check again to make sure there are no active calls. 236 if (mCallsManager.getCalls().isEmpty()) { 237 unbindFromServices(); 238 } 239 } 240 } finally { 241 Log.endSession(); 242 } 243 } 244 }; 245 handler.postDelayed( 246 runnableUnbind, 247 Timeouts.getCallRemoveUnbindInCallServicesDelay( 248 mContext.getContentResolver())); 249 } 250 call.removeListener(mCallListener); 251 mCallIdMapper.removeCall(call); 252 } 253 254 @Override 255 public void onCallStateChanged(Call call, int oldState, int newState) { 256 updateCall(call); 257 } 258 259 @Override 260 public void onConnectionServiceChanged( 261 Call call, 262 ConnectionServiceWrapper oldService, 263 ConnectionServiceWrapper newService) { 264 updateCall(call); 265 } 266 267 @Override 268 public void onCallAudioStateChanged(CallAudioState oldCallAudioState, 269 CallAudioState newCallAudioState) { 270 if (!mInCallServices.isEmpty()) { 271 Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState, 272 newCallAudioState); 273 for (IInCallService inCallService : mInCallServices.values()) { 274 try { 275 inCallService.onCallAudioStateChanged(newCallAudioState); 276 } catch (RemoteException ignored) { 277 } 278 } 279 } 280 } 281 282 @Override 283 public void onCanAddCallChanged(boolean canAddCall) { 284 if (!mInCallServices.isEmpty()) { 285 Log.i(this, "onCanAddCallChanged : %b", canAddCall); 286 for (IInCallService inCallService : mInCallServices.values()) { 287 try { 288 inCallService.onCanAddCallChanged(canAddCall); 289 } catch (RemoteException ignored) { 290 } 291 } 292 } 293 } 294 295 void onPostDialWait(Call call, String remaining) { 296 if (!mInCallServices.isEmpty()) { 297 Log.i(this, "Calling onPostDialWait, remaining = %s", remaining); 298 for (IInCallService inCallService : mInCallServices.values()) { 299 try { 300 inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining); 301 } catch (RemoteException ignored) { 302 } 303 } 304 } 305 } 306 307 @Override 308 public void onIsConferencedChanged(Call call) { 309 Log.d(this, "onIsConferencedChanged %s", call); 310 updateCall(call); 311 } 312 313 void bringToForeground(boolean showDialpad) { 314 if (!mInCallServices.isEmpty()) { 315 for (IInCallService inCallService : mInCallServices.values()) { 316 try { 317 inCallService.bringToForeground(showDialpad); 318 } catch (RemoteException ignored) { 319 } 320 } 321 } else { 322 Log.w(this, "Asking to bring unbound in-call UI to foreground."); 323 } 324 } 325 326 /** 327 * Unbinds an existing bound connection to the in-call app. 328 */ 329 private void unbindFromServices() { 330 Iterator<Map.Entry<ComponentName, InCallServiceConnection>> iterator = 331 mServiceConnections.entrySet().iterator(); 332 while (iterator.hasNext()) { 333 final Map.Entry<ComponentName, InCallServiceConnection> entry = iterator.next(); 334 Log.i(this, "Unbinding from InCallService %s", entry.getKey()); 335 try { 336 mContext.unbindService(entry.getValue()); 337 } catch (Exception e) { 338 Log.e(this, e, "Exception while unbinding from InCallService"); 339 } 340 iterator.remove(); 341 } 342 mInCallServices.clear(); 343 } 344 345 /** 346 * Binds to all the UI-providing InCallService as well as system-implemented non-UI 347 * InCallServices. Method-invoker must check {@link #isBoundToServices()} before invoking. 348 * 349 * @param call The newly added call that triggered the binding to the in-call services. 350 */ 351 private void bindToServices(Call call) { 352 ComponentName inCallUIService = null; 353 ComponentName carModeInCallUIService = null; 354 List<ComponentName> nonUIInCallServices = new LinkedList<>(); 355 356 // Loop through all the InCallService implementations that exist in the devices; 357 PackageManager packageManager = mContext.getPackageManager(); 358 Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE); 359 for (ResolveInfo entry : 360 packageManager.queryIntentServices(serviceIntent, PackageManager.GET_META_DATA)) { 361 ServiceInfo serviceInfo = entry.serviceInfo; 362 if (serviceInfo != null) { 363 ComponentName componentName = 364 new ComponentName(serviceInfo.packageName, serviceInfo.name); 365 366 switch (getInCallServiceType(entry.serviceInfo, packageManager)) { 367 case IN_CALL_SERVICE_TYPE_DIALER_UI: 368 if (inCallUIService == null || 369 inCallUIService.compareTo(componentName) > 0) { 370 inCallUIService = componentName; 371 } 372 break; 373 374 case IN_CALL_SERVICE_TYPE_SYSTEM_UI: 375 // skip, will be added manually 376 break; 377 378 case IN_CALL_SERVICE_TYPE_CAR_MODE_UI: 379 if (carModeInCallUIService == null || 380 carModeInCallUIService.compareTo(componentName) > 0) { 381 carModeInCallUIService = componentName; 382 } 383 break; 384 385 case IN_CALL_SERVICE_TYPE_NON_UI: 386 nonUIInCallServices.add(componentName); 387 break; 388 389 case IN_CALL_SERVICE_TYPE_INVALID: 390 break; 391 392 default: 393 Log.w(this, "unexpected in-call service type"); 394 break; 395 } 396 } 397 } 398 399 Log.i(this, "Car mode InCallService: %s", carModeInCallUIService); 400 Log.i(this, "Dialer InCallService: %s", inCallUIService); 401 402 // Adding the in-call services in order: 403 // (1) The carmode in-call if carmode is on. 404 // (2) The default-dialer in-call if not an emergency call 405 // (3) The system-provided in-call 406 List<ComponentName> orderedInCallUIServices = new LinkedList<>(); 407 if (shouldUseCarModeUI() && carModeInCallUIService != null) { 408 orderedInCallUIServices.add(carModeInCallUIService); 409 } 410 if (!mCallsManager.hasEmergencyCall() && inCallUIService != null) { 411 orderedInCallUIServices.add(inCallUIService); 412 } 413 orderedInCallUIServices.add(mSystemInCallComponentName); 414 415 // TODO: Need to implement the fall-back logic in case the main UI in-call service rejects 416 // the binding request. 417 ComponentName inCallUIServiceToBind = orderedInCallUIServices.get(0); 418 if (!bindToInCallService(inCallUIServiceToBind, call, "ui")) { 419 Log.event(call, Log.Events.ERROR_LOG, 420 "InCallService system UI failed binding: " + inCallUIService); 421 } 422 mInCallUIComponentName = inCallUIService; 423 424 // Bind to the control InCallServices 425 for (ComponentName componentName : nonUIInCallServices) { 426 bindToInCallService(componentName, call, "control"); 427 } 428 } 429 430 /** 431 * Binds to the specified InCallService. 432 */ 433 private boolean bindToInCallService(ComponentName componentName, Call call, String tag) { 434 if (mInCallServices.containsKey(componentName)) { 435 Log.i(this, "An InCallService already exists: %s", componentName); 436 return true; 437 } 438 439 if (mServiceConnections.containsKey(componentName)) { 440 Log.w(this, "The service is already bound for this component %s", componentName); 441 return true; 442 } 443 444 Intent intent = new Intent(InCallService.SERVICE_INTERFACE); 445 intent.setComponent(componentName); 446 if (call != null && !call.isIncoming()){ 447 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, 448 call.getIntentExtras()); 449 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, 450 call.getTargetPhoneAccount()); 451 } 452 453 Log.i(this, "Attempting to bind to [%s] InCall %s, with %s", tag, componentName, intent); 454 InCallServiceConnection inCallServiceConnection = new InCallServiceConnection(); 455 if (mContext.bindServiceAsUser(intent, inCallServiceConnection, 456 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, 457 UserHandle.CURRENT)) { 458 mServiceConnections.put(componentName, inCallServiceConnection); 459 return true; 460 } 461 462 return false; 463 } 464 465 private boolean shouldUseCarModeUI() { 466 return mSystemStateProvider.isCarMode(); 467 } 468 469 /** 470 * Returns the type of InCallService described by the specified serviceInfo. 471 */ 472 private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager) { 473 // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which 474 // enforces that only Telecom can bind to it. 475 boolean hasServiceBindPermission = serviceInfo.permission != null && 476 serviceInfo.permission.equals( 477 Manifest.permission.BIND_INCALL_SERVICE); 478 if (!hasServiceBindPermission) { 479 Log.w(this, "InCallService does not require BIND_INCALL_SERVICE permission: " + 480 serviceInfo.packageName); 481 return IN_CALL_SERVICE_TYPE_INVALID; 482 } 483 484 if (mSystemInCallComponentName.getPackageName().equals(serviceInfo.packageName) && 485 mSystemInCallComponentName.getClassName().equals(serviceInfo.name)) { 486 return IN_CALL_SERVICE_TYPE_SYSTEM_UI; 487 } 488 489 // Check to see if the service is a car-mode UI type by checking that it has the 490 // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the 491 // car-mode UI metadata. 492 boolean hasControlInCallPermission = packageManager.checkPermission( 493 Manifest.permission.CONTROL_INCALL_EXPERIENCE, 494 serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED; 495 boolean isCarModeUIService = serviceInfo.metaData != null && 496 serviceInfo.metaData.getBoolean( 497 TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) && 498 hasControlInCallPermission; 499 if (isCarModeUIService) { 500 return IN_CALL_SERVICE_TYPE_CAR_MODE_UI; 501 } 502 503 504 // Check to see that it is the default dialer package 505 boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName, 506 DefaultDialerManager.getDefaultDialerApplication(mContext)); 507 boolean isUIService = serviceInfo.metaData != null && 508 serviceInfo.metaData.getBoolean( 509 TelecomManager.METADATA_IN_CALL_SERVICE_UI, false); 510 if (isDefaultDialerPackage && isUIService) { 511 return IN_CALL_SERVICE_TYPE_DIALER_UI; 512 } 513 514 // Also allow any in-call service that has the control-experience permission (to ensure 515 // that it is a system app) and doesn't claim to show any UI. 516 if (hasControlInCallPermission && !isUIService) { 517 return IN_CALL_SERVICE_TYPE_NON_UI; 518 } 519 520 // Anything else that remains, we will not bind to. 521 Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b", 522 serviceInfo.packageName, serviceInfo.name, hasControlInCallPermission, 523 isCarModeUIService, isUIService); 524 return IN_CALL_SERVICE_TYPE_INVALID; 525 } 526 527 private void adjustServiceBindingsForEmergency() { 528 if (!Objects.equals(mInCallUIComponentName, mSystemInCallComponentName)) { 529 // The connected UI is not the system UI, so lets check if we should switch them 530 // if there exists an emergency number. 531 if (mCallsManager.hasEmergencyCall()) { 532 // Lets fake a failure here in order to trigger the switch to the system UI. 533 onInCallServiceFailure(mInCallUIComponentName, "emergency adjust"); 534 } 535 } 536 } 537 538 /** 539 * Persists the {@link IInCallService} instance and starts the communication between 540 * this class and in-call app by sending the first update to in-call app. This method is 541 * called after a successful binding connection is established. 542 * 543 * @param componentName The service {@link ComponentName}. 544 * @param service The {@link IInCallService} implementation. 545 */ 546 private void onConnected(ComponentName componentName, IBinder service) { 547 Trace.beginSection("onConnected: " + componentName); 548 Log.i(this, "onConnected to %s", componentName); 549 550 IInCallService inCallService = IInCallService.Stub.asInterface(service); 551 mInCallServices.put(componentName, inCallService); 552 553 try { 554 inCallService.setInCallAdapter( 555 new InCallAdapter( 556 mCallsManager, 557 mCallIdMapper, 558 mLock, 559 componentName.getPackageName())); 560 } catch (RemoteException e) { 561 Log.e(this, e, "Failed to set the in-call adapter."); 562 Trace.endSection(); 563 onInCallServiceFailure(componentName, "setInCallAdapter"); 564 return; 565 } 566 567 // Upon successful connection, send the state of the world to the service. 568 Collection<Call> calls = mCallsManager.getCalls(); 569 if (!calls.isEmpty()) { 570 Log.i(this, "Adding %s calls to InCallService after onConnected: %s", calls.size(), 571 componentName); 572 for (Call call : calls) { 573 try { 574 // Track the call if we don't already know about it. 575 addCall(call); 576 inCallService.addCall(toParcelableCall(call, true /* includeVideoProvider */)); 577 } catch (RemoteException ignored) { 578 } 579 } 580 onCallAudioStateChanged( 581 null, 582 mCallsManager.getAudioState()); 583 onCanAddCallChanged(mCallsManager.canAddCall()); 584 } else { 585 unbindFromServices(); 586 } 587 Trace.endSection(); 588 } 589 590 /** 591 * Cleans up an instance of in-call app after the service has been unbound. 592 * 593 * @param disconnectedComponent The {@link ComponentName} of the service which disconnected. 594 */ 595 private void onDisconnected(ComponentName disconnectedComponent) { 596 Log.i(this, "onDisconnected from %s", disconnectedComponent); 597 598 mInCallServices.remove(disconnectedComponent); 599 if (mServiceConnections.containsKey(disconnectedComponent)) { 600 // One of the services that we were bound to has unexpectedly disconnected. 601 onInCallServiceFailure(disconnectedComponent, "onDisconnect"); 602 } 603 } 604 605 /** 606 * Handles non-recoverable failures by the InCallService. This method performs cleanup and 607 * special handling when the failure is to the UI InCallService. 608 */ 609 private void onInCallServiceFailure(ComponentName componentName, String tag) { 610 Log.i(this, "Cleaning up a failed InCallService [%s]: %s", tag, componentName); 611 612 // We always clean up the connections here. Even in the case where we rebind to the UI 613 // because binding is count based and we could end up double-bound. 614 mInCallServices.remove(componentName); 615 InCallServiceConnection serviceConnection = mServiceConnections.remove(componentName); 616 if (serviceConnection != null) { 617 // We still need to call unbind even though it disconnected. 618 mContext.unbindService(serviceConnection); 619 } 620 621 if (Objects.equals(mInCallUIComponentName, componentName)) { 622 if (!mCallsManager.hasAnyCalls()) { 623 // No calls are left anyway. Lets just disconnect all of them. 624 unbindFromServices(); 625 return; 626 } 627 628 // Whenever the UI crashes, we automatically revert to the System UI for the 629 // remainder of the active calls. 630 mInCallUIComponentName = mSystemInCallComponentName; 631 bindToInCallService(mInCallUIComponentName, null, "reconnecting"); 632 } 633 } 634 635 /** 636 * Informs all {@link InCallService} instances of the updated call information. 637 * 638 * @param call The {@link Call}. 639 */ 640 private void updateCall(Call call) { 641 updateCall(call, false /* videoProviderChanged */); 642 } 643 644 /** 645 * Informs all {@link InCallService} instances of the updated call information. 646 * 647 * @param call The {@link Call}. 648 * @param videoProviderChanged {@code true} if the video provider changed, {@code false} 649 * otherwise. 650 */ 651 private void updateCall(Call call, boolean videoProviderChanged) { 652 if (!mInCallServices.isEmpty()) { 653 ParcelableCall parcelableCall = toParcelableCall(call, 654 videoProviderChanged /* includeVideoProvider */); 655 Log.i(this, "Sending updateCall %s ==> %s", call, parcelableCall); 656 List<ComponentName> componentsUpdated = new ArrayList<>(); 657 for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) { 658 ComponentName componentName = entry.getKey(); 659 IInCallService inCallService = entry.getValue(); 660 componentsUpdated.add(componentName); 661 try { 662 inCallService.updateCall(parcelableCall); 663 } catch (RemoteException ignored) { 664 } 665 } 666 Log.i(this, "Components updated: %s", componentsUpdated); 667 } 668 } 669 670 /** 671 * Parcels all information for a {@link Call} into a new {@link ParcelableCall} instance. 672 * 673 * @param call The {@link Call} to parcel. 674 * @param includeVideoProvider {@code true} if the video provider should be parcelled with the 675 * {@link Call}, {@code false} otherwise. Since the {@link ParcelableCall#getVideoCall()} 676 * method creates a {@link VideoCallImpl} instance on access it is important for the 677 * recipient of the {@link ParcelableCall} to know if the video provider changed. 678 * @return The {@link ParcelableCall} containing all call information from the {@link Call}. 679 */ 680 private ParcelableCall toParcelableCall(Call call, boolean includeVideoProvider) { 681 String callId = mCallIdMapper.getCallId(call); 682 683 int state = getParcelableState(call); 684 int capabilities = convertConnectionToCallCapabilities(call.getConnectionCapabilities()); 685 int properties = convertConnectionToCallProperties(call.getConnectionCapabilities()); 686 if (call.isConference()) { 687 properties |= android.telecom.Call.Details.PROPERTY_CONFERENCE; 688 } 689 690 final PhoneAccountRegistrar phoneAccountRegistrar = 691 mCallsManager.getPhoneAccountRegistrar(); 692 693 if (call.isWorkCall(phoneAccountRegistrar)) { 694 properties |= android.telecom.Call.Details.PROPERTY_WORK_CALL; 695 } 696 697 // If this is a single-SIM device, the "default SIM" will always be the only SIM. 698 boolean isDefaultSmsAccount = 699 phoneAccountRegistrar.isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount()); 700 if (call.isRespondViaSmsCapable() && isDefaultSmsAccount) { 701 capabilities |= android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT; 702 } 703 704 if (call.isEmergencyCall()) { 705 capabilities = removeCapability( 706 capabilities, android.telecom.Call.Details.CAPABILITY_MUTE); 707 } 708 709 if (state == android.telecom.Call.STATE_DIALING) { 710 capabilities = removeCapability(capabilities, 711 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL); 712 capabilities = removeCapability(capabilities, 713 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL); 714 } 715 716 String parentCallId = null; 717 Call parentCall = call.getParentCall(); 718 if (parentCall != null) { 719 parentCallId = mCallIdMapper.getCallId(parentCall); 720 } 721 722 long connectTimeMillis = call.getConnectTimeMillis(); 723 List<Call> childCalls = call.getChildCalls(); 724 List<String> childCallIds = new ArrayList<>(); 725 if (!childCalls.isEmpty()) { 726 long childConnectTimeMillis = Long.MAX_VALUE; 727 for (Call child : childCalls) { 728 if (child.getConnectTimeMillis() > 0) { 729 childConnectTimeMillis = Math.min(child.getConnectTimeMillis(), 730 childConnectTimeMillis); 731 } 732 childCallIds.add(mCallIdMapper.getCallId(child)); 733 } 734 735 if (childConnectTimeMillis != Long.MAX_VALUE) { 736 connectTimeMillis = childConnectTimeMillis; 737 } 738 } 739 740 Uri handle = call.getHandlePresentation() == TelecomManager.PRESENTATION_ALLOWED ? 741 call.getHandle() : null; 742 String callerDisplayName = call.getCallerDisplayNamePresentation() == 743 TelecomManager.PRESENTATION_ALLOWED ? call.getCallerDisplayName() : null; 744 745 List<Call> conferenceableCalls = call.getConferenceableCalls(); 746 List<String> conferenceableCallIds = new ArrayList<String>(conferenceableCalls.size()); 747 for (Call otherCall : conferenceableCalls) { 748 String otherId = mCallIdMapper.getCallId(otherCall); 749 if (otherId != null) { 750 conferenceableCallIds.add(otherId); 751 } 752 } 753 754 return new ParcelableCall( 755 callId, 756 state, 757 call.getDisconnectCause(), 758 call.getCannedSmsResponses(), 759 capabilities, 760 properties, 761 connectTimeMillis, 762 handle, 763 call.getHandlePresentation(), 764 callerDisplayName, 765 call.getCallerDisplayNamePresentation(), 766 call.getGatewayInfo(), 767 call.getTargetPhoneAccount(), 768 includeVideoProvider, 769 includeVideoProvider ? call.getVideoProvider() : null, 770 parentCallId, 771 childCallIds, 772 call.getStatusHints(), 773 call.getVideoState(), 774 conferenceableCallIds, 775 call.getIntentExtras(), 776 call.getExtras()); 777 } 778 779 private static int getParcelableState(Call call) { 780 int state = CallState.NEW; 781 switch (call.getState()) { 782 case CallState.ABORTED: 783 case CallState.DISCONNECTED: 784 state = android.telecom.Call.STATE_DISCONNECTED; 785 break; 786 case CallState.ACTIVE: 787 state = android.telecom.Call.STATE_ACTIVE; 788 break; 789 case CallState.CONNECTING: 790 state = android.telecom.Call.STATE_CONNECTING; 791 break; 792 case CallState.DIALING: 793 state = android.telecom.Call.STATE_DIALING; 794 break; 795 case CallState.DISCONNECTING: 796 state = android.telecom.Call.STATE_DISCONNECTING; 797 break; 798 case CallState.NEW: 799 state = android.telecom.Call.STATE_NEW; 800 break; 801 case CallState.ON_HOLD: 802 state = android.telecom.Call.STATE_HOLDING; 803 break; 804 case CallState.RINGING: 805 state = android.telecom.Call.STATE_RINGING; 806 break; 807 case CallState.SELECT_PHONE_ACCOUNT: 808 state = android.telecom.Call.STATE_SELECT_PHONE_ACCOUNT; 809 break; 810 } 811 812 // If we are marked as 'locally disconnecting' then mark ourselves as disconnecting instead. 813 // Unless we're disconnect*ED*, in which case leave it at that. 814 if (call.isLocallyDisconnecting() && 815 (state != android.telecom.Call.STATE_DISCONNECTED)) { 816 state = android.telecom.Call.STATE_DISCONNECTING; 817 } 818 return state; 819 } 820 821 private static final int[] CONNECTION_TO_CALL_CAPABILITY = new int[] { 822 Connection.CAPABILITY_HOLD, 823 android.telecom.Call.Details.CAPABILITY_HOLD, 824 825 Connection.CAPABILITY_SUPPORT_HOLD, 826 android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD, 827 828 Connection.CAPABILITY_MERGE_CONFERENCE, 829 android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE, 830 831 Connection.CAPABILITY_SWAP_CONFERENCE, 832 android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE, 833 834 Connection.CAPABILITY_RESPOND_VIA_TEXT, 835 android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT, 836 837 Connection.CAPABILITY_MUTE, 838 android.telecom.Call.Details.CAPABILITY_MUTE, 839 840 Connection.CAPABILITY_MANAGE_CONFERENCE, 841 android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE, 842 843 Connection.CAPABILITY_SUPPORTS_VT_LOCAL_RX, 844 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_RX, 845 846 Connection.CAPABILITY_SUPPORTS_VT_LOCAL_TX, 847 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX, 848 849 Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL, 850 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL, 851 852 Connection.CAPABILITY_SUPPORTS_VT_REMOTE_RX, 853 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX, 854 855 Connection.CAPABILITY_SUPPORTS_VT_REMOTE_TX, 856 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_TX, 857 858 Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, 859 android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, 860 861 Connection.CAPABILITY_SEPARATE_FROM_CONFERENCE, 862 android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE, 863 864 Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE, 865 android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE, 866 867 Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO, 868 android.telecom.Call.Details.CAPABILITY_CAN_UPGRADE_TO_VIDEO, 869 870 Connection.CAPABILITY_CAN_PAUSE_VIDEO, 871 android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO, 872 873 Connection.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION, 874 android.telecom.Call.Details.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION 875 }; 876 877 private static int convertConnectionToCallCapabilities(int connectionCapabilities) { 878 int callCapabilities = 0; 879 for (int i = 0; i < CONNECTION_TO_CALL_CAPABILITY.length; i += 2) { 880 if ((CONNECTION_TO_CALL_CAPABILITY[i] & connectionCapabilities) == 881 CONNECTION_TO_CALL_CAPABILITY[i]) { 882 883 callCapabilities |= CONNECTION_TO_CALL_CAPABILITY[i + 1]; 884 } 885 } 886 return callCapabilities; 887 } 888 889 private static final int[] CONNECTION_TO_CALL_PROPERTIES = new int[] { 890 Connection.CAPABILITY_HIGH_DEF_AUDIO, 891 android.telecom.Call.Details.PROPERTY_HIGH_DEF_AUDIO, 892 893 Connection.CAPABILITY_WIFI, 894 android.telecom.Call.Details.PROPERTY_WIFI, 895 896 Connection.CAPABILITY_GENERIC_CONFERENCE, 897 android.telecom.Call.Details.PROPERTY_GENERIC_CONFERENCE, 898 899 Connection.CAPABILITY_SHOW_CALLBACK_NUMBER, 900 android.telecom.Call.Details.PROPERTY_EMERGENCY_CALLBACK_MODE, 901 }; 902 903 private static int convertConnectionToCallProperties(int connectionCapabilities) { 904 int callProperties = 0; 905 for (int i = 0; i < CONNECTION_TO_CALL_PROPERTIES.length; i += 2) { 906 if ((CONNECTION_TO_CALL_PROPERTIES[i] & connectionCapabilities) == 907 CONNECTION_TO_CALL_PROPERTIES[i]) { 908 909 callProperties |= CONNECTION_TO_CALL_PROPERTIES[i + 1]; 910 } 911 } 912 return callProperties; 913 } 914 915 /** 916 * Adds the call to the list of calls tracked by the {@link InCallController}. 917 * @param call The call to add. 918 */ 919 private void addCall(Call call) { 920 if (mCallIdMapper.getCallId(call) == null) { 921 mCallIdMapper.addCall(call); 922 call.addListener(mCallListener); 923 } 924 } 925 926 private boolean isBoundToServices() { 927 return !mInCallServices.isEmpty(); 928 } 929 930 /** 931 * Removes the specified capability from the set of capabilities bits and returns the new set. 932 */ 933 private static int removeCapability(int capabilities, int capability) { 934 return capabilities & ~capability; 935 } 936 937 /** 938 * Dumps the state of the {@link InCallController}. 939 * 940 * @param pw The {@code IndentingPrintWriter} to write the state to. 941 */ 942 public void dump(IndentingPrintWriter pw) { 943 pw.println("mInCallServices (InCalls registered):"); 944 pw.increaseIndent(); 945 for (ComponentName componentName : mInCallServices.keySet()) { 946 pw.println(componentName); 947 } 948 pw.decreaseIndent(); 949 950 pw.println("mServiceConnections (InCalls bound):"); 951 pw.increaseIndent(); 952 for (ComponentName componentName : mServiceConnections.keySet()) { 953 pw.println(componentName); 954 } 955 pw.decreaseIndent(); 956 } 957} 958