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