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
This commit is contained in:
Soonil Nagarkar
2020-09-17 13:15:16 -07:00
parent 559bf65528
commit 87b8cc3866
19 changed files with 540 additions and 260 deletions

View File

@@ -38,71 +38,86 @@ public interface ListenerExecutor {
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 @@ public interface ListenerExecutor {
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);

View File

@@ -475,7 +475,7 @@ public final class LocationRequest implements Parcelable {
/**
* 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 @@ public final class LocationRequest implements Parcelable {
/**
* 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 @@ public final class LocationRequest implements Parcelable {
/**
* 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 @@ public final class LocationRequest implements Parcelable {
}
/**
* 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.
*/

View File

@@ -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 abstract class AbstractLocationProvider {
*/
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 abstract class AbstractLocationProvider {
*/
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));
}
/**

View File

@@ -29,6 +29,7 @@ import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
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.SparseBooleanArray;
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.List;
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 @@ class LocationProviderManager extends
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 @@ class LocationProviderManager extends
}
}
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 @@ class LocationProviderManager extends
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 @@ class LocationProviderManager extends
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 @@ class LocationProviderManager extends
}
@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());
@@ -308,19 +328,15 @@ class LocationProviderManager extends
return null;
}
@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 @@ class LocationProviderManager extends
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 @@ class LocationProviderManager extends
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 @@ class LocationProviderManager extends
@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 @@ class LocationProviderManager extends
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 @@ class LocationProviderManager extends
// 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 @@ class LocationProviderManager extends
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 @@ class LocationProviderManager extends
// 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
@@ -748,6 +779,28 @@ class LocationProviderManager extends
((IBinder) getKey()).unlinkToDeath(this, 0);
}
@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 {
@@ -787,6 +840,28 @@ class LocationProviderManager extends
((PendingIntent) getKey()).unregisterCancelListener(this);
}
@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) {
@@ -838,13 +913,17 @@ class LocationProviderManager extends
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 @@ class LocationProviderManager extends
@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 @@ class LocationProviderManager extends
}
}
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 @@ class LocationProviderManager extends
mProvider = new MockableLocationProvider(mLock, this);
}
@Override
public String getTag() {
return TAG;
}
public void startManager() {
synchronized (mLock) {
mStarted = true;
@@ -1260,8 +1360,7 @@ class LocationProviderManager extends
/**
* 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 @@ class LocationProviderManager extends
@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 @@ class LocationProviderManager extends
@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 @@ class LocationProviderManager extends
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
mProvider.setRequest(ProviderRequest.EMPTY_REQUEST);
mProvider.setRequest(EMPTY_REQUEST);
}
@GuardedBy("mLock")
@@ -1628,7 +1729,7 @@ class LocationProviderManager extends
@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 @@ class LocationProviderManager extends
}
// attempt listener delivery
deliverToListeners(registration -> {
return registration.acceptLocationChange(location);
});
deliverToListeners(registration -> registration.acceptLocationChange(location));
}
@GuardedBy("mLock")
@@ -1951,7 +2050,8 @@ class LocationProviderManager extends
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 @@ class LocationProviderManager extends
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();
}

View File

@@ -64,7 +64,7 @@ class PassiveLocationProviderManager extends LocationProviderManager {
}
@Override
protected ProviderRequest mergeRequests(Collection<Registration> registrations) {
protected ProviderRequest mergeRegistrations(Collection<Registration> registrations) {
ProviderRequest.Builder providerRequest = new ProviderRequest.Builder()
.setIntervalMillis(0);

View File

@@ -41,6 +41,7 @@ import android.stats.location.LocationStatsEnums;
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 @@ import java.util.Objects;
* 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 @@ public class GeofenceManager extends
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 @@ public class GeofenceManager extends
mLocationUsageLogger = injector.getLocationUsageLogger();
}
@Override
public String getTag() {
return TAG;
}
private LocationManager getLocationManager() {
synchronized (mLock) {
if (mLocationManager == null) {
@@ -372,7 +378,8 @@ public class GeofenceManager extends
}
@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 @@ public class GeofenceManager extends
}
@Override
protected LocationRequest mergeRequests(Collection<GeofenceRegistration> registrations) {
protected LocationRequest mergeRegistrations(Collection<GeofenceRegistration> registrations) {
Location location = getLastLocation();
long realtimeMs = SystemClock.elapsedRealtime();

View File

@@ -28,6 +28,7 @@ import com.android.internal.annotations.VisibleForTesting;
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 @@ public class GnssAntennaInfoProvider extends
}
@Override
protected boolean registerWithService(Void ignored) {
protected boolean registerWithService(Void ignored,
Collection<GnssListenerRegistration> registrations) {
Preconditions.checkState(mNative.isAntennaInfoSupported());
if (mNative.startAntennaInfoListening()) {

View File

@@ -26,11 +26,13 @@ import android.location.LocationManagerInternal;
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;
import com.android.server.location.util.UserInfoHelper.UserListener;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Objects;
/**
@@ -52,14 +55,15 @@ import java.util.Objects;
* 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 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
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 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
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 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
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()) {

View File

@@ -76,7 +76,8 @@ public class GnssMeasurementsProvider extends
}
@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 @@ public class GnssMeasurementsProvider extends
}
@Override
protected Boolean mergeRequests(Collection<GnssListenerRegistration> registrations) {
protected Boolean mergeRegistrations(Collection<GnssListenerRegistration> registrations) {
if (mSettingsHelper.isGnssMeasurementsFullTrackingEnabled()) {
return true;
}

View File

@@ -30,6 +30,8 @@ import com.android.internal.util.Preconditions;
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 @@ public class GnssNavigationMessageProvider extends
}
@Override
protected boolean registerWithService(Void ignored) {
protected boolean registerWithService(Void ignored,
Collection<GnssListenerRegistration> registrations) {
Preconditions.checkState(mNative.isNavigationMessageSupported());
if (mNative.startNavigationMessageCollection()) {

View File

@@ -31,6 +31,8 @@ import com.android.server.location.util.AppOpsHelper;
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 @@ public class GnssStatusProvider extends GnssListenerMultiplexer<Void, IGnssStatu
}
@Override
protected boolean registerWithService(Void ignored) {
protected boolean registerWithService(Void ignored,
Collection<GnssListenerRegistration> registrations) {
if (D) {
Log.d(TAG, "starting gnss status");
}

View File

@@ -23,6 +23,8 @@ import android.os.IBinder;
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 @@ import android.util.Log;
* @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 @@ public abstract class BinderListenerRegistration<TRequest, TListener> extends
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
@@ -77,11 +80,21 @@ public abstract class BinderListenerRegistration<TRequest, TListener> extends
*/
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) {

View File

@@ -37,12 +37,20 @@ import java.util.function.Function;
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 @@ import java.util.function.Predicate;
* {@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 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
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 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
/**
* 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 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
}
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);
}
if (mServiceRegistered) {
mCurrentRequest = merged;
} else {
mCurrentRequest = null;
mServiceRegistered = registerWithService(merged, actives);
}
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 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
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 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
* 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 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
* 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 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
onRegistrationActiveChanged(registration);
}
private void execute(TRegistration registration, ListenerOperation<TListener> operation) {
private void execute(TRegistration registration, TListenerOperation operation) {
registration.executeInternal(operation);
}
@@ -539,10 +562,11 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
*/
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");

View File

@@ -21,6 +21,7 @@ import android.annotation.NonNull;
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 @@ import java.util.concurrent.Executor;
* 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);
}
@@ -53,13 +53,6 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
return mExecutor;
}
/**
* 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 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
* 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 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
* 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 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
/**
* 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

View File

@@ -21,6 +21,8 @@ import android.app.PendingIntent;
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 @@ import android.util.Log;
* @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 @@ public abstract class PendingIntentListenerRegistration<TRequest, TListener> ext
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
@@ -69,10 +72,21 @@ public abstract class PendingIntentListenerRegistration<TRequest, TListener> ext
*/
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();

