Refactor ListenerMultiplexer with new capabilities

-Store tag string in multiplexer rather than duplicating in every
registration.
-Add support for failure callbacks at listener operation submission
time, in addition to within the listener operation itself.
-Extract request out of ListenerRegistration and into subclass, so that
the multiplexer does not need to have any knowledge of requests.
-New multiplexer specific execute and failure callbacks within
registrations objects, with strongly typed listener operation objects.
-Fixed resetService() to work properly even if the service is still
registered.
-Added registration info as an argument to registerWithService() so that
this doesn't need to be duplicated if a client wants access to this
information.
-Narrow exception handling so that only the proper exception
(RemoteException for binder calls for instance) results in registration
removal, and other unexpected checked exceptions should still crash the
process.
-Various other minor fixes.

Test: manual + presubmit
Change-Id: I6fdec313222ff1ef01a1aa4d27d8ee56b4be25d4
diff --git a/core/java/com/android/internal/listeners/ListenerExecutor.java b/core/java/com/android/internal/listeners/ListenerExecutor.java
index 9979e60..7d9b9c9 100644
--- a/core/java/com/android/internal/listeners/ListenerExecutor.java
+++ b/core/java/com/android/internal/listeners/ListenerExecutor.java
@@ -38,71 +38,86 @@
         void operate(TListener listener) throws Exception;
 
         /**
-         * Called before this operation is to be run. Some operations may be canceled before they
-         * are run, in which case this method may not be called. {@link #onPostExecute(boolean)}
-         * will only be run if this method was run. This callback is invoked on the calling thread.
+         * Called before this operation is to be run. An operation may be canceled before it is run,
+         * in which case this method may not be invoked. {@link #onPostExecute(boolean)} will only
+         * be invoked if this method was previously invoked. This callback is invoked on the
+         * calling thread.
          */
         default void onPreExecute() {}
 
         /**
          * Called if the operation fails while running. Will not be invoked in the event of a
-         * RuntimeException, which will propagate normally. Implementations of
-         * {@link ListenerExecutor} have the option to override
-         * {@link ListenerExecutor#onOperationFailure(ListenerOperation, Exception)} instead to
-         * intercept failures at the class level. This callback is invoked on the executor thread.
+         * unchecked exception, which will propagate normally. This callback is invoked on the
+         * executor thread.
          */
-        default void onFailure(Exception e) {
-            // implementations should handle any exceptions that may be thrown
-            throw new AssertionError(e);
-        }
+        default void onFailure(Exception e) {}
 
         /**
-         * Called after the operation is run. This method will always be called if
-         * {@link #onPreExecute()} is called. Success implies that the operation was run to
-         * completion with no failures. This callback may be invoked on the calling thread or
-         * executor thread.
+         * Called after the operation may have been run. Will always be invoked for every operation
+         * which has previously had {@link #onPreExecute()} invoked. Success implies that the
+         * operation was run to completion with no failures. If {@code success} is true, this
+         * callback will always be invoked on the executor thread. If {@code success} is false, this
+         * callback may be invoked on the calling thread or executor thread.
          */
         default void onPostExecute(boolean success) {}
 
         /**
-         * Called after this operation is complete (which does not imply that it was necessarily
-         * run). Will always be called once per operation, no matter if the operation was run or
-         * not. Success implies that the operation was run to completion with no failures. This
-         * callback may be invoked on the calling thread or executor thread.
+         * Will always be called once for every operation submitted to
+         * {@link #executeSafely(Executor, Supplier, ListenerOperation)}, no matter if the operation
+         * was run or not. This method is always invoked last, after every other callback. Success
+         * implies that the operation was run to completion with no failures. If {@code success}
+         * is true, this callback will always be invoked on the executor thread. If {@code success}
+         * is false, this callback may be invoked on the calling thread or executor thread.
          */
         default void onComplete(boolean success) {}
     }
 
     /**
-     * May be override to handle operation failures at a class level. Will not be invoked in the
-     * event of a RuntimeException, which will propagate normally. This callback is invoked on the
-     * executor thread.
+     * An callback for listener operation failure.
+     *
+     * @param <TListenerOperation> listener operation type
      */
