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