1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.internal.infra;
17 
18 import android.annotation.CheckResult;
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.UserIdInt;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.ServiceConnection;
26 import android.os.Handler;
27 import android.os.HandlerExecutor;
28 import android.os.IBinder;
29 import android.os.IInterface;
30 import android.os.Looper;
31 import android.os.RemoteException;
32 import android.os.UserHandle;
33 import android.util.Log;
34 import android.util.Slog;
35 
36 import java.io.PrintWriter;
37 import java.util.ArrayDeque;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.List;
41 import java.util.Objects;
42 import java.util.Queue;
43 import java.util.concurrent.CompletableFuture;
44 import java.util.concurrent.CompletionStage;
45 import java.util.concurrent.Executor;
46 import java.util.concurrent.TimeUnit;
47 import java.util.function.BiConsumer;
48 import java.util.function.Function;
49 
50 /**
51  * Takes care of managing a {@link ServiceConnection} and auto-disconnecting from the service upon
52  * a certain timeout.
53  *
54  * <p>
55  * The requests are always processed in the order they are scheduled.
56  *
57  * <p>
58  * Use {@link ServiceConnector.Impl} to construct an instance.
59  *
60  * @param <I> the type of the {@link IInterface ipc interface} for the remote service
61  */
62 public interface ServiceConnector<I extends IInterface> {
63 
64     /**
65      * Schedules to run a given job when service is connected, without providing any means to track
66      * the job's completion.
67      *
68      * <p>
69      * This is slightly more efficient than {@link #post(VoidJob)} as it doesn't require an extra
70      * allocation of a {@link AndroidFuture} for progress tracking.
71      *
72      * @return whether a job was successfully scheduled
73      */
run(@onNull VoidJob<I> job)74     boolean run(@NonNull VoidJob<I> job);
75 
76     /**
77      * Schedules to run a given job when service is connected.
78      *
79      * <p>
80      * You can choose to wait for the job synchronously using {@link AndroidFuture#get} or
81      * attach a listener to it using one of the options such as
82      * {@link AndroidFuture#whenComplete}
83      * You can also {@link AndroidFuture#cancel cancel} the pending job.
84      *
85      * @return a {@link AndroidFuture} tracking the job's completion
86      *
87      * @see #postForResult(Job) for a variant of this that also propagates an arbitrary result
88      *                          back to the caller
89      * @see CompletableFuture for more options on what you can do with a result of an asynchronous
90      *                        operation, including more advanced operations such as
91      *                        {@link CompletableFuture#thenApply transforming} its result,
92      *                        {@link CompletableFuture#thenCombine joining}
93      *                        results of multiple async operation into one,
94      *                        {@link CompletableFuture#thenCompose composing} results of
95      *                        multiple async operations that depend on one another, and more.
96      */
97     @CheckResult(suggest = "#fireAndForget")
post(@onNull VoidJob<I> job)98     AndroidFuture<Void> post(@NonNull VoidJob<I> job);
99 
100     /**
101      * Variant of {@link #post(VoidJob)} that also propagates an arbitrary result back to the
102      * caller asynchronously.
103      *
104      * @param <R> the type of the result this job produces
105      *
106      * @see #post(VoidJob)
107      */
108     @CheckResult(suggest = "#fireAndForget")
postForResult(@onNull Job<I, R> job)109     <R> AndroidFuture<R> postForResult(@NonNull Job<I, R> job);
110 
111     /**
112      * Schedules a job that is itself asynchronous, that is job returns a result in the form of a
113      * {@link CompletableFuture}
114      *
115      * <p>
116      * This takes care of "flattening" the nested futures that would have resulted from 2
117      * asynchronous operations performed in sequence.
118      *
119      * <p>
120      * Like with other options, {@link AndroidFuture#cancel cancelling} the resulting future
121      * will remove the job from the queue, preventing it from running if it hasn't yet started.
122      *
123      * @see #postForResult
124      * @see #post
125      */
postAsync(@onNull Job<I, CompletableFuture<R>> job)126     <R> AndroidFuture<R> postAsync(@NonNull Job<I, CompletableFuture<R>> job);
127 
128     /**
129      * Requests to connect to the service without posting any meaningful job to run.
130      *
131      * <p>
132      * This returns a {@link AndroidFuture} tracking the progress of binding to the service,
133      * which can be used to schedule calls to the service once it's connected.
134      *
135      * <p>
136      * Avoid caching the resulting future as the instance may change due to service disconnecting
137      * and reconnecting.
138      */
connect()139     AndroidFuture<I> connect();
140 
141     /**
142      * Request to unbind from the service as soon as possible.
143      *
144      * <p>
145      * If there are any pending jobs remaining they will be
146      * {@link AndroidFuture#cancel cancelled}.
147      */
unbind()148     void unbind();
149 
150     /**
151      * Registers a {@link ServiceLifecycleCallbacks callbacks} to be invoked when the lifecycle
152      * of the managed service changes.
153      *
154      * @param callbacks The callbacks that will be run, or {@code null} to clear the existing
155      *                 callbacks.
156      */
setServiceLifecycleCallbacks(@ullable ServiceLifecycleCallbacks<I> callbacks)157     void setServiceLifecycleCallbacks(@Nullable ServiceLifecycleCallbacks<I> callbacks);
158 
159     /**
160      * A request to be run when the service is
161      * {@link ServiceConnection#onServiceConnected connected}.
162      *
163      * @param <II> type of the {@link IInterface ipc interface} to be used
164      * @param <R> type of the return value
165      *
166      * @see VoidJob for a variant that doesn't produce any return value
167      */
168     @FunctionalInterface
169     interface Job<II, R> {
170 
171         /**
172          * Perform the remote call using the provided {@link IInterface ipc interface instance}.
173          *
174          * Avoid caching the provided {@code service} instance as it may become invalid when service
175          * disconnects.
176          *
177          * @return the result of this operation to be propagated to the original caller.
178          *         If you do not need to provide a result you can implement {@link VoidJob} instead
179          */
run(@onNull II service)180         R run(@NonNull II service) throws Exception;
181 
182     }
183 
184     /**
185      * Variant of {@link Job} that doesn't return a result
186      *
187      * @param <II> see {@link Job}
188      */
189     @FunctionalInterface
190     interface VoidJob<II> extends Job<II, Void> {
191 
192         /** @see Job#run */
runNoResult(II service)193         void runNoResult(II service) throws Exception;
194 
195         @Override
run(II service)196         default Void run(II service) throws Exception {
197             runNoResult(service);
198             return null;
199         }
200     }
201 
202     /**
203      * Collection of callbacks invoked when the lifecycle of the service changes.
204      *
205      * @param <II> the type of the {@link IInterface ipc interface} for the remote service
206      * @see ServiceConnector#setServiceLifecycleCallbacks(ServiceLifecycleCallbacks)
207      */
208     interface ServiceLifecycleCallbacks<II extends IInterface> {
209         /**
210          * Called when the service has just connected and before any queued jobs are run.
211          */
onConnected(@onNull II service)212         default void onConnected(@NonNull II service) {}
213 
214         /**
215          * Called just before the service is disconnected and unbound.
216          */
onDisconnected(@onNull II service)217         default void onDisconnected(@NonNull II service) {}
218 
219         /**
220          * Called when the service Binder has died.
221          *
222          * In cases where {@link #onBinderDied()} is invoked the service becomes unbound without
223          * a callback to {@link #onDisconnected(IInterface)}.
224          */
onBinderDied()225         default void onBinderDied() {}
226     }
227 
228 
229     /**
230      * Implementation of {@link ServiceConnector}
231      *
232      * <p>
233      * For allocation-efficiency reasons this implements a bunch of interfaces that are not meant to
234      * be a public API of {@link ServiceConnector}.
235      * For this reason prefer to use {@link ServiceConnector} instead of
236      * {@link ServiceConnector.Impl} as the field type when storing an instance.
237      *
238      * <p>
239      * In some rare cases you may want to extend this class, overriding certain methods for further
240      * flexibility.
241      * If you do, it would typically be one of the {@code protected} methods on this class.
242      *
243      * @param <I> see {@link ServiceConnector}
244      */
245     class Impl<I extends IInterface> extends ArrayDeque<Job<I, ?>>
246             implements ServiceConnector<I>, ServiceConnection, IBinder.DeathRecipient, Runnable {
247 
248         static final boolean DEBUG = false;
249         static final String LOG_TAG = "ServiceConnector.Impl";
250 
251         private static final long DEFAULT_DISCONNECT_TIMEOUT_MS = 15_000;
252         private static final long DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
253 
254         private final @NonNull Queue<Job<I, ?>> mQueue = this;
255         private final @NonNull List<CompletionAwareJob<I, ?>> mUnfinishedJobs = new ArrayList<>();
256 
257         private final @NonNull Handler mMainHandler = new Handler(Looper.getMainLooper());
258         private final @NonNull ServiceConnection mServiceConnection = this;
259         private final @NonNull Runnable mTimeoutDisconnect = this;
260 
261         // This context contains the user information.
262         protected final @NonNull Context mContext;
263         private final @NonNull Intent mIntent;
264         private final int mBindingFlags;
265         private final @Nullable Function<IBinder, I> mBinderAsInterface;
266         private final @NonNull Handler mHandler;
267         protected final @NonNull Executor mExecutor;
268 
269         @Nullable
270         private volatile ServiceLifecycleCallbacks<I> mServiceLifecycleCallbacks = null;
271         private volatile I mService = null;
272         private boolean mBinding = false;
273         private boolean mUnbinding = false;
274 
275         private CompletionAwareJob<I, I> mServiceConnectionFutureCache = null;
276 
277         /**
278          * Creates an instance of {@link ServiceConnector}
279          *
280          * See {@code protected} methods for optional parameters you can override.
281          *
282          * @param context to be used for {@link Context#bindServiceAsUser binding} and
283          *                {@link Context#unbindService unbinding}
284          * @param intent to be used for {@link Context#bindServiceAsUser binding}
285          * @param bindingFlags to be used for {@link Context#bindServiceAsUser binding}
286          * @param userId to be used for {@link Context#bindServiceAsUser binding}
287          * @param binderAsInterface to be used for converting an {@link IBinder} provided in
288          *                          {@link ServiceConnection#onServiceConnected} into a specific
289          *                          {@link IInterface}.
290          *                          Typically this is {@code IMyInterface.Stub::asInterface}
291          */
Impl(@onNull Context context, @NonNull Intent intent, int bindingFlags, @UserIdInt int userId, @Nullable Function<IBinder, I> binderAsInterface)292         public Impl(@NonNull Context context, @NonNull Intent intent, int bindingFlags,
293                 @UserIdInt int userId, @Nullable Function<IBinder, I> binderAsInterface) {
294             mContext = context.createContextAsUser(UserHandle.of(userId), 0);
295             mIntent = intent;
296             mBindingFlags = bindingFlags;
297             mBinderAsInterface = binderAsInterface;
298 
299             mHandler = getJobHandler();
300             mExecutor = new HandlerExecutor(mHandler);
301         }
302 
303         /**
304          * {@link Handler} on which {@link Job}s will be called
305          */
getJobHandler()306         protected Handler getJobHandler() {
307             return mMainHandler;
308         }
309 
310         /**
311          * Gets the amount of time spent without any calls before the service is automatically
312          * {@link Context#unbindService unbound}
313          *
314          * @return amount of time in ms, or non-positive (<=0) value to disable automatic unbinding
315          */
getAutoDisconnectTimeoutMs()316         protected long getAutoDisconnectTimeoutMs() {
317             return DEFAULT_DISCONNECT_TIMEOUT_MS;
318         }
319 
320         /**
321          * Gets the amount of time to wait for a request to complete, before finishing it with a
322          * {@link java.util.concurrent.TimeoutException}
323          *
324          * <p>
325          * This includes time spent connecting to the service, if any.
326          *
327          * @return amount of time in ms
328          */
getRequestTimeoutMs()329         protected long getRequestTimeoutMs() {
330             return DEFAULT_REQUEST_TIMEOUT_MS;
331         }
332 
333         /**
334          * {@link Context#bindServiceAsUser Binds} to the service.
335          *
336          * <p>
337          * If overridden, implementation must use at least the provided {@link ServiceConnection}
338          */
bindService(@onNull ServiceConnection serviceConnection)339         protected boolean bindService(@NonNull ServiceConnection serviceConnection) {
340             if (DEBUG) {
341                 logTrace();
342             }
343             return mContext.bindService(mIntent, Context.BIND_AUTO_CREATE | mBindingFlags,
344                     mExecutor, serviceConnection);
345         }
346 
347         /**
348          * Gets the binder interface.
349          * Typically {@code IMyInterface.Stub.asInterface(service)}.
350          *
351          * <p>
352          * Can be overridden instead of provided as a constructor parameter to save a singleton
353          * allocation
354          */
binderAsInterface(@onNull IBinder service)355         protected I binderAsInterface(@NonNull IBinder service) {
356             return mBinderAsInterface.apply(service);
357         }
358 
359         /**
360          * Called when service was {@link Context#unbindService unbound}
361          *
362          * <p>
363          * Can be overridden to perform some cleanup on service disconnect
364          */
onServiceUnbound()365         protected void onServiceUnbound() {
366             if (DEBUG) {
367                 logTrace();
368             }
369         }
370 
dispatchOnServiceConnectionStatusChanged( @onNull I service, boolean isConnected)371         private void dispatchOnServiceConnectionStatusChanged(
372                 @NonNull I service, boolean isConnected) {
373             ServiceLifecycleCallbacks<I> serviceLifecycleCallbacks = mServiceLifecycleCallbacks;
374             if (serviceLifecycleCallbacks != null) {
375                 if (isConnected) {
376                     serviceLifecycleCallbacks.onConnected(service);
377                 } else {
378                     serviceLifecycleCallbacks.onDisconnected(service);
379                 }
380             }
381             onServiceConnectionStatusChanged(service, isConnected);
382         }
383 
384         /**
385          * Called when the service just connected or is about to disconnect
386          */
onServiceConnectionStatusChanged(@onNull I service, boolean isConnected)387         protected void onServiceConnectionStatusChanged(@NonNull I service, boolean isConnected) {}
388 
389         @Override
run(@onNull VoidJob<I> job)390         public boolean run(@NonNull VoidJob<I> job) {
391             if (DEBUG) {
392                 Log.d(LOG_TAG, "Wrapping fireAndForget job to take advantage of its mDebugName");
393                 return !post(job).isCompletedExceptionally();
394             }
395             return enqueue(job);
396         }
397 
398         @Override
post(@onNull VoidJob<I> job)399         public AndroidFuture<Void> post(@NonNull VoidJob<I> job) {
400             return postForResult((Job) job);
401         }
402 
403         @Override
postForResult(@onNull Job<I, R> job)404         public <R> CompletionAwareJob<I, R> postForResult(@NonNull Job<I, R> job) {
405             CompletionAwareJob<I, R> task = new CompletionAwareJob<>();
406             task.mDelegate = Objects.requireNonNull(job);
407             enqueue(task);
408             return task;
409         }
410 
411         @Override
postAsync(@onNull Job<I, CompletableFuture<R>> job)412         public <R> AndroidFuture<R> postAsync(@NonNull Job<I, CompletableFuture<R>> job) {
413             CompletionAwareJob<I, R> task = new CompletionAwareJob<>();
414             task.mDelegate = Objects.requireNonNull((Job) job);
415             task.mAsync = true;
416             enqueue(task);
417             return task;
418         }
419 
420         @Override
connect()421         public synchronized AndroidFuture<I> connect() {
422             if (mServiceConnectionFutureCache == null) {
423                 mServiceConnectionFutureCache = new CompletionAwareJob<>();
424                 mServiceConnectionFutureCache.mDelegate = s -> s;
425                 I service = mService;
426                 if (service != null) {
427                     mServiceConnectionFutureCache.complete(service);
428                 } else {
429                     enqueue(mServiceConnectionFutureCache);
430                 }
431             }
432             return mServiceConnectionFutureCache;
433         }
434 
enqueue(@onNull CompletionAwareJob<I, ?> task)435         private void enqueue(@NonNull CompletionAwareJob<I, ?> task) {
436             if (!enqueue((Job<I, ?>) task)) {
437                 task.completeExceptionally(new IllegalStateException(
438                         "Failed to post a job to handler. Likely "
439                                 + mHandler.getLooper() + " is exiting"));
440             }
441         }
442 
enqueue(@onNull Job<I, ?> job)443         private boolean enqueue(@NonNull Job<I, ?> job) {
444             cancelTimeout();
445             return mHandler.post(() -> enqueueJobThread(job));
446         }
447 
enqueueJobThread(@onNull Job<I, ?> job)448         void enqueueJobThread(@NonNull Job<I, ?> job) {
449             if (DEBUG) {
450                 Log.i(LOG_TAG, "post(" + job + ", this = " + this + ")");
451             }
452             cancelTimeout();
453             if (mUnbinding) {
454                 completeExceptionally(job,
455                         new IllegalStateException("Service is unbinding. Ignoring " + job));
456             } else if (!mQueue.offer(job)) {
457                 completeExceptionally(job,
458                         new IllegalStateException("Failed to add to queue: " + job));
459             } else if (isBound()) {
460                 processQueue();
461             } else if (!mBinding) {
462                 if (bindService(mServiceConnection)) {
463                     mBinding = true;
464                 } else {
465                     completeExceptionally(job,
466                             new IllegalStateException("Failed to bind to service " + mIntent));
467                 }
468             }
469         }
470 
cancelTimeout()471         private void cancelTimeout() {
472             if (DEBUG) {
473                 logTrace();
474             }
475             mMainHandler.removeCallbacks(mTimeoutDisconnect);
476         }
477 
completeExceptionally(@onNull Job<?, ?> job, @NonNull Throwable ex)478         void completeExceptionally(@NonNull Job<?, ?> job, @NonNull Throwable ex) {
479             CompletionAwareJob task = castOrNull(job, CompletionAwareJob.class);
480             boolean taskChanged = false;
481             if (task != null) {
482                 taskChanged = task.completeExceptionally(ex);
483             }
484             if (task == null || (DEBUG && taskChanged)) {
485                 Log.e(LOG_TAG, "Job failed: " + job, ex);
486             }
487         }
488 
castOrNull( @ullable BASE instance, @NonNull Class<T> cls)489         static @Nullable <BASE, T extends BASE> T castOrNull(
490                 @Nullable BASE instance, @NonNull Class<T> cls) {
491             return cls.isInstance(instance) ? (T) instance : null;
492         }
493 
processQueue()494         private void processQueue() {
495             if (DEBUG) {
496                 logTrace();
497             }
498 
499             Job<I, ?> job;
500             while ((job = mQueue.poll()) != null) {
501                 CompletionAwareJob task = castOrNull(job, CompletionAwareJob.class);
502                 try {
503                     I service = mService;
504                     if (service == null) {
505                         return;
506                     }
507                     Object result = job.run(service);
508                     if (DEBUG) {
509                         Log.i(LOG_TAG, "complete(" + job + ", result = " + result + ")");
510                     }
511                     if (task != null) {
512                         if (task.mAsync) {
513                             mUnfinishedJobs.add(task);
514                             ((CompletionStage) result).whenComplete(task);
515                         } else {
516                             task.complete(result);
517                         }
518                     }
519                 } catch (Throwable e) {
520                     completeExceptionally(job, e);
521                 }
522             }
523 
524             maybeScheduleUnbindTimeout();
525         }
526 
maybeScheduleUnbindTimeout()527         private void maybeScheduleUnbindTimeout() {
528             if (mUnfinishedJobs.isEmpty() && mQueue.isEmpty()) {
529                 scheduleUnbindTimeout();
530             }
531         }
532 
scheduleUnbindTimeout()533         private void scheduleUnbindTimeout() {
534             if (DEBUG) {
535                 logTrace();
536             }
537             long timeout = getAutoDisconnectTimeoutMs();
538             if (timeout > 0) {
539                 mMainHandler.postDelayed(mTimeoutDisconnect, timeout);
540             } else if (DEBUG) {
541                 Log.i(LOG_TAG, "Not scheduling unbind for permanently bound " + this);
542             }
543         }
544 
isBound()545         private boolean isBound() {
546             return mService != null;
547         }
548 
549         @Override
unbind()550         public void unbind() {
551             if (DEBUG) {
552                 logTrace();
553             }
554             mUnbinding = true;
555             mHandler.post(this::unbindJobThread);
556         }
557 
558         @Override
setServiceLifecycleCallbacks(@ullable ServiceLifecycleCallbacks<I> callbacks)559         public void setServiceLifecycleCallbacks(@Nullable ServiceLifecycleCallbacks<I> callbacks) {
560             mServiceLifecycleCallbacks = callbacks;
561         }
562 
unbindJobThread()563         void unbindJobThread() {
564             cancelTimeout();
565             I service = mService;
566             // TODO(b/224695239): This is actually checking wasConnected. Rename and/or fix
567             // implementation based on what this should actually be checking. At least the first
568             // check for calling unbind is the correct behavior, though.
569             boolean wasBound = service != null;
570             if (wasBound || mBinding) {
571                 try {
572                     mContext.unbindService(mServiceConnection);
573                 } catch (IllegalArgumentException e) {  // TODO(b/224697137): Fix the race condition
574                                                         // that requires catching this (crashes if
575                                                         // service isn't currently bound).
576                     Slog.e(LOG_TAG, "Failed to unbind: " + e);
577                 }
578             }
579             if (wasBound) {
580                 dispatchOnServiceConnectionStatusChanged(service, false);
581                 service.asBinder().unlinkToDeath(this, 0);
582                 mService = null;
583             }
584             mBinding = false;
585             mUnbinding = false;
586             synchronized (this) {
587                 if (mServiceConnectionFutureCache != null) {
588                     mServiceConnectionFutureCache.cancel(true);
589                     mServiceConnectionFutureCache = null;
590                 }
591             }
592 
593             cancelPendingJobs();
594 
595             if (wasBound) {
596                 onServiceUnbound();
597             }
598         }
599 
cancelPendingJobs()600         protected void cancelPendingJobs() {
601             Job<I, ?> job;
602             while ((job = mQueue.poll()) != null) {
603                 if (DEBUG) {
604                     Log.i(LOG_TAG, "cancel(" + job + ")");
605                 }
606                 CompletionAwareJob task = castOrNull(job, CompletionAwareJob.class);
607                 if (task != null) {
608                     task.cancel(/* mayInterruptWhileRunning= */ false);
609                 }
610             }
611         }
612 
613         @Override
onServiceConnected(@onNull ComponentName name, @NonNull IBinder binder)614         public void onServiceConnected(@NonNull ComponentName name, @NonNull IBinder binder) {
615             if (mUnbinding) {
616                 Log.i(LOG_TAG, "Ignoring onServiceConnected due to ongoing unbinding: " + this);
617                 return;
618             }
619             if (DEBUG) {
620                 logTrace();
621             }
622             I service = binderAsInterface(binder);
623             mService = service;
624             mBinding = false;
625             try {
626                 binder.linkToDeath(ServiceConnector.Impl.this, 0);
627             } catch (RemoteException e) {
628                 Log.e(LOG_TAG, "onServiceConnected " + name + ": ", e);
629             }
630             dispatchOnServiceConnectionStatusChanged(service, true);
631             processQueue();
632         }
633 
634         @Override
onServiceDisconnected(@onNull ComponentName name)635         public void onServiceDisconnected(@NonNull ComponentName name) {
636             if (DEBUG) {
637                 logTrace();
638             }
639             mBinding = true;
640             I service = mService;
641             if (service != null) {
642                 dispatchOnServiceConnectionStatusChanged(service, false);
643                 mService = null;
644             }
645         }
646 
647         @Override
onBindingDied(@onNull ComponentName name)648         public void onBindingDied(@NonNull ComponentName name) {
649             if (DEBUG) {
650                 logTrace();
651             }
652             binderDied();
653         }
654 
655         @Override
binderDied()656         public void binderDied() {
657             if (DEBUG) {
658                 logTrace();
659             }
660             mService = null;
661             unbind();
662             dispatchOnBinderDied();
663         }
664 
dispatchOnBinderDied()665         private void dispatchOnBinderDied() {
666             ServiceLifecycleCallbacks<I> serviceLifecycleCallbacks = mServiceLifecycleCallbacks;
667             if (serviceLifecycleCallbacks != null) {
668                 serviceLifecycleCallbacks.onBinderDied();
669             }
670         }
671 
672         @Override
run()673         public void run() {
674             onTimeout();
675         }
676 
onTimeout()677         private void onTimeout() {
678             if (DEBUG) {
679                 logTrace();
680             }
681             unbind();
682         }
683 
684         @Override
toString()685         public String toString() {
686             StringBuilder sb = new StringBuilder("ServiceConnector@")
687                     .append(System.identityHashCode(this) % 1000).append("(")
688                     .append(mIntent).append(", user: ").append(mContext.getUser().getIdentifier())
689                     .append(")[").append(stateToString());
690             if (!mQueue.isEmpty()) {
691                 sb.append(", ").append(mQueue.size()).append(" pending job(s)");
692                 if (DEBUG) {
693                     sb.append(": ").append(super.toString());
694                 }
695             }
696             if (!mUnfinishedJobs.isEmpty()) {
697                 sb.append(", ").append(mUnfinishedJobs.size()).append(" unfinished async job(s)");
698             }
699             return sb.append("]").toString();
700         }
701 
dump(@onNull String prefix, @NonNull PrintWriter pw)702         public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
703             String tab = "  ";
704             pw.append(prefix).append("ServiceConnector:").println();
705             pw.append(prefix).append(tab).append(String.valueOf(mIntent)).println();
706             pw.append(prefix).append(tab).append("userId: ")
707                     .append(String.valueOf(mContext.getUser().getIdentifier())).println();
708             pw.append(prefix).append(tab)
709                     .append("State: ").append(stateToString()).println();
710             pw.append(prefix).append(tab)
711                     .append("Pending jobs: ").append(String.valueOf(mQueue.size())).println();
712             if (DEBUG) {
713                 for (Job<I, ?> pendingJob : mQueue) {
714                     pw.append(prefix).append(tab).append(tab)
715                             .append(String.valueOf(pendingJob)).println();
716                 }
717             }
718             pw.append(prefix).append(tab)
719                     .append("Unfinished async jobs: ")
720                     .append(String.valueOf(mUnfinishedJobs.size())).println();
721         }
722 
stateToString()723         private String stateToString() {
724             if (mBinding) {
725                 return "Binding...";
726             } else if (mUnbinding) {
727                 return "Unbinding...";
728             } else if (isBound()) {
729                 return "Bound";
730             } else {
731                 return "Unbound";
732             }
733         }
734 
logTrace()735         private void logTrace() {
736             Log.i(LOG_TAG, "See stacktrace", new Throwable());
737         }
738 
739         /**
740          * {@link Job} + {@link AndroidFuture}
741          */
742         class CompletionAwareJob<II, R> extends AndroidFuture<R>
743                 implements Job<II, R>, BiConsumer<R, Throwable> {
744             Job<II, R> mDelegate;
745             boolean mAsync = false;
746             private String mDebugName;
747             {
748                 // The timeout handler must be set before any calls to set timeouts on the
749                 // AndroidFuture, to ensure they are posted on the proper thread.
getJobHandler()750                 setTimeoutHandler(getJobHandler());
751 
752                 long requestTimeout = getRequestTimeoutMs();
753                 if (requestTimeout > 0) {
orTimeout(requestTimeout, TimeUnit.MILLISECONDS)754                     orTimeout(requestTimeout, TimeUnit.MILLISECONDS);
755                 }
756 
757                 if (DEBUG) {
758                     mDebugName = Arrays.stream(Thread.currentThread().getStackTrace())
759                             .skip(2)
760                             .filter(st ->
761                                     !st.getClassName().contains(ServiceConnector.class.getName()))
762                             .findFirst()
763                             .get()
764                             .getMethodName();
765                 }
766             }
767 
768             @Override
run(@onNull II service)769             public R run(@NonNull II service) throws Exception {
770                 return mDelegate.run(service);
771             }
772 
773             @Override
cancel(boolean mayInterruptIfRunning)774             public boolean cancel(boolean mayInterruptIfRunning) {
775                 if (mayInterruptIfRunning) {
776                     Log.w(LOG_TAG, "mayInterruptIfRunning not supported - ignoring");
777                 }
778                 boolean wasRemoved = mQueue.remove(this);
779                 return super.cancel(mayInterruptIfRunning) || wasRemoved;
780             }
781 
782             @Override
toString()783             public String toString() {
784                 if (DEBUG) {
785                     return mDebugName;
786                 }
787                 return mDelegate + " wrapped into " + super.toString();
788             }
789 
790             @Override
accept(@ullable R res, @Nullable Throwable err)791             public void accept(@Nullable R res, @Nullable Throwable err) {
792                 if (err != null) {
793                     completeExceptionally(err);
794                 } else {
795                     complete(res);
796                 }
797             }
798 
799             @Override
onCompleted(R res, Throwable err)800             protected void onCompleted(R res, Throwable err) {
801                 super.onCompleted(res, err);
802                 if (mUnfinishedJobs.remove(this)) {
803                     maybeScheduleUnbindTimeout();
804                 }
805             }
806         }
807     }
808 
809     /**
810      * A {@link ServiceConnector} that doesn't connect to anything.
811      *
812      * @param <T> the type of the {@link IInterface ipc interface} for the remote service
813      */
814     class NoOp<T extends IInterface> extends AndroidFuture<Object> implements ServiceConnector<T> {
815         {
completeExceptionally(new IllegalStateException("ServiceConnector is a no-op"))816             completeExceptionally(new IllegalStateException("ServiceConnector is a no-op"));
817         }
818 
819         @Override
run(@onNull VoidJob<T> job)820         public boolean run(@NonNull VoidJob<T> job) {
821             return false;
822         }
823 
824         @Override
post(@onNull VoidJob<T> job)825         public AndroidFuture<Void> post(@NonNull VoidJob<T> job) {
826             return (AndroidFuture) this;
827         }
828 
829         @Override
postForResult(@onNull Job<T, R> job)830         public <R> AndroidFuture<R> postForResult(@NonNull Job<T, R> job) {
831             return (AndroidFuture) this;
832         }
833 
834         @Override
postAsync(@onNull Job<T, CompletableFuture<R>> job)835         public <R> AndroidFuture<R> postAsync(@NonNull Job<T, CompletableFuture<R>> job) {
836             return (AndroidFuture) this;
837         }
838 
839         @Override
connect()840         public AndroidFuture<T> connect() {
841             return (AndroidFuture) this;
842         }
843 
844         @Override
unbind()845         public void unbind() {}
846 
847         @Override
setServiceLifecycleCallbacks(@ullable ServiceLifecycleCallbacks<T> callbacks)848         public void setServiceLifecycleCallbacks(@Nullable ServiceLifecycleCallbacks<T> callbacks) {
849             // Do nothing.
850         }
851     }
852 }
853