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.getComponentName()); 291 disconnect(); // Unbind explicitly if we get disconnected. 292 if (mListener != null) { 293 mListener.onDisconnect(InCallServiceBindingConnection.this); 294 } 295 } 296 } 297 298 /** 299 * A version of the InCallServiceBindingConnection that proxies all calls to a secondary 300 * connection until it finds an emergency call, or the other connection dies. When one of those 301 * two things happen, this class instance will take over the connection. 302 */ 303 private class EmergencyInCallServiceConnection extends InCallServiceBindingConnection { 304 private boolean mIsProxying = true; 305 private boolean mIsConnected = false; 306 private final InCallServiceConnection mSubConnection; 307 308 private Listener mSubListener = new Listener() { 309 @Override 310 public void onDisconnect(InCallServiceConnection subConnection) { 311 if (subConnection == mSubConnection) { 312 if (mIsConnected && mIsProxying) { 313 // At this point we know that we need to be connected to the InCallService 314 // and we are proxying to the sub connection. However, the sub-connection 315 // just died so we need to stop proxying and connect to the system in-call 316 // service instead. 317 mIsProxying = false; 318 connect(null); 319 } 320 } 321 } 322 }; 323 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 onRttInitiationFailure(Call call, int reason) { 681 notifyRttInitiationFailure(call, reason); 682 updateCall(call, false, true); 683 } 684 685 @Override 686 public void onRemoteRttRequest(Call call, int requestId) { 687 notifyRemoteRttRequest(call, requestId); 688 } 689 }; 690 691 private final SystemStateListener mSystemStateListener = new SystemStateListener() { 692 @Override 693 public void onCarModeChanged(boolean isCarMode) { 694 if (mInCallServiceConnection != null) { 695 mInCallServiceConnection.setCarMode(shouldUseCarModeUI()); 696 } 697 } 698 }; 699 700 private static final int IN_CALL_SERVICE_TYPE_INVALID = 0; 701 private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1; 702 private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2; 703 private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3; 704 private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4; 705 706 /** The in-call app implementations, see {@link IInCallService}. */ 707 private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>(); 708 709 /** 710 * The {@link ComponentName} of the bound In-Call UI Service. 711 */ 712 private ComponentName mInCallUIComponentName; 713 714 private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId); 715 716 /** The {@link ComponentName} of the default InCall UI. */ 717 private final ComponentName mSystemInCallComponentName; 718 719 private final Context mContext; 720 private final TelecomSystem.SyncRoot mLock; 721 private final CallsManager mCallsManager; 722 private final SystemStateProvider mSystemStateProvider; 723 private final Timeouts.Adapter mTimeoutsAdapter; 724 private final DefaultDialerCache mDefaultDialerCache; 725 private final EmergencyCallHelper mEmergencyCallHelper; 726 private CarSwappingInCallServiceConnection mInCallServiceConnection; 727 private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections; 728 InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, SystemStateProvider systemStateProvider, DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper)729 public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, 730 SystemStateProvider systemStateProvider, 731 DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter, 732 EmergencyCallHelper emergencyCallHelper) { 733 mContext = context; 734 mLock = lock; 735 mCallsManager = callsManager; 736 mSystemStateProvider = systemStateProvider; 737 mTimeoutsAdapter = timeoutsAdapter; 738 mDefaultDialerCache = defaultDialerCache; 739 mEmergencyCallHelper = emergencyCallHelper; 740 741 Resources resources = mContext.getResources(); 742 mSystemInCallComponentName = new ComponentName( 743 resources.getString(R.string.ui_default_package), 744 resources.getString(R.string.incall_default_class)); 745 746 mSystemStateProvider.addListener(mSystemStateListener); 747 } 748 749 @Override onCallAdded(Call call)750 public void onCallAdded(Call call) { 751 if (!isBoundAndConnectedToServices()) { 752 Log.i(this, "onCallAdded: %s; not bound or connected.", call); 753 // We are not bound, or we're not connected. 754 bindToServices(call); 755 } else { 756 // We are bound, and we are connected. 757 adjustServiceBindingsForEmergency(); 758 759 Log.i(this, "onCallAdded: %s", call); 760 // Track the call if we don't already know about it. 761 addCall(call); 762 763 Log.i(this, "mInCallServiceConnection isConnected=%b", 764 mInCallServiceConnection.isConnected()); 765 766 List<ComponentName> componentsUpdated = new ArrayList<>(); 767 for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) { 768 InCallServiceInfo info = entry.getKey(); 769 770 if (call.isExternalCall() && !info.isExternalCallsSupported()) { 771 continue; 772 } 773 774 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) { 775 continue; 776 } 777 778 // Only send the RTT call if it's a UI in-call service 779 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo()); 780 781 componentsUpdated.add(info.getComponentName()); 782 IInCallService inCallService = entry.getValue(); 783 784 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call, 785 true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(), 786 info.isExternalCallsSupported(), includeRttCall); 787 try { 788 inCallService.addCall(parcelableCall); 789 } catch (RemoteException ignored) { 790 } 791 } 792 Log.i(this, "Call added to components: %s", componentsUpdated); 793 } 794 } 795 796 @Override onCallRemoved(Call call)797 public void onCallRemoved(Call call) { 798 Log.i(this, "onCallRemoved: %s", call); 799 if (mCallsManager.getCalls().isEmpty()) { 800 /** Let's add a 2 second delay before we send unbind to the services to hopefully 801 * give them enough time to process all the pending messages. 802 */ 803 Handler handler = new Handler(Looper.getMainLooper()); 804 handler.postDelayed(new Runnable("ICC.oCR", mLock) { 805 @Override 806 public void loggedRun() { 807 // Check again to make sure there are no active calls. 808 if (mCallsManager.getCalls().isEmpty()) { 809 unbindFromServices(); 810 } 811 } 812 }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay( 813 mContext.getContentResolver())); 814 } 815 call.removeListener(mCallListener); 816 mCallIdMapper.removeCall(call); 817 } 818 819 @Override onExternalCallChanged(Call call, boolean isExternalCall)820 public void onExternalCallChanged(Call call, boolean isExternalCall) { 821 Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall); 822 823 List<ComponentName> componentsUpdated = new ArrayList<>(); 824 if (!isExternalCall) { 825 // The call was external but it is no longer external. We must now add it to any 826 // InCallServices which do not support external calls. 827 for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) { 828 InCallServiceInfo info = entry.getKey(); 829 830 if (info.isExternalCallsSupported()) { 831 // For InCallServices which support external calls, the call will have already 832 // been added to the connection service, so we do not need to add it again. 833 continue; 834 } 835 836 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) { 837 continue; 838 } 839 840 componentsUpdated.add(info.getComponentName()); 841 IInCallService inCallService = entry.getValue(); 842 843 // Only send the RTT call if it's a UI in-call service 844 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo()); 845 846 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call, 847 true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(), 848 info.isExternalCallsSupported(), includeRttCall); 849 try { 850 inCallService.addCall(parcelableCall); 851 } catch (RemoteException ignored) { 852 } 853 } 854 Log.i(this, "Previously external call added to components: %s", componentsUpdated); 855 } else { 856 // The call was regular but it is now external. We must now remove it from any 857 // InCallServices which do not support external calls. 858 // Remove the call by sending a call update indicating the call was disconnected. 859 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall( 860 call, 861 false /* includeVideoProvider */, 862 mCallsManager.getPhoneAccountRegistrar(), 863 false /* supportsExternalCalls */, 864 android.telecom.Call.STATE_DISCONNECTED /* overrideState */, 865 false /* includeRttCall */); 866 867 Log.i(this, "Removing external call %s ==> %s", call, parcelableCall); 868 for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) { 869 InCallServiceInfo info = entry.getKey(); 870 if (info.isExternalCallsSupported()) { 871 // For InCallServices which support external calls, we do not need to remove 872 // the call. 873 continue; 874 } 875 876 componentsUpdated.add(info.getComponentName()); 877 IInCallService inCallService = entry.getValue(); 878 879 try { 880 inCallService.updateCall(parcelableCall); 881 } catch (RemoteException ignored) { 882 } 883 } 884 Log.i(this, "External call removed from components: %s", componentsUpdated); 885 } 886 } 887 888 @Override onCallStateChanged(Call call, int oldState, int newState)889 public void onCallStateChanged(Call call, int oldState, int newState) { 890 updateCall(call); 891 } 892 893 @Override onConnectionServiceChanged( Call call, ConnectionServiceWrapper oldService, ConnectionServiceWrapper newService)894 public void onConnectionServiceChanged( 895 Call call, 896 ConnectionServiceWrapper oldService, 897 ConnectionServiceWrapper newService) { 898 updateCall(call); 899 } 900 901 @Override onCallAudioStateChanged(CallAudioState oldCallAudioState, CallAudioState newCallAudioState)902 public void onCallAudioStateChanged(CallAudioState oldCallAudioState, 903 CallAudioState newCallAudioState) { 904 if (!mInCallServices.isEmpty()) { 905 Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState, 906 newCallAudioState); 907 for (IInCallService inCallService : mInCallServices.values()) { 908 try { 909 inCallService.onCallAudioStateChanged(newCallAudioState); 910 } catch (RemoteException ignored) { 911 } 912 } 913 } 914 } 915 916 @Override onCanAddCallChanged(boolean canAddCall)917 public void onCanAddCallChanged(boolean canAddCall) { 918 if (!mInCallServices.isEmpty()) { 919 Log.i(this, "onCanAddCallChanged : %b", canAddCall); 920 for (IInCallService inCallService : mInCallServices.values()) { 921 try { 922 inCallService.onCanAddCallChanged(canAddCall); 923 } catch (RemoteException ignored) { 924 } 925 } 926 } 927 } 928 onPostDialWait(Call call, String remaining)929 void onPostDialWait(Call call, String remaining) { 930 if (!mInCallServices.isEmpty()) { 931 Log.i(this, "Calling onPostDialWait, remaining = %s", remaining); 932 for (IInCallService inCallService : mInCallServices.values()) { 933 try { 934 inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining); 935 } catch (RemoteException ignored) { 936 } 937 } 938 } 939 } 940 941 @Override onIsConferencedChanged(Call call)942 public void onIsConferencedChanged(Call call) { 943 Log.d(this, "onIsConferencedChanged %s", call); 944 updateCall(call); 945 } 946 bringToForeground(boolean showDialpad)947 void bringToForeground(boolean showDialpad) { 948 if (!mInCallServices.isEmpty()) { 949 for (IInCallService inCallService : mInCallServices.values()) { 950 try { 951 inCallService.bringToForeground(showDialpad); 952 } catch (RemoteException ignored) { 953 } 954 } 955 } else { 956 Log.w(this, "Asking to bring unbound in-call UI to foreground."); 957 } 958 } 959 silenceRinger()960 void silenceRinger() { 961 if (!mInCallServices.isEmpty()) { 962 for (IInCallService inCallService : mInCallServices.values()) { 963 try { 964 inCallService.silenceRinger(); 965 } catch (RemoteException ignored) { 966 } 967 } 968 } 969 } 970 notifyConnectionEvent(Call call, String event, Bundle extras)971 private void notifyConnectionEvent(Call call, String event, Bundle extras) { 972 if (!mInCallServices.isEmpty()) { 973 for (IInCallService inCallService : mInCallServices.values()) { 974 try { 975 Log.i(this, "notifyConnectionEvent {Call: %s, Event: %s, Extras:[%s]}", 976 (call != null ? call.toString() :"null"), 977 (event != null ? event : "null") , 978 (extras != null ? extras.toString() : "null")); 979 inCallService.onConnectionEvent(mCallIdMapper.getCallId(call), event, extras); 980 } catch (RemoteException ignored) { 981 } 982 } 983 } 984 } 985 notifyRttInitiationFailure(Call call, int reason)986 private void notifyRttInitiationFailure(Call call, int reason) { 987 if (!mInCallServices.isEmpty()) { 988 mInCallServices.entrySet().stream() 989 .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo())) 990 .forEach((entry) -> { 991 try { 992 Log.i(this, "notifyRttFailure, call %s, incall %s", 993 call, entry.getKey()); 994 entry.getValue().onRttInitiationFailure(mCallIdMapper.getCallId(call), 995 reason); 996 } catch (RemoteException ignored) { 997 } 998 }); 999 } 1000 } 1001 notifyRemoteRttRequest(Call call, int requestId)1002 private void notifyRemoteRttRequest(Call call, int requestId) { 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, "notifyRemoteRttRequest, call %s, incall %s", 1009 call, entry.getKey()); 1010 entry.getValue().onRttUpgradeRequest( 1011 mCallIdMapper.getCallId(call), requestId); 1012 } catch (RemoteException ignored) { 1013 } 1014 }); 1015 } 1016 } 1017 /** 1018 * Unbinds an existing bound connection to the in-call app. 1019 */ unbindFromServices()1020 private void unbindFromServices() { 1021 if (mInCallServiceConnection != null) { 1022 mInCallServiceConnection.disconnect(); 1023 mInCallServiceConnection = null; 1024 } 1025 if (mNonUIInCallServiceConnections != null) { 1026 mNonUIInCallServiceConnections.disconnect(); 1027 mNonUIInCallServiceConnections = null; 1028 } 1029 } 1030 1031 /** 1032 * Binds to all the UI-providing InCallService as well as system-implemented non-UI 1033 * InCallServices. Method-invoker must check {@link #isBoundAndConnectedToServices()} before invoking. 1034 * 1035 * @param call The newly added call that triggered the binding to the in-call services. 1036 */ 1037 @VisibleForTesting bindToServices(Call call)1038 public void bindToServices(Call call) { 1039 if (mInCallServiceConnection == null) { 1040 InCallServiceConnection dialerInCall = null; 1041 InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent(); 1042 Log.i(this, "defaultDialer: " + defaultDialerComponentInfo); 1043 if (defaultDialerComponentInfo != null && 1044 !defaultDialerComponentInfo.getComponentName().equals( 1045 mSystemInCallComponentName)) { 1046 dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo); 1047 } 1048 Log.i(this, "defaultDialer: " + dialerInCall); 1049 1050 InCallServiceInfo systemInCallInfo = getInCallServiceComponent( 1051 mSystemInCallComponentName, IN_CALL_SERVICE_TYPE_SYSTEM_UI); 1052 EmergencyInCallServiceConnection systemInCall = 1053 new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall); 1054 systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall()); 1055 1056 InCallServiceConnection carModeInCall = null; 1057 InCallServiceInfo carModeComponentInfo = getCarModeComponent(); 1058 if (carModeComponentInfo != null && 1059 !carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) { 1060 carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo); 1061 } 1062 1063 mInCallServiceConnection = 1064 new CarSwappingInCallServiceConnection(systemInCall, carModeInCall); 1065 } 1066 1067 mInCallServiceConnection.setCarMode(shouldUseCarModeUI()); 1068 1069 // Actually try binding to the UI InCallService. If the response 1070 if (mInCallServiceConnection.connect(call) == 1071 InCallServiceConnection.CONNECTION_SUCCEEDED) { 1072 // Only connect to the non-ui InCallServices if we actually connected to the main UI 1073 // one. 1074 connectToNonUiInCallServices(call); 1075 } else { 1076 Log.i(this, "bindToServices: current UI doesn't support call; not binding."); 1077 } 1078 } 1079 connectToNonUiInCallServices(Call call)1080 private void connectToNonUiInCallServices(Call call) { 1081 List<InCallServiceInfo> nonUIInCallComponents = 1082 getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI); 1083 List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>(); 1084 for (InCallServiceInfo serviceInfo : nonUIInCallComponents) { 1085 nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo)); 1086 } 1087 mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls); 1088 mNonUIInCallServiceConnections.connect(call); 1089 } 1090 getDefaultDialerComponent()1091 private InCallServiceInfo getDefaultDialerComponent() { 1092 String packageName = mDefaultDialerCache.getDefaultDialerApplication( 1093 mCallsManager.getCurrentUserHandle().getIdentifier()); 1094 Log.d(this, "Default Dialer package: " + packageName); 1095 1096 return getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI); 1097 } 1098 getCarModeComponent()1099 private InCallServiceInfo getCarModeComponent() { 1100 // Seems strange to cast a String to null, but the signatures of getInCallServiceComponent 1101 // differ in the types of the first parameter, and passing in null is inherently ambiguous. 1102 return getInCallServiceComponent((String) null, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); 1103 } 1104 getInCallServiceComponent(ComponentName componentName, int type)1105 private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) { 1106 List<InCallServiceInfo> list = getInCallServiceComponents(componentName, type); 1107 if (list != null && !list.isEmpty()) { 1108 return list.get(0); 1109 } else { 1110 // Last Resort: Try to bind to the ComponentName given directly. 1111 Log.e(this, new Exception(), "Package Manager could not find ComponentName: " 1112 + componentName +". Trying to bind anyway."); 1113 return new InCallServiceInfo(componentName, false, false, type); 1114 } 1115 } 1116 getInCallServiceComponent(String packageName, int type)1117 private InCallServiceInfo getInCallServiceComponent(String packageName, int type) { 1118 List<InCallServiceInfo> list = getInCallServiceComponents(packageName, type); 1119 if (list != null && !list.isEmpty()) { 1120 return list.get(0); 1121 } 1122 return null; 1123 } 1124 getInCallServiceComponents(int type)1125 private List<InCallServiceInfo> getInCallServiceComponents(int type) { 1126 return getInCallServiceComponents(null, null, type); 1127 } 1128 getInCallServiceComponents(String packageName, int type)1129 private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type) { 1130 return getInCallServiceComponents(packageName, null, type); 1131 } 1132 getInCallServiceComponents(ComponentName componentName, int type)1133 private List<InCallServiceInfo> getInCallServiceComponents(ComponentName componentName, 1134 int type) { 1135 return getInCallServiceComponents(null, componentName, type); 1136 } 1137 getInCallServiceComponents(String packageName, ComponentName componentName, int requestedType)1138 private List<InCallServiceInfo> getInCallServiceComponents(String packageName, 1139 ComponentName componentName, int requestedType) { 1140 1141 List<InCallServiceInfo> retval = new LinkedList<>(); 1142 1143 Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE); 1144 if (packageName != null) { 1145 serviceIntent.setPackage(packageName); 1146 } 1147 if (componentName != null) { 1148 serviceIntent.setComponent(componentName); 1149 } 1150 1151 PackageManager packageManager = mContext.getPackageManager(); 1152 for (ResolveInfo entry : packageManager.queryIntentServicesAsUser( 1153 serviceIntent, 1154 PackageManager.GET_META_DATA, 1155 mCallsManager.getCurrentUserHandle().getIdentifier())) { 1156 ServiceInfo serviceInfo = entry.serviceInfo; 1157 1158 if (serviceInfo != null) { 1159 boolean isExternalCallsSupported = serviceInfo.metaData != null && 1160 serviceInfo.metaData.getBoolean( 1161 TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, false); 1162 boolean isSelfManageCallsSupported = serviceInfo.metaData != null && 1163 serviceInfo.metaData.getBoolean( 1164 TelecomManager.METADATA_INCLUDE_SELF_MANAGED_CALLS, false); 1165 1166 int currentType = getInCallServiceType(entry.serviceInfo, packageManager); 1167 if (requestedType == 0 || requestedType == currentType) { 1168 if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) { 1169 // We enforce the rule that self-managed calls are not supported by non-ui 1170 // InCallServices. 1171 isSelfManageCallsSupported = false; 1172 } 1173 retval.add(new InCallServiceInfo( 1174 new ComponentName(serviceInfo.packageName, serviceInfo.name), 1175 isExternalCallsSupported, isSelfManageCallsSupported, requestedType)); 1176 } 1177 } 1178 } 1179 1180 return retval; 1181 } 1182 shouldUseCarModeUI()1183 private boolean shouldUseCarModeUI() { 1184 return mSystemStateProvider.isCarMode(); 1185 } 1186 1187 /** 1188 * Returns the type of InCallService described by the specified serviceInfo. 1189 */ getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager)1190 private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager) { 1191 // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which 1192 // enforces that only Telecom can bind to it. 1193 boolean hasServiceBindPermission = serviceInfo.permission != null && 1194 serviceInfo.permission.equals( 1195 Manifest.permission.BIND_INCALL_SERVICE); 1196 if (!hasServiceBindPermission) { 1197 Log.w(this, "InCallService does not require BIND_INCALL_SERVICE permission: " + 1198 serviceInfo.packageName); 1199 return IN_CALL_SERVICE_TYPE_INVALID; 1200 } 1201 1202 if (mSystemInCallComponentName.getPackageName().equals(serviceInfo.packageName) && 1203 mSystemInCallComponentName.getClassName().equals(serviceInfo.name)) { 1204 return IN_CALL_SERVICE_TYPE_SYSTEM_UI; 1205 } 1206 1207 // Check to see if the service is a car-mode UI type by checking that it has the 1208 // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the 1209 // car-mode UI metadata. 1210 boolean hasControlInCallPermission = packageManager.checkPermission( 1211 Manifest.permission.CONTROL_INCALL_EXPERIENCE, 1212 serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED; 1213 boolean isCarModeUIService = serviceInfo.metaData != null && 1214 serviceInfo.metaData.getBoolean( 1215 TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) && 1216 hasControlInCallPermission; 1217 if (isCarModeUIService) { 1218 return IN_CALL_SERVICE_TYPE_CAR_MODE_UI; 1219 } 1220 1221 // Check to see that it is the default dialer package 1222 boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName, 1223 mDefaultDialerCache.getDefaultDialerApplication( 1224 mCallsManager.getCurrentUserHandle().getIdentifier())); 1225 boolean isUIService = serviceInfo.metaData != null && 1226 serviceInfo.metaData.getBoolean( 1227 TelecomManager.METADATA_IN_CALL_SERVICE_UI, false); 1228 if (isDefaultDialerPackage && isUIService) { 1229 return IN_CALL_SERVICE_TYPE_DIALER_UI; 1230 } 1231 1232 // Also allow any in-call service that has the control-experience permission (to ensure 1233 // that it is a system app) and doesn't claim to show any UI. 1234 if (hasControlInCallPermission && !isUIService) { 1235 return IN_CALL_SERVICE_TYPE_NON_UI; 1236 } 1237 1238 // Anything else that remains, we will not bind to. 1239 Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b", 1240 serviceInfo.packageName, serviceInfo.name, hasControlInCallPermission, 1241 isCarModeUIService, isUIService); 1242 return IN_CALL_SERVICE_TYPE_INVALID; 1243 } 1244 adjustServiceBindingsForEmergency()1245 private void adjustServiceBindingsForEmergency() { 1246 // The connected UI is not the system UI, so lets check if we should switch them 1247 // if there exists an emergency number. 1248 if (mCallsManager.hasEmergencyCall()) { 1249 mInCallServiceConnection.setHasEmergency(true); 1250 } 1251 } 1252 1253 /** 1254 * Persists the {@link IInCallService} instance and starts the communication between 1255 * this class and in-call app by sending the first update to in-call app. This method is 1256 * called after a successful binding connection is established. 1257 * 1258 * @param info Info about the service, including its {@link ComponentName}. 1259 * @param service The {@link IInCallService} implementation. 1260 * @return True if we successfully connected. 1261 */ onConnected(InCallServiceInfo info, IBinder service)1262 private boolean onConnected(InCallServiceInfo info, IBinder service) { 1263 Trace.beginSection("onConnected: " + info.getComponentName()); 1264 Log.i(this, "onConnected to %s", info.getComponentName()); 1265 1266 IInCallService inCallService = IInCallService.Stub.asInterface(service); 1267 mInCallServices.put(info, inCallService); 1268 1269 try { 1270 inCallService.setInCallAdapter( 1271 new InCallAdapter( 1272 mCallsManager, 1273 mCallIdMapper, 1274 mLock, 1275 info.getComponentName().getPackageName())); 1276 } catch (RemoteException e) { 1277 Log.e(this, e, "Failed to set the in-call adapter."); 1278 Trace.endSection(); 1279 return false; 1280 } 1281 1282 // Upon successful connection, send the state of the world to the service. 1283 List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls()); 1284 Log.i(this, "Adding %s calls to InCallService after onConnected: %s, including external " + 1285 "calls", calls.size(), info.getComponentName()); 1286 int numCallsSent = 0; 1287 for (Call call : calls) { 1288 try { 1289 if ((call.isSelfManaged() && !info.isSelfManagedCallsSupported()) || 1290 (call.isExternalCall() && !info.isExternalCallsSupported())) { 1291 continue; 1292 } 1293 1294 // Only send the RTT call if it's a UI in-call service 1295 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo()); 1296 1297 // Track the call if we don't already know about it. 1298 addCall(call); 1299 numCallsSent += 1; 1300 inCallService.addCall(ParcelableCallUtils.toParcelableCall( 1301 call, 1302 true /* includeVideoProvider */, 1303 mCallsManager.getPhoneAccountRegistrar(), 1304 info.isExternalCallsSupported(), 1305 includeRttCall)); 1306 } catch (RemoteException ignored) { 1307 } 1308 } 1309 try { 1310 inCallService.onCallAudioStateChanged(mCallsManager.getAudioState()); 1311 inCallService.onCanAddCallChanged(mCallsManager.canAddCall()); 1312 } catch (RemoteException ignored) { 1313 } 1314 Log.i(this, "%s calls sent to InCallService.", numCallsSent); 1315 Trace.endSection(); 1316 return true; 1317 } 1318 1319 /** 1320 * Cleans up an instance of in-call app after the service has been unbound. 1321 * 1322 * @param disconnectedComponent The {@link ComponentName} of the service which disconnected. 1323 */ onDisconnected(ComponentName disconnectedComponent)1324 private void onDisconnected(ComponentName disconnectedComponent) { 1325 Log.i(this, "onDisconnected from %s", disconnectedComponent); 1326 1327 mInCallServices.remove(disconnectedComponent); 1328 } 1329 1330 /** 1331 * Informs all {@link InCallService} instances of the updated call information. 1332 * 1333 * @param call The {@link Call}. 1334 */ updateCall(Call call)1335 private void updateCall(Call call) { 1336 updateCall(call, false /* videoProviderChanged */, false); 1337 } 1338 1339 /** 1340 * Informs all {@link InCallService} instances of the updated call information. 1341 * 1342 * @param call The {@link Call}. 1343 * @param videoProviderChanged {@code true} if the video provider changed, {@code false} 1344 * otherwise. 1345 * @param rttInfoChanged {@code true} if any information about the RTT session changed, 1346 * {@code false} otherwise. 1347 */ updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged)1348 private void updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged) { 1349 if (!mInCallServices.isEmpty()) { 1350 Log.i(this, "Sending updateCall %s", call); 1351 List<ComponentName> componentsUpdated = new ArrayList<>(); 1352 for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) { 1353 InCallServiceInfo info = entry.getKey(); 1354 if (call.isExternalCall() && !info.isExternalCallsSupported()) { 1355 continue; 1356 } 1357 1358 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) { 1359 continue; 1360 } 1361 1362 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall( 1363 call, 1364 videoProviderChanged /* includeVideoProvider */, 1365 mCallsManager.getPhoneAccountRegistrar(), 1366 info.isExternalCallsSupported(), 1367 rttInfoChanged && info.equals(mInCallServiceConnection.getInfo())); 1368 ComponentName componentName = info.getComponentName(); 1369 IInCallService inCallService = entry.getValue(); 1370 componentsUpdated.add(componentName); 1371 1372 try { 1373 inCallService.updateCall(parcelableCall); 1374 } catch (RemoteException ignored) { 1375 } 1376 } 1377 Log.i(this, "Components updated: %s", componentsUpdated); 1378 } 1379 } 1380 1381 /** 1382 * Adds the call to the list of calls tracked by the {@link InCallController}. 1383 * @param call The call to add. 1384 */ addCall(Call call)1385 private void addCall(Call call) { 1386 if (mCallIdMapper.getCallId(call) == null) { 1387 mCallIdMapper.addCall(call); 1388 call.addListener(mCallListener); 1389 } 1390 } 1391 1392 /** 1393 * @return true if we are bound to the UI InCallService and it is connected. 1394 */ isBoundAndConnectedToServices()1395 private boolean isBoundAndConnectedToServices() { 1396 return mInCallServiceConnection != null && mInCallServiceConnection.isConnected(); 1397 } 1398 1399 /** 1400 * Dumps the state of the {@link InCallController}. 1401 * 1402 * @param pw The {@code IndentingPrintWriter} to write the state to. 1403 */ dump(IndentingPrintWriter pw)1404 public void dump(IndentingPrintWriter pw) { 1405 pw.println("mInCallServices (InCalls registered):"); 1406 pw.increaseIndent(); 1407 for (InCallServiceInfo info : mInCallServices.keySet()) { 1408 pw.println(info); 1409 } 1410 pw.decreaseIndent(); 1411 1412 pw.println("ServiceConnections (InCalls bound):"); 1413 pw.increaseIndent(); 1414 if (mInCallServiceConnection != null) { 1415 mInCallServiceConnection.dump(pw); 1416 } 1417 pw.decreaseIndent(); 1418 } 1419 doesConnectedDialerSupportRinging()1420 public boolean doesConnectedDialerSupportRinging() { 1421 String ringingPackage = null; 1422 if (mInCallUIComponentName != null) { 1423 ringingPackage = mInCallUIComponentName.getPackageName().trim(); 1424 } 1425 1426 if (TextUtils.isEmpty(ringingPackage)) { 1427 // The current in-call UI returned nothing, so lets use the default dialer. 1428 ringingPackage = DefaultDialerManager.getDefaultDialerApplication( 1429 mContext, UserHandle.USER_CURRENT); 1430 } 1431 if (TextUtils.isEmpty(ringingPackage)) { 1432 return false; 1433 } 1434 1435 Intent intent = new Intent(InCallService.SERVICE_INTERFACE) 1436 .setPackage(ringingPackage); 1437 List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser( 1438 intent, PackageManager.GET_META_DATA, 1439 mCallsManager.getCurrentUserHandle().getIdentifier()); 1440 if (entries.isEmpty()) { 1441 return false; 1442 } 1443 1444 ResolveInfo info = entries.get(0); 1445 if (info.serviceInfo == null || info.serviceInfo.metaData == null) { 1446 return false; 1447 } 1448 1449 return info.serviceInfo.metaData 1450 .getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_RINGING, false); 1451 } 1452 orderCallsWithChildrenFirst(Collection<Call> calls)1453 private List<Call> orderCallsWithChildrenFirst(Collection<Call> calls) { 1454 LinkedList<Call> parentCalls = new LinkedList<>(); 1455 LinkedList<Call> childCalls = new LinkedList<>(); 1456 for (Call call : calls) { 1457 if (call.getChildCalls().size() > 0) { 1458 parentCalls.add(call); 1459 } else { 1460 childCalls.add(call); 1461 } 1462 } 1463 childCalls.addAll(parentCalls); 1464 return childCalls; 1465 } 1466 } 1467