Merge "Refactor listener multiplexer"

This commit is contained in:
Soonil Nagarkar
2022-08-19 16:34:29 +00:00
committed by Android (Google) Code Review
21 changed files with 898 additions and 1031 deletions

View File

@@ -124,15 +124,22 @@ public final class CallerIdentity {
packageName, attributionTag, listenerId);
}
// in some tests these constants are loaded too early leading to an "incorrect" view of the
// current pid and uid. load lazily to prevent this problem in tests.
private static class Loader {
private static final int MY_UID = Process.myUid();
private static final int MY_PID = Process.myPid();
}
private final int mUid;
private final int mPid;
private final String mPackageName;
private final @Nullable String mAttributionTag;
@Nullable private final String mAttributionTag;
private final @Nullable String mListenerId;
@Nullable private final String mListenerId;
private CallerIdentity(int uid, int pid, String packageName,
@Nullable String attributionTag, @Nullable String listenerId) {
@@ -181,6 +188,24 @@ public final class CallerIdentity {
return mUid == Process.SYSTEM_UID;
}
/** Returns true if this identity represents the same user this code is running in. */
public boolean isMyUser() {
return UserHandle.getUserId(mUid) == UserHandle.getUserId(Loader.MY_UID);
}
/** Returns true if this identity represents the same uid this code is running in. */
public boolean isMyUid() {
return mUid == Loader.MY_UID;
}
/**
* Returns true if this identity represents the same process this code is running in. Returns
* false if the identity process is unknown.
*/
public boolean isMyProcess() {
return mPid == Loader.MY_PID;
}
/**
* Adds this identity to the worksource supplied, or if not worksource is supplied, creates a
* new worksource representing this identity.

View File

@@ -26,8 +26,10 @@ import android.app.AppOpsManager;
import android.content.Context;
import android.os.Binder;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** Utility class for dealing with location permissions. */
public final class LocationPermissions {
@@ -49,6 +51,7 @@ public final class LocationPermissions {
*/
public static final int PERMISSION_FINE = 2;
@Target(ElementType.TYPE_USE)
@IntDef({PERMISSION_NONE, PERMISSION_COARSE, PERMISSION_FINE})
@Retention(RetentionPolicy.SOURCE)
public @interface PermissionLevel {}

View File

@@ -1,60 +0,0 @@
/*
* 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.geofence;
import android.app.PendingIntent;
import android.location.Geofence;
import com.android.server.location.listeners.PendingIntentListenerRegistration;
import java.util.Objects;
// geofencing unfortunately allows multiple geofences under the same pending intent, even though
// this makes no real sense. therefore we manufacture an artificial key to use (pendingintent +
// geofence) instead of (pendingintent).
final class GeofenceKey implements PendingIntentListenerRegistration.PendingIntentKey {
private final PendingIntent mPendingIntent;
private final Geofence mGeofence;
GeofenceKey(PendingIntent pendingIntent, Geofence geofence) {
mPendingIntent = Objects.requireNonNull(pendingIntent);
mGeofence = Objects.requireNonNull(geofence);
}
@Override
public PendingIntent getPendingIntent() {
return mPendingIntent;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof GeofenceKey)) {
return false;
}
GeofenceKey that = (GeofenceKey) o;
return mPendingIntent.equals(that.mPendingIntent) && mGeofence.equals(that.mGeofence);
}
@Override
public int hashCode() {
return mPendingIntent.hashCode();
}
}

View File

@@ -59,8 +59,8 @@ import java.util.Objects;
* Manages all geofences.
*/
public class GeofenceManager extends
ListenerMultiplexer<GeofenceKey, PendingIntent, GeofenceManager.GeofenceRegistration,
LocationRequest> implements
ListenerMultiplexer<GeofenceManager.GeofenceKey, PendingIntent,
GeofenceManager.GeofenceRegistration, LocationRequest> implements
LocationListener {
private static final String TAG = "GeofenceManager";
@@ -73,13 +73,49 @@ public class GeofenceManager extends
private static final long MAX_LOCATION_AGE_MS = 5 * 60 * 1000L; // five minutes
private static final long MAX_LOCATION_INTERVAL_MS = 2 * 60 * 60 * 1000; // two hours
protected final class GeofenceRegistration extends
PendingIntentListenerRegistration<Geofence, PendingIntent> {
// geofencing unfortunately allows multiple geofences under the same pending intent, even though
// this makes no real sense. therefore we manufacture an artificial key to use (pendingintent +
// geofence) instead of (pendingintent).
static class GeofenceKey {
private final PendingIntent mPendingIntent;
private final Geofence mGeofence;
GeofenceKey(PendingIntent pendingIntent, Geofence geofence) {
mPendingIntent = Objects.requireNonNull(pendingIntent);
mGeofence = Objects.requireNonNull(geofence);
}
public PendingIntent getPendingIntent() {
return mPendingIntent;
}
@Override
public boolean equals(Object o) {
if (o instanceof GeofenceKey) {
GeofenceKey that = (GeofenceKey) o;
return mPendingIntent.equals(that.mPendingIntent) && mGeofence.equals(
that.mGeofence);
}
return false;
}
@Override
public int hashCode() {
return mPendingIntent.hashCode();
}
}
protected class GeofenceRegistration extends
PendingIntentListenerRegistration<GeofenceKey, PendingIntent> {
private static final int STATE_UNKNOWN = 0;
private static final int STATE_INSIDE = 1;
private static final int STATE_OUTSIDE = 2;
private final Geofence mGeofence;
private final CallerIdentity mIdentity;
private final Location mCenter;
private final PowerManager.WakeLock mWakeLock;
@@ -89,13 +125,15 @@ public class GeofenceManager extends
// spam us, and because checking the values may be more expensive
private boolean mPermitted;
private @Nullable Location mCachedLocation;
@Nullable private Location mCachedLocation;
private float mCachedLocationDistanceM;
protected GeofenceRegistration(Geofence geofence, CallerIdentity identity,
GeofenceRegistration(Geofence geofence, CallerIdentity identity,
PendingIntent pendingIntent) {
super(geofence, identity, pendingIntent);
super(pendingIntent);
mGeofence = geofence;
mIdentity = identity;
mCenter = new Location("");
mCenter.setLatitude(geofence.getLatitude());
mCenter.setLongitude(geofence.getLongitude());
@@ -107,16 +145,36 @@ public class GeofenceManager extends
mWakeLock.setWorkSource(identity.addToWorkSource(null));
}
public Geofence getGeofence() {
return mGeofence;
}
public CallerIdentity getIdentity() {
return mIdentity;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected PendingIntent getPendingIntentFromKey(GeofenceKey geofenceKey) {
return geofenceKey.getPendingIntent();
}
@Override
protected GeofenceManager getOwner() {
return GeofenceManager.this;
}
@Override
protected void onPendingIntentListenerRegister() {
protected void onRegister() {
super.onRegister();
mGeofenceState = STATE_UNKNOWN;
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
getIdentity());
mIdentity);
}
@Override
@@ -132,7 +190,7 @@ public class GeofenceManager extends
}
boolean onLocationPermissionsChanged(@Nullable String packageName) {
if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
if (packageName == null || mIdentity.getPackageName().equals(packageName)) {
return onLocationPermissionsChanged();
}
@@ -140,7 +198,7 @@ public class GeofenceManager extends
}
boolean onLocationPermissionsChanged(int uid) {
if (getIdentity().getUid() == uid) {
if (mIdentity.getUid() == uid) {
return onLocationPermissionsChanged();
}
@@ -149,7 +207,7 @@ public class GeofenceManager extends
private boolean onLocationPermissionsChanged() {
boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
getIdentity());
mIdentity);
if (permitted != mPermitted) {
mPermitted = permitted;
return true;
@@ -164,12 +222,12 @@ public class GeofenceManager extends
mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
}
return Math.abs(getRequest().getRadius() - mCachedLocationDistanceM);
return Math.abs(mGeofence.getRadius() - mCachedLocationDistanceM);
}
ListenerOperation<PendingIntent> onLocationChanged(Location location) {
// remove expired fences
if (getRequest().isExpired()) {
if (mGeofence.isExpired()) {
remove();
return null;
}
@@ -178,7 +236,7 @@ public class GeofenceManager extends
mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
int oldState = mGeofenceState;
float radius = Math.max(getRequest().getRadius(), location.getAccuracy());
float radius = Math.max(mGeofence.getRadius(), location.getAccuracy());
if (mCachedLocationDistanceM <= radius) {
mGeofenceState = STATE_INSIDE;
if (oldState != STATE_INSIDE) {
@@ -206,14 +264,14 @@ public class GeofenceManager extends
null, null, PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
} catch (PendingIntent.CanceledException e) {
mWakeLock.release();
removeRegistration(new GeofenceKey(pendingIntent, getRequest()), this);
removeRegistration(new GeofenceKey(pendingIntent, mGeofence), this);
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(getIdentity());
builder.append(mIdentity);
ArraySet<String> flags = new ArraySet<>(1);
if (!mPermitted) {
@@ -223,7 +281,7 @@ public class GeofenceManager extends
builder.append(" ").append(flags);
}
builder.append(" ").append(getRequest());
builder.append(" ").append(mGeofence);
return builder.toString();
}
}
@@ -258,10 +316,10 @@ public class GeofenceManager extends
protected final LocationUsageLogger mLocationUsageLogger;
@GuardedBy("mLock")
private @Nullable LocationManager mLocationManager;
@Nullable private LocationManager mLocationManager;
@GuardedBy("mLock")
private @Nullable Location mLastLocation;
@Nullable private Location mLastLocation;
public GeofenceManager(Context context, Injector injector) {
mContext = context.createAttributionContext(ATTRIBUTION_TAG);
@@ -271,11 +329,6 @@ public class GeofenceManager extends
mLocationUsageLogger = injector.getLocationUsageLogger();
}
@Override
public String getTag() {
return TAG;
}
private LocationManager getLocationManager() {
synchronized (mLock) {
if (mLocationManager == null) {
@@ -375,7 +428,7 @@ public class GeofenceManager extends
/* LocationRequest= */ null,
/* hasListener= */ false,
true,
registration.getRequest(), true);
registration.getGeofence(), true);
}
@Override
@@ -389,7 +442,7 @@ public class GeofenceManager extends
/* LocationRequest= */ null,
/* hasListener= */ false,
true,
registration.getRequest(), true);
registration.getGeofence(), true);
}
@Override
@@ -417,7 +470,7 @@ public class GeofenceManager extends
WorkSource workSource = null;
double minFenceDistanceM = Double.MAX_VALUE;
for (GeofenceRegistration registration : registrations) {
if (registration.getRequest().isExpired(realtimeMs)) {
if (registration.getGeofence().isExpired(realtimeMs)) {
continue;
}

View File

@@ -16,6 +16,7 @@
package com.android.server.location.gnss;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.gnss.GnssManagerService.TAG;
import android.annotation.Nullable;
@@ -25,6 +26,7 @@ import android.location.util.identity.CallerIdentity;
import android.os.Binder;
import android.os.IBinder;
import com.android.server.FgThread;
import com.android.server.location.gnss.hal.GnssNative;
import com.android.server.location.listeners.BinderListenerRegistration;
import com.android.server.location.listeners.ListenerMultiplexer;
@@ -45,17 +47,35 @@ public class GnssAntennaInfoProvider extends
* Registration object for GNSS listeners.
*/
protected class AntennaInfoListenerRegistration extends
BinderListenerRegistration<Void, IGnssAntennaInfoListener> {
BinderListenerRegistration<IBinder, IGnssAntennaInfoListener> {
protected AntennaInfoListenerRegistration(CallerIdentity callerIdentity,
private final CallerIdentity mIdentity;
protected AntennaInfoListenerRegistration(CallerIdentity identity,
IGnssAntennaInfoListener listener) {
super(null, callerIdentity, listener);
super(identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR, listener);
mIdentity = identity;
}
@Override
protected String getTag() {
return TAG;
}
@Override
protected GnssAntennaInfoProvider getOwner() {
return GnssAntennaInfoProvider.this;
}
@Override
protected IBinder getBinderFromKey(IBinder key) {
return key;
}
@Override
public String toString() {
return mIdentity.toString();
}
}
private final GnssNative mGnssNative;
@@ -72,11 +92,6 @@ public class GnssAntennaInfoProvider extends
return mAntennaInfos;
}
@Override
public String getTag() {
return TAG;
}
public boolean isSupported() {
return mGnssNative.isAntennaInfoSupported();
}

View File

@@ -18,6 +18,7 @@ package com.android.server.location.gnss;
import static android.location.LocationManager.GPS_PROVIDER;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.gnss.GnssManagerService.TAG;
@@ -33,6 +34,7 @@ import android.os.Process;
import android.util.ArraySet;
import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.location.injector.AppForegroundHelper;
import com.android.server.location.injector.Injector;
@@ -67,16 +69,34 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
* Registration object for GNSS listeners.
*/
protected class GnssListenerRegistration extends
BinderListenerRegistration<TRequest, TListener> {
BinderListenerRegistration<IBinder, TListener> {
private final TRequest mRequest;
private final CallerIdentity mIdentity;
// we store these values because we don't trust the listeners not to give us dupes, not to
// spam us, and because checking the values may be more expensive
private boolean mForeground;
private boolean mPermitted;
protected GnssListenerRegistration(@Nullable TRequest request,
CallerIdentity callerIdentity, TListener listener) {
super(request, callerIdentity, listener);
protected GnssListenerRegistration(TRequest request, CallerIdentity identity,
TListener listener) {
super(identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR, listener);
mRequest = request;
mIdentity = identity;
}
public final TRequest getRequest() {
return mRequest;
}
public final CallerIdentity getIdentity() {
return mIdentity;
}
@Override
public String getTag() {
return TAG;
}
@Override
@@ -84,6 +104,11 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
return GnssListenerMultiplexer.this;
}
@Override
protected IBinder getBinderFromKey(IBinder key) {
return key;
}
/**
* Returns true if this registration is currently in the foreground.
*/
@@ -96,31 +121,16 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
@Override
protected final void onBinderListenerRegister() {
protected void onRegister() {
super.onRegister();
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
getIdentity());
mForeground = mAppForegroundHelper.isAppForeground(getIdentity().getUid());
onGnssListenerRegister();
mIdentity);
mForeground = mAppForegroundHelper.isAppForeground(mIdentity.getUid());
}
@Override
protected final void onBinderListenerUnregister() {
onGnssListenerUnregister();
}
/**
* May be overridden in place of {@link #onBinderListenerRegister()}.
*/
protected void onGnssListenerRegister() {}
/**
* May be overridden in place of {@link #onBinderListenerUnregister()}.
*/
protected void onGnssListenerUnregister() {}
boolean onLocationPermissionsChanged(@Nullable String packageName) {
if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
if (packageName == null || mIdentity.getPackageName().equals(packageName)) {
return onLocationPermissionsChanged();
}
@@ -128,7 +138,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
boolean onLocationPermissionsChanged(int uid) {
if (getIdentity().getUid() == uid) {
if (mIdentity.getUid() == uid) {
return onLocationPermissionsChanged();
}
@@ -137,7 +147,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
private boolean onLocationPermissionsChanged() {
boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
getIdentity());
mIdentity);
if (permitted != mPermitted) {
mPermitted = permitted;
return true;
@@ -147,7 +157,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
boolean onForegroundChanged(int uid, boolean foreground) {
if (getIdentity().getUid() == uid && foreground != mForeground) {
if (mIdentity.getUid() == uid && foreground != mForeground) {
mForeground = foreground;
return true;
}
@@ -158,7 +168,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(getIdentity());
builder.append(mIdentity);
ArraySet<String> flags = new ArraySet<>(2);
if (!mForeground) {
@@ -171,8 +181,8 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
builder.append(" ").append(flags);
}
if (getRequest() != null) {
builder.append(" ").append(getRequest());
if (mRequest != null) {
builder.append(" ").append(mRequest);
}
return builder.toString();
}
@@ -218,11 +228,6 @@ 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

View File

@@ -40,10 +40,7 @@ import com.android.server.location.injector.SettingsHelper;
import java.util.Collection;
/**
* An base implementation for GNSS measurements provider. It abstracts out the responsibility of
* handling listeners, while still allowing technology specific implementations to be built.
*
* @hide
* GNSS measurements HAL module and listener multiplexer.
*/
public final class GnssMeasurementsProvider extends
GnssListenerMultiplexer<GnssMeasurementRequest, IGnssMeasurementsListener,
@@ -61,7 +58,9 @@ public final class GnssMeasurementsProvider extends
}
@Override
protected void onGnssListenerRegister() {
protected void onRegister() {
super.onRegister();
executeOperation(listener -> listener.onStatusChanged(
GnssMeasurementsEvent.Callback.STATUS_READY));
}

View File

@@ -32,11 +32,7 @@ import com.android.server.location.injector.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
* specific implementations to be built.
*
* @hide
* GNSS navigation message HAL module and listener multiplexer.
*/
public class GnssNavigationMessageProvider extends
GnssListenerMultiplexer<Void, IGnssNavigationMessageListener, Void> implements
@@ -51,7 +47,9 @@ public class GnssNavigationMessageProvider extends
}
@Override
protected void onGnssListenerRegister() {
protected void onRegister() {
super.onRegister();
executeOperation(listener -> listener.onStatusChanged(
GnssNavigationMessage.Callback.STATUS_READY));
}

View File

@@ -34,7 +34,7 @@ import java.util.Collection;
import java.util.function.Function;
/**
* Implementation of a handler for {@link IGnssNmeaListener}.
* GNSS NMEA HAL module and listener multiplexer.
*/
class GnssNmeaProvider extends GnssListenerMultiplexer<Void, IGnssNmeaListener, Void> implements
GnssNative.BaseCallbacks, GnssNative.NmeaCallbacks {
@@ -97,7 +97,7 @@ class GnssNmeaProvider extends GnssListenerMultiplexer<Void, IGnssNmeaListener,
ListenerExecutor.ListenerOperation<IGnssNmeaListener>>() {
// only read in the nmea string if we need to
private @Nullable String mNmea;
@Nullable private String mNmea;
@Override
public ListenerExecutor.ListenerOperation<IGnssNmeaListener> apply(

View File

@@ -35,7 +35,7 @@ import com.android.server.location.injector.LocationUsageLogger;
import java.util.Collection;
/**
* Implementation of a handler for {@link IGnssStatusListener}.
* GNSS status HAL module and listener multiplexer.
*/
public class GnssStatusProvider extends
GnssListenerMultiplexer<Void, IGnssStatusListener, Void> implements

View File

@@ -16,71 +16,59 @@
package com.android.server.location.listeners;
import android.annotation.Nullable;
import android.location.util.identity.CallerIdentity;
import android.os.Binder;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.util.Log;
import java.util.NoSuchElementException;
import java.util.concurrent.Executor;
/**
* 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
* {@link IBinder} or a {@link BinderKey}.
* remove the registration if the binder dies.
*
* @param <TRequest> request type
* @param <TKey> key type
* @param <TListener> listener type
*/
public abstract class BinderListenerRegistration<TRequest, TListener> extends
RemoteListenerRegistration<TRequest, TListener> implements Binder.DeathRecipient {
public abstract class BinderListenerRegistration<TKey, TListener> extends
RemovableListenerRegistration<TKey, TListener> implements DeathRecipient {
/**
* Interface to allow binder retrieval when keys are not themselves IBinders.
*/
public interface BinderKey {
/**
* Returns the binder associated with this key.
*/
IBinder getBinder();
protected BinderListenerRegistration(Executor executor, TListener listener) {
super(executor, listener);
}
protected BinderListenerRegistration(@Nullable TRequest request, CallerIdentity callerIdentity,
TListener listener) {
super(request, callerIdentity, listener);
}
protected abstract IBinder getBinderFromKey(TKey key);
@Override
protected final void onRemovableListenerRegister() {
IBinder binder = getBinderFromKey(getKey());
protected void onRegister() {
super.onRegister();
try {
binder.linkToDeath(this, 0);
getBinderFromKey(getKey()).linkToDeath(this, 0);
} catch (RemoteException e) {
remove();
}
onBinderListenerRegister();
}
@Override
protected final void onRemovableListenerUnregister() {
onBinderListenerUnregister();
getBinderFromKey(getKey()).unlinkToDeath(this, 0);
protected void onUnregister() {
try {
getBinderFromKey(getKey()).unlinkToDeath(this, 0);
} catch (NoSuchElementException e) {
// the only way this exception can occur should be if another exception has been thrown
// prior to registration completing, and that exception is currently unwinding the call
// stack and causing this cleanup. since that exception should crash us anyways, drop
// this exception so we're not hiding the original exception.
Log.w(getTag(), "failed to unregister binder death listener", e);
}
super.onUnregister();
}
/**
* May be overridden in place of {@link #onRemovableListenerRegister()}.
*/
protected void onBinderListenerRegister() {}
/**
* May be overridden in place of {@link #onRemovableListenerUnregister()}.
*/
protected void onBinderListenerUnregister() {}
@Override
public void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
if (e instanceof RemoteException) {
Log.w(getOwner().getTag(), "registration " + this + " removed", e);
Log.w(getTag(), "registration " + this + " removed", e);
remove();
} else {
super.onOperationFailure(operation, e);
@@ -90,9 +78,10 @@ public abstract class BinderListenerRegistration<TRequest, TListener> extends
@Override
public void binderDied() {
try {
if (Log.isLoggable(getOwner().getTag(), Log.DEBUG)) {
Log.d(getOwner().getTag(), "binder registration " + getIdentity() + " died");
if (Log.isLoggable(getTag(), Log.DEBUG)) {
Log.d(getTag(), "binder registration " + this + " died");
}
remove();
} catch (RuntimeException e) {
// the caller may swallow runtime exceptions, so we rethrow as assertion errors to
@@ -100,14 +89,4 @@ public abstract class BinderListenerRegistration<TRequest, TListener> extends
throw new AssertionError(e);
}
}
private static IBinder getBinderFromKey(Object key) {
if (key instanceof IBinder) {
return (IBinder) key;
} else if (key instanceof BinderKey) {
return ((BinderKey) key).getBinder();
} else {
throw new IllegalArgumentException("key must be IBinder or BinderKey");
}
}
}

View File

@@ -18,7 +18,6 @@ package com.android.server.location.listeners;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Build;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -37,40 +36,48 @@ import java.util.function.Function;
import java.util.function.Predicate;
/**
* A base class to multiplex client listener registrations within system server. Every listener is
* A base class to multiplex some event source to multiple listener registrations. 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.
* <p>Registrations are divided into two categories, active registrations and inactive
* registrations, as defined by {@link #isActive(ListenerRegistration)}. The set of active
* registrations is combined into a single merged registration, which is submitted to the backing
* event source when necessary in order to register with the event source. The merged registration
* is updated whenever the set of active registration changes. 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.
* <p>In order to inform the multiplexer of state changes, if a registration's active state changes,
* or if the merged registration changes, {@link #updateRegistrations(Predicate)} or {@link
* #updateRegistration(Object, Predicate)} must be invoked and return true for any registration
* whose state may have changed in such a way that the active state or merged registration state has
* changed. It is acceptable to return true from a predicate even if nothing has changed, though
* this may result in extra pointless work.
*
* Callbacks invoked for various changes will always be ordered according to this lifecycle list:
* <p>Callbacks invoked for various changes will always be ordered according to this lifecycle list:
*
* <ul>
* <li>{@link #onRegister()}</li>
* <li>{@link ListenerRegistration#onRegister(Object)}</li>
* <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}</li>
* <li>{@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} (only
* invoked if this registration is replacing a prior registration)</li>
* <li>{@link #onActive()}</li>
* <li>{@link ListenerRegistration#onActive()}</li>
* <li>{@link ListenerRegistration#onInactive()}</li>
* <li>{@link #onInactive()}</li>
* <li>{@link #onRegistrationRemoved(Object, ListenerRegistration)}</li>
* <li>{@link ListenerRegistration#onUnregister()}</li>
* <li>{@link #onUnregister()}</li>
* <li>{@link #onRegister()}
* <li>{@link ListenerRegistration#onRegister(Object)}
* <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}
* <li>{@link #onActive()}
* <li>{@link ListenerRegistration#onActive()}
* <li>{@link ListenerRegistration#onInactive()}
* <li>{@link #onInactive()}
* <li>{@link #onRegistrationRemoved(Object, ListenerRegistration)}
* <li>{@link ListenerRegistration#onUnregister()}
* <li>{@link #onUnregister()}
* </ul>
*
* Adding registrations is not allowed to be called re-entrantly (ie, while in the middle of some
* other operation or callback. Removal is allowed re-entrantly, however only via
* {@link #removeRegistration(Object, ListenerRegistration)}, not via any other removal method. This
* <p>If one registration replaces another, then {@link #onRegistrationReplaced(Object,
* ListenerRegistration, Object, ListenerRegistration)} is invoked instead of {@link
* #onRegistrationRemoved(Object, ListenerRegistration)} and {@link #onRegistrationAdded(Object,
* ListenerRegistration)}.
*
* <p>Adding registrations is not allowed to be called re-entrantly (ie, while in the middle of some
* other operation or callback). Removal is allowed re-entrantly, however only via {@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
@@ -81,29 +88,30 @@ import java.util.function.Predicate;
public abstract class ListenerMultiplexer<TKey, TListener,
TRegistration extends ListenerRegistration<TListener>, TMergedRegistration> {
@GuardedBy("mRegistrations")
/**
* The lock object used by the multiplexer. Acquiring this lock allows for multiple operations
* on the multiplexer to be completed atomically. Otherwise, it is not required to hold this
* lock. This lock is held while invoking all lifecycle callbacks on both the multiplexer and
* any registrations.
*/
protected final Object mMultiplexerLock = new Object();
@GuardedBy("mMultiplexerLock")
private final ArrayMap<TKey, TRegistration> mRegistrations = new ArrayMap<>();
@GuardedBy("mRegistrations")
private final UpdateServiceBuffer mUpdateServiceBuffer = new UpdateServiceBuffer();
@GuardedBy("mRegistrations")
private final ReentrancyGuard mReentrancyGuard = new ReentrancyGuard();
@GuardedBy("mRegistrations")
@GuardedBy("mMultiplexerLock")
private int mActiveRegistrationsCount = 0;
@GuardedBy("mRegistrations")
@GuardedBy("mMultiplexerLock")
private boolean mServiceRegistered = false;
@GuardedBy("mRegistrations")
@GuardedBy("mMultiplexerLock")
@Nullable private TMergedRegistration mMerged;
/**
* 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()}
@@ -120,6 +128,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* @see #mergeRegistrations(Collection)
* @see #reregisterWithService(Object, Object, Collection)
*/
@GuardedBy("mMultiplexerLock")
protected abstract boolean registerWithService(TMergedRegistration merged,
@NonNull Collection<TRegistration> registrations);
@@ -130,6 +139,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
*
* @see #registerWithService(Object, Collection)
*/
@GuardedBy("mMultiplexerLock")
protected boolean reregisterWithService(TMergedRegistration oldMerged,
TMergedRegistration newMerged, @NonNull Collection<TRegistration> registrations) {
return registerWithService(newMerged, registrations);
@@ -138,6 +148,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
/**
* Should be implemented to unregister from the backing service.
*/
@GuardedBy("mMultiplexerLock")
protected abstract void unregisterWithService();
/**
@@ -147,6 +158,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* {@link #updateRegistrations(Predicate)} must be invoked with a function that returns true for
* any registrations that may have changed their active state.
*/
@GuardedBy("mMultiplexerLock")
protected abstract boolean isActive(@NonNull TRegistration registration);
/**
@@ -157,7 +169,8 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* {@link #reregisterWithService(Object, Object, Collection)} will be invoked with the new
* merged registration so that the backing service can be updated.
*/
protected abstract @Nullable TMergedRegistration mergeRegistrations(
@GuardedBy("mMultiplexerLock")
protected abstract TMergedRegistration mergeRegistrations(
@NonNull Collection<TRegistration> registrations);
/**
@@ -166,6 +179,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* present while there are any registrations. Invoked while holding the multiplexer's internal
* lock.
*/
@GuardedBy("mMultiplexerLock")
protected void onRegister() {}
/**
@@ -174,28 +188,38 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* present while there are any registrations. Invoked while holding the multiplexer's internal
* lock.
*/
@GuardedBy("mMultiplexerLock")
protected void onUnregister() {}
/**
* Invoked when a registration is added. Invoked while holding the multiplexer's internal lock.
*/
@GuardedBy("mMultiplexerLock")
protected void onRegistrationAdded(@NonNull TKey key, @NonNull TRegistration registration) {}
/**
* Invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)} if a
* registration is replacing an old registration. The old registration will have already been
* unregistered. Invoked while holding the multiplexer's internal lock. The default behavior is
* simply to call into {@link #onRegistrationAdded(Object, ListenerRegistration)}.
* Invoked when one registration replaces another (through {@link #replaceRegistration(Object,
* Object, ListenerRegistration)}). The old registration has already been unregistered at this
* point. Invoked while holding the multiplexer's internal lock.
*
* <p>The default behavior is simply to call first {@link #onRegistrationRemoved(Object,
* ListenerRegistration)} and then {@link #onRegistrationAdded(Object, ListenerRegistration)}.
*/
protected void onRegistrationReplaced(@NonNull TKey key, @NonNull TRegistration oldRegistration,
@GuardedBy("mMultiplexerLock")
protected void onRegistrationReplaced(
@NonNull TKey oldKey,
@NonNull TRegistration oldRegistration,
@NonNull TKey newKey,
@NonNull TRegistration newRegistration) {
onRegistrationAdded(key, newRegistration);
onRegistrationRemoved(oldKey, oldRegistration);
onRegistrationAdded(newKey, newRegistration);
}
/**
* Invoked when a registration is removed. Invoked while holding the multiplexer's internal
* lock.
*/
@GuardedBy("mMultiplexerLock")
protected void onRegistrationRemoved(@NonNull TKey key, @NonNull TRegistration registration) {}
/**
@@ -204,6 +228,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* need to be present while there are active registrations. Invoked while holding the
* multiplexer's internal lock.
*/
@GuardedBy("mMultiplexerLock")
protected void onActive() {}
/**
@@ -212,6 +237,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* need to be present while there are active registrations. Invoked while holding the
* multiplexer's internal lock.
*/
@GuardedBy("mMultiplexerLock")
protected void onInactive() {}
/**
@@ -224,13 +250,12 @@ public abstract class ListenerMultiplexer<TKey, TListener,
/**
* Atomically removes the registration with the old key and adds a new registration with the
* given key. If there was a registration for the old key,
* {@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} will be
* invoked for the new registration and key instead of
* {@link #onRegistrationAdded(Object, ListenerRegistration)}, even though they may not share
* the same key. The old key may be the same value as the new key, in which case this function
* is equivalent to {@link #putRegistration(Object, ListenerRegistration)}. This method cannot
* be called to add a registration re-entrantly.
* given key. If there was a registration for the old key, {@link
* #onRegistrationReplaced(Object, ListenerRegistration, Object, ListenerRegistration)} will be
* invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)}, even if they
* share the same key. The old key may be the same value as the new key, in which case this
* function is equivalent to {@link #putRegistration(Object, ListenerRegistration)}. This method
* cannot be called to add a registration re-entrantly.
*/
protected final void replaceRegistration(@NonNull TKey oldKey, @NonNull TKey key,
@NonNull TRegistration registration) {
@@ -238,7 +263,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
Objects.requireNonNull(key);
Objects.requireNonNull(registration);
synchronized (mRegistrations) {
synchronized (mMultiplexerLock) {
// adding listeners reentrantly is not supported
Preconditions.checkState(!mReentrancyGuard.isReentrant());
@@ -257,12 +282,18 @@ public abstract class ListenerMultiplexer<TKey, TListener,
boolean wasEmpty = mRegistrations.isEmpty();
TRegistration oldRegistration = null;
int index = mRegistrations.indexOfKey(oldKey);
if (index >= 0) {
oldRegistration = removeRegistration(index, oldKey != key);
int oldIndex = mRegistrations.indexOfKey(oldKey);
if (oldIndex >= 0) {
// remove ourselves instead of using remove(), to balance registration callbacks
oldRegistration = mRegistrations.valueAt(oldIndex);
unregister(oldRegistration);
oldRegistration.onUnregister();
if (oldKey != key) {
mRegistrations.removeAt(oldIndex);
}
}
if (oldKey == key && index >= 0) {
mRegistrations.setValueAt(index, registration);
if (oldKey == key && oldIndex >= 0) {
mRegistrations.setValueAt(oldIndex, registration);
} else {
mRegistrations.put(key, registration);
}
@@ -274,37 +305,19 @@ public abstract class ListenerMultiplexer<TKey, TListener,
if (oldRegistration == null) {
onRegistrationAdded(key, registration);
} else {
onRegistrationReplaced(key, oldRegistration, registration);
onRegistrationReplaced(oldKey, oldRegistration, key, registration);
}
onRegistrationActiveChanged(registration);
}
}
}
/**
* Removes the registration with the given key. This method cannot be called to remove a
* registration re-entrantly.
*/
protected final void removeRegistration(@NonNull Object key) {
synchronized (mRegistrations) {
// this method does not support removing listeners reentrantly
Preconditions.checkState(!mReentrancyGuard.isReentrant());
int index = mRegistrations.indexOfKey(key);
if (index < 0) {
return;
}
removeRegistration(index, true);
}
}
/**
* Removes all registrations with keys that satisfy the given predicate. This method cannot be
* called to remove a registration re-entrantly.
*/
protected final void removeRegistrationIf(@NonNull Predicate<TKey> predicate) {
synchronized (mRegistrations) {
synchronized (mMultiplexerLock) {
// this method does not support removing listeners reentrantly
Preconditions.checkState(!mReentrancyGuard.isReentrant());
@@ -328,14 +341,32 @@ public abstract class ListenerMultiplexer<TKey, TListener,
}
}
/**
* Removes the registration with the given key. This method cannot be called to remove a
* registration re-entrantly.
*/
protected final void removeRegistration(TKey key) {
synchronized (mMultiplexerLock) {
// this method does not support removing listeners reentrantly
Preconditions.checkState(!mReentrancyGuard.isReentrant());
int index = mRegistrations.indexOfKey(key);
if (index < 0) {
return;
}
removeRegistration(index);
}
}
/**
* Removes the given registration with the given key. If the given key has a different
* registration at the time this method is called, nothing happens. This method allows for
* re-entrancy, and may be called to remove a registration re-entrantly.
*/
protected final void removeRegistration(@NonNull Object key,
protected final void removeRegistration(@NonNull TKey key,
@NonNull ListenerRegistration<?> registration) {
synchronized (mRegistrations) {
synchronized (mMultiplexerLock) {
int index = mRegistrations.indexOfKey(key);
if (index < 0) {
return;
@@ -350,17 +381,13 @@ public abstract class ListenerMultiplexer<TKey, TListener,
unregister(typedRegistration);
mReentrancyGuard.markForRemoval(key, typedRegistration);
} else {
removeRegistration(index, true);
removeRegistration(index);
}
}
}
@GuardedBy("mRegistrations")
private TRegistration removeRegistration(int index, boolean removeEntry) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mRegistrations));
}
@GuardedBy("mMultiplexerLock")
private void removeRegistration(int index) {
TKey key = mRegistrations.keyAt(index);
TRegistration registration = mRegistrations.valueAt(index);
@@ -376,15 +403,11 @@ public abstract class ListenerMultiplexer<TKey, TListener,
unregister(registration);
onRegistrationRemoved(key, registration);
registration.onUnregister();
if (removeEntry) {
mRegistrations.removeAt(index);
if (mRegistrations.isEmpty()) {
onUnregister();
}
mRegistrations.removeAt(index);
if (mRegistrations.isEmpty()) {
onUnregister();
}
}
return registration;
}
/**
@@ -392,14 +415,14 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* registration accordingly.
*/
protected final void updateService() {
synchronized (mRegistrations) {
synchronized (mMultiplexerLock) {
if (mUpdateServiceBuffer.isBuffered()) {
mUpdateServiceBuffer.markUpdateServiceRequired();
return;
}
ArrayList<TRegistration> actives = new ArrayList<>(mRegistrations.size());
final int size = mRegistrations.size();
ArrayList<TRegistration> actives = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (registration.isActive()) {
@@ -413,17 +436,17 @@ public abstract class ListenerMultiplexer<TKey, TListener,
mServiceRegistered = false;
unregisterWithService();
}
return;
}
TMergedRegistration merged = mergeRegistrations(actives);
if (!mServiceRegistered || !Objects.equals(merged, mMerged)) {
} else {
TMergedRegistration merged = mergeRegistrations(actives);
if (mServiceRegistered) {
mServiceRegistered = reregisterWithService(mMerged, merged, actives);
if (!Objects.equals(merged, mMerged)) {
mServiceRegistered = reregisterWithService(mMerged, merged, actives);
mMerged = mServiceRegistered ? merged : null;
}
} else {
mServiceRegistered = registerWithService(merged, actives);
mMerged = mServiceRegistered ? merged : null;
}
mMerged = mServiceRegistered ? merged : null;
}
}
}
@@ -437,7 +460,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* reinitialized.
*/
protected final void resetService() {
synchronized (mRegistrations) {
synchronized (mMultiplexerLock) {
if (mServiceRegistered) {
mMerged = null;
mServiceRegistered = false;
@@ -453,7 +476,31 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* buffering {@code updateService()} until after multiple adds/removes/updates occur.
*/
public UpdateServiceLock newUpdateServiceLock() {
return new UpdateServiceLock(mUpdateServiceBuffer.acquire());
return new UpdateServiceLock(mUpdateServiceBuffer);
}
/**
* Evaluates the predicate on all registrations until the predicate returns true, at which point
* evaluation will cease. Returns true if the predicate ever returned true, and returns false
* otherwise.
*/
protected final boolean findRegistration(Predicate<TRegistration> predicate) {
synchronized (mMultiplexerLock) {
// we only acquire a reentrancy guard in case of removal while iterating. this method
// does not directly affect active state or merged state, so there is no advantage to
// acquiring an update source buffer.
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
final int size = mRegistrations.size();
for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (predicate.test(registration)) {
return true;
}
}
}
return false;
}
}
/**
@@ -463,7 +510,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* the resulting changes.
*/
protected final void updateRegistrations(@NonNull Predicate<TRegistration> predicate) {
synchronized (mRegistrations) {
synchronized (mMultiplexerLock) {
// since updating a registration can invoke a variety of callbacks, we need to ensure
// those callbacks themselves do not re-enter, as this could lead to out-of-order
// callbacks. note that try-with-resources ordering is meaningful here as well. we want
@@ -492,7 +539,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
*/
protected final boolean updateRegistration(@NonNull Object key,
@NonNull Predicate<TRegistration> predicate) {
synchronized (mRegistrations) {
synchronized (mMultiplexerLock) {
// since updating a registration can invoke a variety of callbacks, we need to ensure
// those callbacks themselves do not re-enter, as this could lead to out-of-order
// callbacks. note that try-with-resources ordering is meaningful here as well. we want
@@ -515,12 +562,8 @@ public abstract class ListenerMultiplexer<TKey, TListener,
}
}
@GuardedBy("mRegistrations")
@GuardedBy("mMultiplexerLock")
private void onRegistrationActiveChanged(TRegistration registration) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mRegistrations));
}
boolean active = registration.isRegistered() && isActive(registration);
boolean changed = registration.setActive(active);
if (changed) {
@@ -547,7 +590,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
*/
protected final void deliverToListeners(
@NonNull Function<TRegistration, ListenerOperation<TListener>> function) {
synchronized (mRegistrations) {
synchronized (mMultiplexerLock) {
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
final int size = mRegistrations.size();
for (int i = 0; i < size; i++) {
@@ -571,7 +614,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* </pre>
*/
protected final void deliverToListeners(@NonNull ListenerOperation<TListener> operation) {
synchronized (mRegistrations) {
synchronized (mMultiplexerLock) {
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
final int size = mRegistrations.size();
for (int i = 0; i < size; i++) {
@@ -584,6 +627,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
}
}
@GuardedBy("mMultiplexerLock")
private void unregister(TRegistration registration) {
registration.unregisterInternal();
onRegistrationActiveChanged(registration);
@@ -593,7 +637,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* Dumps debug information.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mRegistrations) {
synchronized (mMultiplexerLock) {
pw.print("service: ");
pw.print(getServiceState());
pw.println();
@@ -620,6 +664,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* May be overridden to provide additional details on service state when dumping the manager
* state. Invoked while holding the multiplexer's internal lock.
*/
@GuardedBy("mMultiplexerLock")
protected String getServiceState() {
if (mServiceRegistered) {
if (mMerged != null) {
@@ -643,61 +688,63 @@ public abstract class ListenerMultiplexer<TKey, TListener,
*/
private final class ReentrancyGuard implements AutoCloseable {
@GuardedBy("mRegistrations")
@GuardedBy("mMultiplexerLock")
private int mGuardCount;
@GuardedBy("mRegistrations")
private @Nullable ArraySet<Entry<Object, ListenerRegistration<?>>> mScheduledRemovals;
@GuardedBy("mMultiplexerLock")
@Nullable private ArraySet<Entry<TKey, ListenerRegistration<?>>> mScheduledRemovals;
ReentrancyGuard() {
mGuardCount = 0;
mScheduledRemovals = null;
}
@GuardedBy("mRegistrations")
boolean isReentrant() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mRegistrations));
synchronized (mMultiplexerLock) {
return mGuardCount != 0;
}
return mGuardCount != 0;
}
@GuardedBy("mRegistrations")
void markForRemoval(Object key, ListenerRegistration<?> registration) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mRegistrations));
}
Preconditions.checkState(isReentrant());
void markForRemoval(TKey key, ListenerRegistration<?> registration) {
synchronized (mMultiplexerLock) {
Preconditions.checkState(isReentrant());
if (mScheduledRemovals == null) {
mScheduledRemovals = new ArraySet<>(mRegistrations.size());
if (mScheduledRemovals == null) {
mScheduledRemovals = new ArraySet<>(mRegistrations.size());
}
mScheduledRemovals.add(new AbstractMap.SimpleImmutableEntry<>(key, registration));
}
mScheduledRemovals.add(new AbstractMap.SimpleImmutableEntry<>(key, registration));
}
ReentrancyGuard acquire() {
++mGuardCount;
return this;
synchronized (mMultiplexerLock) {
++mGuardCount;
return this;
}
}
@Override
public void close() {
ArraySet<Entry<Object, ListenerRegistration<?>>> scheduledRemovals = null;
synchronized (mMultiplexerLock) {
Preconditions.checkState(mGuardCount > 0);
Preconditions.checkState(mGuardCount > 0);
if (--mGuardCount == 0) {
scheduledRemovals = mScheduledRemovals;
mScheduledRemovals = null;
}
ArraySet<Entry<TKey, ListenerRegistration<?>>> scheduledRemovals = null;
if (scheduledRemovals == null) {
return;
}
if (--mGuardCount == 0) {
scheduledRemovals = mScheduledRemovals;
mScheduledRemovals = null;
}
try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
final int size = scheduledRemovals.size();
for (int i = 0; i < size; i++) {
Entry<Object, ListenerRegistration<?>> entry = scheduledRemovals.valueAt(i);
removeRegistration(entry.getKey(), entry.getValue());
if (scheduledRemovals == null) {
return;
}
try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
final int size = scheduledRemovals.size();
for (int i = 0; i < size; i++) {
Entry<TKey, ListenerRegistration<?>> entry = scheduledRemovals.valueAt(i);
removeRegistration(entry.getKey(), entry.getValue());
}
}
}
}
@@ -721,6 +768,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
@GuardedBy("this")
private int mBufferCount;
@GuardedBy("this")
private boolean mUpdateServiceRequired;
@@ -765,18 +813,18 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* {@link #close()}ed. This can be used to save work by acquiring the lock before multiple calls
* to updateService() are expected, and closing the lock after.
*/
public final class UpdateServiceLock implements AutoCloseable {
public static final class UpdateServiceLock implements AutoCloseable {
private @Nullable UpdateServiceBuffer mUpdateServiceBuffer;
@Nullable private ListenerMultiplexer<?, ?, ?, ?>.UpdateServiceBuffer mUpdateServiceBuffer;
UpdateServiceLock(UpdateServiceBuffer updateServiceBuffer) {
mUpdateServiceBuffer = updateServiceBuffer;
UpdateServiceLock(ListenerMultiplexer<?, ?, ?, ?>.UpdateServiceBuffer updateServiceBuffer) {
mUpdateServiceBuffer = updateServiceBuffer.acquire();
}
@Override
public void close() {
if (mUpdateServiceBuffer != null) {
UpdateServiceBuffer buffer = mUpdateServiceBuffer;
ListenerMultiplexer<?, ?, ?, ?>.UpdateServiceBuffer buffer = mUpdateServiceBuffer;
mUpdateServiceBuffer = null;
buffer.close();
}

View File

@@ -35,7 +35,7 @@ public class ListenerRegistration<TListener> implements ListenerExecutor {
private boolean mActive;
private volatile @Nullable TListener mListener;
@Nullable private volatile TListener mListener;
protected ListenerRegistration(Executor executor, TListener listener) {
mExecutor = Objects.requireNonNull(executor);
@@ -43,6 +43,13 @@ public class ListenerRegistration<TListener> implements ListenerExecutor {
mListener = Objects.requireNonNull(listener);
}
/**
* Returns a tag to use for logging. Should be overridden by subclasses.
*/
protected String getTag() {
return "ListenerRegistration";
}
protected final Executor getExecutor() {
return mExecutor;
}
@@ -50,26 +57,36 @@ public class ListenerRegistration<TListener> implements ListenerExecutor {
/**
* May be overridden by subclasses. Invoked when registration occurs. Invoked while holding the
* owning multiplexer's internal lock.
*
* <p>If overridden you must ensure the superclass method is invoked (usually as the first thing
* in the overridden method).
*/
protected void onRegister(Object key) {}
/**
* May be overridden by subclasses. Invoked when unregistration occurs. Invoked while holding
* the owning multiplexer's internal lock.
*
* <p>If overridden you must ensure the superclass method is invoked (usually as the last thing
* in the overridden method).
*/
protected void onUnregister() {}
/**
* May be overridden by subclasses. Invoked when this registration becomes active. If this
* returns a non-null operation, that operation will be invoked for the listener. Invoked
* while holding the owning multiplexer's internal lock.
* May be overridden by subclasses. Invoked when this registration becomes active. Invoked while
* holding the owning multiplexer's internal lock.
*
* <p>If overridden you must ensure the superclass method is invoked (usually as the first thing
* in the overridden method).
*/
protected void onActive() {}
/**
* May be overridden by subclasses. Invoked when registration becomes inactive. If this returns
* a non-null operation, that operation will be invoked for the listener. Invoked while holding
* the owning multiplexer's internal lock.
* May be overridden by subclasses. Invoked when registration becomes inactive. Invoked while
* holding the owning multiplexer's internal lock.
*
* <p>If overridden you must ensure the superclass method is invoked (usually as the last thing
* in the overridden method).
*/
protected void onInactive() {}

View File

@@ -16,63 +16,47 @@
package com.android.server.location.listeners;
import android.annotation.Nullable;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import android.app.PendingIntent;
import android.location.util.identity.CallerIdentity;
import android.util.Log;
/**
* 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
* registration must either be a {@link PendingIntent} or a {@link PendingIntentKey}.
* automatically remove the registration if the PendingIntent is canceled.
*
* @param <TRequest> request type
* @param <TKey> key type
* @param <TListener> listener type
*/
public abstract class PendingIntentListenerRegistration<TRequest, TListener> extends
RemoteListenerRegistration<TRequest, TListener> implements PendingIntent.CancelListener {
public abstract class PendingIntentListenerRegistration<TKey, TListener> extends
RemovableListenerRegistration<TKey, TListener> implements PendingIntent.CancelListener {
/**
* Interface to allowed pending intent retrieval when keys are not themselves PendingIntents.
*/
public interface PendingIntentKey {
/**
* Returns the pending intent associated with this key.
*/
PendingIntent getPendingIntent();
protected PendingIntentListenerRegistration(TListener listener) {
super(DIRECT_EXECUTOR, listener);
}
protected PendingIntentListenerRegistration(@Nullable TRequest request,
CallerIdentity callerIdentity, TListener listener) {
super(request, callerIdentity, listener);
protected abstract PendingIntent getPendingIntentFromKey(TKey key);
@Override
protected void onRegister() {
super.onRegister();
if (!getPendingIntentFromKey(getKey()).addCancelListener(DIRECT_EXECUTOR, this)) {
remove();
}
}
@Override
protected final void onRemovableListenerRegister() {
getPendingIntentFromKey(getKey()).registerCancelListener(this);
onPendingIntentListenerRegister();
protected void onUnregister() {
getPendingIntentFromKey(getKey()).removeCancelListener(this);
super.onUnregister();
}
@Override
protected final void onRemovableListenerUnregister() {
onPendingIntentListenerUnregister();
getPendingIntentFromKey(getKey()).unregisterCancelListener(this);
}
/**
* May be overridden in place of {@link #onRemovableListenerRegister()}.
*/
protected void onPendingIntentListenerRegister() {}
/**
* May be overridden in place of {@link #onRemovableListenerUnregister()}.
*/
protected void onPendingIntentListenerUnregister() {}
@Override
protected void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
if (e instanceof PendingIntent.CanceledException) {
Log.w(getOwner().getTag(), "registration " + this + " removed", e);
Log.w(getTag(), "registration " + this + " removed", e);
remove();
} else {
super.onOperationFailure(operation, e);
@@ -81,21 +65,10 @@ public abstract class PendingIntentListenerRegistration<TRequest, TListener> ext
@Override
public void onCanceled(PendingIntent intent) {
if (Log.isLoggable(getOwner().getTag(), Log.DEBUG)) {
Log.d(getOwner().getTag(),
"pending intent registration " + getIdentity() + " canceled");
if (Log.isLoggable(getTag(), Log.DEBUG)) {
Log.d(getTag(), "pending intent registration " + this + " canceled");
}
remove();
}
private PendingIntent getPendingIntentFromKey(Object key) {
if (key instanceof PendingIntent) {
return (PendingIntent) key;
} else if (key instanceof PendingIntentKey) {
return ((PendingIntentKey) key).getPendingIntent();
} else {
throw new IllegalArgumentException("key must be PendingIntent or PendingIntentKey");
}
}
}
}

View File

@@ -1,75 +0,0 @@
/*
* 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 static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import android.annotation.Nullable;
import android.location.util.identity.CallerIdentity;
import android.os.Process;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.FgThread;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* A listener registration representing a remote (possibly from a different process) listener.
* Listeners from a different process will be run on a direct executor, since the x-process listener
* 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
*/
public abstract class RemoteListenerRegistration<TRequest, TListener> extends
RemovableListenerRegistration<TRequest, TListener> {
@VisibleForTesting
public static final Executor IN_PROCESS_EXECUTOR = FgThread.getExecutor();
private static Executor chooseExecutor(CallerIdentity identity) {
// if a client is in the same process as us, binder calls will execute synchronously and
// we shouldn't run callbacks directly since they might be run under lock and deadlock
if (identity.getPid() == Process.myPid()) {
// there's a slight loophole here for pending intents - pending intent callbacks can
// always be run on the direct executor since they're always asynchronous, but honestly
// you shouldn't be using pending intent callbacks within the same process anyways
return IN_PROCESS_EXECUTOR;
} else {
return DIRECT_EXECUTOR;
}
}
private final CallerIdentity mIdentity;
protected RemoteListenerRegistration(@Nullable TRequest request, CallerIdentity identity,
TListener listener) {
super(chooseExecutor(identity), request, listener);
mIdentity = Objects.requireNonNull(identity);
}
/**
* Returns the listener identity.
*/
public final CallerIdentity getIdentity() {
return mIdentity;
}
}

View File

@@ -20,22 +20,23 @@ import android.annotation.Nullable;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 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 <TKey> key type
* @param <TListener> listener type
*/
public abstract class RemovableListenerRegistration<TRequest, TListener> extends
RequestListenerRegistration<TRequest, TListener> {
public abstract class RemovableListenerRegistration<TKey, TListener> extends
ListenerRegistration<TListener> {
private volatile @Nullable Object mKey;
@Nullable private volatile TKey mKey;
private final AtomicBoolean mRemoved = new AtomicBoolean(false);
protected RemovableListenerRegistration(Executor executor, @Nullable TRequest request,
TListener listener) {
super(executor, request, listener);
protected RemovableListenerRegistration(Executor executor, TListener listener) {
super(executor, listener);
}
/**
@@ -43,46 +44,76 @@ 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 TListener, ?, ?> getOwner();
protected abstract ListenerMultiplexer<TKey, ? super TListener, ?, ?> getOwner();
/**
* Returns the key associated with this registration. May not be invoked before
* {@link #onRegister(Object)} or after {@link #onUnregister()}.
*/
protected final Object getKey() {
protected final TKey getKey() {
return Objects.requireNonNull(mKey);
}
/**
* Removes this registration. Does nothing if invoked before {@link #onRegister(Object)} or
* after {@link #onUnregister()}. It is safe to invoke this from within either function.
* Convenience method equivalent to invoking {@link #remove(boolean)} with the
* {@code immediately} parameter set to true.
*/
public final void remove() {
Object key = mKey;
if (key != null) {
getOwner().removeRegistration(key, this);
remove(true);
}
/**
* Removes this registration. If the {@code immediately} parameter is true, all pending listener
* invocations will fail. If the {@code immediately} parameter is false, listener invocations
* that were scheduled before remove was invoked (including invocations scheduled within {@link
* #onRemove(boolean)}) will continue, but any listener invocations scheduled after remove was
* invoked will fail.
*
* <p>Only the first call to this method will ever go through (and so {@link #onRemove(boolean)}
* will only ever be invoked once).
*
* <p>Does nothing if invoked before {@link #onRegister()} or after {@link #onUnregister()}.
*/
public final void remove(boolean immediately) {
TKey key = mKey;
if (key != null && !mRemoved.getAndSet(true)) {
onRemove(immediately);
if (immediately) {
getOwner().removeRegistration(key, this);
} else {
executeOperation(listener -> getOwner().removeRegistration(key, this));
}
}
}
/**
* Invoked just before this registration is removed due to {@link #remove(boolean)}, on the same
* thread as the responsible {@link #remove(boolean)} call.
*
* <p>This method will only ever be invoked once, no matter how many calls to {@link
* #remove(boolean)} are made, as any registration can only be removed once.
*/
protected void onRemove(boolean immediately) {}
@Override
protected final void onRegister(Object key) {
mKey = Objects.requireNonNull(key);
onRemovableListenerRegister();
super.onRegister(key);
mKey = (TKey) Objects.requireNonNull(key);
onRegister();
}
/**
* May be overridden by subclasses. Invoked when registration occurs. Invoked while holding the
* owning multiplexer's internal lock.
*
* <p>If overridden you must ensure the superclass method is invoked (usually as the first thing
* in the overridden method).
*/
protected void onRegister() {}
@Override
protected final void onUnregister() {
onRemovableListenerUnregister();
protected void onUnregister() {
mKey = null;
super.onUnregister();
}
/**
* May be overridden in place of {@link #onRegister(Object)}.
*/
protected void onRemovableListenerRegister() {}
/**
* May be overridden in place of {@link #onUnregister()}.
*/
protected void onRemovableListenerUnregister() {}
}

View File

@@ -1,54 +0,0 @@
/*
* 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 java.util.concurrent.Executor;
/**
* A listener registration object which includes an associated request.
*
* @param <TRequest> request type
* @param <TListener> listener type
*/
public class RequestListenerRegistration<TRequest, TListener> extends
ListenerRegistration<TListener> {
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

@@ -54,7 +54,7 @@ public class PassiveLocationProviderManager extends LocationProviderManager {
* Reports a new location to passive location provider clients.
*/
public void updateLocation(LocationResult locationResult) {
synchronized (mLock) {
synchronized (mMultiplexerLock) {
PassiveLocationProvider passive = (PassiveLocationProvider) mProvider.getProvider();
Preconditions.checkState(passive != null);

View File

@@ -58,8 +58,10 @@ public class ListenerMultiplexerTest {
void onRegistrationAdded(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration);
void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration oldRegistration, TestListenerRegistration newRegistration);
void onRegistrationReplaced(Consumer<TestListenerRegistration> oldConsumer,
TestListenerRegistration oldRegistration,
Consumer<TestListenerRegistration> newConsumer,
TestListenerRegistration newRegistration);
void onRegistrationRemoved(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration);
@@ -93,10 +95,10 @@ public class ListenerMultiplexerTest {
assertThat(mMultiplexer.mMergedRequest).isEqualTo(0);
mMultiplexer.addListener(1, consumer);
mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(consumer),
any(TestListenerRegistration.class));
mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer),
any(TestListenerRegistration.class), any(TestListenerRegistration.class));
any(TestListenerRegistration.class),
eq(consumer),
any(TestListenerRegistration.class));
assertThat(mMultiplexer.mRegistered).isTrue();
assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
@@ -115,10 +117,10 @@ public class ListenerMultiplexerTest {
any(TestListenerRegistration.class));
mInOrder.verify(mCallbacks).onActive();
mMultiplexer.replaceListener(1, oldConsumer, consumer);
mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(oldConsumer),
mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(oldConsumer),
any(TestListenerRegistration.class),
eq(consumer),
any(TestListenerRegistration.class));
mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer),
any(TestListenerRegistration.class), any(TestListenerRegistration.class));
assertThat(mMultiplexer.mRegistered).isTrue();
assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
@@ -352,13 +354,19 @@ public class ListenerMultiplexerTest {
}
private static class TestListenerRegistration extends
RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>> {
ListenerRegistration<Consumer<TestListenerRegistration>> {
private final Integer mInteger;
boolean mActive = true;
protected TestListenerRegistration(Integer integer,
Consumer<TestListenerRegistration> consumer) {
super(DIRECT_EXECUTOR, integer, consumer);
super(DIRECT_EXECUTOR, consumer);
mInteger = integer;
}
public Integer getInteger() {
return mInteger;
}
}
@@ -375,11 +383,6 @@ public class ListenerMultiplexerTest {
mCallbacks = callbacks;
}
@Override
public String getTag() {
return "TestMultiplexer";
}
public void addListener(Integer request, Consumer<TestListenerRegistration> consumer) {
putRegistration(consumer, new TestListenerRegistration(request, consumer));
}
@@ -399,9 +402,9 @@ public class ListenerMultiplexerTest {
removeRegistration(consumer, registration);
}
public void setActive(Integer request, boolean active) {
public void setActive(Integer integer, boolean active) {
updateRegistrations(testRegistration -> {
if (testRegistration.getRequest().equals(request)) {
if (testRegistration.getInteger().equals(integer)) {
testRegistration.mActive = active;
return true;
}
@@ -458,10 +461,11 @@ public class ListenerMultiplexerTest {
}
@Override
protected void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer,
protected void onRegistrationReplaced(Consumer<TestListenerRegistration> oldKey,
TestListenerRegistration oldRegistration,
Consumer<TestListenerRegistration> newKey,
TestListenerRegistration newRegistration) {
mCallbacks.onRegistrationReplaced(consumer, oldRegistration, newRegistration);
mCallbacks.onRegistrationReplaced(oldKey, oldRegistration, newKey, newRegistration);
}
@Override
@@ -475,8 +479,8 @@ public class ListenerMultiplexerTest {
Collection<TestListenerRegistration> testRegistrations) {
int max = Integer.MIN_VALUE;
for (TestListenerRegistration registration : testRegistrations) {
if (registration.getRequest() > max) {
max = registration.getRequest();
if (registration.getInteger() > max) {
max = registration.getInteger();
}
}
mMergeCount++;
@@ -493,7 +497,7 @@ public class ListenerMultiplexerTest {
@Override
protected void onRegistrationAdded(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration) {
addListener(registration.getRequest(), consumer);
addListener(registration.getInteger(), consumer);
}
}
}

View File

@@ -32,7 +32,6 @@ import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.LocationUtils.createLocation;
import static com.android.server.location.LocationUtils.createLocationResult;
import static com.android.server.location.listeners.RemoteListenerRegistration.IN_PROCESS_EXECUTOR;
import static com.google.common.truth.Truth.assertThat;
@@ -534,7 +533,7 @@ public class LocationProviderManagerTest {
listener);
CountDownLatch blocker = new CountDownLatch(1);
IN_PROCESS_EXECUTOR.execute(() -> {
FgThread.getExecutor().execute(() -> {
try {
blocker.await();
} catch (InterruptedException e) {
@@ -661,7 +660,7 @@ public class LocationProviderManagerTest {
listener);
CountDownLatch blocker = new CountDownLatch(1);
IN_PROCESS_EXECUTOR.execute(() -> {
FgThread.getExecutor().execute(() -> {
try {
blocker.await();
} catch (InterruptedException e) {