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