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