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