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