• 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.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