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