-    default <TListener> void onOperationFailure(ListenerOperation<TListener> operation,
-            Exception exception) {
-        operation.onFailure(exception);
+    interface FailureCallback<TListenerOperation extends ListenerOperation<?>> {
+
+        /**
+         * Called if a listener operation fails while running with a checked exception. This
+         * callback is invoked on the executor thread.
+         */
+        void onFailure(TListenerOperation operation, Exception exception);
+    }
+
+    /**
+     * See {@link #executeSafely(Executor, Supplier, ListenerOperation, FailureCallback)}.
+     */
+    default <TListener> void executeSafely(Executor executor, Supplier<TListener> listenerSupplier,
+            @Nullable ListenerOperation<TListener> operation) {
+        executeSafely(executor, listenerSupplier, operation, null);
     }
 
     /**
      * Executes the given listener operation on the given executor, using the provided listener
      * supplier. If the supplier returns a null value, or a value during the operation that does not
      * match the value prior to the operation, then the operation is considered canceled. If a null
-     * operation is supplied, nothing happens.
+     * operation is supplied, nothing happens. If a failure callback is supplied, this will be
+     * invoked on the executor thread in the event a checked exception is thrown from the listener
+     * operation.
      */
-    default <TListener> void executeSafely(Executor executor, Supplier<TListener> listenerSupplier,
-            @Nullable ListenerOperation<TListener> operation) {
+    default <TListener, TListenerOperation extends ListenerOperation<TListener>> void executeSafely(
+            Executor executor, Supplier<TListener> listenerSupplier,
+            @Nullable TListenerOperation operation,
+            @Nullable FailureCallback<TListenerOperation> failureCallback) {
         if (operation == null) {
             return;
         }
 
+        TListener listener = listenerSupplier.get();
+        if (listener == null) {
+            return;
+        }
+
         boolean executing = false;
         boolean preexecute = false;
         try {
-            TListener listener = listenerSupplier.get();
-            if (listener == null) {
-                return;
-            }
-
             operation.onPreExecute();
             preexecute = true;
             executor.execute(() -> {
@@ -116,7 +131,10 @@
                     if (e instanceof RuntimeException) {
                         throw (RuntimeException) e;
                     } else {
-                        onOperationFailure(operation, e);
+                        operation.onFailure(e);
+                        if (failureCallback != null) {
+                            failureCallback.onFailure(operation, e);
+                        }
                     }
                 } finally {
                     operation.onPostExecute(success);
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 911b9ab..0521b10 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -475,7 +475,7 @@
 
     /**
      * Returns the minimum update interval. If location updates are available faster than the
-     * request interval then locations will only be delivered if the minimum update interval has
+     * request interval then locations will only be updated if the minimum update interval has
      * expired since the last location update.
      *
      * <p class=note><strong>Note:</strong> Some allowance for jitter is already built into the
@@ -951,9 +951,9 @@
 
         /**
          * Sets an explicit minimum update interval. If location updates are available faster than
-         * the request interval then locations will only be delivered if the minimum update interval
-         * has expired since the last location update. Defaults to no explicit minimum update
-         * interval set, which means the minimum update interval is the same as the interval.
+         * the request interval then an update will only occur if the minimum update interval has
+         * expired since the last location update. Defaults to no explicit minimum update interval
+         * set, which means the minimum update interval is the same as the interval.
          *
          * <p class=note><strong>Note:</strong> Some allowance for jitter is already built into the
          * minimum update interval, so you need not worry about updates blocked simply because they
@@ -972,7 +972,7 @@
 
         /**
          * Clears an explicitly set minimum update interval and reverts to an implicit minimum
-         * update interval (ie, the minimum update interval is same value as the interval).
+         * update interval (ie, the minimum update interval is the same value as the interval).
          */
         public @NonNull Builder clearMinUpdateIntervalMillis() {
             mMinUpdateIntervalMillis = IMPLICIT_MIN_UPDATE_INTERVAL;
@@ -980,8 +980,8 @@
         }
 
         /**
-         * Sets the minimum update distance between delivered locations. If a potential location
-         * update is closer to the last delivered location than the minimum update distance, then
+         * Sets the minimum update distance between location updates. If a potential location
+         * update is closer to the last location update than the minimum update distance, then
          * the potential location update will not occur. Defaults to 0, which represents no minimum
          * update distance.
          */
diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java
index 06e7a76..56982a8 100644
--- a/services/core/java/com/android/server/location/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java
@@ -16,8 +16,6 @@
 
 package com.android.server.location;
 
-import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
-
 import android.annotation.Nullable;
 import android.location.Location;
 import android.location.util.identity.CallerIdentity;
@@ -342,8 +340,7 @@
      */
     public final void setRequest(ProviderRequest request) {
         // all calls into the provider must be moved onto the provider thread to prevent deadlock
-        mExecutor.execute(obtainRunnable(AbstractLocationProvider::onSetRequest, this, request)
-                .recycleOnUse());
+        mExecutor.execute(() -> onSetRequest(request));
     }
 
     /**
@@ -356,13 +353,7 @@
      */
     public final void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
         // all calls into the provider must be moved onto the provider thread to prevent deadlock
-
-        // the integer boxing done here likely cancels out any gains from removing lambda
-        // allocation, but since this an infrequently used api with no real performance needs, we
-        // we use pooled lambdas anyways for consistency.
-        mExecutor.execute(
-                obtainRunnable(AbstractLocationProvider::onExtraCommand, this, uid, pid, command,
-                        extras).recycleOnUse());
+        mExecutor.execute(() -> onExtraCommand(uid, pid, command, extras));
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java
index e224a9d..2a5e035 100644
--- a/services/core/java/com/android/server/location/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/LocationProviderManager.java
@@ -29,6 +29,7 @@
 import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
 import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
 
+import static com.android.internal.location.ProviderRequest.EMPTY_REQUEST;
 import static com.android.server.location.LocationManagerService.D;
 import static com.android.server.location.LocationManagerService.TAG;
 import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
@@ -77,6 +78,7 @@
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
 import com.android.internal.util.Preconditions;
@@ -111,7 +113,8 @@
 import java.util.Objects;
 
 class LocationProviderManager extends
-        ListenerMultiplexer<Object, LocationRequest, LocationProviderManager.LocationTransport,
+        ListenerMultiplexer<Object, LocationProviderManager.LocationTransport,
+                LocationProviderManager.LocationListenerOperation,
                 LocationProviderManager.Registration, ProviderRequest> implements
         AbstractLocationProvider.Listener {
 
@@ -121,16 +124,16 @@
     private static final String WAKELOCK_TAG = "*location*";
     private static final long WAKELOCK_TIMEOUT_MS = 30 * 1000;
 
-    // maximum interval to be considered "high power" request
+    // max interval to be considered "high power" request
     private static final long MAX_HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
 
-    // maximum age of a location before it is no longer considered "current"
+    // max age of a location before it is no longer considered "current"
     private static final long MAX_CURRENT_LOCATION_AGE_MS = 10 * 1000;
 
     // max timeout allowed for getting the current location
     private static final long GET_CURRENT_LOCATION_MAX_TIMEOUT_MS = 30 * 1000;
 
-    // maximum jitter allowed for fastest interval evaluation
+    // max jitter allowed for fastest interval evaluation
     private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 100;
 
     protected interface LocationTransport {
@@ -214,8 +217,15 @@
         }
     }
 
-    protected abstract class Registration extends
-            RemoteListenerRegistration<LocationRequest, LocationTransport> {
+    protected interface LocationListenerOperation extends ListenerOperation<LocationTransport> {
+        /**
+         * Must be implemented to return the location this operation intends to deliver.
+         */
+        Location getLocation();
+    }
+
+    protected abstract class Registration extends RemoteListenerRegistration<LocationRequest,
+            LocationTransport, LocationListenerOperation> {
 
         @PermissionLevel protected final int mPermissionLevel;
         private final WorkSource mWorkSource;
@@ -226,9 +236,11 @@
         private LocationRequest mProviderLocationRequest;
         private boolean mIsUsingHighPower;
 
+        @Nullable private Location mLastLocation = null;
+
         protected Registration(LocationRequest request, CallerIdentity identity,
                 LocationTransport transport, @PermissionLevel int permissionLevel) {
-            super(TAG, Objects.requireNonNull(request), identity, transport);
+            super(Objects.requireNonNull(request), identity, transport);
 
             Preconditions.checkArgument(permissionLevel > PERMISSION_NONE);
             mPermissionLevel = permissionLevel;
@@ -291,7 +303,11 @@
         protected void onProviderListenerUnregister() {}
 
         @Override
-        protected final ListenerOperation<LocationTransport> onActive() {
+        protected final LocationListenerOperation onActive() {
+            if (Build.IS_DEBUGGABLE) {
+                Preconditions.checkState(Thread.holdsLock(mLock));
+            }
+
             if (!getRequest().isHiddenFromAppOps()) {
                 mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
             }
@@ -300,7 +316,11 @@
         }
 
         @Override
-        protected final ListenerOperation<LocationTransport> onInactive() {
+        protected final LocationListenerOperation onInactive() {
+            if (Build.IS_DEBUGGABLE) {
+                Preconditions.checkState(Thread.holdsLock(mLock));
+            }
+
             onHighPowerUsageChanged();
             if (!getRequest().isHiddenFromAppOps()) {
                 mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey());
@@ -309,18 +329,14 @@
         }
 
         @Override
-        public final <Listener> void onOperationFailure(ListenerOperation<Listener> operation,
-                Exception e) {
-            synchronized (mLock) {
-                super.onOperationFailure(operation, e);
-            }
-        }
-
-        @Override
         public final LocationRequest getRequest() {
             return mProviderLocationRequest;
         }
 
+        public final Location getLastDeliveredLocation() {
+            return mLastLocation;
+        }
+
         public final boolean isForeground() {
             return mForeground;
         }
@@ -480,8 +496,17 @@
             return mLocationManagerInternal.isProvider(null, getIdentity());
         }
 
+        @GuardedBy("mLock")
+        @Override
+        protected final LocationListenerOperation onExecuteOperation(
+                LocationListenerOperation operation) {
+            mLastLocation = operation.getLocation();
+            return super.onExecuteOperation(operation);
+        }
+
+        @GuardedBy("mLock")
         @Nullable
-        abstract ListenerOperation<LocationTransport> acceptLocationChange(Location fineLocation);
+        abstract LocationListenerOperation acceptLocationChange(Location fineLocation);
 
         @Override
         public String toString() {
@@ -514,7 +539,6 @@
         private final PowerManager.WakeLock mWakeLock;
 
         private volatile ProviderTransport mProviderTransport;
-        @Nullable private Location mLastLocation = null;
         private int mNumLocationsDelivered = 0;
         private long mExpirationRealtimeMs = Long.MAX_VALUE;
 
@@ -606,7 +630,7 @@
         @GuardedBy("mLock")
         @Nullable
         @Override
-        ListenerOperation<LocationTransport> acceptLocationChange(Location fineLocation) {
+        LocationListenerOperation acceptLocationChange(Location fineLocation) {
             if (Build.IS_DEBUGGABLE) {
                 Preconditions.checkState(Thread.holdsLock(mLock));
             }
@@ -631,11 +655,12 @@
                     throw new AssertionError();
             }
 
-            if (mLastLocation != null) {
+            Location lastDeliveredLocation = getLastDeliveredLocation();
+            if (lastDeliveredLocation != null) {
                 // check fastest interval
                 long deltaMs = NANOSECONDS.toMillis(
                         location.getElapsedRealtimeNanos()
-                                - mLastLocation.getElapsedRealtimeNanos());
+                                - lastDeliveredLocation.getElapsedRealtimeNanos());
                 if (deltaMs < getRequest().getMinUpdateIntervalMillis()
                         - MAX_FASTEST_INTERVAL_JITTER_MS) {
                     return null;
@@ -643,7 +668,7 @@
 
                 // check smallest displacement
                 double smallestDisplacementM = getRequest().getMinUpdateDistanceMeters();
-                if (smallestDisplacementM > 0.0 && location.distanceTo(mLastLocation)
+                if (smallestDisplacementM > 0.0 && location.distanceTo(lastDeliveredLocation)
                         <= smallestDisplacementM) {
                     return null;
                 }
@@ -658,10 +683,12 @@
                 return null;
             }
 
-            // update last location
-            mLastLocation = location;
+            return new LocationListenerOperation() {
+                @Override
+                public Location getLocation() {
+                    return location;
+                }
 
-            return new ListenerOperation<LocationTransport>() {
                 @Override
                 public void onPreExecute() {
                     // don't acquire a wakelock for mock locations to prevent abuse
@@ -720,8 +747,12 @@
 
             // we choose not to hold a wakelock for provider enabled changed events
             executeSafely(getExecutor(), () -> mProviderTransport,
-                    listener -> listener.deliverOnProviderEnabledChanged(mName, enabled));
+                    listener -> listener.deliverOnProviderEnabledChanged(mName, enabled),
+                    this::onProviderOperationFailure);
         }
+
+        protected abstract void onProviderOperationFailure(
+                ListenerOperation<ProviderTransport> operation, Exception exception);
     }
 
     protected final class LocationListenerRegistration extends LocationRegistration implements
@@ -749,6 +780,28 @@
         }
 
         @Override
+        protected void onProviderOperationFailure(ListenerOperation<ProviderTransport> operation,
+                Exception exception) {
+            onTransportFailure(exception);
+        }
+
+        @Override
+        public void onOperationFailure(LocationListenerOperation operation, Exception exception) {
+            onTransportFailure(exception);
+        }
+
+        private void onTransportFailure(Exception exception) {
+            if (exception instanceof RemoteException) {
+                Log.w(TAG, "registration " + this + " removed", exception);
+                synchronized (mLock) {
+                    remove();
+                }
+            } else {
+                throw new AssertionError(exception);
+            }
+        }
+
+        @Override
         public void binderDied() {
             try {
                 if (D) {
@@ -788,6 +841,28 @@
         }
 
         @Override
+        protected void onProviderOperationFailure(ListenerOperation<ProviderTransport> operation,
+                Exception exception) {
+            onTransportFailure(exception);
+        }
+
+        @Override
+        public void onOperationFailure(LocationListenerOperation operation, Exception exception) {
+            onTransportFailure(exception);
+        }
+
+        private void onTransportFailure(Exception exception) {
+            if (exception instanceof PendingIntent.CanceledException) {
+                Log.w(TAG, "registration " + this + " removed", exception);
+                synchronized (mLock) {
+                    remove();
+                }
+            } else {
+                throw new AssertionError(exception);
+            }
+        }
+
+        @Override
         public void onCancelled(PendingIntent intent) {
             synchronized (mLock) {
                 remove();
@@ -838,13 +913,17 @@
                         0, this, FgThread.getHandler(), getWorkSource());
             }
 
-            // start listening for provider enabled/disabled events
-            addEnabledListener(this);
+            // if this request is ignoring location settings, then we don't want to immediately fail
+            // it if the provider is disabled or becomes disabled.
+            if (!getRequest().isLocationSettingsIgnored()) {
+                // start listening for provider enabled/disabled events
+                addEnabledListener(this);
 
-            // if the provider is currently disabled fail immediately
-            int userId = getIdentity().getUserId();
-            if (!getRequest().isLocationSettingsIgnored() && !isEnabled(userId)) {
-                deliverLocation(null);
+                // if the provider is currently disabled fail immediately
+                int userId = getIdentity().getUserId();
+                if (!getRequest().isLocationSettingsIgnored() && !isEnabled(userId)) {
+                    deliverLocation(null);
+                }
             }
         }
 
@@ -880,7 +959,7 @@
         @GuardedBy("mLock")
         @Nullable
         @Override
-        ListenerOperation<LocationTransport> acceptLocationChange(@Nullable Location fineLocation) {
+        LocationListenerOperation acceptLocationChange(@Nullable Location fineLocation) {
             if (Build.IS_DEBUGGABLE) {
                 Preconditions.checkState(Thread.holdsLock(mLock));
             }
@@ -909,19 +988,35 @@
                 }
             }
 
-            return listener -> {
-                // if delivering to the same process, make a copy of the location first (since
-                // location is mutable)
-                Location deliveryLocation = location;
-                if (getIdentity().getPid() == Process.myPid() && location != null) {
-                    deliveryLocation = new Location(location);
+            return new LocationListenerOperation() {
+                @Override
+                public Location getLocation() {
+                    return location;
                 }
 
-                // we currently don't hold a wakelock for getCurrentLocation deliveries
-                listener.deliverOnLocationChanged(deliveryLocation, null);
+                @Override
+                public void operate(LocationTransport listener) {
+                    // if delivering to the same process, make a copy of the location first (since
+                    // location is mutable)
+                    Location deliveryLocation = location;
+                    if (getIdentity().getPid() == Process.myPid() && location != null) {
+                        deliveryLocation = new Location(location);
+                    }
 
-                synchronized (mLock) {
-                    remove();
+                    // we currently don't hold a wakelock for getCurrentLocation deliveries
+                    try {
+                        listener.deliverOnLocationChanged(deliveryLocation, null);
+                    } catch (Exception exception) {
+                        if (exception instanceof RemoteException) {
+                            Log.w(TAG, "registration " + this + " failed", exception);
+                        } else {
+                            throw new AssertionError(exception);
+                        }
+                    }
+
+                    synchronized (mLock) {
+                        remove();
+                    }
                 }
             };
         }
@@ -1054,6 +1149,11 @@
         mProvider = new MockableLocationProvider(mLock, this);
     }
 
+    @Override
+    public String getTag() {
+        return TAG;
+    }
+
     public void startManager() {
         synchronized (mLock) {
             mStarted = true;
@@ -1260,8 +1360,7 @@
 
     /**
      * This function does not perform any permissions or safety checks, by calling it you are
-     * committing to performing all applicable checks yourself. Prefer
-     * {@link #getLastLocation(CallerIdentity, int, boolean)} where possible.
+     * committing to performing all applicable checks yourself.
      */
     @Nullable
     public Location getLastLocationUnsafe(int userId, @PermissionLevel int permissionLevel,
@@ -1554,7 +1653,8 @@
 
     @GuardedBy("mLock")
     @Override
-    protected boolean registerWithService(ProviderRequest mergedRequest) {
+    protected boolean registerWithService(ProviderRequest mergedRequest,
+            Collection<Registration> registrations) {
         if (Build.IS_DEBUGGABLE) {
             Preconditions.checkState(Thread.holdsLock(mLock));
         }
@@ -1566,8 +1666,8 @@
     @GuardedBy("mLock")
     @Override
     protected boolean reregisterWithService(ProviderRequest oldRequest,
-            ProviderRequest newRequest) {
-        return registerWithService(newRequest);
+            ProviderRequest newRequest, Collection<Registration> registrations) {
+        return registerWithService(newRequest, registrations);
     }
 
     @GuardedBy("mLock")
@@ -1576,7 +1676,8 @@
         if (Build.IS_DEBUGGABLE) {
             Preconditions.checkState(Thread.holdsLock(mLock));
         }
-        mProvider.setRequest(ProviderRequest.EMPTY_REQUEST);
+
+        mProvider.setRequest(EMPTY_REQUEST);
     }
 
     @GuardedBy("mLock")
@@ -1628,7 +1729,7 @@
 
     @GuardedBy("mLock")
     @Override
-    protected ProviderRequest mergeRequests(Collection<Registration> registrations) {
+    protected ProviderRequest mergeRegistrations(Collection<Registration> registrations) {
         if (Build.IS_DEBUGGABLE) {
             Preconditions.checkState(Thread.holdsLock(mLock));
         }
@@ -1814,9 +1915,7 @@
         }
 
         // attempt listener delivery
-        deliverToListeners(registration -> {
-            return registration.acceptLocationChange(location);
-        });
+        deliverToListeners(registration -> registration.acceptLocationChange(location));
     }
 
     @GuardedBy("mLock")
@@ -1951,7 +2050,8 @@
 
     public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
         synchronized (mLock) {
-            ipw.print(mName + " provider");
+            ipw.print(mName);
+            ipw.print(" provider");
             if (mProvider.isMock()) {
                 ipw.print(" [mock]");
             }
@@ -1963,12 +2063,15 @@
             int[] userIds = mUserInfoHelper.getRunningUserIds();
             for (int userId : userIds) {
                 if (userIds.length != 1) {
-                    ipw.println("user " + userId + ":");
+                    ipw.print("user ");
+                    ipw.print(userId);
+                    ipw.println(":");
                     ipw.increaseIndent();
                 }
-                ipw.println(
-                        "last location=" + getLastLocationUnsafe(userId, PERMISSION_FINE, false));
-                ipw.println("enabled=" + isEnabled(userId));
+                ipw.print("last location=");
+                ipw.println(getLastLocationUnsafe(userId, PERMISSION_FINE, false));
+                ipw.print("enabled=");
+                ipw.println(isEnabled(userId));
                 if (userIds.length != 1) {
                     ipw.decreaseIndent();
                 }
diff --git a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
index 5133683..afeb644 100644
--- a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
@@ -64,7 +64,7 @@
     }
 
     @Override
-    protected ProviderRequest mergeRequests(Collection<Registration> registrations) {
+    protected ProviderRequest mergeRegistrations(Collection<Registration> registrations) {
         ProviderRequest.Builder providerRequest = new ProviderRequest.Builder()
                 .setIntervalMillis(0);
 
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index 7f02b2a..58e6d59 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -41,6 +41,7 @@
 import android.util.ArraySet;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
 import com.android.server.PendingIntentUtils;
 import com.android.server.location.LocationPermissions;
 import com.android.server.location.listeners.ListenerMultiplexer;
@@ -59,7 +60,7 @@
  * Manages all geofences.
  */
 public class GeofenceManager extends
-        ListenerMultiplexer<GeofenceKey, Geofence, PendingIntent,
+        ListenerMultiplexer<GeofenceKey, PendingIntent, ListenerOperation<PendingIntent>,
                         GeofenceManager.GeofenceRegistration, LocationRequest> implements
         LocationListener {
 
@@ -94,7 +95,7 @@
 
         protected GeofenceRegistration(Geofence geofence, CallerIdentity identity,
                 PendingIntent pendingIntent) {
-            super(TAG, geofence, identity, pendingIntent);
+            super(geofence, identity, pendingIntent);
 
             mCenter = new Location("");
             mCenter.setLatitude(geofence.getLatitude());
@@ -273,6 +274,11 @@
         mLocationUsageLogger = injector.getLocationUsageLogger();
     }
 
+    @Override
+    public String getTag() {
+        return TAG;
+    }
+
     private LocationManager getLocationManager() {
         synchronized (mLock) {
             if (mLocationManager == null) {
@@ -372,7 +378,8 @@
     }
 
     @Override
-    protected boolean registerWithService(LocationRequest locationRequest) {
+    protected boolean registerWithService(LocationRequest locationRequest,
+            Collection<GeofenceRegistration> registrations) {
         getLocationManager().requestLocationUpdates(FUSED_PROVIDER, locationRequest,
                 DIRECT_EXECUTOR, this);
         return true;
@@ -387,7 +394,7 @@
     }
 
     @Override
-    protected LocationRequest mergeRequests(Collection<GeofenceRegistration> registrations) {
+    protected LocationRequest mergeRegistrations(Collection<GeofenceRegistration> registrations) {
         Location location = getLastLocation();
 
         long realtimeMs = SystemClock.elapsedRealtime();
diff --git a/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java b/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
index 5ddc6a1..b99e269 100644
--- a/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
@@ -28,6 +28,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.location.util.Injector;
 
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -59,7 +60,8 @@
     }
 
     @Override
-    protected boolean registerWithService(Void ignored) {
+    protected boolean registerWithService(Void ignored,
+            Collection<GnssListenerRegistration> registrations) {
         Preconditions.checkState(mNative.isAntennaInfoSupported());
 
         if (mNative.startAntennaInfoListening()) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index a9fdacc..a830e2f 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -26,11 +26,13 @@
 import android.location.LocationManagerInternal.ProviderEnabledListener;
 import android.location.util.identity.CallerIdentity;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.Process;
 import android.util.ArraySet;
 
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.location.listeners.BinderListenerRegistration;
@@ -43,6 +45,7 @@
 import com.android.server.location.util.UserInfoHelper.UserListener;
 
 import java.io.PrintWriter;
+import java.util.Collection;
 import java.util.Objects;
 
 /**
@@ -52,14 +55,15 @@
  * Listeners must be registered with the associated IBinder as the key, if the IBinder dies, the
  * registration will automatically be removed.
  *
- * @param <TRequest>       request type
- * @param <TListener>      listener type
- * @param <TMergedRequest> merged request type
+ * @param <TRequest>            request type
+ * @param <TListener>           listener type
+ * @param <TMergedRegistration> merged registration type
  */
 public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInterface,
-        TMergedRequest> extends
-        ListenerMultiplexer<IBinder, TRequest, TListener, GnssListenerMultiplexer<TRequest,
-                        TListener, TMergedRequest>.GnssListenerRegistration, TMergedRequest> {
+        TMergedRegistration> extends
+        ListenerMultiplexer<IBinder, TListener, ListenerOperation<TListener>,
+                GnssListenerMultiplexer<TRequest, TListener, TMergedRegistration>
+                        .GnssListenerRegistration, TMergedRegistration> {
 
     /**
      * Registration object for GNSS listeners.
@@ -74,11 +78,11 @@
 
         protected GnssListenerRegistration(@Nullable TRequest request,
                 CallerIdentity callerIdentity, TListener listener) {
-            super(TAG, request, callerIdentity, listener);
+            super(request, callerIdentity, listener);
         }
 
         @Override
-        protected GnssListenerMultiplexer<TRequest, TListener, TMergedRequest> getOwner() {
+        protected GnssListenerMultiplexer<TRequest, TListener, TMergedRegistration> getOwner() {
             return GnssListenerMultiplexer.this;
         }
 
@@ -199,6 +203,11 @@
                 LocalServices.getService(LocationManagerInternal.class));
     }
 
+    @Override
+    public String getTag() {
+        return TAG;
+    }
+
     /**
      * May be overridden by subclasses to return whether the service is supported or not. This value
      * should never change for the lifetime of the multiplexer. If the service is unsupported, all
@@ -271,6 +280,21 @@
         return mLocationManagerInternal.isProvider(null, identity);
     }
 
+    // this provides a default implementation for all further subclasses which assumes that there is
+    // never an associated request object, and thus nothing interesting to merge. the majority of
+    // gnss listener multiplexers do not current have associated requests, and the ones that do can
+    // override this implementation.
+    protected TMergedRegistration mergeRegistrations(
+            Collection<GnssListenerRegistration> gnssListenerRegistrations) {
+        if (Build.IS_DEBUGGABLE) {
+            for (GnssListenerRegistration registration : gnssListenerRegistrations) {
+                Preconditions.checkState(registration.getRequest() == null);
+            }
+        }
+
+        return null;
+    }
+
     @Override
     protected void onRegister() {
         if (!isServiceSupported()) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index 37db023..2faa15f 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -76,7 +76,8 @@
     }
 
     @Override
-    protected boolean registerWithService(Boolean fullTrackingRequest) {
+    protected boolean registerWithService(Boolean fullTrackingRequest,
+            Collection<GnssListenerRegistration> registrations) {
         Preconditions.checkState(mNative.isMeasurementSupported());
 
         if (mNative.startMeasurementCollection(fullTrackingRequest)) {
@@ -121,7 +122,7 @@
     }
 
     @Override
-    protected Boolean mergeRequests(Collection<GnssListenerRegistration> registrations) {
+    protected Boolean mergeRegistrations(Collection<GnssListenerRegistration> registrations) {
         if (mSettingsHelper.isGnssMeasurementsFullTrackingEnabled()) {
             return true;
         }
diff --git a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
index 7dcffc6..861abc3 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
@@ -30,6 +30,8 @@
 import com.android.server.location.util.AppOpsHelper;
 import com.android.server.location.util.Injector;
 
+import java.util.Collection;
+
 /**
  * An base implementation for GPS navigation messages provider.
  * It abstracts out the responsibility of handling listeners, while still allowing technology
@@ -61,7 +63,8 @@
     }
 
     @Override
-    protected boolean registerWithService(Void ignored) {
+    protected boolean registerWithService(Void ignored,
+            Collection<GnssListenerRegistration> registrations) {
         Preconditions.checkState(mNative.isNavigationMessageSupported());
 
         if (mNative.startNavigationMessageCollection()) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
index 68813b3..248d38a 100644
--- a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
@@ -31,6 +31,8 @@
 import com.android.server.location.util.Injector;
 import com.android.server.location.util.LocationUsageLogger;
 
+import java.util.Collection;
+
 /**
  * Implementation of a handler for {@link IGnssStatusListener}.
  */
@@ -51,7 +53,8 @@
     }
 
     @Override
-    protected boolean registerWithService(Void ignored) {
+    protected boolean registerWithService(Void ignored,
+            Collection<GnssListenerRegistration> registrations) {
         if (D) {
             Log.d(TAG, "starting gnss status");
         }
diff --git a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
index 58aabda..bc675ce 100644
--- a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
+
 /**
  * A registration that works with IBinder keys, and registers a DeathListener to automatically
  * remove the registration if the binder dies. The key for this registration must either be an
@@ -32,7 +34,8 @@
  * @param <TListener> listener type
  */
 public abstract class BinderListenerRegistration<TRequest, TListener> extends
-        RemoteListenerRegistration<TRequest, TListener> implements Binder.DeathRecipient {
+        RemoteListenerRegistration<TRequest, TListener, ListenerOperation<TListener>> implements
+        Binder.DeathRecipient {
 
     /**
      * Interface to allow binder retrieval when keys are not themselves IBinders.
@@ -44,9 +47,9 @@
         IBinder getBinder();
     }
 
-    protected BinderListenerRegistration(String tag, @Nullable TRequest request,
-            CallerIdentity callerIdentity, TListener listener) {
-        super(tag, request, callerIdentity, listener);
+    protected BinderListenerRegistration(@Nullable TRequest request, CallerIdentity callerIdentity,
+            TListener listener) {
+        super(request, callerIdentity, listener);
     }
 
     @Override
@@ -78,10 +81,20 @@
     protected void onBinderListenerUnregister() {}
 
     @Override
+    public void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
+        if (e instanceof RemoteException) {
+            Log.w(getOwner().getTag(), "registration " + this + " removed", e);
+            remove();
+        } else {
+            super.onOperationFailure(operation, e);
+        }
+    }
+
+    @Override
     public void binderDied() {
         try {
-            if (Log.isLoggable(mTag, Log.DEBUG)) {
-                Log.d(mTag, "binder registration " + getIdentity() + " died");
+            if (Log.isLoggable(getOwner().getTag(), Log.DEBUG)) {
+                Log.d(getOwner().getTag(), "binder registration " + getIdentity() + " died");
             }
             remove();
         } catch (RuntimeException e) {
diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
index feb4fbd..87d668a 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
@@ -37,12 +37,20 @@
 import java.util.function.Predicate;
 
 /**
- * A base class to multiplex client listener registrations within system server. Registrations are
- * divided into two categories, active registrations and inactive registrations, as defined by
- * {@link #isActive(ListenerRegistration)}. If a registration's active state changes,
+ * A base class to multiplex client listener registrations within system server. Every listener is
+ * represented by a registration object which stores all required state for a listener. Keys are
+ * used to uniquely identify every registration. Listener operations may be executed on
+ * registrations in order to invoke the represented listener.
+ *
+ * Registrations are divided into two categories, active registrations and inactive registrations,
+ * as defined by {@link #isActive(ListenerRegistration)}. If a registration's active state changes,
  * {@link #updateRegistrations(Predicate)} must be invoked and return true for any registration
  * whose active state may have changed. Listeners will only be invoked for active registrations.
  *
+ * The set of active registrations is combined into a single merged registration, which is submitted
+ * to the backing service when necessary in order to register the service. The merged registration
+ * is updated whenever the set of active registration changes.
+ *
  * Callbacks invoked for various changes will always be ordered according to this lifecycle list:
  *
  * <ul>
@@ -63,14 +71,16 @@
  * {@link #removeRegistration(Object, ListenerRegistration)}, not via any other removal method. This
  * ensures re-entrant removal does not accidentally remove the incorrect registration.
  *
- * @param <TKey>           key type
- * @param <TRequest>       request type
- * @param <TListener>      listener type
- * @param <TRegistration>  registration type
- * @param <TMergedRequest> merged request type
+ * @param <TKey>                key type
+ * @param <TListener>           listener type
+ * @param <TListenerOperation>  listener operation type
+ * @param <TRegistration>       registration type
+ * @param <TMergedRegistration> merged registration type
  */
-public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
-        TRegistration extends ListenerRegistration<TRequest, TListener>, TMergedRequest> {
+public abstract class ListenerMultiplexer<TKey, TListener,
+        TListenerOperation extends ListenerOperation<TListener>,
+        TRegistration extends ListenerRegistration<TListener, TListenerOperation>,
+        TMergedRegistration> {
 
     @GuardedBy("mRegistrations")
     private final ArrayMap<TKey, TRegistration> mRegistrations = new ArrayMap<>();
@@ -88,25 +98,42 @@
     private boolean mServiceRegistered = false;
 
     @GuardedBy("mRegistrations")
-    @Nullable private TMergedRequest mCurrentRequest;
+    @Nullable private TMergedRegistration mMerged;
 
     /**
-     * Should be implemented to register with the backing service with the given merged request, and
-     * should return true if a matching call to {@link #unregisterWithService()} is required to
-     * unregister (ie, if registration succeeds).
+     * Should be implemented to return a unique identifying tag that may be used for logging, etc...
+     */
+    public abstract @NonNull String getTag();
+
+    /**
+     * Should be implemented to register with the backing service with the given merged
+     * registration, and should return true if a matching call to {@link #unregisterWithService()}
+     * is required to unregister (ie, if registration succeeds). The set of registrations passed in
+     * is the same set passed into {@link #mergeRegistrations(Collection)} to generate the merged
+     * registration.
      *
-     * @see #reregisterWithService(Object, Object)
+     * <p class="note">It may seem redundant to pass in the set of active registrations when they
+     * have already been used to generate the merged request, and indeed, for many implementations
+     * this parameter can likely simply be ignored. However, some implementations may require access
+     * to the set of registrations used to generate the merged requestion for further logic even
+     * after the merged registration has been generated.
+     *
+     * @see #mergeRegistrations(Collection)
+     * @see #reregisterWithService(Object, Object, Collection)
      */
-    protected abstract boolean registerWithService(TMergedRequest newRequest);
+    protected abstract boolean registerWithService(TMergedRegistration merged,
+            @NonNull Collection<TRegistration> registrations);
 
     /**
-     * Invoked when the service already has a request, and it is being replaced with a new request.
-     * The default implementation unregisters first, then registers with the new merged request, but
-     * this may be overridden by subclasses in order to reregister more efficiently.
+     * Invoked when the service has already been registered with some merged registration, and is
+     * now being registered with a different merged registration. The default implementation simply
+     * invokes {@link #registerWithService(Object, Collection)}.
+     *
+     * @see #registerWithService(Object, Collection)
      */
-    protected boolean reregisterWithService(TMergedRequest oldRequest, TMergedRequest newRequest) {
-        unregisterWithService();
-        return registerWithService(newRequest);
+    protected boolean reregisterWithService(TMergedRegistration oldMerged,
+            TMergedRegistration newMerged, @NonNull Collection<TRegistration> registrations) {
+        return registerWithService(newMerged, registrations);
     }
 
     /**
@@ -116,28 +143,23 @@
 
     /**
      * Defines whether a registration is currently active or not. Only active registrations will be
-     * considered within {@link #mergeRequests(Collection)} to calculate the merged request, and
-     * listener invocations will only be delivered to active requests. If a registration's active
-     * state changes, {@link #updateRegistrations(Predicate)} must be invoked with a function that
-     * returns true for any registrations that may have changed their active state.
+     * forwarded to {@link #registerWithService(Object, Collection)}, and listener invocations will
+     * only be delivered to active requests. If a registration's active state changes,
+     * {@link #updateRegistrations(Predicate)} must be invoked with a function that returns true for
+     * any registrations that may have changed their active state.
      */
     protected abstract boolean isActive(@NonNull TRegistration registration);
 
     /**
-     * Called in order to generate a merged request from the given registrations. The list of
-     * registrations will never be empty.
+     * Called in order to generate a merged registration from the given set of active registrations.
+     * The list of registrations will never be empty. If the resulting merged registration is equal
+     * to the currently registered merged registration, nothing further will happen. If the merged
+     * registration differs, {@link #registerWithService(Object, Collection)} or
+     * {@link #reregisterWithService(Object, Object, Collection)} will be invoked with the new
+     * merged registration so that the backing service can be updated.
      */
-    protected @Nullable TMergedRequest mergeRequests(
-            @NonNull Collection<TRegistration> registrations) {
-        if (Build.IS_DEBUGGABLE) {
-            for (TRegistration registration : registrations) {
-                // if using non-null requests then implementations must override this method
-                Preconditions.checkState(registration.getRequest() == null);
-            }
-        }
-
-        return null;
-    }
+    protected abstract @Nullable TMergedRegistration mergeRegistrations(
+            @NonNull Collection<TRegistration> registrations);
 
     /**
      * Invoked when the multiplexer goes from having no registrations to having some registrations.
@@ -348,41 +370,42 @@
             }
 
             if (actives.isEmpty()) {
-                mCurrentRequest = null;
                 if (mServiceRegistered) {
+                    mMerged = null;
                     mServiceRegistered = false;
-                    mCurrentRequest = null;
                     unregisterWithService();
                 }
                 return;
             }
 
-            TMergedRequest merged = mergeRequests(actives);
-            if (!mServiceRegistered || !Objects.equals(merged, mCurrentRequest)) {
+            TMergedRegistration merged = mergeRegistrations(actives);
+            if (!mServiceRegistered || !Objects.equals(merged, mMerged)) {
                 if (mServiceRegistered) {
-                    mServiceRegistered = reregisterWithService(mCurrentRequest, merged);
+                    mServiceRegistered = reregisterWithService(mMerged, merged, actives);
                 } else {
-                    mServiceRegistered = registerWithService(merged);
+                    mServiceRegistered = registerWithService(merged, actives);
                 }
-                if (mServiceRegistered) {
-                    mCurrentRequest = merged;
-                } else {
-                    mCurrentRequest = null;
-                }
+                mMerged = mServiceRegistered ? merged : null;
             }
         }
     }
 
     /**
-     * Clears currently stored service state, and invokes {@link #updateService()} to force a new
-     * call to {@link #registerWithService(Object)} if necessary. This is useful, for instance, if
-     * the backing service has crashed or otherwise lost state, and needs to be re-initialized.
+     * If the service is currently registered, unregisters it and then calls
+     * {@link #updateService()} so that {@link #registerWithService(Object, Collection)} will be
+     * re-invoked. This is useful, for instance, if the backing service has crashed or otherwise
+     * lost state, and needs to be re-initialized. Because this unregisters first, this is safe to
+     * use even if there is a possibility the backing server has not crashed, or has already been
+     * reinitialized.
      */
     protected final void resetService() {
         synchronized (mRegistrations) {
-            mServiceRegistered = false;
-            mCurrentRequest = null;
-            updateService();
+            if (mServiceRegistered) {
+                mMerged = null;
+                mServiceRegistered = false;
+                unregisterWithService();
+                updateService();
+            }
         }
     }
 
@@ -435,12 +458,12 @@
                 if (++mActiveRegistrationsCount == 1) {
                     onActive();
                 }
-                ListenerOperation<TListener> operation = registration.onActive();
+                TListenerOperation operation = registration.onActive();
                 if (operation != null) {
                     execute(registration, operation);
                 }
             } else {
-                ListenerOperation<TListener> operation = registration.onInactive();
+                TListenerOperation operation = registration.onInactive();
                 if (operation != null) {
                     execute(registration, operation);
                 }
@@ -459,14 +482,14 @@
      * change the active state of the registration.
      */
     protected final void deliverToListeners(
-            @NonNull Function<TRegistration, ListenerOperation<TListener>> function) {
+            @NonNull Function<TRegistration, TListenerOperation> function) {
         synchronized (mRegistrations) {
             try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
                 final int size = mRegistrations.size();
                 for (int i = 0; i < size; i++) {
                     TRegistration registration = mRegistrations.valueAt(i);
                     if (registration.isActive()) {
-                        ListenerOperation<TListener> operation = function.apply(registration);
+                        TListenerOperation operation = function.apply(registration);
                         if (operation != null) {
                             execute(registration, operation);
                         }
@@ -483,7 +506,7 @@
      * deliverToListeners(registration -> operation);
      * </pre>
      */
-    protected final void deliverToListeners(@NonNull ListenerOperation<TListener> operation) {
+    protected final void deliverToListeners(@NonNull TListenerOperation operation) {
         synchronized (mRegistrations) {
             try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
                 final int size = mRegistrations.size();
@@ -502,7 +525,7 @@
         onRegistrationActiveChanged(registration);
     }
 
-    private void execute(TRegistration registration, ListenerOperation<TListener> operation) {
+    private void execute(TRegistration registration, TListenerOperation operation) {
         registration.executeInternal(operation);
     }
 
@@ -539,10 +562,11 @@
      */
     protected void dumpServiceState(PrintWriter pw) {
         if (mServiceRegistered) {
-            if (mCurrentRequest == null) {
-                pw.print("registered");
-            } else {
-                pw.print("registered with " + mCurrentRequest);
+            pw.print("registered");
+            if (mMerged != null) {
+                pw.print(" [");
+                pw.print(mMerged);
+                pw.print("]");
             }
         } else {
             pw.print("unregistered");
diff --git a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
index deb9660..d7ecbcb 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 
 import com.android.internal.listeners.ListenerExecutor;
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
 
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -29,22 +30,21 @@
  * A listener registration object which holds data associated with the listener, such as an optional
  * request, and an executor responsible for listener invocations.
  *
- * @param <TRequest>  request type
- * @param <TListener> listener type
+ * @param <TListener>          listener type
+ * @param <TListenerOperation> listener operation type
  */
-public class ListenerRegistration<TRequest, TListener> implements ListenerExecutor {
+public class ListenerRegistration<TListener,
+        TListenerOperation extends ListenerOperation<TListener>> implements
+        ListenerExecutor {
 
     private final Executor mExecutor;
-    private final @Nullable TRequest mRequest;
 
     private boolean mActive;
 
     private volatile @Nullable TListener mListener;
 
-    protected ListenerRegistration(Executor executor, @Nullable TRequest request,
-            TListener listener) {
+    protected ListenerRegistration(Executor executor, TListener listener) {
         mExecutor = Objects.requireNonNull(executor);
-        mRequest = request;
         mActive = false;
         mListener = Objects.requireNonNull(listener);
     }
@@ -54,13 +54,6 @@
     }
 
     /**
-     * Returns the request associated with this listener, or null if one wasn't supplied.
-     */
-    public @Nullable TRequest getRequest() {
-        return mRequest;
-    }
-
-    /**
      * May be overridden by subclasses. Invoked when registration occurs. Invoked while holding the
      * owning multiplexer's internal lock.
      */
@@ -77,7 +70,7 @@
      * returns a non-null operation, that operation will be invoked for the listener. Invoked
      * while holding the owning multiplexer's internal lock.
      */
-    protected @Nullable ListenerOperation<TListener> onActive() {
+    protected @Nullable TListenerOperation onActive() {
         return null;
     }
 
@@ -86,7 +79,7 @@
      * a non-null operation, that operation will be invoked for the listener. Invoked while holding
      * the owning multiplexer's internal lock.
      */
-    protected @Nullable ListenerOperation<TListener> onInactive() {
+    protected @Nullable TListenerOperation onInactive() {
         return null;
     }
 
@@ -115,21 +108,38 @@
     /**
      * May be overridden by subclasses, however should rarely be needed. Invoked when the listener
      * associated with this registration is unregistered, which may occur before the registration
-     * itself is unregistered. This immediately prevents the listener from being further invoked.
+     * itself is unregistered. This immediately prevents the listener from being further invoked
+     * until the registration itself can be finalized and unregistered completely.
      */
-    protected void onListenerUnregister() {};
+    protected void onListenerUnregister() {}
 
-    final void executeInternal(@NonNull ListenerOperation<TListener> operation) {
-        executeSafely(mExecutor, () -> mListener, operation);
+    /**
+     * May be overridden by subclasses, however should rarely be needed. Invoked whenever a listener
+     * operation is submitted for execution, and allows the registration a chance to replace the
+     * listener operation or perform related bookkeeping. There is no guarantee a listener operation
+     * submitted or returned here will ever be invoked. Will always be invoked on the calling
+     * thread.
+     */
+    protected TListenerOperation onExecuteOperation(@NonNull TListenerOperation operation) {
+        return operation;
+    }
+
+    /**
+     * May be overridden by subclasses to handle listener operation failures. The default behavior
+     * is to further propagate any exceptions. Will always be invoked on the executor thread.
+     */
+    protected void onOperationFailure(TListenerOperation operation, Exception exception) {
+        throw new AssertionError(exception);
+    }
+
+    final void executeInternal(@NonNull TListenerOperation operation) {
+        executeSafely(mExecutor, () -> mListener,
+                onExecuteOperation(Objects.requireNonNull(operation)), this::onOperationFailure);
     }
 
     @Override
     public String toString() {
-        if (mRequest == null) {
-            return "[]";
-        } else {
-            return mRequest.toString();
-        }
+        return "[]";
     }
 
     @Override
diff --git a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
index 7b6154e..e57b532 100644
--- a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
@@ -21,6 +21,8 @@
 import android.location.util.identity.CallerIdentity;
 import android.util.Log;
 
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
+
 /**
  * A registration that works with PendingIntent keys, and registers a CancelListener to
  * automatically remove the registration if the PendingIntent is canceled. The key for this
@@ -30,7 +32,8 @@
  * @param <TListener> listener type
  */
 public abstract class PendingIntentListenerRegistration<TRequest, TListener> extends
-        RemoteListenerRegistration<TRequest, TListener> implements PendingIntent.CancelListener {
+        RemoteListenerRegistration<TRequest, TListener, ListenerOperation<TListener>> implements
+        PendingIntent.CancelListener {
 
     /**
      * Interface to allowed pending intent retrieval when keys are not themselves PendingIntents.
@@ -42,9 +45,9 @@
         PendingIntent getPendingIntent();
     }
 
-    protected PendingIntentListenerRegistration(String tag, @Nullable TRequest request,
+    protected PendingIntentListenerRegistration(@Nullable TRequest request,
             CallerIdentity callerIdentity, TListener listener) {
-        super(tag, request, callerIdentity, listener);
+        super(request, callerIdentity, listener);
     }
 
     @Override
@@ -70,9 +73,20 @@
     protected void onPendingIntentListenerUnregister() {}
 
     @Override
+    public void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
+        if (e instanceof PendingIntent.CanceledException) {
+            Log.w(getOwner().getTag(), "registration " + this + " removed", e);
+            remove();
+        } else {
+            super.onOperationFailure(operation, e);
+        }
+    }
+
+    @Override
     public void onCancelled(PendingIntent intent) {
-        if (Log.isLoggable(mTag, Log.DEBUG)) {
-            Log.d(mTag, "pending intent registration " + getIdentity() + " canceled");
+        if (Log.isLoggable(getOwner().getTag(), Log.DEBUG)) {
+            Log.d(getOwner().getTag(),
+                    "pending intent registration " + getIdentity() + " canceled");
         }
 
         remove();
diff --git a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
index e4b0b19..242bf32 100644
--- a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
@@ -24,6 +24,7 @@
 import android.os.Process;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
 import com.android.server.FgThread;
 
 import java.util.Objects;
@@ -35,11 +36,13 @@
  * invocation should already be asynchronous. Listeners from the same process will be run on a
  * normal executor, since in-process listener invocation may be synchronous.
  *
- * @param <TRequest>  request type
- * @param <TListener> listener type
+ * @param <TRequest>           request type
+ * @param <TListener>          listener type
+ * @param <TListenerOperation> listener operation type
  */
-public abstract class RemoteListenerRegistration<TRequest, TListener> extends
-        RemovableListenerRegistration<TRequest, TListener> {
+public abstract class RemoteListenerRegistration<TRequest, TListener,
+        TListenerOperation extends ListenerOperation<TListener>> extends
+        RemovableListenerRegistration<TRequest, TListener, TListenerOperation> {
 
     @VisibleForTesting
     public static final Executor IN_PROCESS_EXECUTOR = FgThread.getExecutor();
@@ -59,9 +62,9 @@
 
     private final CallerIdentity mIdentity;
 
-    protected RemoteListenerRegistration(String tag, @Nullable TRequest request,
-            CallerIdentity identity, TListener listener) {
-        super(tag, chooseExecutor(identity), request, listener);
+    protected RemoteListenerRegistration(@Nullable TRequest request, CallerIdentity identity,
+            TListener listener) {
+        super(chooseExecutor(identity), request, listener);
         mIdentity = Objects.requireNonNull(identity);
     }
 
diff --git a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
index 2383bec..d3b5f66 100644
--- a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
@@ -17,7 +17,8 @@
 package com.android.server.location.listeners;
 
 import android.annotation.Nullable;
-import android.util.Log;
+
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
 
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -26,20 +27,19 @@
  * A listener registration that stores its own key, and thus can remove itself. By default it will
  * remove itself if any checked exception occurs on listener execution.
  *
- * @param <TRequest>  request type
- * @param <TListener> listener type
+ * @param <TRequest>           request type
+ * @param <TListener>          listener type
+ * @param <TListenerOperation> listener operation type
  */
-public abstract class RemovableListenerRegistration<TRequest, TListener> extends
-        ListenerRegistration<TRequest, TListener> {
-
-    protected final String mTag;
+public abstract class RemovableListenerRegistration<TRequest, TListener,
+        TListenerOperation extends ListenerOperation<TListener>> extends
+        RequestListenerRegistration<TRequest, TListener, TListenerOperation> {
 
     private volatile @Nullable Object mKey;
 
-    protected RemovableListenerRegistration(String tag, Executor executor,
-            @Nullable TRequest request, TListener listener) {
+    protected RemovableListenerRegistration(Executor executor, @Nullable TRequest request,
+            TListener listener) {
         super(executor, request, listener);
-        mTag = Objects.requireNonNull(tag);
     }
 
     /**
@@ -47,7 +47,8 @@
      * with. Often this is easiest to accomplish by defining registration subclasses as non-static
      * inner classes of the multiplexer they are to be used with.
      */
-    protected abstract ListenerMultiplexer<?, ? super TRequest, ? super TListener, ?, ?> getOwner();
+    protected abstract ListenerMultiplexer<?, ? super TListener, ?
+            super TListenerOperation, ?, ?> getOwner();
 
     /**
      * Returns the key associated with this registration. May not be invoked before
@@ -69,12 +70,6 @@
     }
 
     @Override
-    public <Listener> void onOperationFailure(ListenerOperation<Listener> operation, Exception e) {
-        Log.w(mTag, "registration " + this + " removed due to unexpected exception", e);
-        remove();
-    }
-
-    @Override
     protected final void onRegister(Object key) {
         mKey = Objects.requireNonNull(key);
         onRemovableListenerRegister();
diff --git a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
new file mode 100644
index 0000000..d97abae
--- /dev/null
+++ b/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.listeners;
+
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A listener registration object which includes an associated request.
+ *
+ * @param <TRequest>           request type
+ * @param <TListener>          listener type
+ * @param <TListenerOperation> listener operation type
+ */
+public class RequestListenerRegistration<TRequest, TListener,
+        TListenerOperation extends ListenerOperation<TListener>> extends
+        ListenerRegistration<TListener, TListenerOperation> {
+
+    private final TRequest mRequest;
+
+    protected RequestListenerRegistration(Executor executor, TRequest request,
+            TListener listener) {
+        super(executor, listener);
+        mRequest = request;
+    }
+
+    /**
+     * Returns the request associated with this listener, or null if one wasn't supplied.
+     */
+    public TRequest getRequest() {
+        return mRequest;
+    }
+
+    @Override
+    public String toString() {
+        if (mRequest == null) {
+            return "[]";
+        } else {
+            return mRequest.toString();
+        }
+    }
+}
+
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
index 0b5a699..29d3f29 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
@@ -34,6 +34,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
 import com.android.server.location.listeners.ListenerMultiplexer.UpdateServiceLock;
 
 import org.junit.Before;
@@ -318,7 +319,8 @@
     }
 
     private static class TestListenerRegistration extends
-            ListenerRegistration<Integer, Consumer<TestListenerRegistration>> {
+            RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>,
+                    ListenerOperation<Consumer<TestListenerRegistration>>> {
 
         boolean mActive = true;
 
@@ -329,8 +331,10 @@
     }
 
     private static class TestMultiplexer extends
-            ListenerMultiplexer<Consumer<TestListenerRegistration>, Integer,
-                    Consumer<TestListenerRegistration>, TestListenerRegistration, Integer> {
+            ListenerMultiplexer<Consumer<TestListenerRegistration>,
+                    Consumer<TestListenerRegistration>,
+                    ListenerOperation<Consumer<TestListenerRegistration>>, TestListenerRegistration,
+                    Integer> {
 
         boolean mRegistered;
         int mMergedRequest;
@@ -341,6 +345,11 @@
             mCallbacks = callbacks;
         }
 
+        @Override
+        public String getTag() {
+            return "TestMultiplexer";
+        }
+
         public void addListener(Integer request, Consumer<TestListenerRegistration> consumer) {
             addRegistration(consumer, new TestListenerRegistration(request, consumer));
         }
@@ -369,7 +378,8 @@
         }
 
         @Override
-        protected boolean registerWithService(Integer mergedRequest) {
+        protected boolean registerWithService(Integer mergedRequest,
+                Collection<TestListenerRegistration> registrations) {
             mRegistered = true;
             mMergedRequest = mergedRequest;
             return true;
@@ -418,7 +428,8 @@
         }
 
         @Override
-        protected Integer mergeRequests(Collection<TestListenerRegistration> testRegistrations) {
+        protected Integer mergeRegistrations(
+                Collection<TestListenerRegistration> testRegistrations) {
             int max = Integer.MIN_VALUE;
             for (TestListenerRegistration registration : testRegistrations) {
                 if (registration.getRequest() > max) {