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