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