1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.telecom; 18 19 import android.Manifest; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.ServiceConnection; 24 import android.content.pm.PackageManager; 25 import android.content.pm.ResolveInfo; 26 import android.content.pm.ServiceInfo; 27 import android.content.res.Resources; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.IBinder; 31 import android.os.Looper; 32 import android.os.RemoteException; 33 import android.os.Trace; 34 import android.os.UserHandle; 35 import android.telecom.CallAudioState; 36 import android.telecom.ConnectionService; 37 import android.telecom.DefaultDialerManager; 38 import android.telecom.InCallService; 39 import android.telecom.ParcelableCall; 40 import android.telecom.TelecomManager; 41 import android.text.TextUtils; 42 import android.util.ArrayMap; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 // TODO: Needed for move to system service: import com.android.internal.R; 46 import com.android.internal.telecom.IInCallService; 47 import com.android.internal.util.IndentingPrintWriter; 48 import com.android.server.telecom.SystemStateProvider.SystemStateListener; 49 import com.android.server.telecom.TelecomServiceImpl.DefaultDialerManagerAdapter; 50 51 import java.util.ArrayList; 52 import java.util.Collection; 53 import java.util.LinkedList; 54 import java.util.List; 55 import java.util.Map; 56 import java.util.Objects; 57 58 /** 59 * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it 60 * can send updates to the in-call app. This class is created and owned by CallsManager and retains 61 * a binding to the {@link IInCallService} (implemented by the in-call app). 62 */ 63 public final class InCallController extends CallsManagerListenerBase { 64 65 public class InCallServiceConnection { 66 public class Listener { onDisconnect(InCallServiceConnection conn)67 public void onDisconnect(InCallServiceConnection conn) {} 68 } 69 70 protected Listener mListener; 71 connect(Call call)72 public boolean connect(Call call) { return false; } disconnect()73 public void disconnect() {} setHasEmergency(boolean hasEmergency)74 public void setHasEmergency(boolean hasEmergency) {} setListener(Listener l)75 public void setListener(Listener l) { 76 mListener = l; 77 } dump(IndentingPrintWriter pw)78 public void dump(IndentingPrintWriter pw) {} 79 } 80 81 private class InCallServiceBindingConnection extends InCallServiceConnection { 82 83 private final ServiceConnection mServiceConnection = new ServiceConnection() { 84 @Override 85 public void onServiceConnected(ComponentName name, IBinder service) { 86 Log.startSession("ICSBC.oSC"); 87 synchronized (mLock) { 88 try { 89 Log.d(this, "onServiceConnected: %s %b %b", name, mIsBound, mIsConnected); 90 mIsBound = true; 91 if (mIsConnected) { 92 // Only proceed if we are supposed to be connected. 93 onConnected(service); 94 } 95 } finally { 96 Log.endSession(); 97 } 98 } 99 } 100 101 @Override 102 public void onServiceDisconnected(ComponentName name) { 103 Log.startSession("ICSBC.oSD"); 104 synchronized (mLock) { 105 try { 106 Log.d(this, "onDisconnected: %s", name); 107 mIsBound = false; 108 onDisconnected(); 109 } finally { 110 Log.endSession(); 111 } 112 } 113 } 114 }; 115 116 private final ComponentName mComponentName; 117 private boolean mIsConnected = false; 118 private boolean mIsBound = false; 119 InCallServiceBindingConnection(ComponentName componentName)120 public InCallServiceBindingConnection(ComponentName componentName) { 121 mComponentName = componentName; 122 } 123 124 @Override connect(Call call)125 public boolean connect(Call call) { 126 if (mIsConnected) { 127 Log.event(call, Log.Events.INFO, "Already connected, ignoring request."); 128 return true; 129 } 130 131 Intent intent = new Intent(InCallService.SERVICE_INTERFACE); 132 intent.setComponent(mComponentName); 133 if (call != null && !call.isIncoming() && !call.isExternalCall()){ 134 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, 135 call.getIntentExtras()); 136 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, 137 call.getTargetPhoneAccount()); 138 } 139 140 Log.i(this, "Attempting to bind to InCall %s, with %s", mComponentName, intent); 141 mIsConnected = true; 142 if (!mContext.bindServiceAsUser(intent, mServiceConnection, 143 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, 144 UserHandle.CURRENT)) { 145 Log.w(this, "Failed to connect."); 146 mIsConnected = false; 147 } 148 149 return mIsConnected; 150 } 151 152 @Override disconnect()153 public void disconnect() { 154 if (mIsConnected) { 155 mContext.unbindService(mServiceConnection); 156 mIsConnected = false; 157 } else { 158 Log.event(null, Log.Events.INFO, "Already disconnected, ignoring request."); 159 } 160 } 161 162 @Override dump(IndentingPrintWriter pw)163 public void dump(IndentingPrintWriter pw) { 164 pw.append("BindingConnection ["); 165 pw.append(mIsConnected ? "" : "not ").append("connected, "); 166 pw.append(mIsBound ? "" : "not ").append("bound]\n"); 167 } 168 onConnected(IBinder service)169 protected void onConnected(IBinder service) { 170 boolean shouldRemainConnected = 171 InCallController.this.onConnected(mComponentName, service); 172 if (!shouldRemainConnected) { 173 // Sometimes we can opt to disconnect for certain reasons, like if the 174 // InCallService rejected our intialization step, or the calls went away 175 // in the time it took us to bind to the InCallService. In such cases, we go 176 // ahead and disconnect ourselves. 177 disconnect(); 178 } 179 } 180 onDisconnected()181 protected void onDisconnected() { 182 InCallController.this.onDisconnected(mComponentName); 183 disconnect(); // Unbind explicitly if we get disconnected. 184 if (mListener != null) { 185 mListener.onDisconnect(InCallServiceBindingConnection.this); 186 } 187 } 188 } 189 190 /** 191 * A version of the InCallServiceBindingConnection that proxies all calls to a secondary 192 * connection until it finds an emergency call, or the other connection dies. When one of those 193 * two things happen, this class instance will take over the connection. 194 */ 195 private class EmergencyInCallServiceConnection extends InCallServiceBindingConnection { 196 private boolean mIsProxying = true; 197 private boolean mIsConnected = false; 198 private final InCallServiceConnection mSubConnection; 199 200 private Listener mSubListener = new Listener() { 201 @Override 202 public void onDisconnect(InCallServiceConnection subConnection) { 203 if (subConnection == mSubConnection) { 204 if (mIsConnected && mIsProxying) { 205 // At this point we know that we need to be connected to the InCallService 206 // and we are proxying to the sub connection. However, the sub-connection 207 // just died so we need to stop proxying and connect to the system in-call 208 // service instead. 209 mIsProxying = false; 210 connect(null); 211 } 212 } 213 } 214 }; 215 EmergencyInCallServiceConnection( ComponentName componentName, InCallServiceConnection subConnection)216 public EmergencyInCallServiceConnection( 217 ComponentName componentName, InCallServiceConnection subConnection) { 218 super(componentName); 219 mSubConnection = subConnection; 220 if (mSubConnection != null) { 221 mSubConnection.setListener(mSubListener); 222 } 223 mIsProxying = (mSubConnection != null); 224 } 225 226 @Override connect(Call call)227 public boolean connect(Call call) { 228 mIsConnected = true; 229 if (mIsProxying) { 230 if (mSubConnection.connect(call)) { 231 return true; 232 } 233 // Could not connect to child, stop proxying. 234 mIsProxying = false; 235 } 236 237 // If we are here, we didn't or could not connect to child. So lets connect ourselves. 238 return super.connect(call); 239 } 240 241 @Override disconnect()242 public void disconnect() { 243 Log.i(this, "Disconnect forced!"); 244 if (mIsProxying) { 245 mSubConnection.disconnect(); 246 } else { 247 super.disconnect(); 248 } 249 mIsConnected = false; 250 } 251 252 @Override setHasEmergency(boolean hasEmergency)253 public void setHasEmergency(boolean hasEmergency) { 254 if (hasEmergency) { 255 takeControl(); 256 } 257 } 258 259 @Override onDisconnected()260 protected void onDisconnected() { 261 // Save this here because super.onDisconnected() could force us to explicitly 262 // disconnect() as a cleanup step and that sets mIsConnected to false. 263 boolean shouldReconnect = mIsConnected; 264 super.onDisconnected(); 265 // We just disconnected. Check if we are expected to be connected, and reconnect. 266 if (shouldReconnect && !mIsProxying) { 267 connect(null); // reconnect 268 } 269 } 270 271 @Override dump(IndentingPrintWriter pw)272 public void dump(IndentingPrintWriter pw) { 273 pw.println("Emergency ICS Connection"); 274 pw.increaseIndent(); 275 pw.print("Emergency: "); 276 super.dump(pw); 277 if (mSubConnection != null) { 278 pw.print("Default-Dialer: "); 279 mSubConnection.dump(pw); 280 } 281 pw.decreaseIndent(); 282 } 283 284 /** 285 * Forces the connection to take control from it's subConnection. 286 */ takeControl()287 private void takeControl() { 288 if (mIsProxying) { 289 mIsProxying = false; 290 if (mIsConnected) { 291 mSubConnection.disconnect(); 292 super.connect(null); 293 } 294 } 295 } 296 } 297 298 /** 299 * A version of InCallServiceConnection which switches UI between two separate sub-instances of 300 * InCallServicesConnections. 301 */ 302 private class CarSwappingInCallServiceConnection extends InCallServiceConnection { 303 private final InCallServiceConnection mDialerConnection; 304 private final InCallServiceConnection mCarModeConnection; 305 private InCallServiceConnection mCurrentConnection; 306 private boolean mIsCarMode = false; 307 private boolean mIsConnected = false; 308 CarSwappingInCallServiceConnection( InCallServiceConnection dialerConnection, InCallServiceConnection carModeConnection)309 public CarSwappingInCallServiceConnection( 310 InCallServiceConnection dialerConnection, 311 InCallServiceConnection carModeConnection) { 312 mDialerConnection = dialerConnection; 313 mCarModeConnection = carModeConnection; 314 mCurrentConnection = getCurrentConnection(); 315 } 316 setCarMode(boolean isCarMode)317 public synchronized void setCarMode(boolean isCarMode) { 318 Log.i(this, "carmodechange: " + mIsCarMode + " => " + isCarMode); 319 if (isCarMode != mIsCarMode) { 320 mIsCarMode = isCarMode; 321 InCallServiceConnection newConnection = getCurrentConnection(); 322 if (newConnection != mCurrentConnection) { 323 if (mIsConnected) { 324 mCurrentConnection.disconnect(); 325 newConnection.connect(null); 326 } 327 mCurrentConnection = newConnection; 328 } 329 } 330 } 331 332 @Override connect(Call call)333 public boolean connect(Call call) { 334 if (mIsConnected) { 335 Log.i(this, "already connected"); 336 return true; 337 } else { 338 if (mCurrentConnection.connect(call)) { 339 mIsConnected = true; 340 return true; 341 } 342 } 343 344 return false; 345 } 346 347 @Override disconnect()348 public void disconnect() { 349 if (mIsConnected) { 350 mCurrentConnection.disconnect(); 351 mIsConnected = false; 352 } else { 353 Log.i(this, "already disconnected"); 354 } 355 } 356 357 @Override setHasEmergency(boolean hasEmergency)358 public void setHasEmergency(boolean hasEmergency) { 359 if (mDialerConnection != null) { 360 mDialerConnection.setHasEmergency(hasEmergency); 361 } 362 if (mCarModeConnection != null) { 363 mCarModeConnection.setHasEmergency(hasEmergency); 364 } 365 } 366 367 @Override dump(IndentingPrintWriter pw)368 public void dump(IndentingPrintWriter pw) { 369 pw.println("Car Swapping ICS"); 370 pw.increaseIndent(); 371 if (mDialerConnection != null) { 372 pw.print("Dialer: "); 373 mDialerConnection.dump(pw); 374 } 375 if (mCarModeConnection != null) { 376 pw.print("Car Mode: "); 377 mCarModeConnection.dump(pw); 378 } 379 } 380 getCurrentConnection()381 private InCallServiceConnection getCurrentConnection() { 382 if (mIsCarMode && mCarModeConnection != null) { 383 return mCarModeConnection; 384 } else { 385 return mDialerConnection; 386 } 387 } 388 } 389 390 private class NonUIInCallServiceConnectionCollection extends InCallServiceConnection { 391 private final List<InCallServiceBindingConnection> mSubConnections; 392 NonUIInCallServiceConnectionCollection( List<InCallServiceBindingConnection> subConnections)393 public NonUIInCallServiceConnectionCollection( 394 List<InCallServiceBindingConnection> subConnections) { 395 mSubConnections = subConnections; 396 } 397 398 @Override connect(Call call)399 public boolean connect(Call call) { 400 for (InCallServiceBindingConnection subConnection : mSubConnections) { 401 subConnection.connect(call); 402 } 403 return true; 404 } 405 406 @Override disconnect()407 public void disconnect() { 408 for (InCallServiceBindingConnection subConnection : mSubConnections) { 409 subConnection.disconnect(); 410 } 411 } 412 413 @Override dump(IndentingPrintWriter pw)414 public void dump(IndentingPrintWriter pw) { 415 pw.println("Non-UI Connections:"); 416 pw.increaseIndent(); 417 for (InCallServiceBindingConnection subConnection : mSubConnections) { 418 subConnection.dump(pw); 419 } 420 pw.decreaseIndent(); 421 } 422 } 423 424 private final Call.Listener mCallListener = new Call.ListenerBase() { 425 @Override 426 public void onConnectionCapabilitiesChanged(Call call) { 427 updateCall(call); 428 } 429 430 @Override 431 public void onConnectionPropertiesChanged(Call call) { 432 updateCall(call); 433 } 434 435 @Override 436 public void onCannedSmsResponsesLoaded(Call call) { 437 updateCall(call); 438 } 439 440 @Override 441 public void onVideoCallProviderChanged(Call call) { 442 updateCall(call, true /* videoProviderChanged */); 443 } 444 445 @Override 446 public void onStatusHintsChanged(Call call) { 447 updateCall(call); 448 } 449 450 /** 451 * Listens for changes to extras reported by a Telecom {@link Call}. 452 * 453 * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService} 454 * so we will only trigger an update of the call information if the source of the extras 455 * change was a {@link ConnectionService}. 456 * 457 * @param call The call. 458 * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or 459 * {@link Call#SOURCE_INCALL_SERVICE}). 460 * @param extras The extras. 461 */ 462 @Override 463 public void onExtrasChanged(Call call, int source, Bundle extras) { 464 // Do not inform InCallServices of changes which originated there. 465 if (source == Call.SOURCE_INCALL_SERVICE) { 466 return; 467 } 468 updateCall(call); 469 } 470 471 /** 472 * Listens for changes to extras reported by a Telecom {@link Call}. 473 * 474 * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService} 475 * so we will only trigger an update of the call information if the source of the extras 476 * change was a {@link ConnectionService}. 477 * @param call The call. 478 * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or 479 * {@link Call#SOURCE_INCALL_SERVICE}). 480 * @param keys The extra key removed 481 */ 482 @Override 483 public void onExtrasRemoved(Call call, int source, List<String> keys) { 484 // Do not inform InCallServices of changes which originated there. 485 if (source == Call.SOURCE_INCALL_SERVICE) { 486 return; 487 } 488 updateCall(call); 489 } 490 491 @Override 492 public void onHandleChanged(Call call) { 493 updateCall(call); 494 } 495 496 @Override 497 public void onCallerDisplayNameChanged(Call call) { 498 updateCall(call); 499 } 500 501 @Override 502 public void onVideoStateChanged(Call call) { 503 updateCall(call); 504 } 505 506 @Override 507 public void onTargetPhoneAccountChanged(Call call) { 508 updateCall(call); 509 } 510 511 @Override 512 public void onConferenceableCallsChanged(Call call) { 513 updateCall(call); 514 } 515 516 @Override 517 public void onConnectionEvent(Call call, String event, Bundle extras) { 518 notifyConnectionEvent(call, event, extras); 519 } 520 }; 521 522 private final SystemStateListener mSystemStateListener = new SystemStateListener() { 523 @Override 524 public void onCarModeChanged(boolean isCarMode) { 525 if (mInCallServiceConnection != null) { 526 mInCallServiceConnection.setCarMode(shouldUseCarModeUI()); 527 } 528 } 529 }; 530 531 private static final int IN_CALL_SERVICE_TYPE_INVALID = 0; 532 private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1; 533 private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2; 534 private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3; 535 private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4; 536 537 /** The in-call app implementations, see {@link IInCallService}. */ 538 private final Map<ComponentName, IInCallService> mInCallServices = new ArrayMap<>(); 539 540 /** 541 * The {@link ComponentName} of the bound In-Call UI Service. 542 */ 543 private ComponentName mInCallUIComponentName; 544 545 private final CallIdMapper mCallIdMapper = new CallIdMapper(); 546 547 /** The {@link ComponentName} of the default InCall UI. */ 548 private final ComponentName mSystemInCallComponentName; 549 550 private final Context mContext; 551 private final TelecomSystem.SyncRoot mLock; 552 private final CallsManager mCallsManager; 553 private final SystemStateProvider mSystemStateProvider; 554 private final DefaultDialerManagerAdapter mDefaultDialerAdapter; 555 private CarSwappingInCallServiceConnection mInCallServiceConnection; 556 private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections; 557 InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, SystemStateProvider systemStateProvider, DefaultDialerManagerAdapter defaultDialerAdapter)558 public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, 559 SystemStateProvider systemStateProvider, 560 DefaultDialerManagerAdapter defaultDialerAdapter) { 561 mContext = context; 562 mLock = lock; 563 mCallsManager = callsManager; 564 mSystemStateProvider = systemStateProvider; 565 mDefaultDialerAdapter = defaultDialerAdapter; 566 567 Resources resources = mContext.getResources(); 568 mSystemInCallComponentName = new ComponentName( 569 resources.getString(R.string.ui_default_package), 570 resources.getString(R.string.incall_default_class)); 571 572 mSystemStateProvider.addListener(mSystemStateListener); 573 } 574 575 @Override onCallAdded(Call call)576 public void onCallAdded(Call call) { 577 if (!isBoundToServices()) { 578 bindToServices(call); 579 } else { 580 adjustServiceBindingsForEmergency(); 581 582 Log.i(this, "onCallAdded: %s", call); 583 // Track the call if we don't already know about it. 584 addCall(call); 585 586 for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) { 587 ComponentName componentName = entry.getKey(); 588 IInCallService inCallService = entry.getValue(); 589 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call, 590 true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar()); 591 try { 592 inCallService.addCall(parcelableCall); 593 } catch (RemoteException ignored) { 594 } 595 } 596 } 597 } 598 599 @Override onCallRemoved(Call call)600 public void onCallRemoved(Call call) { 601 Log.i(this, "onCallRemoved: %s", call); 602 if (mCallsManager.getCalls().isEmpty()) { 603 /** Let's add a 2 second delay before we send unbind to the services to hopefully 604 * give them enough time to process all the pending messages. 605 */ 606 Handler handler = new Handler(Looper.getMainLooper()); 607 handler.postDelayed(new Runnable("ICC.oCR") { 608 @Override 609 public void loggedRun() { 610 synchronized (mLock) { 611 // Check again to make sure there are no active calls. 612 if (mCallsManager.getCalls().isEmpty()) { 613 unbindFromServices(); 614 } 615 } 616 } 617 }.prepare(), Timeouts.getCallRemoveUnbindInCallServicesDelay( 618 mContext.getContentResolver())); 619 } 620 call.removeListener(mCallListener); 621 mCallIdMapper.removeCall(call); 622 } 623 624 @Override onExternalCallChanged(Call call, boolean isExternalCall)625 public void onExternalCallChanged(Call call, boolean isExternalCall) { 626 Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall); 627 // TODO: Need to add logic which ensures changes to a call's external state adds or removes 628 // the call from the InCallServices depending on whether they support external calls. 629 } 630 631 @Override onCallStateChanged(Call call, int oldState, int newState)632 public void onCallStateChanged(Call call, int oldState, int newState) { 633 updateCall(call); 634 } 635 636 @Override onConnectionServiceChanged( Call call, ConnectionServiceWrapper oldService, ConnectionServiceWrapper newService)637 public void onConnectionServiceChanged( 638 Call call, 639 ConnectionServiceWrapper oldService, 640 ConnectionServiceWrapper newService) { 641 updateCall(call); 642 } 643 644 @Override onCallAudioStateChanged(CallAudioState oldCallAudioState, CallAudioState newCallAudioState)645 public void onCallAudioStateChanged(CallAudioState oldCallAudioState, 646 CallAudioState newCallAudioState) { 647 if (!mInCallServices.isEmpty()) { 648 Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState, 649 newCallAudioState); 650 for (IInCallService inCallService : mInCallServices.values()) { 651 try { 652 inCallService.onCallAudioStateChanged(newCallAudioState); 653 } catch (RemoteException ignored) { 654 } 655 } 656 } 657 } 658 659 @Override onCanAddCallChanged(boolean canAddCall)660 public void onCanAddCallChanged(boolean canAddCall) { 661 if (!mInCallServices.isEmpty()) { 662 Log.i(this, "onCanAddCallChanged : %b", canAddCall); 663 for (IInCallService inCallService : mInCallServices.values()) { 664 try { 665 inCallService.onCanAddCallChanged(canAddCall); 666 } catch (RemoteException ignored) { 667 } 668 } 669 } 670 } 671 onPostDialWait(Call call, String remaining)672 void onPostDialWait(Call call, String remaining) { 673 if (!mInCallServices.isEmpty()) { 674 Log.i(this, "Calling onPostDialWait, remaining = %s", remaining); 675 for (IInCallService inCallService : mInCallServices.values()) { 676 try { 677 inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining); 678 } catch (RemoteException ignored) { 679 } 680 } 681 } 682 } 683 684 @Override onIsConferencedChanged(Call call)685 public void onIsConferencedChanged(Call call) { 686 Log.d(this, "onIsConferencedChanged %s", call); 687 updateCall(call); 688 } 689 bringToForeground(boolean showDialpad)690 void bringToForeground(boolean showDialpad) { 691 if (!mInCallServices.isEmpty()) { 692 for (IInCallService inCallService : mInCallServices.values()) { 693 try { 694 inCallService.bringToForeground(showDialpad); 695 } catch (RemoteException ignored) { 696 } 697 } 698 } else { 699 Log.w(this, "Asking to bring unbound in-call UI to foreground."); 700 } 701 } 702 silenceRinger()703 void silenceRinger() { 704 if (!mInCallServices.isEmpty()) { 705 for (IInCallService inCallService : mInCallServices.values()) { 706 try { 707 inCallService.silenceRinger(); 708 } catch (RemoteException ignored) { 709 } 710 } 711 } 712 } 713 notifyConnectionEvent(Call call, String event, Bundle extras)714 private void notifyConnectionEvent(Call call, String event, Bundle extras) { 715 if (!mInCallServices.isEmpty()) { 716 for (IInCallService inCallService : mInCallServices.values()) { 717 try { 718 inCallService.onConnectionEvent(mCallIdMapper.getCallId(call), event, extras); 719 } catch (RemoteException ignored) { 720 } 721 } 722 } 723 } 724 725 /** 726 * Unbinds an existing bound connection to the in-call app. 727 */ unbindFromServices()728 private void unbindFromServices() { 729 if (isBoundToServices()) { 730 mInCallServiceConnection.disconnect(); 731 mInCallServiceConnection = null; 732 mNonUIInCallServiceConnections.disconnect(); 733 mNonUIInCallServiceConnections = null; 734 } 735 } 736 737 /** 738 * Binds to all the UI-providing InCallService as well as system-implemented non-UI 739 * InCallServices. Method-invoker must check {@link #isBoundToServices()} before invoking. 740 * 741 * @param call The newly added call that triggered the binding to the in-call services. 742 */ 743 @VisibleForTesting bindToServices(Call call)744 public void bindToServices(Call call) { 745 InCallServiceConnection dialerInCall = null; 746 ComponentName defaultDialerComponent = getDefaultDialerComponent(); 747 Log.i(this, "defaultDialer: " + defaultDialerComponent); 748 if (defaultDialerComponent != null && 749 !defaultDialerComponent.equals(mSystemInCallComponentName)) { 750 dialerInCall = new InCallServiceBindingConnection(defaultDialerComponent); 751 } 752 Log.i(this, "defaultDialer: " + dialerInCall); 753 754 EmergencyInCallServiceConnection systemInCall = 755 new EmergencyInCallServiceConnection(mSystemInCallComponentName, dialerInCall); 756 systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall()); 757 758 InCallServiceConnection carModeInCall = null; 759 ComponentName carModeComponent = getCarModeComponent(); 760 if (carModeComponent != null && 761 !carModeComponent.equals(mSystemInCallComponentName)) { 762 carModeInCall = new InCallServiceBindingConnection(carModeComponent); 763 } 764 765 mInCallServiceConnection = 766 new CarSwappingInCallServiceConnection(systemInCall, carModeInCall); 767 mInCallServiceConnection.setCarMode(shouldUseCarModeUI()); 768 mInCallServiceConnection.connect(call); 769 770 771 List<ComponentName> nonUIInCallComponents = 772 getInCallServiceComponents(null, IN_CALL_SERVICE_TYPE_NON_UI); 773 List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>(); 774 for (ComponentName componentName : nonUIInCallComponents) { 775 nonUIInCalls.add(new InCallServiceBindingConnection(componentName)); 776 } 777 mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls); 778 mNonUIInCallServiceConnections.connect(call); 779 } 780 getDefaultDialerComponent()781 private ComponentName getDefaultDialerComponent() { 782 String packageName = mDefaultDialerAdapter.getDefaultDialerApplication( 783 mContext, mCallsManager.getCurrentUserHandle().getIdentifier()); 784 Log.d(this, "Default Dialer package: " + packageName); 785 786 return getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI); 787 } 788 getCarModeComponent()789 private ComponentName getCarModeComponent() { 790 return getInCallServiceComponent(null, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); 791 } 792 getInCallServiceComponent(String packageName, int type)793 private ComponentName getInCallServiceComponent(String packageName, int type) { 794 List<ComponentName> list = getInCallServiceComponents(packageName, type); 795 if (list != null && !list.isEmpty()) { 796 return list.get(0); 797 } 798 return null; 799 } 800 getInCallServiceComponents(String packageName, int type)801 private List<ComponentName> getInCallServiceComponents(String packageName, int type) { 802 List<ComponentName> retval = new LinkedList<>(); 803 804 Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE); 805 if (packageName != null) { 806 serviceIntent.setPackage(packageName); 807 } 808 809 PackageManager packageManager = mContext.getPackageManager(); 810 for (ResolveInfo entry : packageManager.queryIntentServicesAsUser( 811 serviceIntent, 812 PackageManager.GET_META_DATA, 813 mCallsManager.getCurrentUserHandle().getIdentifier())) { 814 ServiceInfo serviceInfo = entry.serviceInfo; 815 816 if (serviceInfo != null) { 817 if (type == 0 || type == getInCallServiceType(entry.serviceInfo, packageManager)) { 818 retval.add(new ComponentName(serviceInfo.packageName, serviceInfo.name)); 819 } 820 } 821 } 822 823 return retval; 824 } 825 shouldUseCarModeUI()826 private boolean shouldUseCarModeUI() { 827 return mSystemStateProvider.isCarMode(); 828 } 829 830 /** 831 * Returns the type of InCallService described by the specified serviceInfo. 832 */ getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager)833 private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager) { 834 // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which 835 // enforces that only Telecom can bind to it. 836 boolean hasServiceBindPermission = serviceInfo.permission != null && 837 serviceInfo.permission.equals( 838 Manifest.permission.BIND_INCALL_SERVICE); 839 if (!hasServiceBindPermission) { 840 Log.w(this, "InCallService does not require BIND_INCALL_SERVICE permission: " + 841 serviceInfo.packageName); 842 return IN_CALL_SERVICE_TYPE_INVALID; 843 } 844 845 if (mSystemInCallComponentName.getPackageName().equals(serviceInfo.packageName) && 846 mSystemInCallComponentName.getClassName().equals(serviceInfo.name)) { 847 return IN_CALL_SERVICE_TYPE_SYSTEM_UI; 848 } 849 850 // Check to see if the service is a car-mode UI type by checking that it has the 851 // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the 852 // car-mode UI metadata. 853 boolean hasControlInCallPermission = packageManager.checkPermission( 854 Manifest.permission.CONTROL_INCALL_EXPERIENCE, 855 serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED; 856 boolean isCarModeUIService = serviceInfo.metaData != null && 857 serviceInfo.metaData.getBoolean( 858 TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) && 859 hasControlInCallPermission; 860 if (isCarModeUIService) { 861 return IN_CALL_SERVICE_TYPE_CAR_MODE_UI; 862 } 863 864 865 // Check to see that it is the default dialer package 866 boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName, 867 mDefaultDialerAdapter.getDefaultDialerApplication( 868 mContext, mCallsManager.getCurrentUserHandle().getIdentifier())); 869 boolean isUIService = serviceInfo.metaData != null && 870 serviceInfo.metaData.getBoolean( 871 TelecomManager.METADATA_IN_CALL_SERVICE_UI, false); 872 if (isDefaultDialerPackage && isUIService) { 873 return IN_CALL_SERVICE_TYPE_DIALER_UI; 874 } 875 876 // Also allow any in-call service that has the control-experience permission (to ensure 877 // that it is a system app) and doesn't claim to show any UI. 878 if (hasControlInCallPermission && !isUIService) { 879 return IN_CALL_SERVICE_TYPE_NON_UI; 880 } 881 882 // Anything else that remains, we will not bind to. 883 Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b", 884 serviceInfo.packageName, serviceInfo.name, hasControlInCallPermission, 885 isCarModeUIService, isUIService); 886 return IN_CALL_SERVICE_TYPE_INVALID; 887 } 888 adjustServiceBindingsForEmergency()889 private void adjustServiceBindingsForEmergency() { 890 // The connected UI is not the system UI, so lets check if we should switch them 891 // if there exists an emergency number. 892 if (mCallsManager.hasEmergencyCall()) { 893 mInCallServiceConnection.setHasEmergency(true); 894 } 895 } 896 897 /** 898 * Persists the {@link IInCallService} instance and starts the communication between 899 * this class and in-call app by sending the first update to in-call app. This method is 900 * called after a successful binding connection is established. 901 * 902 * @param componentName The service {@link ComponentName}. 903 * @param service The {@link IInCallService} implementation. 904 * @return True if we successfully connected. 905 */ onConnected(ComponentName componentName, IBinder service)906 private boolean onConnected(ComponentName componentName, IBinder service) { 907 Trace.beginSection("onConnected: " + componentName); 908 Log.i(this, "onConnected to %s", componentName); 909 910 IInCallService inCallService = IInCallService.Stub.asInterface(service); 911 mInCallServices.put(componentName, inCallService); 912 913 try { 914 inCallService.setInCallAdapter( 915 new InCallAdapter( 916 mCallsManager, 917 mCallIdMapper, 918 mLock, 919 componentName.getPackageName())); 920 } catch (RemoteException e) { 921 Log.e(this, e, "Failed to set the in-call adapter."); 922 Trace.endSection(); 923 return false; 924 } 925 926 // Upon successful connection, send the state of the world to the service. 927 List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls()); 928 if (!calls.isEmpty()) { 929 Log.i(this, "Adding %s calls to InCallService after onConnected: %s", calls.size(), 930 componentName); 931 for (Call call : calls) { 932 try { 933 // Track the call if we don't already know about it. 934 addCall(call); 935 inCallService.addCall(ParcelableCallUtils.toParcelableCall( 936 call, 937 true /* includeVideoProvider */, 938 mCallsManager.getPhoneAccountRegistrar())); 939 } catch (RemoteException ignored) { 940 } 941 } 942 try { 943 inCallService.onCallAudioStateChanged(mCallsManager.getAudioState()); 944 inCallService.onCanAddCallChanged(mCallsManager.canAddCall()); 945 } catch (RemoteException ignored) { 946 } 947 } else { 948 return false; 949 } 950 Trace.endSection(); 951 return true; 952 } 953 954 /** 955 * Cleans up an instance of in-call app after the service has been unbound. 956 * 957 * @param disconnectedComponent The {@link ComponentName} of the service which disconnected. 958 */ onDisconnected(ComponentName disconnectedComponent)959 private void onDisconnected(ComponentName disconnectedComponent) { 960 Log.i(this, "onDisconnected from %s", disconnectedComponent); 961 962 mInCallServices.remove(disconnectedComponent); 963 } 964 965 /** 966 * Informs all {@link InCallService} instances of the updated call information. 967 * 968 * @param call The {@link Call}. 969 */ updateCall(Call call)970 private void updateCall(Call call) { 971 updateCall(call, false /* videoProviderChanged */); 972 } 973 974 /** 975 * Informs all {@link InCallService} instances of the updated call information. 976 * 977 * @param call The {@link Call}. 978 * @param videoProviderChanged {@code true} if the video provider changed, {@code false} 979 * otherwise. 980 */ updateCall(Call call, boolean videoProviderChanged)981 private void updateCall(Call call, boolean videoProviderChanged) { 982 if (!mInCallServices.isEmpty()) { 983 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall( 984 call, 985 videoProviderChanged /* includeVideoProvider */, 986 mCallsManager.getPhoneAccountRegistrar()); 987 Log.i(this, "Sending updateCall %s ==> %s", call, parcelableCall); 988 List<ComponentName> componentsUpdated = new ArrayList<>(); 989 for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) { 990 ComponentName componentName = entry.getKey(); 991 IInCallService inCallService = entry.getValue(); 992 componentsUpdated.add(componentName); 993 try { 994 inCallService.updateCall(parcelableCall); 995 } catch (RemoteException ignored) { 996 } 997 } 998 Log.i(this, "Components updated: %s", componentsUpdated); 999 } 1000 } 1001 1002 /** 1003 * Adds the call to the list of calls tracked by the {@link InCallController}. 1004 * @param call The call to add. 1005 */ addCall(Call call)1006 private void addCall(Call call) { 1007 if (mCallIdMapper.getCallId(call) == null) { 1008 mCallIdMapper.addCall(call); 1009 call.addListener(mCallListener); 1010 } 1011 } 1012 isBoundToServices()1013 private boolean isBoundToServices() { 1014 return mInCallServiceConnection != null; 1015 } 1016 1017 /** 1018 * Dumps the state of the {@link InCallController}. 1019 * 1020 * @param pw The {@code IndentingPrintWriter} to write the state to. 1021 */ dump(IndentingPrintWriter pw)1022 public void dump(IndentingPrintWriter pw) { 1023 pw.println("mInCallServices (InCalls registered):"); 1024 pw.increaseIndent(); 1025 for (ComponentName componentName : mInCallServices.keySet()) { 1026 pw.println(componentName); 1027 } 1028 pw.decreaseIndent(); 1029 1030 pw.println("ServiceConnections (InCalls bound):"); 1031 pw.increaseIndent(); 1032 if (mInCallServiceConnection != null) { 1033 mInCallServiceConnection.dump(pw); 1034 } 1035 pw.decreaseIndent(); 1036 } 1037 doesConnectedDialerSupportRinging()1038 public boolean doesConnectedDialerSupportRinging() { 1039 String ringingPackage = null; 1040 if (mInCallUIComponentName != null) { 1041 ringingPackage = mInCallUIComponentName.getPackageName().trim(); 1042 } 1043 1044 if (TextUtils.isEmpty(ringingPackage)) { 1045 // The current in-call UI returned nothing, so lets use the default dialer. 1046 ringingPackage = DefaultDialerManager.getDefaultDialerApplication( 1047 mContext, UserHandle.USER_CURRENT); 1048 } 1049 if (TextUtils.isEmpty(ringingPackage)) { 1050 return false; 1051 } 1052 1053 Intent intent = new Intent(InCallService.SERVICE_INTERFACE) 1054 .setPackage(ringingPackage); 1055 List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser( 1056 intent, PackageManager.GET_META_DATA, 1057 mCallsManager.getCurrentUserHandle().getIdentifier()); 1058 if (entries.isEmpty()) { 1059 return false; 1060 } 1061 1062 ResolveInfo info = entries.get(0); 1063 if (info.serviceInfo == null || info.serviceInfo.metaData == null) { 1064 return false; 1065 } 1066 1067 return info.serviceInfo.metaData 1068 .getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_RINGING, false); 1069 } 1070 orderCallsWithChildrenFirst(Collection<Call> calls)1071 private List<Call> orderCallsWithChildrenFirst(Collection<Call> calls) { 1072 LinkedList<Call> parentCalls = new LinkedList<>(); 1073 LinkedList<Call> childCalls = new LinkedList<>(); 1074 for (Call call : calls) { 1075 if (call.getChildCalls().size() > 0) { 1076 parentCalls.add(call); 1077 } else { 1078 childCalls.add(call); 1079 } 1080 } 1081 childCalls.addAll(parentCalls); 1082 return childCalls; 1083 } 1084 } 1085