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