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