• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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