InCallController.java revision 9ecbb1cbae4dbb62a892c1347d8cb8691550ad1b
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.os.Bundle; 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.CallAudioState; 36import android.telecom.ConnectionService; 37import android.telecom.DefaultDialerManager; 38import android.telecom.InCallService; 39import android.telecom.ParcelableCall; 40import android.telecom.TelecomManager; 41import android.text.TextUtils; 42import android.util.ArrayMap; 43 44import com.android.internal.annotations.VisibleForTesting; 45// TODO: Needed for move to system service: import com.android.internal.R; 46import com.android.internal.telecom.IInCallService; 47import com.android.internal.util.IndentingPrintWriter; 48import com.android.server.telecom.SystemStateProvider.SystemStateListener; 49import com.android.server.telecom.TelecomServiceImpl.DefaultDialerManagerAdapter; 50 51import java.util.ArrayList; 52import java.util.Collection; 53import java.util.Iterator; 54import java.util.LinkedList; 55import java.util.List; 56import java.util.Map; 57import java.util.Objects; 58import java.util.concurrent.ConcurrentHashMap; 59 60/** 61 * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it 62 * can send updates to the in-call app. This class is created and owned by CallsManager and retains 63 * a binding to the {@link IInCallService} (implemented by the in-call app). 64 */ 65public final class InCallController extends CallsManagerListenerBase { 66 67 public class InCallServiceConnection { 68 public class Listener { 69 public void onDisconnect(InCallServiceConnection conn) {} 70 } 71 72 protected Listener mListener; 73 74 public boolean connect(Call call) { return false; } 75 public void disconnect() {} 76 public void setHasEmergency(boolean hasEmergency) {} 77 public void setListener(Listener l) { 78 mListener = l; 79 } 80 public void dump(IndentingPrintWriter pw) {} 81 } 82 83 private class InCallServiceBindingConnection extends InCallServiceConnection { 84 85 private final ServiceConnection mServiceConnection = new ServiceConnection() { 86 @Override 87 public void onServiceConnected(ComponentName name, IBinder service) { 88 Log.startSession("ICSBC.oSC"); 89 try { 90 Log.d(this, "onServiceConnected: %s %b %b", name, mIsBound, mIsConnected); 91 mIsBound = true; 92 if (mIsConnected) { 93 // Only proceed if we are supposed to be connected. 94 onConnected(service); 95 } 96 } finally { 97 Log.endSession(); 98 } 99 } 100 101 @Override 102 public void onServiceDisconnected(ComponentName name) { 103 Log.startSession("ICSBC.oSD"); 104 try { 105 Log.d(this, "onDisconnected: %s", name); 106 mIsBound = false; 107 onDisconnected(); 108 } finally { 109 Log.endSession(); 110 } 111 } 112 }; 113 114 private final ComponentName mComponentName; 115 private boolean mIsConnected = false; 116 private boolean mIsBound = false; 117 118 public InCallServiceBindingConnection(ComponentName componentName) { 119 mComponentName = componentName; 120 } 121 122 @Override 123 public boolean connect(Call call) { 124 if (mIsConnected) { 125 Log.event(call, Log.Events.INFO, "Already connected, ignoring request."); 126 return true; 127 } 128 129 Intent intent = new Intent(InCallService.SERVICE_INTERFACE); 130 intent.setComponent(mComponentName); 131 if (call != null && !call.isIncoming() && !call.isExternalCall()){ 132 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, 133 call.getIntentExtras()); 134 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, 135 call.getTargetPhoneAccount()); 136 } 137 138 Log.i(this, "Attempting to bind to InCall %s, with %s", mComponentName, intent); 139 mIsConnected = true; 140 if (!mContext.bindServiceAsUser(intent, mServiceConnection, 141 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, 142 UserHandle.CURRENT)) { 143 Log.w(this, "Failed to connect."); 144 mIsConnected = false; 145 } 146 147 return mIsConnected; 148 } 149 150 @Override 151 public void disconnect() { 152 if (mIsConnected) { 153 mContext.unbindService(mServiceConnection); 154 mIsConnected = false; 155 } else { 156 Log.event(null, Log.Events.INFO, "Already disconnected, ignoring request."); 157 } 158 } 159 160 @Override 161 public void dump(IndentingPrintWriter pw) { 162 pw.append("BindingConnection ["); 163 pw.append(mIsConnected ? "" : "not ").append("connected, "); 164 pw.append(mIsBound ? "" : "not ").append("bound]\n"); 165 } 166 167 protected void onConnected(IBinder service) { 168 boolean shouldRemainConnected = 169 InCallController.this.onConnected(mComponentName, service); 170 if (!shouldRemainConnected) { 171 // Sometimes we can opt to disconnect for certain reasons, like if the 172 // InCallService rejected our intialization step, or the calls went away 173 // in the time it took us to bind to the InCallService. In such cases, we go 174 // ahead and disconnect ourselves. 175 disconnect(); 176 } 177 } 178 179 protected void onDisconnected() { 180 InCallController.this.onDisconnected(mComponentName); 181 disconnect(); // Unbind explicitly if we get disconnected. 182 if (mListener != null) { 183 mListener.onDisconnect(InCallServiceBindingConnection.this); 184 } 185 } 186 } 187 188 /** 189 * A version of the InCallServiceBindingConnection that proxies all calls to a secondary 190 * connection until it finds an emergency call, or the other connection dies. When one of those 191 * two things happen, this class instance will take over the connection. 192 */ 193 private class EmergencyInCallServiceConnection extends InCallServiceBindingConnection { 194 private boolean mIsProxying = true; 195 private boolean mIsConnected = false; 196 private final InCallServiceConnection mSubConnection; 197 198 private Listener mSubListener = new Listener() { 199 @Override 200 public void onDisconnect(InCallServiceConnection subConnection) { 201 if (subConnection == mSubConnection) { 202 if (mIsConnected && mIsProxying) { 203 // At this point we know that we need to be connected to the InCallService 204 // and we are proxying to the sub connection. However, the sub-connection 205 // just died so we need to stop proxying and connect to the system in-call 206 // service instead. 207 mIsProxying = false; 208 connect(null); 209 } 210 } 211 } 212 }; 213 214 public EmergencyInCallServiceConnection( 215 ComponentName componentName, InCallServiceConnection subConnection) { 216 super(componentName); 217 mSubConnection = subConnection; 218 if (mSubConnection != null) { 219 mSubConnection.setListener(mSubListener); 220 } 221 mIsProxying = (mSubConnection != null); 222 } 223 224 @Override 225 public boolean connect(Call call) { 226 mIsConnected = true; 227 if (mIsProxying) { 228 if (mSubConnection.connect(call)) { 229 return true; 230 } 231 // Could not connect to child, stop proxying. 232 mIsProxying = false; 233 } 234 235 // If we are here, we didn't or could not connect to child. So lets connect ourselves. 236 return super.connect(call); 237 } 238 239 @Override 240 public void disconnect() { 241 Log.i(this, "Disconnect forced!"); 242 if (mIsProxying) { 243 mSubConnection.disconnect(); 244 } else { 245 super.disconnect(); 246 } 247 mIsConnected = false; 248 } 249 250 @Override 251 public void setHasEmergency(boolean hasEmergency) { 252 if (hasEmergency) { 253 takeControl(); 254 } 255 } 256 257 @Override 258 protected void onDisconnected() { 259 // Save this here because super.onDisconnected() could force us to explicitly 260 // disconnect() as a cleanup step and that sets mIsConnected to false. 261 boolean shouldReconnect = mIsConnected; 262 super.onDisconnected(); 263 // We just disconnected. Check if we are expected to be connected, and reconnect. 264 if (shouldReconnect && !mIsProxying) { 265 connect(null); // reconnect 266 } 267 } 268 269 @Override 270 public void dump(IndentingPrintWriter pw) { 271 pw.println("Emergency ICS Connection"); 272 pw.increaseIndent(); 273 pw.print("Emergency: "); 274 super.dump(pw); 275 if (mSubConnection != null) { 276 pw.print("Default-Dialer: "); 277 mSubConnection.dump(pw); 278 } 279 pw.decreaseIndent(); 280 } 281 282 /** 283 * Forces the connection to take control from it's subConnection. 284 */ 285 private void takeControl() { 286 if (mIsProxying) { 287 mIsProxying = false; 288 if (mIsConnected) { 289 mSubConnection.disconnect(); 290 super.connect(null); 291 } 292 } 293 } 294 } 295 296 /** 297 * A version of InCallServiceConnection which switches UI between two separate sub-instances of 298 * InCallServicesConnections. 299 */ 300 private class CarSwappingInCallServiceConnection extends InCallServiceConnection { 301 private final InCallServiceConnection mDialerConnection; 302 private final InCallServiceConnection mCarModeConnection; 303 private InCallServiceConnection mCurrentConnection; 304 private boolean mIsCarMode = false; 305 private boolean mIsConnected = false; 306 307 public CarSwappingInCallServiceConnection( 308 InCallServiceConnection dialerConnection, 309 InCallServiceConnection carModeConnection) { 310 mDialerConnection = dialerConnection; 311 mCarModeConnection = carModeConnection; 312 mCurrentConnection = getCurrentConnection(); 313 } 314 315 public synchronized void setCarMode(boolean isCarMode) { 316 Log.i(this, "carmodechange: " + mIsCarMode + " => " + isCarMode); 317 if (isCarMode != mIsCarMode) { 318 mIsCarMode = isCarMode; 319 InCallServiceConnection newConnection = getCurrentConnection(); 320 if (newConnection != mCurrentConnection) { 321 if (mIsConnected) { 322 mCurrentConnection.disconnect(); 323 newConnection.connect(null); 324 } 325 mCurrentConnection = newConnection; 326 } 327 } 328 } 329 330 @Override 331 public boolean connect(Call call) { 332 if (mIsConnected) { 333 Log.i(this, "already connected"); 334 return true; 335 } else { 336 if (mCurrentConnection.connect(call)) { 337 mIsConnected = true; 338 return true; 339 } 340 } 341 342 return false; 343 } 344 345 @Override 346 public void disconnect() { 347 if (mIsConnected) { 348 mCurrentConnection.disconnect(); 349 mIsConnected = false; 350 } else { 351 Log.i(this, "already disconnected"); 352 } 353 } 354 355 @Override 356 public void setHasEmergency(boolean hasEmergency) { 357 if (mDialerConnection != null) { 358 mDialerConnection.setHasEmergency(hasEmergency); 359 } 360 if (mCarModeConnection != null) { 361 mCarModeConnection.setHasEmergency(hasEmergency); 362 } 363 } 364 365 @Override 366 public void dump(IndentingPrintWriter pw) { 367 pw.println("Car Swapping ICS"); 368 pw.increaseIndent(); 369 if (mDialerConnection != null) { 370 pw.print("Dialer: "); 371 mDialerConnection.dump(pw); 372 } 373 if (mCarModeConnection != null) { 374 pw.print("Car Mode: "); 375 mCarModeConnection.dump(pw); 376 } 377 } 378 379 private InCallServiceConnection getCurrentConnection() { 380 if (mIsCarMode && mCarModeConnection != null) { 381 return mCarModeConnection; 382 } else { 383 return mDialerConnection; 384 } 385 } 386 } 387 388 private class NonUIInCallServiceConnectionCollection extends InCallServiceConnection { 389 private final List<InCallServiceBindingConnection> mSubConnections; 390 391 public NonUIInCallServiceConnectionCollection( 392 List<InCallServiceBindingConnection> subConnections) { 393 mSubConnections = subConnections; 394 } 395 396 @Override 397 public boolean connect(Call call) { 398 for (InCallServiceBindingConnection subConnection : mSubConnections) { 399 subConnection.connect(call); 400 } 401 return true; 402 } 403 404 @Override 405 public void disconnect() { 406 for (InCallServiceBindingConnection subConnection : mSubConnections) { 407 subConnection.disconnect(); 408 } 409 } 410 411 @Override 412 public void dump(IndentingPrintWriter pw) { 413 pw.println("Non-UI Connections:"); 414 pw.increaseIndent(); 415 for (InCallServiceBindingConnection subConnection : mSubConnections) { 416 subConnection.dump(pw); 417 } 418 pw.decreaseIndent(); 419 } 420 } 421 422 private final Call.Listener mCallListener = new Call.ListenerBase() { 423 @Override 424 public void onConnectionCapabilitiesChanged(Call call) { 425 updateCall(call); 426 } 427 428 @Override 429 public void onConnectionPropertiesChanged(Call call) { 430 updateCall(call); 431 } 432 433 @Override 434 public void onCannedSmsResponsesLoaded(Call call) { 435 updateCall(call); 436 } 437 438 @Override 439 public void onVideoCallProviderChanged(Call call) { 440 updateCall(call, true /* videoProviderChanged */); 441 } 442 443 @Override 444 public void onStatusHintsChanged(Call call) { 445 updateCall(call); 446 } 447 448 /** 449 * Listens for changes to extras reported by a Telecom {@link Call}. 450 * 451 * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService} 452 * so we will only trigger an update of the call information if the source of the extras 453 * change was a {@link ConnectionService}. 454 * 455 * @param call The call. 456 * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or 457 * {@link Call#SOURCE_INCALL_SERVICE}). 458 * @param extras The extras. 459 */ 460 @Override 461 public void onExtrasChanged(Call call, int source, Bundle extras) { 462 // Do not inform InCallServices of changes which originated there. 463 if (source == Call.SOURCE_INCALL_SERVICE) { 464 return; 465 } 466 updateCall(call); 467 } 468 469 /** 470 * Listens for changes to extras reported by a Telecom {@link Call}. 471 * 472 * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService} 473 * so we will only trigger an update of the call information if the source of the extras 474 * change was a {@link ConnectionService}. 475 * @param call The call. 476 * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or 477 * {@link Call#SOURCE_INCALL_SERVICE}). 478 * @param keys The extra key removed 479 */ 480 @Override 481 public void onExtrasRemoved(Call call, int source, List<String> keys) { 482 // Do not inform InCallServices of changes which originated there. 483 if (source == Call.SOURCE_INCALL_SERVICE) { 484 return; 485 } 486 updateCall(call); 487 } 488 489 @Override 490 public void onHandleChanged(Call call) { 491 updateCall(call); 492 } 493 494 @Override 495 public void onCallerDisplayNameChanged(Call call) { 496 updateCall(call); 497 } 498 499 @Override 500 public void onVideoStateChanged(Call call) { 501 updateCall(call); 502 } 503 504 @Override 505 public void onTargetPhoneAccountChanged(Call call) { 506 updateCall(call); 507 } 508 509 @Override 510 public void onConferenceableCallsChanged(Call call) { 511 updateCall(call); 512 } 513 514 @Override 515 public void onConnectionEvent(Call call, String event, Bundle extras) { 516 notifyConnectionEvent(call, event, extras); 517 } 518 }; 519 520 private final SystemStateListener mSystemStateListener = new SystemStateListener() { 521 @Override 522 public void onCarModeChanged(boolean isCarMode) { 523 if (mInCallServiceConnection != null) { 524 mInCallServiceConnection.setCarMode(shouldUseCarModeUI()); 525 } 526 } 527 }; 528 529 private static final int IN_CALL_SERVICE_TYPE_INVALID = 0; 530 private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1; 531 private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2; 532 private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3; 533 private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4; 534 535 /** The in-call app implementations, see {@link IInCallService}. */ 536 private final Map<ComponentName, IInCallService> mInCallServices = new ArrayMap<>(); 537 538 /** 539 * The {@link ComponentName} of the bound In-Call UI Service. 540 */ 541 private ComponentName mInCallUIComponentName; 542 543 private final CallIdMapper mCallIdMapper = new CallIdMapper(); 544 545 /** The {@link ComponentName} of the default InCall UI. */ 546 private final ComponentName mSystemInCallComponentName; 547 548 private final Context mContext; 549 private final TelecomSystem.SyncRoot mLock; 550 private final CallsManager mCallsManager; 551 private final SystemStateProvider mSystemStateProvider; 552 private final DefaultDialerManagerAdapter mDefaultDialerAdapter; 553 private CarSwappingInCallServiceConnection mInCallServiceConnection; 554 private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections; 555 556 public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, 557 SystemStateProvider systemStateProvider, 558 DefaultDialerManagerAdapter defaultDialerAdapter) { 559 mContext = context; 560 mLock = lock; 561 mCallsManager = callsManager; 562 mSystemStateProvider = systemStateProvider; 563 mDefaultDialerAdapter = defaultDialerAdapter; 564 565 Resources resources = mContext.getResources(); 566 mSystemInCallComponentName = new ComponentName( 567 resources.getString(R.string.ui_default_package), 568 resources.getString(R.string.incall_default_class)); 569 570 mSystemStateProvider.addListener(mSystemStateListener); 571 } 572 573 @Override 574 public void onCallAdded(Call call) { 575 if (!isBoundToServices()) { 576 bindToServices(call); 577 } else { 578 adjustServiceBindingsForEmergency(); 579 580 Log.i(this, "onCallAdded: %s", call); 581 // Track the call if we don't already know about it. 582 addCall(call); 583 584 for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) { 585 ComponentName componentName = entry.getKey(); 586 IInCallService inCallService = entry.getValue(); 587 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call, 588 true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar()); 589 try { 590 inCallService.addCall(parcelableCall); 591 } catch (RemoteException ignored) { 592 } 593 } 594 } 595 } 596 597 @Override 598 public void onCallRemoved(Call call) { 599 Log.i(this, "onCallRemoved: %s", call); 600 if (mCallsManager.getCalls().isEmpty()) { 601 /** Let's add a 2 second delay before we send unbind to the services to hopefully 602 * give them enough time to process all the pending messages. 603 */ 604 Handler handler = new Handler(Looper.getMainLooper()); 605 handler.postDelayed(new Runnable("ICC.oCR") { 606 @Override 607 public void loggedRun() { 608 synchronized (mLock) { 609 // Check again to make sure there are no active calls. 610 if (mCallsManager.getCalls().isEmpty()) { 611 unbindFromServices(); 612 } 613 } 614 } 615 }.prepare(), Timeouts.getCallRemoveUnbindInCallServicesDelay( 616 mContext.getContentResolver())); 617 } 618 call.removeListener(mCallListener); 619 mCallIdMapper.removeCall(call); 620 } 621 622 @Override 623 public void onCallStateChanged(Call call, int oldState, int newState) { 624 updateCall(call); 625 } 626 627 @Override 628 public void onConnectionServiceChanged( 629 Call call, 630 ConnectionServiceWrapper oldService, 631 ConnectionServiceWrapper newService) { 632 updateCall(call); 633 } 634 635 @Override 636 public void onCallAudioStateChanged(CallAudioState oldCallAudioState, 637 CallAudioState newCallAudioState) { 638 if (!mInCallServices.isEmpty()) { 639 Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState, 640 newCallAudioState); 641 for (IInCallService inCallService : mInCallServices.values()) { 642 try { 643 inCallService.onCallAudioStateChanged(newCallAudioState); 644 } catch (RemoteException ignored) { 645 } 646 } 647 } 648 } 649 650 @Override 651 public void onCanAddCallChanged(boolean canAddCall) { 652 if (!mInCallServices.isEmpty()) { 653 Log.i(this, "onCanAddCallChanged : %b", canAddCall); 654 for (IInCallService inCallService : mInCallServices.values()) { 655 try { 656 inCallService.onCanAddCallChanged(canAddCall); 657 } catch (RemoteException ignored) { 658 } 659 } 660 } 661 } 662 663 void onPostDialWait(Call call, String remaining) { 664 if (!mInCallServices.isEmpty()) { 665 Log.i(this, "Calling onPostDialWait, remaining = %s", remaining); 666 for (IInCallService inCallService : mInCallServices.values()) { 667 try { 668 inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining); 669 } catch (RemoteException ignored) { 670 } 671 } 672 } 673 } 674 675 @Override 676 public void onIsConferencedChanged(Call call) { 677 Log.d(this, "onIsConferencedChanged %s", call); 678 updateCall(call); 679 } 680 681 void bringToForeground(boolean showDialpad) { 682 if (!mInCallServices.isEmpty()) { 683 for (IInCallService inCallService : mInCallServices.values()) { 684 try { 685 inCallService.bringToForeground(showDialpad); 686 } catch (RemoteException ignored) { 687 } 688 } 689 } else { 690 Log.w(this, "Asking to bring unbound in-call UI to foreground."); 691 } 692 } 693 694 void silenceRinger() { 695 if (!mInCallServices.isEmpty()) { 696 for (IInCallService inCallService : mInCallServices.values()) { 697 try { 698 inCallService.silenceRinger(); 699 } catch (RemoteException ignored) { 700 } 701 } 702 } 703 } 704 705 private void notifyConnectionEvent(Call call, String event, Bundle extras) { 706 if (!mInCallServices.isEmpty()) { 707 for (IInCallService inCallService : mInCallServices.values()) { 708 try { 709 inCallService.onConnectionEvent(mCallIdMapper.getCallId(call), event, extras); 710 } catch (RemoteException ignored) { 711 } 712 } 713 } 714 } 715 716 /** 717 * Unbinds an existing bound connection to the in-call app. 718 */ 719 private void unbindFromServices() { 720 if (isBoundToServices()) { 721 mInCallServiceConnection.disconnect(); 722 mInCallServiceConnection = null; 723 mNonUIInCallServiceConnections.disconnect(); 724 mNonUIInCallServiceConnections = null; 725 } 726 } 727 728 /** 729 * Binds to all the UI-providing InCallService as well as system-implemented non-UI 730 * InCallServices. Method-invoker must check {@link #isBoundToServices()} before invoking. 731 * 732 * @param call The newly added call that triggered the binding to the in-call services. 733 */ 734 @VisibleForTesting 735 public void bindToServices(Call call) { 736 InCallServiceConnection dialerInCall = null; 737 ComponentName defaultDialerComponent = getDefaultDialerComponent(); 738 Log.i(this, "defaultDialer: " + defaultDialerComponent); 739 if (defaultDialerComponent != null && 740 !defaultDialerComponent.equals(mSystemInCallComponentName)) { 741 dialerInCall = new InCallServiceBindingConnection(defaultDialerComponent); 742 } 743 Log.i(this, "defaultDialer: " + dialerInCall); 744 745 EmergencyInCallServiceConnection systemInCall = 746 new EmergencyInCallServiceConnection(mSystemInCallComponentName, dialerInCall); 747 systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall()); 748 749 InCallServiceConnection carModeInCall = null; 750 ComponentName carModeComponent = getCarModeComponent(); 751 if (carModeComponent != null && 752 !carModeComponent.equals(mSystemInCallComponentName)) { 753 carModeInCall = new InCallServiceBindingConnection(carModeComponent); 754 } 755 756 mInCallServiceConnection = 757 new CarSwappingInCallServiceConnection(systemInCall, carModeInCall); 758 mInCallServiceConnection.setCarMode(shouldUseCarModeUI()); 759 mInCallServiceConnection.connect(call); 760 761 762 List<ComponentName> nonUIInCallComponents = 763 getInCallServiceComponents(null, IN_CALL_SERVICE_TYPE_NON_UI); 764 List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>(); 765 for (ComponentName componentName : nonUIInCallComponents) { 766 nonUIInCalls.add(new InCallServiceBindingConnection(componentName)); 767 } 768 mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls); 769 mNonUIInCallServiceConnections.connect(call); 770 } 771 772 private ComponentName getDefaultDialerComponent() { 773 String packageName = mDefaultDialerAdapter.getDefaultDialerApplication( 774 mContext, mCallsManager.getCurrentUserHandle().getIdentifier()); 775 Log.d(this, "Default Dialer package: " + packageName); 776 777 return getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI); 778 } 779 780 private ComponentName getCarModeComponent() { 781 return getInCallServiceComponent(null, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); 782 } 783 784 private ComponentName getInCallServiceComponent(String packageName, int type) { 785 List<ComponentName> list = getInCallServiceComponents(packageName, type); 786 if (list != null && !list.isEmpty()) { 787 return list.get(0); 788 } 789 return null; 790 } 791 792 private List<ComponentName> getInCallServiceComponents(String packageName, int type) { 793 List<ComponentName> retval = new LinkedList<>(); 794 795 Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE); 796 if (packageName != null) { 797 serviceIntent.setPackage(packageName); 798 } 799 800 PackageManager packageManager = mContext.getPackageManager(); 801 for (ResolveInfo entry : packageManager.queryIntentServicesAsUser( 802 serviceIntent, 803 PackageManager.GET_META_DATA, 804 mCallsManager.getCurrentUserHandle().getIdentifier())) { 805 ServiceInfo serviceInfo = entry.serviceInfo; 806 807 if (serviceInfo != null) { 808 if (type == 0 || type == getInCallServiceType(entry.serviceInfo, packageManager)) { 809 retval.add(new ComponentName(serviceInfo.packageName, serviceInfo.name)); 810 } 811 } 812 } 813 814 return retval; 815 } 816 817 private boolean shouldUseCarModeUI() { 818 return mSystemStateProvider.isCarMode(); 819 } 820 821 /** 822 * Returns the type of InCallService described by the specified serviceInfo. 823 */ 824 private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager) { 825 // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which 826 // enforces that only Telecom can bind to it. 827 boolean hasServiceBindPermission = serviceInfo.permission != null && 828 serviceInfo.permission.equals( 829 Manifest.permission.BIND_INCALL_SERVICE); 830 if (!hasServiceBindPermission) { 831 Log.w(this, "InCallService does not require BIND_INCALL_SERVICE permission: " + 832 serviceInfo.packageName); 833 return IN_CALL_SERVICE_TYPE_INVALID; 834 } 835 836 if (mSystemInCallComponentName.getPackageName().equals(serviceInfo.packageName) && 837 mSystemInCallComponentName.getClassName().equals(serviceInfo.name)) { 838 return IN_CALL_SERVICE_TYPE_SYSTEM_UI; 839 } 840 841 // Check to see if the service is a car-mode UI type by checking that it has the 842 // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the 843 // car-mode UI metadata. 844 boolean hasControlInCallPermission = packageManager.checkPermission( 845 Manifest.permission.CONTROL_INCALL_EXPERIENCE, 846 serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED; 847 boolean isCarModeUIService = serviceInfo.metaData != null && 848 serviceInfo.metaData.getBoolean( 849 TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) && 850 hasControlInCallPermission; 851 if (isCarModeUIService) { 852 return IN_CALL_SERVICE_TYPE_CAR_MODE_UI; 853 } 854 855 856 // Check to see that it is the default dialer package 857 boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName, 858 mDefaultDialerAdapter.getDefaultDialerApplication( 859 mContext, mCallsManager.getCurrentUserHandle().getIdentifier())); 860 boolean isUIService = serviceInfo.metaData != null && 861 serviceInfo.metaData.getBoolean( 862 TelecomManager.METADATA_IN_CALL_SERVICE_UI, false); 863 if (isDefaultDialerPackage && isUIService) { 864 return IN_CALL_SERVICE_TYPE_DIALER_UI; 865 } 866 867 // Also allow any in-call service that has the control-experience permission (to ensure 868 // that it is a system app) and doesn't claim to show any UI. 869 if (hasControlInCallPermission && !isUIService) { 870 return IN_CALL_SERVICE_TYPE_NON_UI; 871 } 872 873 // Anything else that remains, we will not bind to. 874 Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b", 875 serviceInfo.packageName, serviceInfo.name, hasControlInCallPermission, 876 isCarModeUIService, isUIService); 877 return IN_CALL_SERVICE_TYPE_INVALID; 878 } 879 880 private void adjustServiceBindingsForEmergency() { 881 // The connected UI is not the system UI, so lets check if we should switch them 882 // if there exists an emergency number. 883 if (mCallsManager.hasEmergencyCall()) { 884 mInCallServiceConnection.setHasEmergency(true); 885 } 886 } 887 888 /** 889 * Persists the {@link IInCallService} instance and starts the communication between 890 * this class and in-call app by sending the first update to in-call app. This method is 891 * called after a successful binding connection is established. 892 * 893 * @param componentName The service {@link ComponentName}. 894 * @param service The {@link IInCallService} implementation. 895 * @return True if we successfully connected. 896 */ 897 private boolean onConnected(ComponentName componentName, IBinder service) { 898 Trace.beginSection("onConnected: " + componentName); 899 Log.i(this, "onConnected to %s", componentName); 900 901 IInCallService inCallService = IInCallService.Stub.asInterface(service); 902 mInCallServices.put(componentName, inCallService); 903 904 try { 905 inCallService.setInCallAdapter( 906 new InCallAdapter( 907 mCallsManager, 908 mCallIdMapper, 909 mLock, 910 componentName.getPackageName())); 911 } catch (RemoteException e) { 912 Log.e(this, e, "Failed to set the in-call adapter."); 913 Trace.endSection(); 914 return false; 915 } 916 917 // Upon successful connection, send the state of the world to the service. 918 List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls()); 919 if (!calls.isEmpty()) { 920 Log.i(this, "Adding %s calls to InCallService after onConnected: %s", calls.size(), 921 componentName); 922 for (Call call : calls) { 923 try { 924 // Track the call if we don't already know about it. 925 addCall(call); 926 inCallService.addCall(ParcelableCallUtils.toParcelableCall( 927 call, 928 true /* includeVideoProvider */, 929 mCallsManager.getPhoneAccountRegistrar())); 930 } catch (RemoteException ignored) { 931 } 932 } 933 try { 934 inCallService.onCallAudioStateChanged(mCallsManager.getAudioState()); 935 inCallService.onCanAddCallChanged(mCallsManager.canAddCall()); 936 } catch (RemoteException ignored) { 937 } 938 } else { 939 return false; 940 } 941 Trace.endSection(); 942 return true; 943 } 944 945 /** 946 * Cleans up an instance of in-call app after the service has been unbound. 947 * 948 * @param disconnectedComponent The {@link ComponentName} of the service which disconnected. 949 */ 950 private void onDisconnected(ComponentName disconnectedComponent) { 951 Log.i(this, "onDisconnected from %s", disconnectedComponent); 952 953 mInCallServices.remove(disconnectedComponent); 954 } 955 956 /** 957 * Informs all {@link InCallService} instances of the updated call information. 958 * 959 * @param call The {@link Call}. 960 */ 961 private void updateCall(Call call) { 962 updateCall(call, false /* videoProviderChanged */); 963 } 964 965 /** 966 * Informs all {@link InCallService} instances of the updated call information. 967 * 968 * @param call The {@link Call}. 969 * @param videoProviderChanged {@code true} if the video provider changed, {@code false} 970 * otherwise. 971 */ 972 private void updateCall(Call call, boolean videoProviderChanged) { 973 if (!mInCallServices.isEmpty()) { 974 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall( 975 call, 976 videoProviderChanged /* includeVideoProvider */, 977 mCallsManager.getPhoneAccountRegistrar()); 978 Log.i(this, "Sending updateCall %s ==> %s", call, parcelableCall); 979 List<ComponentName> componentsUpdated = new ArrayList<>(); 980 for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) { 981 ComponentName componentName = entry.getKey(); 982 IInCallService inCallService = entry.getValue(); 983 componentsUpdated.add(componentName); 984 try { 985 inCallService.updateCall(parcelableCall); 986 } catch (RemoteException ignored) { 987 } 988 } 989 Log.i(this, "Components updated: %s", componentsUpdated); 990 } 991 } 992 993 /** 994 * Adds the call to the list of calls tracked by the {@link InCallController}. 995 * @param call The call to add. 996 */ 997 private void addCall(Call call) { 998 if (mCallIdMapper.getCallId(call) == null) { 999 mCallIdMapper.addCall(call); 1000 call.addListener(mCallListener); 1001 } 1002 } 1003 1004 private boolean isBoundToServices() { 1005 return mInCallServiceConnection != null; 1006 } 1007 1008 /** 1009 * Dumps the state of the {@link InCallController}. 1010 * 1011 * @param pw The {@code IndentingPrintWriter} to write the state to. 1012 */ 1013 public void dump(IndentingPrintWriter pw) { 1014 pw.println("mInCallServices (InCalls registered):"); 1015 pw.increaseIndent(); 1016 for (ComponentName componentName : mInCallServices.keySet()) { 1017 pw.println(componentName); 1018 } 1019 pw.decreaseIndent(); 1020 1021 pw.println("ServiceConnections (InCalls bound):"); 1022 pw.increaseIndent(); 1023 if (mInCallServiceConnection != null) { 1024 mInCallServiceConnection.dump(pw); 1025 } 1026 pw.decreaseIndent(); 1027 } 1028 1029 public boolean doesConnectedDialerSupportRinging() { 1030 String ringingPackage = null; 1031 if (mInCallUIComponentName != null) { 1032 ringingPackage = mInCallUIComponentName.getPackageName().trim(); 1033 } 1034 1035 if (TextUtils.isEmpty(ringingPackage)) { 1036 // The current in-call UI returned nothing, so lets use the default dialer. 1037 ringingPackage = DefaultDialerManager.getDefaultDialerApplication( 1038 mContext, UserHandle.USER_CURRENT); 1039 } 1040 if (TextUtils.isEmpty(ringingPackage)) { 1041 return false; 1042 } 1043 1044 Intent intent = new Intent(InCallService.SERVICE_INTERFACE) 1045 .setPackage(ringingPackage); 1046 List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser( 1047 intent, PackageManager.GET_META_DATA, 1048 mCallsManager.getCurrentUserHandle().getIdentifier()); 1049 if (entries.isEmpty()) { 1050 return false; 1051 } 1052 1053 ResolveInfo info = entries.get(0); 1054 if (info.serviceInfo == null || info.serviceInfo.metaData == null) { 1055 return false; 1056 } 1057 1058 return info.serviceInfo.metaData 1059 .getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_RINGING, false); 1060 } 1061 1062 private List<Call> orderCallsWithChildrenFirst(Collection<Call> calls) { 1063 LinkedList<Call> parentCalls = new LinkedList<>(); 1064 LinkedList<Call> childCalls = new LinkedList<>(); 1065 for (Call call : calls) { 1066 if (call.getChildCalls().size() > 0) { 1067 parentCalls.add(call); 1068 } else { 1069 childCalls.add(call); 1070 } 1071 } 1072 childCalls.addAll(parentCalls); 1073 return childCalls; 1074 } 1075} 1076