Simplified how AbstractRemoteService subclasses run an async request.

In these cases the request never times out, so it can be simplified by using
a lambda to represent the request.

Bug: 117779333
Test: atest CtsContentCaptureServiceTestCases CtsAutoFillServiceTestCases

Change-Id: Iba52aad1315ae7d3982671a0fdeabe87a6d6ee04
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 5a0d12c..fa62ef8 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -76,7 +76,6 @@
 import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.infra.AbstractPerUserSystemService;
-import com.android.server.infra.AbstractRemoteService;
 import com.android.server.infra.FrameworkResourcesServiceNameResolver;
 import com.android.server.infra.SecureSettingsServiceNameResolver;
 
@@ -1031,8 +1030,7 @@
                 return null;
             }
             final ComponentName componentName = RemoteAugmentedAutofillService.getComponentName(
-                    getContext(), serviceName, mUserId,
-                    mAugmentedAutofillResolver.isTemporaryLocked());
+                    serviceName, mUserId, mAugmentedAutofillResolver.isTemporaryLocked());
             if (componentName == null) return null;
             if (sVerbose) {
                 Slog.v(TAG, "getRemoteAugmentedAutofillServiceLocked(): " + componentName);
@@ -1041,8 +1039,7 @@
             mRemoteAugmentedAutofillService = new RemoteAugmentedAutofillService(getContext(),
                     componentName, mUserId, new RemoteAugmentedAutofillServiceCallbacks() {
                         @Override
-                        public void onServiceDied(
-                                AbstractRemoteService<? extends AbstractRemoteService<?>> service) {
+                        public void onServiceDied(@NonNull RemoteAugmentedAutofillService service) {
                             // TODO(b/111330312): properly implement
                             Slog.w(TAG, "remote augmented autofill service died");
                         }
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 222888c..fc7265d 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -26,7 +26,6 @@
 import android.content.pm.ServiceInfo;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.os.IInterface;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.service.autofill.augmented.AugmentedAutofillService;
@@ -43,7 +42,8 @@
 import com.android.server.infra.AbstractSinglePendingRequestRemoteService;
 
 final class RemoteAugmentedAutofillService
-        extends AbstractSinglePendingRequestRemoteService<RemoteAugmentedAutofillService> {
+        extends AbstractSinglePendingRequestRemoteService<RemoteAugmentedAutofillService,
+            IAugmentedAutofillService> {
 
     private static final String TAG = RemoteAugmentedAutofillService.class.getSimpleName();
 
@@ -51,20 +51,16 @@
     private static final long TIMEOUT_IDLE_BIND_MILLIS = 2 * DateUtils.MINUTE_IN_MILLIS;
     private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
 
-    private final RemoteAugmentedAutofillServiceCallbacks mCallbacks;
-    private IAugmentedAutofillService mService;
-
     RemoteAugmentedAutofillService(Context context, ComponentName serviceName,
             int userId, RemoteAugmentedAutofillServiceCallbacks callbacks,
             boolean bindInstantServiceAllowed, boolean verbose) {
         super(context, AugmentedAutofillService.SERVICE_INTERFACE, serviceName, userId, callbacks,
                 bindInstantServiceAllowed, verbose);
-        mCallbacks = callbacks;
     }
 
     @Nullable
-    public static ComponentName getComponentName(@NonNull Context context,
-            @NonNull String componentName, @UserIdInt int userId, boolean isTemporary) {
+    public static ComponentName getComponentName(@NonNull String componentName,
+            @UserIdInt int userId, boolean isTemporary) {
         int flags = PackageManager.GET_META_DATA;
         if (!isTemporary) {
             flags |= PackageManager.MATCH_SYSTEM_ONLY;
@@ -88,9 +84,8 @@
     }
 
     @Override // from AbstractRemoteService
-    protected IInterface getServiceInterface(IBinder service) {
-        mService = IAugmentedAutofillService.Stub.asInterface(service);
-        return mService;
+    protected IAugmentedAutofillService getServiceInterface(IBinder service) {
+        return IAugmentedAutofillService.Stub.asInterface(service);
     }
 
     @Override // from AbstractRemoteService
@@ -109,7 +104,6 @@
     public void onRequestAutofillLocked(int sessionId, @NonNull IAutoFillManagerClient client,
             int taskId, @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId,
             @Nullable AutofillValue focusedValue) {
-        cancelScheduledUnbind();
         scheduleRequest(new PendingAutofillRequest(this, sessionId, client, taskId,
                 activityComponent, focusedId, focusedValue));
     }
@@ -118,12 +112,12 @@
      * Called by {@link Session} when it's time to destroy all augmented autofill requests.
      */
     public void onDestroyAutofillWindowsRequest(int sessionId) {
-        cancelScheduledUnbind();
-        scheduleRequest(new PendingDestroyAutofillWindowsRequest(this, sessionId));
+        scheduleAsyncRequest((s) -> s.onDestroyFillWindowRequest(sessionId));
     }
 
+    // TODO(b/111330312): inline into PendingAutofillRequest if it doesn't have any other subclass
     private abstract static class MyPendingRequest
-            extends PendingRequest<RemoteAugmentedAutofillService> {
+            extends PendingRequest<RemoteAugmentedAutofillService, IAugmentedAutofillService> {
         protected final int mSessionId;
 
         private MyPendingRequest(@NonNull RemoteAugmentedAutofillService service, int sessionId) {
@@ -196,38 +190,8 @@
 
     }
 
-    private static final class PendingDestroyAutofillWindowsRequest extends MyPendingRequest {
-
-        protected PendingDestroyAutofillWindowsRequest(
-                @NonNull RemoteAugmentedAutofillService service, @NonNull int sessionId) {
-            super(service, sessionId);
-        }
-
-        @Override
-        public void run() {
-            final RemoteAugmentedAutofillService remoteService = getService();
-            if (remoteService == null) return;
-
-            try {
-                remoteService.mService.onDestroyFillWindowRequest(mSessionId);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "exception handling onDestroyAutofillWindowsRequest() for "
-                        + mSessionId + ": " + e);
-            } finally {
-                // Service is not calling back, so we finish right away.
-                finish();
-            }
-        }
-
-        @Override
-        protected void onTimeout(RemoteAugmentedAutofillService remoteService) {
-            // Should not happen because we called finish() on run(), although currently it might
-            // be called if the service is destroyed while showing it.
-            Slog.e(TAG, "timed out: " + this);
-        }
-    }
-
-    public interface RemoteAugmentedAutofillServiceCallbacks extends VultureCallback {
+    public interface RemoteAugmentedAutofillServiceCallbacks
+            extends VultureCallback<RemoteAugmentedAutofillService> {
         // NOTE: so far we don't need to notify the callback implementation (an inner class on
         // AutofillManagerServiceImpl) of the request results (success, timeouts, etc..), so this
         // callback interface is empty.
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 4b7d290..417ea9c 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -28,7 +28,6 @@
 import android.content.IntentSender;
 import android.os.IBinder;
 import android.os.ICancellationSignal;
-import android.os.IInterface;
 import android.os.RemoteException;
 import android.service.autofill.AutofillService;
 import android.service.autofill.FillRequest;
@@ -42,15 +41,15 @@
 
 import com.android.server.infra.AbstractSinglePendingRequestRemoteService;
 
-final class RemoteFillService extends AbstractSinglePendingRequestRemoteService<RemoteFillService> {
+final class RemoteFillService
+        extends AbstractSinglePendingRequestRemoteService<RemoteFillService, IAutoFillService> {
 
     private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
     private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
 
     private final FillServiceCallbacks mCallbacks;
-    private IAutoFillService mAutoFillService;
 
-    public interface FillServiceCallbacks extends VultureCallback {
+    public interface FillServiceCallbacks extends VultureCallback<RemoteFillService> {
         void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
                 @NonNull String servicePackageName, int requestFlags);
         void onFillRequestFailure(int requestId, @Nullable CharSequence message);
@@ -71,21 +70,20 @@
 
     @Override // from AbstractRemoteService
     protected void handleOnConnectedStateChanged(boolean state) {
-        if (mAutoFillService == null) {
+        if (mService == null) {
             Slog.w(mTag, "onConnectedStateChanged(): null service");
             return;
         }
         try {
-            mAutoFillService.onConnectedStateChanged(state);
+            mService.onConnectedStateChanged(state);
         } catch (Exception e) {
             Slog.w(mTag, "Exception calling onConnectedStateChanged(): " + e);
         }
     }
 
     @Override // from AbstractRemoteService
-    protected IInterface getServiceInterface(IBinder service) {
-        mAutoFillService = IAutoFillService.Stub.asInterface(service);
-        return mAutoFillService;
+    protected IAutoFillService getServiceInterface(IBinder service) {
+        return IAutoFillService.Stub.asInterface(service);
     }
 
     @Override // from AbstractRemoteService
@@ -127,17 +125,15 @@
     }
 
     public void onFillRequest(@NonNull FillRequest request) {
-        cancelScheduledUnbind();
         scheduleRequest(new PendingFillRequest(request, this));
     }
 
     public void onSaveRequest(@NonNull SaveRequest request) {
-        cancelScheduledUnbind();
         scheduleRequest(new PendingSaveRequest(request, this));
     }
 
     private boolean handleResponseCallbackCommon(
-            @NonNull PendingRequest<RemoteFillService> pendingRequest) {
+            @NonNull PendingRequest<RemoteFillService, IAutoFillService> pendingRequest) {
         if (isDestroyed()) return false;
 
         if (mPendingRequest == pendingRequest) {
@@ -204,7 +200,8 @@
         });
     }
 
-    private static final class PendingFillRequest extends PendingRequest<RemoteFillService> {
+    private static final class PendingFillRequest
+            extends PendingRequest<RemoteFillService, IAutoFillService> {
         private final FillRequest mRequest;
         private final IFillCallback mCallback;
         private ICancellationSignal mCancellation;
@@ -282,7 +279,7 @@
             if (remoteService != null) {
                 if (sVerbose) Slog.v(mTag, "calling onFillRequest() for id=" + mRequest.getId());
                 try {
-                    remoteService.mAutoFillService.onFillRequest(mRequest, mCallback);
+                    remoteService.mService.onFillRequest(mRequest, mCallback);
                 } catch (RemoteException e) {
                     Slog.e(mTag, "Error calling on fill request", e);
 
@@ -310,7 +307,8 @@
         }
     }
 
-    private static final class PendingSaveRequest extends PendingRequest<RemoteFillService> {
+    private static final class PendingSaveRequest
+            extends PendingRequest<RemoteFillService, IAutoFillService> {
         private final SaveRequest mRequest;
         private final ISaveCallback mCallback;
 
@@ -355,7 +353,7 @@
             if (remoteService != null) {
                 if (sVerbose) Slog.v(mTag, "calling onSaveRequest()");
                 try {
-                    remoteService.mAutoFillService.onSaveRequest(mRequest, mCallback);
+                    remoteService.mService.onSaveRequest(mRequest, mCallback);
                 } catch (RemoteException e) {
                     Slog.e(mTag, "Error calling on save request", e);
 
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index d76a5df..2633d20 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -98,7 +98,6 @@
 import com.android.server.autofill.AutofillManagerService.SmartSuggestionMode;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.autofill.ui.PendingUi;
-import com.android.server.infra.AbstractRemoteService;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -909,7 +908,7 @@
 
     // VultureCallback
     @Override
-    public void onServiceDied(AbstractRemoteService<? extends AbstractRemoteService<?>> service) {
+    public void onServiceDied(@NonNull RemoteFillService service) {
         Slog.w(TAG, "removing session because service died");
         forceRemoveSelfLocked();
     }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java
index 2302b7d..a4012d5 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java
@@ -29,7 +29,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks;
-import com.android.server.infra.AbstractRemoteService;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -124,8 +123,8 @@
         }
     }
 
-    @Override // from RemoteScreenObservationServiceCallbacks
-    public void onServiceDied(AbstractRemoteService<?> service) {
+    @Override // from RemoteContentCaptureServiceCallbacks
+    public void onServiceDied(@NonNull RemoteContentCaptureService service) {
         // TODO(b/111276913): implement (remove session from PerUserSession?)
         if (mService.isDebug()) {
             Slog.d(TAG, "onServiceDied() for " + mId);
@@ -135,17 +134,6 @@
         }
     }
 
-    @Override // from RemoteScreenObservationServiceCallbacks
-    public void onFailureOrTimeout(boolean timedOut) {
-        // TODO(b/111276913): log metrics on whether timed out or not
-        if (mService.isDebug()) {
-            Slog.d(TAG, "onFailureOrTimeout(" + mId + "): timed out=" + timedOut);
-        }
-        synchronized (mLock) {
-            removeSelfLocked(/* notifyRemoteService= */ false);
-        }
-    }
-
     @GuardedBy("mLock")
     public void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
         pw.print(prefix); pw.print("id: ");  pw.print(mId); pw.println();
diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
index 6a111f2..33b6c8d 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
@@ -20,14 +20,11 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.IBinder;
-import android.os.IInterface;
-import android.os.RemoteException;
 import android.service.contentcapture.ContentCaptureEventsRequest;
 import android.service.contentcapture.IContentCaptureService;
 import android.service.contentcapture.InteractionContext;
 import android.service.contentcapture.SnapshotData;
 import android.text.format.DateUtils;
-import android.util.Slog;
 import android.view.contentcapture.ContentCaptureEvent;
 
 import com.android.server.infra.AbstractMultiplePendingRequestsRemoteService;
@@ -35,30 +32,24 @@
 import java.util.List;
 
 final class RemoteContentCaptureService
-        extends AbstractMultiplePendingRequestsRemoteService<RemoteContentCaptureService> {
-
-    private static final String TAG = RemoteContentCaptureService.class.getSimpleName();
+        extends AbstractMultiplePendingRequestsRemoteService<RemoteContentCaptureService,
+        IContentCaptureService> {
 
     // TODO(b/117779333): changed it so it's permanentely bound
     private static final long TIMEOUT_IDLE_BIND_MILLIS = 2 * DateUtils.MINUTE_IN_MILLIS;
     private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
 
-    private final ContentCaptureServiceCallbacks mCallbacks;
-    private IContentCaptureService mService;
-
     RemoteContentCaptureService(Context context, String serviceInterface,
             ComponentName componentName, int userId,
             ContentCaptureServiceCallbacks callbacks, boolean bindInstantServiceAllowed,
             boolean verbose) {
         super(context, serviceInterface, componentName, userId, callbacks,
                 bindInstantServiceAllowed, verbose, /* initialCapacity= */ 2);
-        mCallbacks = callbacks;
     }
 
     @Override // from RemoteService
-    protected IInterface getServiceInterface(@NonNull IBinder service) {
-        mService = IContentCaptureService.Stub.asInterface(service);
-        return mService;
+    protected IContentCaptureService getServiceInterface(@NonNull IBinder service) {
+        return IContentCaptureService.Stub.asInterface(service);
     }
 
     // TODO(b/111276913): modify super class to allow permanent binding when value is 0 or negative
@@ -81,8 +72,7 @@
      */
     public void onSessionLifecycleRequest(@Nullable InteractionContext context,
             @NonNull String sessionId) {
-        cancelScheduledUnbind();
-        scheduleRequest(new PendingSessionLifecycleRequest(this, context, sessionId));
+        scheduleAsyncRequest((s) -> s.onSessionLifecycle(context, sessionId));
     }
 
     /**
@@ -90,8 +80,8 @@
      */
     public void onContentCaptureEventsRequest(@NonNull String sessionId,
             @NonNull List<ContentCaptureEvent> events) {
-        cancelScheduledUnbind();
-        scheduleRequest(new PendingOnContentCaptureEventsRequest(this, sessionId, events));
+        scheduleAsyncRequest((s) -> s.onContentCaptureEventsRequest(sessionId,
+                new ContentCaptureEventsRequest(events)));
     }
 
     /**
@@ -99,103 +89,13 @@
      */
     public void onActivitySnapshotRequest(@NonNull String sessionId,
             @NonNull SnapshotData snapshotData) {
-        cancelScheduledUnbind();
-        scheduleRequest(new PendingOnActivitySnapshotRequest(this, sessionId, snapshotData));
+        scheduleAsyncRequest((s) -> s.onActivitySnapshot(sessionId, snapshotData));
     }
 
-    private abstract static class MyPendingRequest
-            extends PendingRequest<RemoteContentCaptureService> {
-        protected final String mSessionId;
-
-        private MyPendingRequest(@NonNull RemoteContentCaptureService service,
-                @NonNull String sessionId) {
-            super(service);
-            mSessionId = sessionId;
-        }
-
-        @Override // from PendingRequest
-        protected final void onTimeout(RemoteContentCaptureService remoteService) {
-            Slog.w(TAG, "timed out handling " + getClass().getSimpleName() + " for "
-                    + mSessionId);
-            remoteService.mCallbacks.onFailureOrTimeout(/* timedOut= */ true);
-        }
-
-        @Override // from PendingRequest
-        public final void run() {
-            final RemoteContentCaptureService remoteService = getService();
-            if (remoteService != null) {
-                try {
-                    // We don't expect the service to call us back, so we finish right away.
-                    myRun(remoteService);
-                    // TODO(b/111330312): not true anymore!!
-                    finish();
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "exception handling " + getClass().getSimpleName() + " for "
-                            + mSessionId + ": " + e);
-                    remoteService.mCallbacks.onFailureOrTimeout(/* timedOut= */ false);
-                }
-            }
-        }
-
-        protected abstract void myRun(@NonNull RemoteContentCaptureService service)
-                throws RemoteException;
-
-    }
-
-    private static final class PendingSessionLifecycleRequest extends MyPendingRequest {
-
-        private final InteractionContext mContext;
-
-        protected PendingSessionLifecycleRequest(@NonNull RemoteContentCaptureService service,
-                @Nullable InteractionContext context, @NonNull String sessionId) {
-            super(service, sessionId);
-            mContext = context;
-        }
-
-        @Override // from MyPendingRequest
-        public void myRun(@NonNull RemoteContentCaptureService remoteService)
-                throws RemoteException {
-            remoteService.mService.onSessionLifecycle(mContext, mSessionId);
-        }
-    }
-
-    private static final class PendingOnContentCaptureEventsRequest extends MyPendingRequest {
-
-        private final List<ContentCaptureEvent> mEvents;
-
-        protected PendingOnContentCaptureEventsRequest(@NonNull RemoteContentCaptureService service,
-                @NonNull String sessionId, @NonNull List<ContentCaptureEvent> events) {
-            super(service, sessionId);
-            mEvents = events;
-        }
-
-        @Override // from MyPendingRequest
-        public void myRun(@NonNull RemoteContentCaptureService remoteService)
-                throws RemoteException {
-            remoteService.mService.onContentCaptureEventsRequest(mSessionId,
-                    new ContentCaptureEventsRequest(mEvents));
-        }
-    }
-
-    private static final class PendingOnActivitySnapshotRequest extends MyPendingRequest {
-
-        private final SnapshotData mSnapshotData;
-
-        protected PendingOnActivitySnapshotRequest(@NonNull RemoteContentCaptureService service,
-                @NonNull String sessionId, @NonNull SnapshotData snapshotData) {
-            super(service, sessionId);
-            mSnapshotData = snapshotData;
-        }
-
-        @Override // from MyPendingRequest
-        protected void myRun(@NonNull RemoteContentCaptureService remoteService)
-                throws RemoteException {
-            remoteService.mService.onActivitySnapshot(mSessionId, mSnapshotData);
-        }
-    }
-
-    public interface ContentCaptureServiceCallbacks extends VultureCallback {
-        // To keep it simple, we use the same callback for all failures / timeouts.
-        void onFailureOrTimeout(boolean timedOut);
+    public interface ContentCaptureServiceCallbacks
+            extends VultureCallback<RemoteContentCaptureService> {
+        // NOTE: so far we don't need to notify the callback implementation (an inner class on
+        // AutofillManagerServiceImpl) of the request results (success, timeouts, etc..), so this
+        // callback interface is empty.
     }
 }
diff --git a/services/core/java/com/android/server/infra/AbstractMultiplePendingRequestsRemoteService.java b/services/core/java/com/android/server/infra/AbstractMultiplePendingRequestsRemoteService.java
index 513a6a3..aaea45e 100644
--- a/services/core/java/com/android/server/infra/AbstractMultiplePendingRequestsRemoteService.java
+++ b/services/core/java/com/android/server/infra/AbstractMultiplePendingRequestsRemoteService.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.IInterface;
 import android.util.Slog;
 
 import java.io.PrintWriter;
@@ -29,21 +30,21 @@
  * bound.
  *
  * @param <S> the concrete remote service class
- *
+ * @param <I> the interface of the binder service
  * @hide
  */
-public abstract class AbstractMultiplePendingRequestsRemoteService<
-        S extends AbstractMultiplePendingRequestsRemoteService<S>>
-        extends AbstractRemoteService<S> {
+public abstract class AbstractMultiplePendingRequestsRemoteService<S
+        extends AbstractMultiplePendingRequestsRemoteService<S, I>, I extends IInterface>
+        extends AbstractRemoteService<S, I> {
 
     private final int mInitialCapacity;
 
-    protected ArrayList<PendingRequest<S>> mPendingRequests;
+    protected ArrayList<PendingRequest<S, I>> mPendingRequests;
 
     public AbstractMultiplePendingRequestsRemoteService(@NonNull Context context,
             @NonNull String serviceInterface, @NonNull ComponentName componentName, int userId,
-            @NonNull VultureCallback callback, boolean bindInstantServiceAllowed, boolean verbose,
-            int initialCapacity) {
+            @NonNull VultureCallback<S> callback, boolean bindInstantServiceAllowed,
+            boolean verbose, int initialCapacity) {
         super(context, serviceInterface, componentName, userId, callback, bindInstantServiceAllowed,
                 verbose);
         mInitialCapacity = initialCapacity;
@@ -84,7 +85,7 @@
     }
 
     @Override // from AbstractRemoteService
-    void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S> pendingRequest) {
+    void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S, I> pendingRequest) {
         if (mPendingRequests == null) {
             mPendingRequests = new ArrayList<>(mInitialCapacity);
         }
diff --git a/services/core/java/com/android/server/infra/AbstractRemoteService.java b/services/core/java/com/android/server/infra/AbstractRemoteService.java
index 67b3ecf..41dcf89 100644
--- a/services/core/java/com/android/server/infra/AbstractRemoteService.java
+++ b/services/core/java/com/android/server/infra/AbstractRemoteService.java
@@ -54,13 +54,13 @@
  * (no pun intended) example of how to use it.
  *
  * @param <S> the concrete remote service class
+ * @param <I> the interface of the binder service
  *
  * @hide
  */
 //TODO(b/117779333): improve javadoc above instead of using Autofill as an example
-public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>>
-        implements DeathRecipient {
-
+public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I>,
+        I extends IInterface> implements DeathRecipient {
     private static final int MSG_UNBIND = 1;
 
     protected static final int LAST_PRIVATE_MSG = MSG_UNBIND;
@@ -74,11 +74,11 @@
 
     private final Context mContext;
     private final Intent mIntent;
-    private final VultureCallback mVultureCallback;
+    private final VultureCallback<S> mVultureCallback;
     private final int mUserId;
     private final ServiceConnection mServiceConnection = new RemoteServiceConnection();
     private final boolean mBindInstantServiceAllowed;
-    private IInterface mServiceInterface;
+    protected I mService;
 
     private boolean mBinding;
     private boolean mDestroyed;
@@ -87,19 +87,21 @@
 
     /**
      * Callback called when the service dies.
+     *
+     * @param <T> service class
      */
-    public interface VultureCallback {
+    public interface VultureCallback<T> {
         /**
          * Called when the service dies.
          *
          * @param service service that died!
          */
-        void onServiceDied(AbstractRemoteService<? extends AbstractRemoteService<?>> service);
+        void onServiceDied(T service);
     }
 
     // NOTE: must be package-protected so this class is not extend outside
     AbstractRemoteService(@NonNull Context context, @NonNull String serviceInterface,
-            @NonNull ComponentName componentName, int userId, @NonNull VultureCallback callback,
+            @NonNull ComponentName componentName, int userId, @NonNull VultureCallback<S> callback,
             boolean bindInstantServiceAllowed, boolean verbose) {
         mContext = context;
         mVultureCallback = callback;
@@ -150,7 +152,7 @@
      * Gets the base Binder interface from the service.
      */
     @NonNull
-    protected abstract IInterface getServiceInterface(@NonNull IBinder service);
+    protected abstract I getServiceInterface(@NonNull IBinder service);
 
     /**
      * Defines How long after the last interaction with the service we would unbind.
@@ -183,12 +185,14 @@
 
     private void handleBinderDied() {
         if (checkIfDestroyed()) return;
-        if (mServiceInterface != null) {
-            mServiceInterface.asBinder().unlinkToDeath(this, 0);
+        if (mService != null) {
+            mService.asBinder().unlinkToDeath(this, 0);
         }
-        mServiceInterface = null;
+        mService = null;
         mServiceDied = true;
-        mVultureCallback.onServiceDied(this);
+        @SuppressWarnings("unchecked") // TODO(b/117779333): fix this warning
+        final S castService = (S) this;
+        mVultureCallback.onServiceDied(castService);
     }
 
     // Note: we are dumping without a lock held so this is a bit racy but
@@ -216,12 +220,35 @@
         pw.println();
     }
 
-    protected void scheduleRequest(@NonNull PendingRequest<S> pendingRequest) {
+    /**
+     * Schedules a "sync" request.
+     *
+     * <p>This request must be responded by the service somehow (typically using a callback),
+     * othewise it will trigger a {@link PendingRequest#onTimeout(AbstractRemoteService)} if the
+     * service doesn't respond.
+     */
+    protected void scheduleRequest(@NonNull PendingRequest<S, I> pendingRequest) {
+        cancelScheduledUnbind();
         mHandler.sendMessage(obtainMessage(
                 AbstractRemoteService::handlePendingRequest, this, pendingRequest));
     }
 
-    protected void cancelScheduledUnbind() {
+    /**
+     * Schedules an async request.
+     *
+     * <p>This request is not expecting a callback from the service, hence it's represented by
+     * a simple {@link Runnable}.
+     */
+    protected void scheduleAsyncRequest(@NonNull AsyncRequest<I> request) {
+        cancelScheduledUnbind();
+        // TODO(b/117779333): fix generics below
+        @SuppressWarnings({"unchecked", "rawtypes"})
+        final MyAsyncPendingRequest<S, I> asyncRequest = new MyAsyncPendingRequest(this, request);
+        mHandler.sendMessage(
+                obtainMessage(AbstractRemoteService::handlePendingRequest, this, asyncRequest));
+    }
+
+    private void cancelScheduledUnbind() {
         mHandler.removeMessages(MSG_UNBIND);
     }
 
@@ -244,7 +271,7 @@
      * Handles a request, either processing it right now when bound, or saving it to be handled when
      * bound.
      */
-    protected final void handlePendingRequest(@NonNull PendingRequest<S> pendingRequest) {
+    protected final void handlePendingRequest(@NonNull PendingRequest<S, I> pendingRequest) {
         if (checkIfDestroyed() || mCompleted) return;
 
         if (!handleIsBound()) {
@@ -263,10 +290,10 @@
     /**
      * Defines what to do with a request that arrives while not bound to the service.
      */
-    abstract void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S> pendingRequest);
+    abstract void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S, I> pendingRequest);
 
     private boolean handleIsBound() {
-        return mServiceInterface != null;
+        return mService != null;
     }
 
     private void handleEnsureBound() {
@@ -300,9 +327,9 @@
         mBinding = false;
         if (handleIsBound()) {
             handleOnConnectedStateChangedInternal(false);
-            if (mServiceInterface != null) {
-                mServiceInterface.asBinder().unlinkToDeath(this, 0);
-                mServiceInterface = null;
+            if (mService != null) {
+                mService.asBinder().unlinkToDeath(this, 0);
+                mService = null;
             }
         }
         mContext.unbindService(mServiceConnection);
@@ -318,7 +345,7 @@
                 return;
             }
             mBinding = false;
-            mServiceInterface = getServiceInterface(service);
+            mService = getServiceInterface(service);
             try {
                 service.linkToDeath(AbstractRemoteService.this, 0);
             } catch (RemoteException re) {
@@ -332,7 +359,7 @@
         @Override
         public void onServiceDisconnected(ComponentName name) {
             mBinding = true;
-            mServiceInterface = null;
+            mService = null;
         }
     }
 
@@ -349,10 +376,15 @@
     /**
      * Base class for the requests serviced by the remote service.
      *
+     * <p><b>NOTE: </b> this class is typically used when the service needs to use a callback to
+     * communicate back with the system server. For cases where that's not needed, you should use
+     * {@link AbstractRemoteService#scheduleAsyncRequest(AsyncRequest)} instead.
+     *
      * @param <S> the remote service class
+     * @param <I> the interface of the binder service
      */
-    public abstract static class PendingRequest<S extends AbstractRemoteService<S>>
-            implements Runnable {
+    public abstract static class PendingRequest<S extends AbstractRemoteService<S, I>,
+            I extends IInterface> implements Runnable {
         protected final String mTag = getClass().getSimpleName();
         protected final Object mLock = new Object();
 
@@ -366,7 +398,7 @@
         @GuardedBy("mLock")
         private boolean mCompleted;
 
-        protected PendingRequest(S service) {
+        protected PendingRequest(@NonNull S service) {
             mWeakService = new WeakReference<>(service);
             mServiceHandler = service.mHandler;
             mTimeoutTrigger = () -> {
@@ -452,4 +484,50 @@
             return false;
         }
     }
+
+    /**
+     * Represents a request that does not expect a callback from the remote service.
+     *
+     * @param <I> the interface of the binder service
+     */
+    public interface AsyncRequest<I extends IInterface> {
+
+        /**
+         * Run Forrest, run!
+         */
+        void run(@NonNull I binder) throws RemoteException;
+    }
+
+    private static final class MyAsyncPendingRequest<S extends AbstractRemoteService<S, I>,
+            I extends IInterface> extends PendingRequest<S, I> {
+        private static final String TAG = MyAsyncPendingRequest.class.getSimpleName();
+
+        private final AsyncRequest<I> mRequest;
+
+        protected MyAsyncPendingRequest(@NonNull S service, @NonNull AsyncRequest<I> request) {
+            super(service);
+
+            mRequest = request;
+        }
+
+        @Override
+        public void run() {
+            final S remoteService = getService();
+            if (remoteService == null) return;
+            try {
+                mRequest.run(remoteService.mService);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "exception handling async request (" + this + "): " + e);
+            } finally {
+                finish();
+            }
+        }
+
+        @Override
+        protected void onTimeout(S remoteService) {
+            // TODO(b/117779333): should not happen because we called finish() on run(), although
+            // currently it might be called if the service is destroyed while showing it.
+            Slog.w(TAG, "AsyncPending requested timed out");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/infra/AbstractSinglePendingRequestRemoteService.java b/services/core/java/com/android/server/infra/AbstractSinglePendingRequestRemoteService.java
index 37a1f54..d32f13b 100644
--- a/services/core/java/com/android/server/infra/AbstractSinglePendingRequestRemoteService.java
+++ b/services/core/java/com/android/server/infra/AbstractSinglePendingRequestRemoteService.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.IInterface;
 import android.util.Slog;
 
 import java.io.PrintWriter;
@@ -29,17 +30,19 @@
  * <p>If another request is received while not bound, the previous one will be canceled.
  *
  * @param <S> the concrete remote service class
+ * @param <I> the interface of the binder service
  *
  * @hide
  */
-public abstract class AbstractSinglePendingRequestRemoteService<
-        S extends AbstractSinglePendingRequestRemoteService<S>> extends AbstractRemoteService<S> {
+public abstract class AbstractSinglePendingRequestRemoteService<S
+        extends AbstractSinglePendingRequestRemoteService<S, I>, I extends IInterface>
+        extends AbstractRemoteService<S, I> {
 
-    protected PendingRequest<S> mPendingRequest;
+    protected PendingRequest<S, I> mPendingRequest;
 
     public AbstractSinglePendingRequestRemoteService(@NonNull Context context,
             @NonNull String serviceInterface, @NonNull ComponentName componentName, int userId,
-            @NonNull VultureCallback callback, boolean bindInstantServiceAllowed,
+            @NonNull VultureCallback<S> callback, boolean bindInstantServiceAllowed,
             boolean verbose) {
         super(context, serviceInterface, componentName, userId, callback, bindInstantServiceAllowed,
                 verbose);
@@ -48,7 +51,7 @@
     @Override // from AbstractRemoteService
     void handlePendingRequests() {
         if (mPendingRequest != null) {
-            final PendingRequest<S> pendingRequest = mPendingRequest;
+            final PendingRequest<S, I> pendingRequest = mPendingRequest;
             mPendingRequest = null;
             handlePendingRequest(pendingRequest);
         }
@@ -70,7 +73,7 @@
     }
 
     @Override // from AbstractRemoteService
-    void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S> pendingRequest) {
+    void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S, I> pendingRequest) {
         if (mPendingRequest != null) {
             if (mVerbose) {
                 Slog.v(mTag, "handlePendingRequestWhileUnBound(): cancelling " + mPendingRequest