Refactor listener multiplexer
-Simplifies class structure around ListenerRegistration, moving responsibility for requests into subclasses, adding an onRemove() callback, and simplifying the overall class structure. -Eliminates two locks (1 in ListenerMultiplexer, 1 in LocationProviderManager) in favor of sharing the same lock. This simplifies locking and reduces the changes of deadlock by messing something up. -Fixes a bug around callback invocation ordering ListenerMultiplexer.onRegistrationReplaced. -Overall normalizes ListenerMultiplexer usages with respect to other codebases. Test: presubmits Change-Id: I8ad92c1ffe802eee17f5a5774c8ecee1d875252f
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user