1 /* 2 * Copyright 2021 Google LLC 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 * https://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 package com.google.android.enterprise.connectedapps; 17 18 import static com.google.android.enterprise.connectedapps.CrossProfileSDKUtilities.filterUsersByAvailabilityRestrictions; 19 import static com.google.android.enterprise.connectedapps.CrossProfileSDKUtilities.selectUserHandleToBind; 20 import static java.util.Collections.newSetFromMap; 21 import static java.util.Collections.synchronizedSet; 22 import static java.util.concurrent.TimeUnit.MILLISECONDS; 23 import static java.util.concurrent.TimeUnit.MINUTES; 24 import static java.util.concurrent.TimeUnit.SECONDS; 25 26 import android.content.BroadcastReceiver; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.ServiceConnection; 32 import android.content.pm.CrossProfileApps; 33 import android.os.Build.VERSION; 34 import android.os.Build.VERSION_CODES; 35 import android.os.Bundle; 36 import android.os.IBinder; 37 import android.os.Looper; 38 import android.os.UserHandle; 39 import android.os.UserManager; 40 import android.util.Log; 41 import com.google.android.enterprise.connectedapps.annotations.AvailabilityRestrictions; 42 import com.google.android.enterprise.connectedapps.exceptions.MissingApiException; 43 import com.google.android.enterprise.connectedapps.exceptions.ProfileRuntimeException; 44 import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException; 45 import com.google.android.enterprise.connectedapps.internal.BundleCallReceiver; 46 import com.google.android.enterprise.connectedapps.internal.BundleUtilities; 47 import com.google.android.enterprise.connectedapps.internal.Bundler; 48 import com.google.android.enterprise.connectedapps.internal.CrossProfileBundleCallSender; 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.Collections; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.Objects; 55 import java.util.Set; 56 import java.util.WeakHashMap; 57 import java.util.concurrent.ConcurrentHashMap; 58 import java.util.concurrent.ConcurrentLinkedDeque; 59 import java.util.concurrent.CountDownLatch; 60 import java.util.concurrent.ScheduledExecutorService; 61 import java.util.concurrent.ScheduledFuture; 62 import java.util.concurrent.atomic.AtomicBoolean; 63 import java.util.concurrent.atomic.AtomicReference; 64 import org.checkerframework.checker.nullness.qual.Nullable; 65 66 /** 67 * This class is used internally by the Connected Apps SDK to send messages across users and 68 * profiles. 69 */ 70 public final class CrossProfileSender { 71 72 private static final class CrossProfileCall implements ExceptionCallback { 73 private final long crossProfileTypeIdentifier; 74 private final int methodIdentifier; 75 private final Bundle params; 76 private final LocalCallback callback; 77 CrossProfileCall( long crossProfileTypeIdentifier, int methodIdentifier, Bundle params, LocalCallback callback)78 CrossProfileCall( 79 long crossProfileTypeIdentifier, 80 int methodIdentifier, 81 Bundle params, 82 LocalCallback callback) { 83 if (params == null || callback == null) { 84 throw new NullPointerException(); 85 } 86 this.crossProfileTypeIdentifier = crossProfileTypeIdentifier; 87 this.methodIdentifier = methodIdentifier; 88 this.params = params; 89 this.callback = callback; 90 } 91 92 @Override equals(@ullable Object o)93 public boolean equals(@Nullable Object o) { 94 if (this == o) { 95 return true; 96 } 97 if (o == null || getClass() != o.getClass()) { 98 return false; 99 } 100 CrossProfileCall that = (CrossProfileCall) o; 101 return crossProfileTypeIdentifier == that.crossProfileTypeIdentifier 102 && methodIdentifier == that.methodIdentifier 103 && params.equals(that.params) 104 && callback.equals(that.callback); 105 } 106 107 @Override hashCode()108 public int hashCode() { 109 return Objects.hash(crossProfileTypeIdentifier, methodIdentifier, params, callback); 110 } 111 112 @Override onException(Throwable throwable)113 public void onException(Throwable throwable) { 114 callback.onException(createThrowableBundle(throwable)); 115 } 116 } 117 118 private static final class OngoingCrossProfileCall extends ICrossProfileCallback.Stub { 119 120 private final CrossProfileSender sender; 121 private final CrossProfileCall call; 122 private final BundleCallReceiver bundleCallReceiver = new BundleCallReceiver(); 123 OngoingCrossProfileCall(CrossProfileSender sender, CrossProfileCall call)124 private OngoingCrossProfileCall(CrossProfileSender sender, CrossProfileCall call) { 125 if (sender == null || call == null) { 126 throw new NullPointerException(); 127 } 128 this.sender = sender; 129 this.call = call; 130 } 131 132 @Override prepareResult(long callId, int blockId, int numBytes, byte[] params)133 public void prepareResult(long callId, int blockId, int numBytes, byte[] params) { 134 bundleCallReceiver.prepareCall(callId, blockId, numBytes, params); 135 } 136 137 @Override prepareBundle(long callId, int bundleId, Bundle bundle)138 public void prepareBundle(long callId, int bundleId, Bundle bundle) { 139 bundleCallReceiver.prepareBundle(callId, bundleId, bundle); 140 } 141 142 @Override onResult(long callId, int blockId, int methodIdentifier, byte[] paramsBytes)143 public void onResult(long callId, int blockId, int methodIdentifier, byte[] paramsBytes) { 144 sender.removeConnectionHolder(call); 145 146 Bundle bundle = bundleCallReceiver.getPreparedCall(callId, blockId, paramsBytes); 147 148 call.callback.onResult(methodIdentifier, bundle); 149 } 150 151 @Override onException(long callId, int blockId, byte[] paramsBytes)152 public void onException(long callId, int blockId, byte[] paramsBytes) { 153 Bundle bundle = bundleCallReceiver.getPreparedCall(callId, blockId, paramsBytes); 154 155 onException(bundle); 156 } 157 onException(Bundle exception)158 public void onException(Bundle exception) { 159 sender.removeConnectionHolder(call); 160 161 call.callback.onException(exception); 162 163 sender.scheduledExecutorService.execute(sender::maybeScheduleAutomaticDisconnection); 164 } 165 166 @Override equals(@ullable Object o)167 public boolean equals(@Nullable Object o) { 168 if (this == o) { 169 return true; 170 } 171 if (o == null || getClass() != o.getClass()) { 172 return false; 173 } 174 OngoingCrossProfileCall that = (OngoingCrossProfileCall) o; 175 return sender.equals(that.sender) && call.equals(that.call); 176 } 177 178 @Override hashCode()179 public int hashCode() { 180 return Objects.hash(sender, call); 181 } 182 } 183 184 // Temporary variable until deprecated methods are removed 185 public static final Object MANUAL_MANAGEMENT_CONNECTION_HOLDER = new Object(); 186 187 public static final int MAX_BYTES_PER_BLOCK = 250000; 188 189 private static final String LOG_TAG = "CrossProfileSender"; 190 private static final long INITIAL_BIND_RETRY_DELAY_MS = 500; 191 private static final int DEFAULT_AUTOMATIC_DISCONNECTION_TIMEOUT_SECONDS = 30; 192 193 private static final int NONE = 0; 194 private static final int UNAVAILABLE = 1; 195 private static final int AVAILABLE = 2; 196 private static final int DISCONNECTED = UNAVAILABLE; 197 private static final int CONNECTED = AVAILABLE; 198 199 private final ScheduledExecutorService scheduledExecutorService; 200 private final Context context; 201 private final ComponentName bindToService; 202 private final boolean canUseReflectedApis; 203 private final ConnectionListener connectionListener; 204 private final AvailabilityListener availabilityListener; 205 private final ConnectionBinder binder; 206 private final AvailabilityRestrictions availabilityRestrictions; 207 208 private final AtomicReference<@Nullable ICrossProfileService> iCrossProfileService = 209 new AtomicReference<>(); 210 private final AtomicReference<@Nullable ScheduledFuture<?>> scheduledTryBind = 211 new AtomicReference<>(); 212 private final AtomicReference<ScheduledFuture<?>> scheduledBindTimeout = new AtomicReference<>(); 213 214 // Interaction with connectionHolders, and connectionHolderAliases must 215 // take place on the scheduled executor thread 216 private final Set<Object> connectionHolders = Collections.newSetFromMap(new WeakHashMap<>()); 217 private final Map<Object, Set<Object>> connectionHolderAliases = new WeakHashMap<>(); 218 private final Set<ExceptionCallback> unavailableProfileExceptionWatchers = 219 Collections.newSetFromMap(new ConcurrentHashMap<>()); 220 private final ConcurrentLinkedDeque<CrossProfileCall> asyncCallQueue = 221 new ConcurrentLinkedDeque<>(); 222 223 private final ServiceConnection connection = 224 new ServiceConnection() { 225 226 @Override 227 public void onBindingDied(ComponentName name) { 228 Log.e(LOG_TAG, "onBindingDied for component " + name); 229 scheduledExecutorService.execute( 230 () -> onBindingAttemptFailed("onBindingDied", /* terminal= */ true)); 231 } 232 233 @Override 234 public void onNullBinding(ComponentName name) { 235 Log.e(LOG_TAG, "onNullBinding for component " + name); 236 scheduledExecutorService.execute( 237 () -> onBindingAttemptFailed("onNullBinding", /* terminal= */ true)); 238 } 239 240 // Called when the connection with the service is established 241 @Override 242 public void onServiceConnected(ComponentName name, IBinder service) { 243 Log.i(LOG_TAG, "onServiceConnected for component " + name); 244 scheduledExecutorService.execute( 245 () -> { 246 if (connectionHolders.isEmpty()) { 247 Log.i(LOG_TAG, "Connected but no holders. Disconnecting."); 248 unbind(); 249 return; 250 } 251 iCrossProfileService.set(ICrossProfileService.Stub.asInterface(service)); 252 253 tryMakeAsyncCalls(); 254 checkConnected(); 255 onBindingAttemptSucceeded(); 256 }); 257 } 258 259 // Called when the connection with the service disconnects unexpectedly 260 @Override 261 public void onServiceDisconnected(ComponentName name) { 262 Log.e(LOG_TAG, "Unexpected disconnection for component " + name); 263 attemptReconnect(); 264 } 265 266 private void attemptReconnect() { 267 scheduledExecutorService.execute( 268 () -> { 269 unbind(); 270 throwUnavailableException( 271 new UnavailableProfileException("Lost connection to other profile")); 272 // These disconnections can be temporary - so to avoid an exception on an async 273 // call leading to bad user experience - we send the availability update again 274 // to prompt a retry/refresh 275 updateAvailability(); 276 checkConnected(); 277 cancelAutomaticDisconnection(); 278 bind(); 279 }); 280 } 281 }; 282 283 // This is synchronized which isn't massively performant but it only gets accessed once straight 284 // after creating a Sender, and once each time availability changes 285 private static final Set<CrossProfileSender> senders = 286 synchronizedSet(newSetFromMap(new WeakHashMap<>())); 287 288 private static final BroadcastReceiver profileAvailabilityReceiver = 289 new BroadcastReceiver() { 290 @Override 291 public void onReceive(Context context, Intent intent) { 292 synchronized (senders) { 293 for (CrossProfileSender sender : senders) { 294 sender.scheduledExecutorService.execute(sender::checkAvailability); 295 } 296 } 297 } 298 }; 299 300 private final AtomicReference<ScheduledFuture<Void>> automaticDisconnectionFuture = 301 new AtomicReference<>(); 302 private volatile @Nullable CountDownLatch manuallyBindLatch; 303 304 private long bindRetryDelayMs = INITIAL_BIND_RETRY_DELAY_MS; 305 private int lastReportedAvailabilityStatus = NONE; 306 private int lastReportedConnectedStatus = NONE; 307 CrossProfileSender( Context context, String connectedAppsServiceClassName, ConnectionBinder binder, ConnectionListener connectionListener, AvailabilityListener availabilityListener, ScheduledExecutorService scheduledExecutorService, AvailabilityRestrictions availabilityRestrictions)308 CrossProfileSender( 309 Context context, 310 String connectedAppsServiceClassName, 311 ConnectionBinder binder, 312 ConnectionListener connectionListener, 313 AvailabilityListener availabilityListener, 314 ScheduledExecutorService scheduledExecutorService, 315 AvailabilityRestrictions availabilityRestrictions) { 316 this.context = context.getApplicationContext(); 317 if (connectionListener == null 318 || availabilityListener == null 319 || availabilityRestrictions == null 320 || binder == null 321 || scheduledExecutorService == null) { 322 throw new NullPointerException(); 323 } 324 this.binder = binder; 325 this.connectionListener = connectionListener; 326 this.availabilityListener = availabilityListener; 327 bindToService = new ComponentName(context.getPackageName(), connectedAppsServiceClassName); 328 canUseReflectedApis = ReflectionUtilities.canUseReflectedApis(); 329 this.scheduledExecutorService = new DebuggableScheduledExecutorService(scheduledExecutorService); 330 this.availabilityRestrictions = availabilityRestrictions; 331 332 senders.add(this); 333 beginMonitoringAvailabilityChanges(); 334 } 335 cancelAutomaticDisconnection()336 private void cancelAutomaticDisconnection() { 337 ScheduledFuture<?> disconnectionFuture = automaticDisconnectionFuture.getAndSet(null); 338 if (disconnectionFuture != null) { 339 disconnectionFuture.cancel(/* mayInterruptIfRunning= */ true); 340 } 341 } 342 maybeScheduleAutomaticDisconnection()343 private void maybeScheduleAutomaticDisconnection() { 344 // Always called on scheduled executor service thread 345 if (connectionHolders.isEmpty() && isBound()) { 346 Log.i(LOG_TAG, "Scheduling automatic disconnection"); 347 ScheduledFuture<Void> scheduledDisconnection = 348 scheduledExecutorService.schedule( 349 this::automaticallyDisconnect, 350 DEFAULT_AUTOMATIC_DISCONNECTION_TIMEOUT_SECONDS, 351 SECONDS); 352 353 if (!automaticDisconnectionFuture.compareAndSet(null, scheduledDisconnection)) { 354 Log.i(LOG_TAG, "Already scheduled"); 355 scheduledDisconnection.cancel(/* mayInterruptIfRunning= */ true); 356 } 357 } 358 } 359 automaticallyDisconnect()360 private Void automaticallyDisconnect() { 361 // Always called on scheduled executor service thread 362 if (connectionHolders.isEmpty() && isBound()) { 363 unbind(); 364 } 365 return null; 366 } 367 368 private static final AtomicBoolean isMonitoringAvailabilityChanges = new AtomicBoolean(false); 369 beginMonitoringAvailabilityChanges()370 private void beginMonitoringAvailabilityChanges() { 371 if (isMonitoringAvailabilityChanges.getAndSet(true)) { 372 return; 373 } 374 375 IntentFilter filter = new IntentFilter(); 376 filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED); 377 filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); 378 filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); 379 context.registerReceiver(profileAvailabilityReceiver, filter); 380 } 381 manuallyBind(Object connectionHolder)382 void manuallyBind(Object connectionHolder) throws UnavailableProfileException { 383 Log.e(LOG_TAG, "Calling manuallyBind"); 384 if (isRunningOnUIThread()) { 385 throw new IllegalStateException("connect()/manuallyBind() cannot be called from UI thread"); 386 } 387 388 if (!isBindingPossible()) { 389 throw new UnavailableProfileException("Profile not available"); 390 } 391 392 if (!binder.hasPermissionToBind(context)) { 393 throw new UnavailableProfileException("Permission not granted"); 394 } 395 396 cancelAutomaticDisconnection(); 397 398 scheduledExecutorService.execute( 399 () -> { 400 connectionHolders.add(connectionHolder); 401 }); 402 403 if (isBound()) { 404 // If we're already bound there's no need to block the thread 405 return; 406 } 407 408 if (manuallyBindLatch == null) { 409 synchronized (this) { 410 if (manuallyBindLatch == null) { 411 manuallyBindLatch = new CountDownLatch(1); 412 } 413 } 414 } 415 416 bind(); 417 418 Log.i(LOG_TAG, "Blocking for bind"); 419 try { 420 if (manuallyBindLatch != null) { 421 try { 422 manuallyBindLatch.await(30, SECONDS); 423 } catch (NullPointerException e) { 424 // Ignore - avoiding race condition 425 } 426 } 427 } catch (InterruptedException e) { 428 Log.e(LOG_TAG, "Interrupted waiting for manually bind", e); 429 } 430 431 if (!isBound()) { 432 unbind(); 433 scheduledExecutorService.execute(() -> removeConnectionHolderAndAliases(connectionHolder)); 434 throw new UnavailableProfileException("Profile not available"); 435 } 436 } 437 isRunningOnUIThread()438 private static boolean isRunningOnUIThread() { 439 return Looper.myLooper() == Looper.getMainLooper(); 440 } 441 bind()442 private void bind() { 443 bindRetryDelayMs = INITIAL_BIND_RETRY_DELAY_MS; 444 scheduledExecutorService.execute(this::tryBind); 445 } 446 onBindingAttemptSucceeded()447 private void onBindingAttemptSucceeded() { 448 clearScheduledBindTimeout(); 449 Log.i(LOG_TAG, "Binding attempt succeeded"); 450 checkTriggerManualConnectionLock(); 451 } 452 onBindingAttemptFailed(String reason)453 private void onBindingAttemptFailed(String reason) { 454 onBindingAttemptFailed(reason, /* exception= */ null, /* terminal= */ false); 455 } 456 onBindingAttemptFailed(Exception exception)457 private void onBindingAttemptFailed(Exception exception) { 458 onBindingAttemptFailed(exception.getMessage(), exception, /* terminal= */ false); 459 } 460 onBindingAttemptFailed(String reason, Exception exception)461 private void onBindingAttemptFailed(String reason, Exception exception) { 462 onBindingAttemptFailed(reason, exception, /* terminal= */ false); 463 } 464 onBindingAttemptFailed(String reason, boolean terminal)465 private void onBindingAttemptFailed(String reason, boolean terminal) { 466 onBindingAttemptFailed(reason, /* exception= */ null, terminal); 467 } 468 onBindingAttemptFailed( String reason, @Nullable Exception exception, boolean terminal)469 private void onBindingAttemptFailed( 470 String reason, @Nullable Exception exception, boolean terminal) { 471 // Always called on scheduled executor service thread 472 clearScheduledBindTimeout(); 473 if (exception == null) { 474 Log.i(LOG_TAG, "Binding attempt failed: " + reason); 475 throwUnavailableException(new UnavailableProfileException(reason)); 476 } else { 477 Log.i(LOG_TAG, "Binding attempt failed: " + reason, exception); 478 throwUnavailableException(new UnavailableProfileException(reason, exception)); 479 } 480 481 if (terminal || connectionHolders.isEmpty() || manuallyBindLatch != null) { 482 unbind(); 483 checkTriggerManualConnectionLock(); 484 } else { 485 scheduleBindAttempt(); 486 } 487 } 488 clearScheduledBindTimeout()489 private void clearScheduledBindTimeout() { 490 ScheduledFuture<?> scheduledTimeout = scheduledBindTimeout.getAndSet(null); 491 if (scheduledTimeout != null) { 492 scheduledTimeout.cancel(/* mayInterruptIfRunning= */ true); 493 } 494 } 495 checkTriggerManualConnectionLock()496 private void checkTriggerManualConnectionLock() { 497 if (manuallyBindLatch != null) { 498 synchronized (this) { 499 if (manuallyBindLatch != null) { 500 manuallyBindLatch.countDown(); 501 manuallyBindLatch = null; 502 } 503 } 504 } 505 } 506 507 /** 508 * Stop attempting to bind to the other profile. 509 * 510 * <p>If there is already a binding present, it will be killed. 511 */ unbind()512 private void unbind() { 513 Log.i(LOG_TAG, "Unbind"); 514 boolean isBound = iCrossProfileService.getAndSet(null) != null; 515 if (isBound) { 516 context.unbindService(connection); 517 checkConnected(); 518 cancelAutomaticDisconnection(); 519 } 520 clearScheduledBindTimeout(); 521 throwUnavailableException(new UnavailableProfileException("No profile available")); 522 checkTriggerManualConnectionLock(); 523 } 524 isBindingPossible()525 boolean isBindingPossible() { 526 return binder.bindingIsPossible(context, availabilityRestrictions); 527 } 528 tryBind()529 private void tryBind() { 530 // Always called on scheduled executor service thread 531 Log.i(LOG_TAG, "Attempting to bind"); 532 533 ScheduledFuture<?> scheduledFuture = scheduledTryBind.getAndSet(null); 534 if (scheduledFuture != null) { 535 scheduledFuture.cancel(/* mayInterruptIfRunning= */ false); 536 } 537 538 if (!canUseReflectedApis) { 539 onBindingAttemptFailed("Required APIs are unavailable. Binding is not possible."); 540 return; 541 } 542 543 if (isBound()) { 544 Log.i(LOG_TAG, "Already bound"); 545 onBindingAttemptSucceeded(); 546 return; 547 } 548 549 if (connectionHolders.isEmpty()) { 550 onBindingAttemptFailed("Not trying to bind"); 551 return; 552 } 553 554 if (!binder.hasPermissionToBind(context)) { 555 onBindingAttemptFailed("Permission not granted"); 556 return; 557 } 558 559 if (!isBindingPossible()) { 560 onBindingAttemptFailed("No profile available"); 561 return; 562 } 563 564 if (scheduledBindTimeout.get() != null) { 565 Log.i(LOG_TAG, "Already waiting to bind"); 566 return; 567 } 568 569 try { 570 // Schedule a timeout in case something happens and we never reach onServiceConnected 571 scheduledBindTimeout.set(scheduledExecutorService.schedule(this::timeoutBinding, 1, MINUTES)); 572 if (!binder.tryBind(context, bindToService, connection, availabilityRestrictions)) { 573 onBindingAttemptFailed( 574 "No profile available, app not installed in other profile, or service not included in" 575 + " manifest"); 576 } else { 577 Log.i(LOG_TAG, "binder.tryBind returned true, expecting onServiceConnected"); 578 } 579 } catch (MissingApiException e) { 580 Log.e(LOG_TAG, "MissingApiException when trying to bind", e); 581 onBindingAttemptFailed("Missing API", e); 582 } catch (UnavailableProfileException e) { 583 Log.e(LOG_TAG, "Error while trying to bind", e); 584 onBindingAttemptFailed(e); 585 } catch (IllegalArgumentException e) { 586 Log.e(LOG_TAG, "IllegalArgumentException when trying to bind", e); 587 onBindingAttemptFailed("IllegalArgumentException", e); 588 } 589 } 590 timeoutBinding()591 private void timeoutBinding() { 592 onBindingAttemptFailed("Timed out while waiting for onServiceConnected"); 593 } 594 scheduleBindAttempt()595 private void scheduleBindAttempt() { 596 ScheduledFuture<?> scheduledFuture = scheduledTryBind.get(); 597 if (scheduledFuture != null && !scheduledFuture.isDone()) { 598 return; 599 } 600 601 bindRetryDelayMs *= 2; 602 scheduledTryBind.set( 603 scheduledExecutorService.schedule(this::tryBind, bindRetryDelayMs, MILLISECONDS)); 604 } 605 isBound()606 boolean isBound() { 607 return iCrossProfileService.get() != null; 608 } 609 610 /** 611 * Make a synchronous cross-profile call. 612 * 613 * @return A {@link Bundle} containing the return value under the key \"return\". 614 * @throws UnavailableProfileException if a connection is not already established 615 */ call(long crossProfileTypeIdentifier, int methodIdentifier, Bundle params)616 public Bundle call(long crossProfileTypeIdentifier, int methodIdentifier, Bundle params) 617 throws UnavailableProfileException { 618 try { 619 return callWithExceptions(crossProfileTypeIdentifier, methodIdentifier, params); 620 } catch (UnavailableProfileException | RuntimeException | Error e) { 621 StackTraceElement[] remoteStack = e.getStackTrace(); 622 StackTraceElement[] localStack = Thread.currentThread().getStackTrace(); 623 StackTraceElement[] totalStack = 624 Arrays.copyOf(remoteStack, remoteStack.length + localStack.length - 1); 625 // We cut off the first element of localStack as it is just getting the stack trace 626 System.arraycopy(localStack, 1, totalStack, remoteStack.length, localStack.length - 1); 627 e.setStackTrace(totalStack); 628 throw e; 629 } catch (Throwable e) { 630 throw new UnavailableProfileException("Unexpected checked exception", e); 631 } 632 } 633 634 /** 635 * Make a synchronous cross-profile call which expects some checked exceptions to be thrown. 636 * 637 * <p>Behaves the same as {@link #call(long, int, Bundle)} except that it deals with checked 638 * exceptions by throwing {@link Throwable}. 639 * 640 * @return A {@link Bundle} containing the return value under the "return" key. 641 * @throws UnavailableProfileException if a connection is not already established 642 */ callWithExceptions( long crossProfileTypeIdentifier, int methodIdentifier, Bundle params)643 public Bundle callWithExceptions( 644 long crossProfileTypeIdentifier, int methodIdentifier, Bundle params) throws Throwable { 645 ICrossProfileService service = iCrossProfileService.get(); 646 if (service == null) { 647 throw new UnavailableProfileException("Could not access other profile"); 648 } 649 650 CrossProfileBundleCallSender callSender = 651 new CrossProfileBundleCallSender( 652 service, crossProfileTypeIdentifier, methodIdentifier, /* callback= */ null); 653 Bundle returnBundle = callSender.makeBundleCall(params); 654 655 if (returnBundle.containsKey("throwable")) { 656 Throwable t = BundleUtilities.readThrowableFromBundle(returnBundle, "throwable"); 657 if (t instanceof RuntimeException) { 658 throw new ProfileRuntimeException(t); 659 } 660 throw t; 661 } 662 663 return returnBundle; 664 } 665 666 /** Make an asynchronous cross-profile call. */ callAsync( long crossProfileTypeIdentifier, int methodIdentifier, Bundle params, LocalCallback callback, Object connectionHolderAlias)667 public void callAsync( 668 long crossProfileTypeIdentifier, 669 int methodIdentifier, 670 Bundle params, 671 LocalCallback callback, 672 Object connectionHolderAlias) { 673 if (!isBindingPossible()) { 674 throwUnavailableException(new UnavailableProfileException("Profile not available")); 675 } 676 677 scheduledExecutorService.execute( 678 () -> { 679 CrossProfileCall crossProfileCall = 680 new CrossProfileCall(crossProfileTypeIdentifier, methodIdentifier, params, callback); 681 connectionHolders.add(crossProfileCall); 682 cancelAutomaticDisconnection(); 683 addConnectionHolderAlias(connectionHolderAlias, crossProfileCall); 684 unavailableProfileExceptionWatchers.add(crossProfileCall); 685 686 asyncCallQueue.add(crossProfileCall); 687 688 tryMakeAsyncCalls(); 689 bind(); 690 }); 691 } 692 throwUnavailableException(Throwable throwable)693 private void throwUnavailableException(Throwable throwable) { 694 for (ExceptionCallback callback : unavailableProfileExceptionWatchers) { 695 removeConnectionHolder(callback); 696 callback.onException(throwable); 697 } 698 } 699 tryMakeAsyncCalls()700 private void tryMakeAsyncCalls() { 701 Log.i(LOG_TAG, "tryMakeAsyncCalls"); 702 if (!isBound()) { 703 return; 704 } 705 706 scheduledExecutorService.execute(this::drainAsyncQueue); 707 } 708 drainAsyncQueue()709 private void drainAsyncQueue() { 710 Log.i(LOG_TAG, "drainAsyncQueue"); 711 while (true) { 712 CrossProfileCall call = asyncCallQueue.pollFirst(); 713 if (call == null) { 714 return; 715 } 716 OngoingCrossProfileCall ongoingCall = new OngoingCrossProfileCall(this, call); 717 718 try { 719 ICrossProfileService service = iCrossProfileService.get(); 720 if (service == null) { 721 Log.w(LOG_TAG, "OngoingCrossProfileCall: not bound anymore, adding back to queue"); 722 asyncCallQueue.add(call); 723 return; 724 } 725 CrossProfileBundleCallSender callSender = 726 new CrossProfileBundleCallSender( 727 service, call.crossProfileTypeIdentifier, call.methodIdentifier, ongoingCall); 728 729 Bundle p = callSender.makeBundleCall(call.params); 730 731 if (p.containsKey("throwable")) { 732 RuntimeException exception = 733 (RuntimeException) BundleUtilities.readThrowableFromBundle(p, "throwable"); 734 removeConnectionHolder(ongoingCall.call); 735 throw new ProfileRuntimeException(exception); 736 } 737 } catch (UnavailableProfileException e) { 738 Log.w( 739 LOG_TAG, "OngoingCrossProfileCall: UnavailableProfileException, adding back to queue"); 740 asyncCallQueue.add(call); 741 return; 742 } 743 } 744 } 745 checkAvailability()746 private void checkAvailability() { 747 if (isBindingPossible() && (lastReportedAvailabilityStatus != AVAILABLE)) { 748 updateAvailability(); 749 } else if (!isBindingPossible() && (lastReportedAvailabilityStatus != UNAVAILABLE)) { 750 updateAvailability(); 751 } 752 } 753 updateAvailability()754 private void updateAvailability() { 755 // This is only executed on the executor thread 756 availabilityListener.availabilityChanged(); 757 lastReportedAvailabilityStatus = isBindingPossible() ? AVAILABLE : UNAVAILABLE; 758 } 759 checkConnected()760 private void checkConnected() { 761 // This is only executed on the executor thread 762 if (isBound() && lastReportedConnectedStatus != CONNECTED) { 763 connectionListener.connectionChanged(); 764 lastReportedConnectedStatus = CONNECTED; 765 } else if (!isBound() && lastReportedConnectedStatus != DISCONNECTED) { 766 connectionListener.connectionChanged(); 767 lastReportedConnectedStatus = DISCONNECTED; 768 } 769 } 770 771 /** Create a {@link Bundle} containing a {@link Throwable}. */ createThrowableBundle(Throwable throwable)772 private static Bundle createThrowableBundle(Throwable throwable) { 773 Bundle bundle = new Bundle(Bundler.class.getClassLoader()); 774 BundleUtilities.writeThrowableToBundle(bundle, "throwable", throwable); 775 return bundle; 776 } 777 getOtherUserHandle( Context context, AvailabilityRestrictions availabilityRestrictions)778 static @Nullable UserHandle getOtherUserHandle( 779 Context context, AvailabilityRestrictions availabilityRestrictions) { 780 if (VERSION.SDK_INT < VERSION_CODES.P) { 781 // CrossProfileApps was introduced in P 782 return findDifferentRunningUser( 783 context, android.os.Process.myUserHandle(), availabilityRestrictions); 784 } 785 786 CrossProfileApps crossProfileApps = context.getSystemService(CrossProfileApps.class); 787 List<UserHandle> otherUsers = 788 filterUsersByAvailabilityRestrictions( 789 context, crossProfileApps.getTargetUserProfiles(), availabilityRestrictions); 790 791 return selectUserHandleToBind(context, otherUsers); 792 } 793 findDifferentRunningUser( Context context, UserHandle ignoreUserHandle, AvailabilityRestrictions availabilityRestrictions)794 private static @Nullable UserHandle findDifferentRunningUser( 795 Context context, 796 UserHandle ignoreUserHandle, 797 AvailabilityRestrictions availabilityRestrictions) { 798 UserManager userManager = context.getSystemService(UserManager.class); 799 List<UserHandle> otherUsers = new ArrayList<>(); 800 801 for (UserHandle userHandle : userManager.getUserProfiles()) { 802 if (!userHandle.equals(ignoreUserHandle)) { 803 otherUsers.add(userHandle); 804 } 805 } 806 807 otherUsers = 808 filterUsersByAvailabilityRestrictions(context, otherUsers, availabilityRestrictions); 809 810 return selectUserHandleToBind(context, otherUsers); 811 } 812 addConnectionHolder(Object o)813 void addConnectionHolder(Object o) { 814 scheduledExecutorService.execute( 815 () -> { 816 connectionHolders.add(o); 817 818 cancelAutomaticDisconnection(); 819 bind(); 820 }); 821 } 822 removeConnectionHolder(Object o)823 void removeConnectionHolder(Object o) { 824 if (o == null) { 825 throw new NullPointerException("Connection holder cannot be null"); 826 } 827 828 scheduledExecutorService.execute( 829 () -> { 830 removeConnectionHolderAndAliases(o); 831 832 maybeScheduleAutomaticDisconnection(); 833 }); 834 } 835 clearConnectionHolders()836 void clearConnectionHolders() { 837 scheduledExecutorService.execute( 838 () -> { 839 connectionHolders.clear(); 840 connectionHolderAliases.clear(); 841 842 maybeScheduleAutomaticDisconnection(); 843 }); 844 } 845 removeConnectionHolderAndAliases(Object o)846 private void removeConnectionHolderAndAliases(Object o) { 847 // Always called on scheduled executor thread 848 Set<Object> aliases = connectionHolderAliases.get(o); 849 if (aliases != null) { 850 connectionHolderAliases.remove(o); 851 for (Object alias : aliases) { 852 removeConnectionHolderAndAliases(alias); 853 } 854 } 855 856 connectionHolders.remove(o); 857 unavailableProfileExceptionWatchers.remove(o); 858 } 859 860 /** 861 * Registers a connection holder alias. 862 * 863 * <p>This means that if the key is removed, then the value will also be removed. If the value is 864 * removed, the key will not be removed. 865 */ addConnectionHolderAlias(Object key, Object value)866 void addConnectionHolderAlias(Object key, Object value) { 867 scheduledExecutorService.execute( 868 () -> { 869 Set<Object> aliases = connectionHolderAliases.get(key); 870 if (aliases == null) { 871 aliases = Collections.newSetFromMap(new WeakHashMap<>()); 872 } 873 874 aliases.add(value); 875 876 connectionHolderAliases.put(key, aliases); 877 }); 878 } 879 880 /** 881 * Clear static state. 882 * 883 * <p>This should not be required in production. 884 */ clearStaticState()885 public static void clearStaticState() { 886 isMonitoringAvailabilityChanges.set(false); 887 senders.clear(); 888 } 889 } 890