View File

@@ -24,6 +24,7 @@ import android.location.util.identity.CallerIdentity;
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 @@ import java.util.concurrent.Executor;
* 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 @@ public abstract class RemoteListenerRegistration<TRequest, TListener> extends
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);
}

View File

@@ -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 @@ import java.util.concurrent.Executor;
* 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 @@ public abstract class RemovableListenerRegistration<TRequest, TListener> extends
* 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
@@ -68,12 +69,6 @@ public abstract class RemovableListenerRegistration<TRequest, TListener> extends
}
}
@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);

View File

@@ -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();
}
}
}

View File

@@ -34,6 +34,7 @@ import android.platform.test.annotations.Presubmit;
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 @@ public class ListenerMultiplexerTest {
}
private static class TestListenerRegistration extends
ListenerRegistration<Integer, Consumer<TestListenerRegistration>> {
RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>,
ListenerOperation<Consumer<TestListenerRegistration>>> {
boolean mActive = true;
@@ -329,8 +331,10 @@ public class ListenerMultiplexerTest {
}
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 @@ public class ListenerMultiplexerTest {
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 @@ public class ListenerMultiplexerTest {
}
@Override
protected boolean registerWithService(Integer mergedRequest) {
protected boolean registerWithService(Integer mergedRequest,
Collection<TestListenerRegistration> registrations) {
mRegistered = true;
mMergedRequest = mergedRequest;
return true;
@@ -418,7 +428,8 @@ public class ListenerMultiplexerTest {
}
@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) {