1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.telecom; 18 19 import android.Manifest; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.ServiceConnection; 24 import android.content.pm.PackageManager; 25 import android.content.pm.ResolveInfo; 26 import android.content.pm.ServiceInfo; 27 import android.content.res.Resources; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.IBinder; 31 import android.os.Looper; 32 import android.os.RemoteException; 33 import android.os.Trace; 34 import android.os.UserHandle; 35 import android.telecom.CallAudioState; 36 import android.telecom.ConnectionService; 37 import android.telecom.DefaultDialerManager; 38 import android.telecom.InCallService; 39 import android.telecom.Log; 40 import android.telecom.Logging.Runnable; 41 import android.telecom.ParcelableCall; 42 import android.telecom.TelecomManager; 43 import android.text.TextUtils; 44 import android.util.ArrayMap; 45 46 import com.android.internal.annotations.VisibleForTesting; 47 // TODO: Needed for move to system service: import com.android.internal.R; 48 import com.android.internal.telecom.IInCallService; 49 import com.android.internal.util.IndentingPrintWriter; 50 import com.android.server.telecom.SystemStateProvider.SystemStateListener; 51 52 import java.util.ArrayList; 53 import java.util.Collection; 54 import java.util.LinkedList; 55 import java.util.List; 56 import java.util.Map; 57 import 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 */ 64 public 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 { onDisconnect(InCallServiceConnection conn)83 public void onDisconnect(InCallServiceConnection conn) {} 84 } 85 86 protected Listener mListener; 87 connect(Call call)88 public int connect(Call call) { return CONNECTION_FAILED; } disconnect()89 public void disconnect() {} isConnected()90 public boolean isConnected() { return false; } setHasEmergency(boolean hasEmergency)91 public void setHasEmergency(boolean hasEmergency) {} setListener(Listener l)92 public void setListener(Listener l) { 93 mListener = l; 94 } getInfo()95 public InCallServiceInfo getInfo() { return null; } dump(IndentingPrintWriter pw)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 InCallServiceInfo(ComponentName componentName, boolean isExternalCallsSupported, boolean isSelfManageCallsSupported, int type)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 getComponentName()115 public ComponentName getComponentName() { 116 return mComponentName; 117 } 118 isExternalCallsSupported()119 public boolean isExternalCallsSupported() { 120 return mIsExternalCallsSupported; 121 } 122 isSelfManagedCallsSupported()123 public boolean isSelfManagedCallsSupported() { 124 return mIsSelfManagedCallsSupported; 125 } 126 getType()127 public int getType() { 128 return mType; 129 } 130 131 @Override equals(Object o)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 hashCode()153 public int hashCode() { 154 return Objects.hash(mComponentName, mIsExternalCallsSupported, 155 mIsSelfManagedCallsSupported); 156 } 157 158 @Override toString()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 InCallServiceBindingConnection(InCallServiceInfo info)204 public InCallServiceBindingConnection(InCallServiceInfo info) { 205 mInCallServiceInfo = info; 206 } 207 208 @Override connect(Call call)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 getInfo()251 public InCallServiceInfo getInfo() { 252 return mInCallServiceInfo; 253 } 254 255 @Override disconnect()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 isConnected()266 public boolean isConnected() { 267 return mIsConnected; 268 } 269 270 @Override dump(IndentingPrintWriter pw)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 onConnected(IBinder service)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 onDisconnected()289 protected void onDisconnected() { 290 InCallController.this.onDisconnected(mInCallServiceInfo); 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 EmergencyInCallServiceConnection( InCallServiceInfo info, InCallServiceConnection subConnection)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 connect(Call call)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 mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call, 349 mCallsManager.getCurrentUserHandle()); 350 351 if (call != null && call.isIncoming() 352 && mEmergencyCallHelper.getLastEmergencyCallTimeMillis() > 0) { 353 // Add the last emergency call time to the call 354 Bundle extras = new Bundle(); 355 extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 356 mEmergencyCallHelper.getLastEmergencyCallTimeMillis()); 357 call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras); 358 } 359 360 // If we are here, we didn't or could not connect to child. So lets connect ourselves. 361 return super.connect(call); 362 } 363 364 @Override disconnect()365 public void disconnect() { 366 Log.i(this, "Disconnect forced!"); 367 if (mIsProxying) { 368 mSubConnection.disconnect(); 369 } else { 370 super.disconnect(); 371 mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission(); 372 } 373 mIsConnected = false; 374 } 375 376 @Override setHasEmergency(boolean hasEmergency)377 public void setHasEmergency(boolean hasEmergency) { 378 if (hasEmergency) { 379 takeControl(); 380 } 381 } 382 383 @Override getInfo()384 public InCallServiceInfo getInfo() { 385 if (mIsProxying) { 386 return mSubConnection.getInfo(); 387 } else { 388 return super.getInfo(); 389 } 390 } 391 @Override onDisconnected()392 protected void onDisconnected() { 393 // Save this here because super.onDisconnected() could force us to explicitly 394 // disconnect() as a cleanup step and that sets mIsConnected to false. 395 boolean shouldReconnect = mIsConnected; 396 super.onDisconnected(); 397 // We just disconnected. Check if we are expected to be connected, and reconnect. 398 if (shouldReconnect && !mIsProxying) { 399 connect(null); // reconnect 400 } 401 } 402 403 @Override dump(IndentingPrintWriter pw)404 public void dump(IndentingPrintWriter pw) { 405 pw.print("Emergency ICS Connection ["); 406 pw.append(mIsProxying ? "" : "not ").append("proxying, "); 407 pw.append(mIsConnected ? "" : "not ").append("connected]\n"); 408 pw.increaseIndent(); 409 pw.print("Emergency: "); 410 super.dump(pw); 411 if (mSubConnection != null) { 412 pw.print("Default-Dialer: "); 413 mSubConnection.dump(pw); 414 } 415 pw.decreaseIndent(); 416 } 417 418 /** 419 * Forces the connection to take control from it's subConnection. 420 */ takeControl()421 private void takeControl() { 422 if (mIsProxying) { 423 mIsProxying = false; 424 if (mIsConnected) { 425 mSubConnection.disconnect(); 426 super.connect(null); 427 } 428 } 429 } 430 } 431 432 /** 433 * A version of InCallServiceConnection which switches UI between two separate sub-instances of 434 * InCallServicesConnections. 435 */ 436 private class CarSwappingInCallServiceConnection extends InCallServiceConnection { 437 private final InCallServiceConnection mDialerConnection; 438 private final InCallServiceConnection mCarModeConnection; 439 private InCallServiceConnection mCurrentConnection; 440 private boolean mIsCarMode = false; 441 private boolean mIsConnected = false; 442 CarSwappingInCallServiceConnection( InCallServiceConnection dialerConnection, InCallServiceConnection carModeConnection)443 public CarSwappingInCallServiceConnection( 444 InCallServiceConnection dialerConnection, 445 InCallServiceConnection carModeConnection) { 446 mDialerConnection = dialerConnection; 447 mCarModeConnection = carModeConnection; 448 mCurrentConnection = getCurrentConnection(); 449 } 450 setCarMode(boolean isCarMode)451 public synchronized void setCarMode(boolean isCarMode) { 452 Log.i(this, "carmodechange: " + mIsCarMode + " => " + isCarMode); 453 if (isCarMode != mIsCarMode) { 454 mIsCarMode = isCarMode; 455 InCallServiceConnection newConnection = getCurrentConnection(); 456 if (newConnection != mCurrentConnection) { 457 if (mIsConnected) { 458 mCurrentConnection.disconnect(); 459 int result = newConnection.connect(null); 460 mIsConnected = result == CONNECTION_SUCCEEDED; 461 } 462 mCurrentConnection = newConnection; 463 } 464 } 465 } 466 467 @Override connect(Call call)468 public int connect(Call call) { 469 if (mIsConnected) { 470 Log.i(this, "already connected"); 471 return CONNECTION_SUCCEEDED; 472 } else { 473 int result = mCurrentConnection.connect(call); 474 if (result != CONNECTION_FAILED) { 475 mIsConnected = result == CONNECTION_SUCCEEDED; 476 return result; 477 } 478 } 479 480 return CONNECTION_FAILED; 481 } 482 483 @Override disconnect()484 public void disconnect() { 485 if (mIsConnected) { 486 mCurrentConnection.disconnect(); 487 mIsConnected = false; 488 } else { 489 Log.i(this, "already disconnected"); 490 } 491 } 492 493 @Override isConnected()494 public boolean isConnected() { 495 return mIsConnected; 496 } 497 498 @Override setHasEmergency(boolean hasEmergency)499 public void setHasEmergency(boolean hasEmergency) { 500 if (mDialerConnection != null) { 501 mDialerConnection.setHasEmergency(hasEmergency); 502 } 503 if (mCarModeConnection != null) { 504 mCarModeConnection.setHasEmergency(hasEmergency); 505 } 506 } 507 508 @Override getInfo()509 public InCallServiceInfo getInfo() { 510 return mCurrentConnection.getInfo(); 511 } 512 513 @Override dump(IndentingPrintWriter pw)514 public void dump(IndentingPrintWriter pw) { 515 pw.print("Car Swapping ICS ["); 516 pw.append(mIsConnected ? "" : "not ").append("connected]\n"); 517 pw.increaseIndent(); 518 if (mDialerConnection != null) { 519 pw.print("Dialer: "); 520 mDialerConnection.dump(pw); 521 } 522 if (mCarModeConnection != null) { 523 pw.print("Car Mode: "); 524 mCarModeConnection.dump(pw); 525 } 526 } 527 getCurrentConnection()528 private InCallServiceConnection getCurrentConnection() { 529 if (mIsCarMode && mCarModeConnection != null) { 530 return mCarModeConnection; 531 } else { 532 return mDialerConnection; 533 } 534 } 535 } 536 537 private class NonUIInCallServiceConnectionCollection extends InCallServiceConnection { 538 private final List<InCallServiceBindingConnection> mSubConnections; 539 NonUIInCallServiceConnectionCollection( List<InCallServiceBindingConnection> subConnections)540 public NonUIInCallServiceConnectionCollection( 541 List<InCallServiceBindingConnection> subConnections) { 542 mSubConnections = subConnections; 543 } 544 545 @Override connect(Call call)546 public int connect(Call call) { 547 for (InCallServiceBindingConnection subConnection : mSubConnections) { 548 subConnection.connect(call); 549 } 550 return CONNECTION_SUCCEEDED; 551 } 552 553 @Override disconnect()554 public void disconnect() { 555 for (InCallServiceBindingConnection subConnection : mSubConnections) { 556 if (subConnection.isConnected()) { 557 subConnection.disconnect(); 558 } 559 } 560 } 561 562 @Override isConnected()563 public boolean isConnected() { 564 boolean connected = false; 565 for (InCallServiceBindingConnection subConnection : mSubConnections) { 566 connected = connected || subConnection.isConnected(); 567 } 568 return connected; 569 } 570 571 @Override dump(IndentingPrintWriter pw)572 public void dump(IndentingPrintWriter pw) { 573 pw.println("Non-UI Connections:"); 574 pw.increaseIndent(); 575 for (InCallServiceBindingConnection subConnection : mSubConnections) { 576 subConnection.dump(pw); 577 } 578 pw.decreaseIndent(); 579 } 580 } 581 582 private final Call.Listener mCallListener = new Call.ListenerBase() { 583 @Override 584 public void onConnectionCapabilitiesChanged(Call call) { 585 updateCall(call); 586 } 587 588 @Override 589 public void onConnectionPropertiesChanged(Call call, boolean didRttChange) { 590 updateCall(call, false /* includeVideoProvider */, didRttChange); 591 } 592 593 @Override 594 public void onCannedSmsResponsesLoaded(Call call) { 595 updateCall(call); 596 } 597 598 @Override 599 public void onVideoCallProviderChanged(Call call) { 600 updateCall(call, true /* videoProviderChanged */, false); 601 } 602 603 @Override 604 public void onStatusHintsChanged(Call call) { 605 updateCall(call); 606 } 607 608 /** 609 * Listens for changes to extras reported by a Telecom {@link Call}. 610 * 611 * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService} 612 * so we will only trigger an update of the call information if the source of the extras 613 * change was a {@link ConnectionService}. 614 * 615 * @param call The call. 616 * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or 617 * {@link Call#SOURCE_INCALL_SERVICE}). 618 * @param extras The extras. 619 */ 620 @Override 621 public void onExtrasChanged(Call call, int source, Bundle extras) { 622 // Do not inform InCallServices of changes which originated there. 623 if (source == Call.SOURCE_INCALL_SERVICE) { 624 return; 625 } 626 updateCall(call); 627 } 628 629 /** 630 * Listens for changes to extras reported by a Telecom {@link Call}. 631 * 632 * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService} 633 * so we will only trigger an update of the call information if the source of the extras 634 * change was a {@link ConnectionService}. 635 * @param call The call. 636 * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or 637 * {@link Call#SOURCE_INCALL_SERVICE}). 638 * @param keys The extra key removed 639 */ 640 @Override 641 public void onExtrasRemoved(Call call, int source, List<String> keys) { 642 // Do not inform InCallServices of changes which originated there. 643 if (source == Call.SOURCE_INCALL_SERVICE) { 644 return; 645 } 646 updateCall(call); 647 } 648 649 @Override 650 public void onHandleChanged(Call call) { 651 updateCall(call); 652 } 653 654 @Override 655 public void onCallerDisplayNameChanged(Call call) { 656 updateCall(call); 657 } 658 659 @Override 660 public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) { 661 updateCall(call); 662 } 663 664 @Override 665 public void onTargetPhoneAccountChanged(Call call) { 666 updateCall(call); 667 } 668 669 @Override 670 public void onConferenceableCallsChanged(Call call) { 671 updateCall(call); 672 } 673 674 @Override 675 public void onConnectionEvent(Call call, String event, Bundle extras) { 676 notifyConnectionEvent(call, event, extras); 677 } 678 679 @Override 680 public void onHandoverFailed(Call call, int error) { 681 notifyHandoverFailed(call, error); 682 } 683 684 @Override 685 public void onHandoverComplete(Call call) { 686 notifyHandoverComplete(call); 687 } 688 689 @Override 690 public void onRttInitiationFailure(Call call, int reason) { 691 notifyRttInitiationFailure(call, reason); 692 updateCall(call, false, true); 693 } 694 695 @Override 696 public void onRemoteRttRequest(Call call, int requestId) { 697 notifyRemoteRttRequest(call, requestId); 698 } 699 }; 700 701 private final SystemStateListener mSystemStateListener = new SystemStateListener() { 702 @Override 703 public void onCarModeChanged(boolean isCarMode) { 704 if (mInCallServiceConnection != null) { 705 mInCallServiceConnection.setCarMode(shouldUseCarModeUI()); 706 } 707 } 708 }; 709 710 private static final int IN_CALL_SERVICE_TYPE_INVALID = 0; 711 private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1; 712 private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2; 713 private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3; 714 private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4; 715 716 /** The in-call app implementations, see {@link IInCallService}. */ 717 private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>(); 718 719 /** 720 * The {@link ComponentName} of the bound In-Call UI Service. 721 */ 722 private ComponentName mInCallUIComponentName; 723 724 private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId); 725 726 /** The {@link ComponentName} of the default InCall UI. */ 727 private final ComponentName mSystemInCallComponentName; 728 729 private final Context mContext; 730 private final TelecomSystem.SyncRoot mLock; 731 private final CallsManager mCallsManager; 732 private final SystemStateProvider mSystemStateProvider; 733 private final Timeouts.Adapter mTimeoutsAdapter; 734 private final DefaultDialerCache mDefaultDialerCache; 735 private final EmergencyCallHelper mEmergencyCallHelper; 736 private CarSwappingInCallServiceConnection mInCallServiceConnection; 737 private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections; 738 InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, SystemStateProvider systemStateProvider, DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper)739 public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, 740 SystemStateProvider systemStateProvider, 741 DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter, 742 EmergencyCallHelper emergencyCallHelper) { 743 mContext = context; 744 mLock = lock; 745 mCallsManager = callsManager; 746 mSystemStateProvider = systemStateProvider; 747 mTimeoutsAdapter = timeoutsAdapter; 748 mDefaultDialerCache = defaultDialerCache; 749 mEmergencyCallHelper = emergencyCallHelper; 750 751 Resources resources = mContext.getResources(); 752 mSystemInCallComponentName = new ComponentName( 753 resources.getString(R.string.ui_default_package), 754 resources.getString(R.string.incall_default_class)); 755 756 mSystemStateProvider.addListener(mSystemStateListener); 757 } 758 759 @Override onCallAdded(Call call)760 public void onCallAdded(Call call) { 761 if (!isBoundAndConnectedToServices()) { 762 Log.i(this, "onCallAdded: %s; not bound or connected.", call); 763 // We are not bound, or we're not connected. 764 bindToServices(call); 765 } else { 766 // We are bound, and we are connected. 767 adjustServiceBindingsForEmergency(); 768 769 // This is in case an emergency call is added while there is an existing call. 770 mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call, 771 mCallsManager.getCurrentUserHandle()); 772 773 Log.i(this, "onCallAdded: %s", call); 774 // Track the call if we don't already know about it. 775 addCall(call); 776 777 Log.i(this, "mInCallServiceConnection isConnected=%b", 778 mInCallServiceConnection.isConnected()); 779 780 List<ComponentName> componentsUpdated = new ArrayList<>(); 781 for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) { 782 InCallServiceInfo info = entry.getKey(); 783 784 if (call.isExternalCall() && !info.isExternalCallsSupported()) { 785 continue; 786 } 787 788 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) { 789 continue; 790 } 791 792 // Only send the RTT call if it's a UI in-call service 793 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo()); 794 795 componentsUpdated.add(info.getComponentName()); 796 IInCallService inCallService = entry.getValue(); 797 798 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call, 799 true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(), 800 info.isExternalCallsSupported(), includeRttCall); 801 try { 802 inCallService.addCall(parcelableCall); 803 } catch (RemoteException ignored) { 804 } 805 } 806 Log.i(this, "Call added to components: %s", componentsUpdated); 807 } 808 } 809 810 @Override onCallRemoved(Call call)811 public void onCallRemoved(Call call) { 812 Log.i(this, "onCallRemoved: %s", call); 813 if (mCallsManager.getCalls().isEmpty()) { 814 /** Let's add a 2 second delay before we send unbind to the services to hopefully 815 * give them enough time to process all the pending messages. 816 */ 817 Handler handler = new Handler(Looper.getMainLooper()); 818 handler.postDelayed(new Runnable("ICC.oCR", mLock) { 819 @Override 820 public void loggedRun() { 821 // Check again to make sure there are no active calls. 822 if (mCallsManager.getCalls().isEmpty()) { 823 unbindFromServices(); 824 825 mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission(); 826 } 827 } 828 }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay( 829 mContext.getContentResolver())); 830 } 831 call.removeListener(mCallListener); 832 mCallIdMapper.removeCall(call); 833 } 834 835 @Override onExternalCallChanged(Call call, boolean isExternalCall)836 public void onExternalCallChanged(Call call, boolean isExternalCall) { 837 Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall); 838 839 List<ComponentName> componentsUpdated = new ArrayList<>(); 840 if (!isExternalCall) { 841 // The call was external but it is no longer external. We must now add it to any 842 // InCallServices which do not support external calls. 843 for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) { 844 InCallServiceInfo info = entry.getKey(); 845 846 if (info.isExternalCallsSupported()) { 847 // For InCallServices which support external calls, the call will have already 848 // been added to the connection service, so we do not need to add it again. 849 continue; 850 } 851 852 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) { 853 continue; 854 } 855 856 componentsUpdated.add(info.getComponentName()); 857 IInCallService inCallService = entry.getValue(); 858 859 // Only send the RTT call if it's a UI in-call service 860 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo()); 861 862 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call, 863 true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(), 864 info.isExternalCallsSupported(), includeRttCall); 865 try { 866 inCallService.addCall(parcelableCall); 867 } catch (RemoteException ignored) { 868 } 869 } 870 Log.i(this, "Previously external call added to components: %s", componentsUpdated); 871 } else { 872 // The call was regular but it is now external. We must now remove it from any 873 // InCallServices which do not support external calls. 874 // Remove the call by sending a call update indicating the call was disconnected. 875 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall( 876 call, 877 false /* includeVideoProvider */, 878 mCallsManager.getPhoneAccountRegistrar(), 879 false /* supportsExternalCalls */, 880 android.telecom.Call.STATE_DISCONNECTED /* overrideState */, 881 false /* includeRttCall */); 882 883 Log.i(this, "Removing external call %s ==> %s", call, parcelableCall); 884 for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) { 885 InCallServiceInfo info = entry.getKey(); 886 if (info.isExternalCallsSupported()) { 887 // For InCallServices which support external calls, we do not need to remove 888 // the call. 889 continue; 890 } 891 892 componentsUpdated.add(info.getComponentName()); 893 IInCallService inCallService = entry.getValue(); 894 895 try { 896 inCallService.updateCall(parcelableCall); 897 } catch (RemoteException ignored) { 898 } 899 } 900 Log.i(this, "External call removed from components: %s", componentsUpdated); 901 } 902 } 903 904 @Override onCallStateChanged(Call call, int oldState, int newState)905 public void onCallStateChanged(Call call, int oldState, int newState) { 906 updateCall(call); 907 } 908 909 @Override onConnectionServiceChanged( Call call, ConnectionServiceWrapper oldService, ConnectionServiceWrapper newService)910 public void onConnectionServiceChanged( 911 Call call, 912 ConnectionServiceWrapper oldService, 913 ConnectionServiceWrapper newService) { 914 updateCall(call); 915 } 916 917 @Override onCallAudioStateChanged(CallAudioState oldCallAudioState, CallAudioState newCallAudioState)918 public void onCallAudioStateChanged(CallAudioState oldCallAudioState, 919 CallAudioState newCallAudioState) { 920 if (!mInCallServices.isEmpty()) { 921 Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState, 922 newCallAudioState); 923 for (IInCallService inCallService : mInCallServices.values()) { 924 try { 925 inCallService.onCallAudioStateChanged(newCallAudioState); 926 } catch (RemoteException ignored) { 927 } 928 } 929 } 930 } 931 932 @Override onCanAddCallChanged(boolean canAddCall)933 public void onCanAddCallChanged(boolean canAddCall) { 934 if (!mInCallServices.isEmpty()) { 935 Log.i(this, "onCanAddCallChanged : %b", canAddCall); 936 for (IInCallService inCallService : mInCallServices.values()) { 937 try { 938 inCallService.onCanAddCallChanged(canAddCall); 939 } catch (RemoteException ignored) { 940 } 941 } 942 } 943 } 944 onPostDialWait(Call call, String remaining)945 void onPostDialWait(Call call, String remaining) { 946 if (!mInCallServices.isEmpty()) { 947 Log.i(this, "Calling onPostDialWait, remaining = %s", remaining); 948 for (IInCallService inCallService : mInCallServices.values()) { 949 try { 950 inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining); 951 } catch (RemoteException ignored) { 952 } 953 } 954 } 955 } 956 957 @Override onIsConferencedChanged(Call call)958 public void onIsConferencedChanged(Call call) { 959 Log.d(this, "onIsConferencedChanged %s", call); 960 updateCall(call); 961 } 962 bringToForeground(boolean showDialpad)963 void bringToForeground(boolean showDialpad) { 964 if (!mInCallServices.isEmpty()) { 965 for (IInCallService inCallService : mInCallServices.values()) { 966 try { 967 inCallService.bringToForeground(showDialpad); 968 } catch (RemoteException ignored) { 969 } 970 } 971 } else { 972 Log.w(this, "Asking to bring unbound in-call UI to foreground."); 973 } 974 } 975 silenceRinger()976 void silenceRinger() { 977 if (!mInCallServices.isEmpty()) { 978 for (IInCallService inCallService : mInCallServices.values()) { 979 try { 980 inCallService.silenceRinger(); 981 } catch (RemoteException ignored) { 982 } 983 } 984 } 985 } 986 notifyConnectionEvent(Call call, String event, Bundle extras)987 private void notifyConnectionEvent(Call call, String event, Bundle extras) { 988 if (!mInCallServices.isEmpty()) { 989 for (IInCallService inCallService : mInCallServices.values()) { 990 try { 991 Log.i(this, "notifyConnectionEvent {Call: %s, Event: %s, Extras:[%s]}", 992 (call != null ? call.toString() :"null"), 993 (event != null ? event : "null") , 994 (extras != null ? extras.toString() : "null")); 995 inCallService.onConnectionEvent(mCallIdMapper.getCallId(call), event, extras); 996 } catch (RemoteException ignored) { 997 } 998 } 999 } 1000 } 1001 notifyRttInitiationFailure(Call call, int reason)1002 private void notifyRttInitiationFailure(Call call, int reason) { 1003 if (!mInCallServices.isEmpty()) { 1004 mInCallServices.entrySet().stream() 1005 .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo())) 1006 .forEach((entry) -> { 1007 try { 1008 Log.i(this, "notifyRttFailure, call %s, incall %s", 1009 call, entry.getKey()); 1010 entry.getValue().onRttInitiationFailure(mCallIdMapper.getCallId(call), 1011 reason); 1012 } catch (RemoteException ignored) { 1013 } 1014 }); 1015 } 1016 } 1017 notifyRemoteRttRequest(Call call, int requestId)1018 private void notifyRemoteRttRequest(Call call, int requestId) { 1019 if (!mInCallServices.isEmpty()) { 1020 mInCallServices.entrySet().stream() 1021 .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo())) 1022 .forEach((entry) -> { 1023 try { 1024 Log.i(this, "notifyRemoteRttRequest, call %s, incall %s", 1025 call, entry.getKey()); 1026 entry.getValue().onRttUpgradeRequest( 1027 mCallIdMapper.getCallId(call), requestId); 1028 } catch (RemoteException ignored) { 1029 } 1030 }); 1031 } 1032 } 1033 notifyHandoverFailed(Call call, int error)1034 private void notifyHandoverFailed(Call call, int error) { 1035 if (!mInCallServices.isEmpty()) { 1036 for (IInCallService inCallService : mInCallServices.values()) { 1037 try { 1038 inCallService.onHandoverFailed(mCallIdMapper.getCallId(call), error); 1039 } catch (RemoteException ignored) { 1040 } 1041 } 1042 } 1043 } 1044 notifyHandoverComplete(Call call)1045 private void notifyHandoverComplete(Call call) { 1046 if (!mInCallServices.isEmpty()) { 1047 for (IInCallService inCallService : mInCallServices.values()) { 1048 try { 1049 inCallService.onHandoverComplete(mCallIdMapper.getCallId(call)); 1050 } catch (RemoteException ignored) { 1051 } 1052 } 1053 } 1054 } 1055 1056 /** 1057 * Unbinds an existing bound connection to the in-call app. 1058 */ unbindFromServices()1059 private void unbindFromServices() { 1060 if (mInCallServiceConnection != null) { 1061 mInCallServiceConnection.disconnect(); 1062 mInCallServiceConnection = null; 1063 } 1064 if (mNonUIInCallServiceConnections != null) { 1065 mNonUIInCallServiceConnections.disconnect(); 1066 mNonUIInCallServiceConnections = null; 1067 } 1068 mInCallServices.clear(); 1069 } 1070 1071 /** 1072 * Binds to all the UI-providing InCallService as well as system-implemented non-UI 1073 * InCallServices. Method-invoker must check {@link #isBoundAndConnectedToServices()} before invoking. 1074 * 1075 * @param call The newly added call that triggered the binding to the in-call services. 1076 */ 1077 @VisibleForTesting bindToServices(Call call)1078 public void bindToServices(Call call) { 1079 if (mInCallServiceConnection == null) { 1080 InCallServiceConnection dialerInCall = null; 1081 InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent(); 1082 Log.i(this, "defaultDialer: " + defaultDialerComponentInfo); 1083 if (defaultDialerComponentInfo != null && 1084 !defaultDialerComponentInfo.getComponentName().equals( 1085 mSystemInCallComponentName)) { 1086 dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo); 1087 } 1088 Log.i(this, "defaultDialer: " + dialerInCall); 1089 1090 InCallServiceInfo systemInCallInfo = getInCallServiceComponent( 1091 mSystemInCallComponentName, IN_CALL_SERVICE_TYPE_SYSTEM_UI); 1092 EmergencyInCallServiceConnection systemInCall = 1093 new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall); 1094 systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall()); 1095 1096 InCallServiceConnection carModeInCall = null; 1097 InCallServiceInfo carModeComponentInfo = getCarModeComponent(); 1098 if (carModeComponentInfo != null && 1099 !carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) { 1100 carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo); 1101 } 1102 1103 mInCallServiceConnection = 1104 new CarSwappingInCallServiceConnection(systemInCall, carModeInCall); 1105 } 1106 1107 mInCallServiceConnection.setCarMode(shouldUseCarModeUI()); 1108 1109 // Actually try binding to the UI InCallService. If the response 1110 if (mInCallServiceConnection.connect(call) == 1111 InCallServiceConnection.CONNECTION_SUCCEEDED) { 1112 // Only connect to the non-ui InCallServices if we actually connected to the main UI 1113 // one. 1114 connectToNonUiInCallServices(call); 1115 } else { 1116 Log.i(this, "bindToServices: current UI doesn't support call; not binding."); 1117 } 1118 } 1119 connectToNonUiInCallServices(Call call)1120 private void connectToNonUiInCallServices(Call call) { 1121 List<InCallServiceInfo> nonUIInCallComponents = 1122 getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI); 1123 List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>(); 1124 for (InCallServiceInfo serviceInfo : nonUIInCallComponents) { 1125 nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo)); 1126 } 1127 mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls); 1128 mNonUIInCallServiceConnections.connect(call); 1129 } 1130 getDefaultDialerComponent()1131 private InCallServiceInfo getDefaultDialerComponent() { 1132 String packageName = mDefaultDialerCache.getDefaultDialerApplication( 1133 mCallsManager.getCurrentUserHandle().getIdentifier()); 1134 Log.d(this, "Default Dialer package: " + packageName); 1135 1136 return getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI); 1137 } 1138 getCarModeComponent()1139 private InCallServiceInfo getCarModeComponent() { 1140 // Seems strange to cast a String to null, but the signatures of getInCallServiceComponent 1141 // differ in the types of the first parameter, and passing in null is inherently ambiguous. 1142 return getInCallServiceComponent((String) null, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); 1143 } 1144 getInCallServiceComponent(ComponentName componentName, int type)1145 private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) { 1146 List<InCallServiceInfo> list = getInCallServiceComponents(componentName, type); 1147 if (list != null && !list.isEmpty()) { 1148 return list.get(0); 1149 } else { 1150 // Last Resort: Try to bind to the ComponentName given directly. 1151 Log.e(this, new Exception(), "Package Manager could not find ComponentName: " 1152 + componentName +". Trying to bind anyway."); 1153 return new InCallServiceInfo(componentName, false, false, type); 1154 } 1155 } 1156 getInCallServiceComponent(String packageName, int type)1157 private InCallServiceInfo getInCallServiceComponent(String packageName, int type) { 1158 List<InCallServiceInfo> list = getInCallServiceComponents(packageName, type); 1159 if (list != null && !list.isEmpty()) { 1160 return list.get(0); 1161 } 1162 return null; 1163 } 1164 getInCallServiceComponents(int type)1165 private List<InCallServiceInfo> getInCallServiceComponents(int type) { 1166 return getInCallServiceComponents(null, null, type); 1167 } 1168 getInCallServiceComponents(String packageName, int type)1169 private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type) { 1170 return getInCallServiceComponents(packageName, null, type); 1171 } 1172 getInCallServiceComponents(ComponentName componentName, int type)1173 private List<InCallServiceInfo> getInCallServiceComponents(ComponentName componentName, 1174 int type) { 1175 return getInCallServiceComponents(null, componentName, type); 1176 } 1177 getInCallServiceComponents(String packageName, ComponentName componentName, int requestedType)1178 private List<InCallServiceInfo> getInCallServiceComponents(String packageName, 1179 ComponentName componentName, int requestedType) { 1180 1181 List<InCallServiceInfo> retval = new LinkedList<>(); 1182 1183 Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE); 1184 if (packageName != null) { 1185 serviceIntent.setPackage(packageName); 1186 } 1187 if (componentName != null) { 1188 serviceIntent.setComponent(componentName); 1189 } 1190 1191 PackageManager packageManager = mContext.getPackageManager(); 1192 for (ResolveInfo entry : packageManager.queryIntentServicesAsUser( 1193 serviceIntent, 1194 PackageManager.GET_META_DATA, 1195 mCallsManager.getCurrentUserHandle().getIdentifier())) { 1196 ServiceInfo serviceInfo = entry.serviceInfo; 1197 1198 if (serviceInfo != null) { 1199 boolean isExternalCallsSupported = serviceInfo.metaData != null && 1200 serviceInfo.metaData.getBoolean( 1201 TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, false); 1202 boolean isSelfManageCallsSupported = serviceInfo.metaData != null && 1203 serviceInfo.metaData.getBoolean( 1204 TelecomManager.METADATA_INCLUDE_SELF_MANAGED_CALLS, false); 1205 1206 int currentType = getInCallServiceType(entry.serviceInfo, packageManager); 1207 if (requestedType == 0 || requestedType == currentType) { 1208 if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) { 1209 // We enforce the rule that self-managed calls are not supported by non-ui 1210 // InCallServices. 1211 isSelfManageCallsSupported = false; 1212 } 1213 retval.add(new InCallServiceInfo( 1214 new ComponentName(serviceInfo.packageName, serviceInfo.name), 1215 isExternalCallsSupported, isSelfManageCallsSupported, requestedType)); 1216 } 1217 } 1218 } 1219 1220 return retval; 1221 } 1222 shouldUseCarModeUI()1223 private boolean shouldUseCarModeUI() { 1224 return mSystemStateProvider.isCarMode(); 1225 } 1226 1227 /** 1228 * Returns the type of InCallService described by the specified serviceInfo. 1229 */ getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager)1230 private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager) { 1231 // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which 1232 // enforces that only Telecom can bind to it. 1233 boolean hasServiceBindPermission = serviceInfo.permission != null && 1234 serviceInfo.permission.equals( 1235 Manifest.permission.BIND_INCALL_SERVICE); 1236 if (!hasServiceBindPermission) { 1237 Log.w(this, "InCallService does not require BIND_INCALL_SERVICE permission: " + 1238 serviceInfo.packageName); 1239 return IN_CALL_SERVICE_TYPE_INVALID; 1240 } 1241 1242 if (mSystemInCallComponentName.getPackageName().equals(serviceInfo.packageName) && 1243 mSystemInCallComponentName.getClassName().equals(serviceInfo.name)) { 1244 return IN_CALL_SERVICE_TYPE_SYSTEM_UI; 1245 } 1246 1247 // Check to see if the service is a car-mode UI type by checking that it has the 1248 // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the 1249 // car-mode UI metadata. 1250 boolean hasControlInCallPermission = packageManager.checkPermission( 1251 Manifest.permission.CONTROL_INCALL_EXPERIENCE, 1252 serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED; 1253 boolean isCarModeUIService = serviceInfo.metaData != null && 1254 serviceInfo.metaData.getBoolean( 1255 TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) && 1256 hasControlInCallPermission; 1257 if (isCarModeUIService) { 1258 return IN_CALL_SERVICE_TYPE_CAR_MODE_UI; 1259 } 1260 1261 // Check to see that it is the default dialer package 1262 boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName, 1263 mDefaultDialerCache.getDefaultDialerApplication( 1264 mCallsManager.getCurrentUserHandle().getIdentifier())); 1265 boolean isUIService = serviceInfo.metaData != null && 1266 serviceInfo.metaData.getBoolean( 1267 TelecomManager.METADATA_IN_CALL_SERVICE_UI, false); 1268 if (isDefaultDialerPackage && isUIService) { 1269 return IN_CALL_SERVICE_TYPE_DIALER_UI; 1270 } 1271 1272 // Also allow any in-call service that has the control-experience permission (to ensure 1273 // that it is a system app) and doesn't claim to show any UI. 1274 if (hasControlInCallPermission && !isUIService) { 1275 return IN_CALL_SERVICE_TYPE_NON_UI; 1276 } 1277 1278 // Anything else that remains, we will not bind to. 1279 Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b", 1280 serviceInfo.packageName, serviceInfo.name, hasControlInCallPermission, 1281 isCarModeUIService, isUIService); 1282 return IN_CALL_SERVICE_TYPE_INVALID; 1283 } 1284 adjustServiceBindingsForEmergency()1285 private void adjustServiceBindingsForEmergency() { 1286 // The connected UI is not the system UI, so lets check if we should switch them 1287 // if there exists an emergency number. 1288 if (mCallsManager.hasEmergencyCall()) { 1289 mInCallServiceConnection.setHasEmergency(true); 1290 } 1291 } 1292 1293 /** 1294 * Persists the {@link IInCallService} instance and starts the communication between 1295 * this class and in-call app by sending the first update to in-call app. This method is 1296 * called after a successful binding connection is established. 1297 * 1298 * @param info Info about the service, including its {@link ComponentName}. 1299 * @param service The {@link IInCallService} implementation. 1300 * @return True if we successfully connected. 1301 */ onConnected(InCallServiceInfo info, IBinder service)1302 private boolean onConnected(InCallServiceInfo info, IBinder service) { 1303 Trace.beginSection("onConnected: " + info.getComponentName()); 1304 Log.i(this, "onConnected to %s", info.getComponentName()); 1305 1306 IInCallService inCallService = IInCallService.Stub.asInterface(service); 1307 mInCallServices.put(info, inCallService); 1308 1309 try { 1310 inCallService.setInCallAdapter( 1311 new InCallAdapter( 1312 mCallsManager, 1313 mCallIdMapper, 1314 mLock, 1315 info.getComponentName().getPackageName())); 1316 } catch (RemoteException e) { 1317 Log.e(this, e, "Failed to set the in-call adapter."); 1318 Trace.endSection(); 1319 return false; 1320 } 1321 1322 // Upon successful connection, send the state of the world to the service. 1323 List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls()); 1324 Log.i(this, "Adding %s calls to InCallService after onConnected: %s, including external " + 1325 "calls", calls.size(), info.getComponentName()); 1326 int numCallsSent = 0; 1327 for (Call call : calls) { 1328 try { 1329 if ((call.isSelfManaged() && !info.isSelfManagedCallsSupported()) || 1330 (call.isExternalCall() && !info.isExternalCallsSupported())) { 1331 continue; 1332 } 1333 1334 // Only send the RTT call if it's a UI in-call service 1335 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo()); 1336 1337 // Track the call if we don't already know about it. 1338 addCall(call); 1339 numCallsSent += 1; 1340 inCallService.addCall(ParcelableCallUtils.toParcelableCall( 1341 call, 1342 true /* includeVideoProvider */, 1343 mCallsManager.getPhoneAccountRegistrar(), 1344 info.isExternalCallsSupported(), 1345 includeRttCall)); 1346 } catch (RemoteException ignored) { 1347 } 1348 } 1349 try { 1350 inCallService.onCallAudioStateChanged(mCallsManager.getAudioState()); 1351 inCallService.onCanAddCallChanged(mCallsManager.canAddCall()); 1352 } catch (RemoteException ignored) { 1353 } 1354 Log.i(this, "%s calls sent to InCallService.", numCallsSent); 1355 Trace.endSection(); 1356 return true; 1357 } 1358 1359 /** 1360 * Cleans up an instance of in-call app after the service has been unbound. 1361 * 1362 * @param disconnectedInfo The {@link InCallServiceInfo} of the service which disconnected. 1363 */ onDisconnected(InCallServiceInfo disconnectedInfo)1364 private void onDisconnected(InCallServiceInfo disconnectedInfo) { 1365 Log.i(this, "onDisconnected from %s", disconnectedInfo.getComponentName()); 1366 1367 mInCallServices.remove(disconnectedInfo); 1368 } 1369 1370 /** 1371 * Informs all {@link InCallService} instances of the updated call information. 1372 * 1373 * @param call The {@link Call}. 1374 */ updateCall(Call call)1375 private void updateCall(Call call) { 1376 updateCall(call, false /* videoProviderChanged */, false); 1377 } 1378 1379 /** 1380 * Informs all {@link InCallService} instances of the updated call information. 1381 * 1382 * @param call The {@link Call}. 1383 * @param videoProviderChanged {@code true} if the video provider changed, {@code false} 1384 * otherwise. 1385 * @param rttInfoChanged {@code true} if any information about the RTT session changed, 1386 * {@code false} otherwise. 1387 */ updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged)1388 private void updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged) { 1389 if (!mInCallServices.isEmpty()) { 1390 Log.i(this, "Sending updateCall %s", call); 1391 List<ComponentName> componentsUpdated = new ArrayList<>(); 1392 for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) { 1393 InCallServiceInfo info = entry.getKey(); 1394 if (call.isExternalCall() && !info.isExternalCallsSupported()) { 1395 continue; 1396 } 1397 1398 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) { 1399 continue; 1400 } 1401 1402 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall( 1403 call, 1404 videoProviderChanged /* includeVideoProvider */, 1405 mCallsManager.getPhoneAccountRegistrar(), 1406 info.isExternalCallsSupported(), 1407 rttInfoChanged && info.equals(mInCallServiceConnection.getInfo())); 1408 ComponentName componentName = info.getComponentName(); 1409 IInCallService inCallService = entry.getValue(); 1410 componentsUpdated.add(componentName); 1411 1412 try { 1413 inCallService.updateCall(parcelableCall); 1414 } catch (RemoteException ignored) { 1415 } 1416 } 1417 Log.i(this, "Components updated: %s", componentsUpdated); 1418 } 1419 } 1420 1421 /** 1422 * Adds the call to the list of calls tracked by the {@link InCallController}. 1423 * @param call The call to add. 1424 */ addCall(Call call)1425 private void addCall(Call call) { 1426 if (mCallIdMapper.getCallId(call) == null) { 1427 mCallIdMapper.addCall(call); 1428 call.addListener(mCallListener); 1429 } 1430 } 1431 1432 /** 1433 * @return true if we are bound to the UI InCallService and it is connected. 1434 */ isBoundAndConnectedToServices()1435 private boolean isBoundAndConnectedToServices() { 1436 return mInCallServiceConnection != null && mInCallServiceConnection.isConnected(); 1437 } 1438 1439 /** 1440 * Dumps the state of the {@link InCallController}. 1441 * 1442 * @param pw The {@code IndentingPrintWriter} to write the state to. 1443 */ dump(IndentingPrintWriter pw)1444 public void dump(IndentingPrintWriter pw) { 1445 pw.println("mInCallServices (InCalls registered):"); 1446 pw.increaseIndent(); 1447 for (InCallServiceInfo info : mInCallServices.keySet()) { 1448 pw.println(info); 1449 } 1450 pw.decreaseIndent(); 1451 1452 pw.println("ServiceConnections (InCalls bound):"); 1453 pw.increaseIndent(); 1454 if (mInCallServiceConnection != null) { 1455 mInCallServiceConnection.dump(pw); 1456 } 1457 pw.decreaseIndent(); 1458 } 1459 doesConnectedDialerSupportRinging()1460 public boolean doesConnectedDialerSupportRinging() { 1461 String ringingPackage = null; 1462 if (mInCallUIComponentName != null) { 1463 ringingPackage = mInCallUIComponentName.getPackageName().trim(); 1464 } 1465 1466 if (TextUtils.isEmpty(ringingPackage)) { 1467 // The current in-call UI returned nothing, so lets use the default dialer. 1468 ringingPackage = DefaultDialerManager.getDefaultDialerApplication( 1469 mContext, UserHandle.USER_CURRENT); 1470 } 1471 if (TextUtils.isEmpty(ringingPackage)) { 1472 return false; 1473 } 1474 1475 Intent intent = new Intent(InCallService.SERVICE_INTERFACE) 1476 .setPackage(ringingPackage); 1477 List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser( 1478 intent, PackageManager.GET_META_DATA, 1479 mCallsManager.getCurrentUserHandle().getIdentifier()); 1480 if (entries.isEmpty()) { 1481 return false; 1482 } 1483 1484 ResolveInfo info = entries.get(0); 1485 if (info.serviceInfo == null || info.serviceInfo.metaData == null) { 1486 return false; 1487 } 1488 1489 return info.serviceInfo.metaData 1490 .getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_RINGING, false); 1491 } 1492 orderCallsWithChildrenFirst(Collection<Call> calls)1493 private List<Call> orderCallsWithChildrenFirst(Collection<Call> calls) { 1494 LinkedList<Call> parentCalls = new LinkedList<>(); 1495 LinkedList<Call> childCalls = new LinkedList<>(); 1496 for (Call call : calls) { 1497 if (call.getChildCalls().size() > 0) { 1498 parentCalls.add(call); 1499 } else { 1500 childCalls.add(call); 1501 } 1502 } 1503 childCalls.addAll(parentCalls); 1504 return childCalls; 1505 } 1506 } 1507