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