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