Merge "Refactor GNSS listeners"

This commit is contained in:
TreeHugger Robot
2020-02-27 13:59:03 +00:00
committed by Android (Google) Code Review
28 changed files with 1945 additions and 2045 deletions

View File

@@ -1,220 +0,0 @@
/*
* Copyright (C) 2014 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 android.location;
import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.RemoteException;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* A base class to manage listeners that have a 1:N -> source:listener relationship.
*
* @hide
*/
abstract class AbstractListenerManager<TRequest, TListener> {
private static class Registration<TRequest, TListener> {
private final Executor mExecutor;
@Nullable private TRequest mRequest;
@Nullable private volatile TListener mListener;
private Registration(@Nullable TRequest request, Executor executor, TListener listener) {
Preconditions.checkArgument(listener != null, "invalid null listener/callback");
Preconditions.checkArgument(executor != null, "invalid null executor");
mExecutor = executor;
mListener = listener;
mRequest = request;
}
@Nullable
public TRequest getRequest() {
return mRequest;
}
private void unregister() {
mRequest = null;
mListener = null;
}
private void execute(Consumer<TListener> operation) {
mExecutor.execute(
obtainRunnable(Registration<TRequest, TListener>::accept, this, operation)
.recycleOnUse());
}
private void accept(Consumer<TListener> operation) {
TListener listener = mListener;
if (listener == null) {
return;
}
// we may be under the binder identity if a direct executor is used
long identity = Binder.clearCallingIdentity();
try {
operation.accept(listener);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
@GuardedBy("mListeners")
private final ArrayMap<Object, Registration<TRequest, TListener>> mListeners =
new ArrayMap<>();
@GuardedBy("mListeners")
@Nullable
private TRequest mMergedRequest;
public boolean addListener(@NonNull TListener listener, @NonNull Handler handler)
throws RemoteException {
return addInternal(/* request= */ null, listener, handler);
}
public boolean addListener(@NonNull TListener listener, @NonNull Executor executor)
throws RemoteException {
return addInternal(/* request= */ null, listener, executor);
}
public boolean addListener(@Nullable TRequest request, @NonNull TListener listener,
@NonNull Handler handler) throws RemoteException {
return addInternal(request, listener, handler);
}
public boolean addListener(@Nullable TRequest request, @NonNull TListener listener,
@NonNull Executor executor) throws RemoteException {
return addInternal(request, listener, executor);
}
protected final boolean addInternal(@Nullable TRequest request, @NonNull Object listener,
@NonNull Handler handler) throws RemoteException {
return addInternal(request, listener, new HandlerExecutor(handler));
}
protected final boolean addInternal(@Nullable TRequest request, @NonNull Object listener,
@NonNull Executor executor)
throws RemoteException {
Preconditions.checkArgument(listener != null, "invalid null listener/callback");
return addInternal(listener, new Registration<>(request, executor, convertKey(listener)));
}
private boolean addInternal(Object key, Registration<TRequest, TListener> registration)
throws RemoteException {
Preconditions.checkNotNull(registration);
synchronized (mListeners) {
boolean initialRequest = mListeners.isEmpty();
Registration<TRequest, TListener> oldRegistration = mListeners.put(key, registration);
if (oldRegistration != null) {
oldRegistration.unregister();
}
TRequest merged = mergeRequests();
if (initialRequest || !Objects.equals(merged, mMergedRequest)) {
mMergedRequest = merged;
if (!initialRequest) {
unregisterService();
}
registerService(mMergedRequest);
}
return true;
}
}
public void removeListener(Object listener) throws RemoteException {
synchronized (mListeners) {
Registration<TRequest, TListener> oldRegistration = mListeners.remove(listener);
if (oldRegistration == null) {
return;
}
oldRegistration.unregister();
boolean lastRequest = mListeners.isEmpty();
TRequest merged = lastRequest ? null : mergeRequests();
boolean newRequest = !lastRequest && !Objects.equals(merged, mMergedRequest);
if (lastRequest || newRequest) {
unregisterService();
mMergedRequest = merged;
if (newRequest) {
registerService(mMergedRequest);
}
}
}
}
@SuppressWarnings("unchecked")
protected TListener convertKey(@NonNull Object listener) {
return (TListener) listener;
}
protected abstract boolean registerService(TRequest request) throws RemoteException;
protected abstract void unregisterService() throws RemoteException;
@Nullable
protected TRequest merge(@NonNull TRequest[] requests) {
for (TRequest request : requests) {
Preconditions.checkArgument(request == null,
"merge() has to be overridden for non-null requests.");
}
return null;
}
protected void execute(Consumer<TListener> operation) {
synchronized (mListeners) {
for (Registration<TRequest, TListener> registration : mListeners.values()) {
registration.execute(operation);
}
}
}
@GuardedBy("mListeners")
@SuppressWarnings("unchecked")
@Nullable
private TRequest mergeRequests() {
Preconditions.checkState(Thread.holdsLock(mListeners));
if (mListeners.isEmpty()) {
return null;
}
if (mListeners.size() == 1) {
return mListeners.valueAt(0).getRequest();
}
TRequest[] requests = (TRequest[]) new Object[mListeners.size()];
for (int index = 0; index < mListeners.size(); index++) {
requests[index] = mListeners.valueAt(index).getRequest();
}
return merge(requests);
}
}

View File

@@ -70,7 +70,9 @@ public final class GnssRequest implements Parcelable {
public String toString() {
StringBuilder s = new StringBuilder();
s.append("GnssRequest[");
s.append("FullTracking=").append(mFullTracking);
if (mFullTracking) {
s.append("FullTracking");
}
s.append(']');
return s.toString();
}

View File

@@ -57,10 +57,6 @@ interface ILocationManager
in PendingIntent intent, String packageName, String featureId);
void removeGeofence(in Geofence fence, in PendingIntent intent, String packageName);
boolean registerGnssStatusCallback(IGnssStatusListener callback, String packageName,
String featureId);
void unregisterGnssStatusCallback(IGnssStatusListener callback);
boolean geocoderIsPresent();
String getFromLocation(double latitude, double longitude, int maxResults,
in GeocoderParams params, out List<Address> addrs);
@@ -69,31 +65,30 @@ interface ILocationManager
double upperRightLatitude, double upperRightLongitude, int maxResults,
in GeocoderParams params, out List<Address> addrs);
boolean addGnssMeasurementsListener(in GnssRequest request,
in IGnssMeasurementsListener listener,
String packageName, String featureId);
void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections,
in String packageName);
long getGnssCapabilities();
void removeGnssMeasurementsListener(in IGnssMeasurementsListener listener);
boolean addGnssAntennaInfoListener(in IGnssAntennaInfoListener listener,
String packageName, String featureId);
void removeGnssAntennaInfoListener(in IGnssAntennaInfoListener listener);
boolean addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener,
String packageName, String featureId);
void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener);
int getGnssYearOfHardware();
String getGnssHardwareModelName();
void registerGnssStatusCallback(in IGnssStatusListener callback, String packageName, String featureId);
void unregisterGnssStatusCallback(in IGnssStatusListener callback);
void addGnssMeasurementsListener(in GnssRequest request, in IGnssMeasurementsListener listener, String packageName, String featureId);
void removeGnssMeasurementsListener(in IGnssMeasurementsListener listener);
void addGnssAntennaInfoListener(in IGnssAntennaInfoListener listener, String packageName, String featureId);
void removeGnssAntennaInfoListener(in IGnssAntennaInfoListener listener);
void addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener, String packageName, String featureId);
void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener);
void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections, String packageName);
int getGnssBatchSize(String packageName);
boolean addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName, String featureId);
void addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName, String featureId);
void removeGnssBatchingCallback();
boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName, String featureId);
void startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName, String featureId);
void flushGnssBatch(String packageName);
boolean stopGnssBatch();
void stopGnssBatch();
void injectLocation(in Location location);
List<String> getAllProviders();

View File

@@ -22,6 +22,7 @@ import static android.Manifest.permission.LOCATION_HARDWARE;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.app.AlarmManager.ELAPSED_REALTIME;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
import android.Manifest;
@@ -42,6 +43,7 @@ import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.util.listeners.ClientListenerManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -1921,11 +1923,8 @@ public class LocationManager {
"GpsStatus APIs not supported, please use GnssStatus APIs instead");
}
try {
return mGnssStatusListenerManager.addListener(listener, Runnable::run);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mGnssStatusListenerManager.addListener(listener, DIRECT_EXECUTOR);
return true;
}
/**
@@ -1943,11 +1942,7 @@ public class LocationManager {
"GpsStatus APIs not supported, please use GnssStatus APIs instead");
}
try {
mGnssStatusListenerManager.removeListener(listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mGnssStatusListenerManager.removeListener(listener);
}
/**
@@ -1964,7 +1959,8 @@ public class LocationManager {
@Deprecated
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssStatusCallback(@NonNull GnssStatus.Callback callback) {
return registerGnssStatusCallback(Runnable::run, callback);
mGnssStatusListenerManager.addListener(callback, DIRECT_EXECUTOR);
return true;
}
/**
@@ -1984,11 +1980,8 @@ public class LocationManager {
handler = new Handler();
}
try {
return mGnssStatusListenerManager.addListener(callback, handler);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mGnssStatusListenerManager.addListener(callback, new HandlerExecutor(handler));
return true;
}
/**
@@ -2006,11 +1999,8 @@ public class LocationManager {
public boolean registerGnssStatusCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssStatus.Callback callback) {
try {
return mGnssStatusListenerManager.addListener(callback, executor);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mGnssStatusListenerManager.addListener(callback, executor);
return true;
}
/**
@@ -2019,11 +2009,7 @@ public class LocationManager {
* @param callback GNSS status callback object to remove
*/
public void unregisterGnssStatusCallback(@NonNull GnssStatus.Callback callback) {
try {
mGnssStatusListenerManager.removeListener(callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mGnssStatusListenerManager.removeListener(callback);
}
/**
@@ -2057,7 +2043,7 @@ public class LocationManager {
@Deprecated
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean addNmeaListener(@NonNull OnNmeaMessageListener listener) {
return addNmeaListener(Runnable::run, listener);
return addNmeaListener(DIRECT_EXECUTOR, listener);
}
/**
@@ -2076,11 +2062,9 @@ public class LocationManager {
if (handler == null) {
handler = new Handler();
}
try {
return mGnssStatusListenerManager.addListener(listener, handler);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mGnssStatusListenerManager.addListener(listener, new HandlerExecutor(handler));
return true;
}
/**
@@ -2098,11 +2082,8 @@ public class LocationManager {
public boolean addNmeaListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnNmeaMessageListener listener) {
try {
return mGnssStatusListenerManager.addListener(listener, executor);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mGnssStatusListenerManager.addListener(listener, executor);
return true;
}
/**
@@ -2111,11 +2092,7 @@ public class LocationManager {
* @param listener a {@link OnNmeaMessageListener} object to remove
*/
public void removeNmeaListener(@NonNull OnNmeaMessageListener listener) {
try {
mGnssStatusListenerManager.removeListener(listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mGnssStatusListenerManager.removeListener(listener);
}
/**
@@ -2155,7 +2132,7 @@ public class LocationManager {
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssMeasurementsCallback(
@NonNull GnssMeasurementsEvent.Callback callback) {
return registerGnssMeasurementsCallback(Runnable::run, callback);
return registerGnssMeasurementsCallback(DIRECT_EXECUTOR, callback);
}
/**
@@ -2174,11 +2151,9 @@ public class LocationManager {
if (handler == null) {
handler = new Handler();
}
try {
return mGnssMeasurementsListenerManager.addListener(callback, handler);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return registerGnssMeasurementsCallback(new GnssRequest.Builder().build(),
new HandlerExecutor(handler), callback);
}
/**
@@ -2196,11 +2171,8 @@ public class LocationManager {
public boolean registerGnssMeasurementsCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssMeasurementsEvent.Callback callback) {
try {
return mGnssMeasurementsListenerManager.addListener(callback, executor);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return registerGnssMeasurementsCallback(new GnssRequest.Builder().build(), executor,
callback);
}
/**
@@ -2225,11 +2197,8 @@ public class LocationManager {
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssMeasurementsEvent.Callback callback) {
Preconditions.checkArgument(request != null, "invalid null request");
try {
return mGnssMeasurementsListenerManager.addListener(request, callback, executor);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mGnssMeasurementsListenerManager.addListener(request, callback, executor);
return true;
}
/**
@@ -2262,11 +2231,7 @@ public class LocationManager {
*/
public void unregisterGnssMeasurementsCallback(
@NonNull GnssMeasurementsEvent.Callback callback) {
try {
mGnssMeasurementsListenerManager.removeListener(callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mGnssMeasurementsListenerManager.removeListener(callback);
}
/**
@@ -2285,11 +2250,8 @@ public class LocationManager {
public boolean registerAntennaInfoListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssAntennaInfo.Listener listener) {
try {
return mGnssAntennaInfoListenerManager.addListener(listener, executor);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mGnssAntennaInfoListenerManager.addListener(listener, executor);
return true;
}
/**
@@ -2298,11 +2260,7 @@ public class LocationManager {
* @param listener a {@link GnssAntennaInfo.Listener} object to remove.
*/
public void unregisterAntennaInfoListener(@NonNull GnssAntennaInfo.Listener listener) {
try {
mGnssAntennaInfoListenerManager.removeListener(listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mGnssAntennaInfoListenerManager.removeListener(listener);
}
/**
@@ -2341,7 +2299,7 @@ public class LocationManager {
@Deprecated
public boolean registerGnssNavigationMessageCallback(
@NonNull GnssNavigationMessage.Callback callback) {
return registerGnssNavigationMessageCallback(Runnable::run, callback);
return registerGnssNavigationMessageCallback(DIRECT_EXECUTOR, callback);
}
/**
@@ -2361,11 +2319,8 @@ public class LocationManager {
handler = new Handler();
}
try {
return mGnssNavigationMessageListenerTransport.addListener(callback, handler);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mGnssNavigationMessageListenerTransport.addListener(callback, new HandlerExecutor(handler));
return true;
}
/**
@@ -2383,11 +2338,8 @@ public class LocationManager {
public boolean registerGnssNavigationMessageCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssNavigationMessage.Callback callback) {
try {
return mGnssNavigationMessageListenerTransport.addListener(callback, executor);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mGnssNavigationMessageListenerTransport.addListener(callback, executor);
return true;
}
/**
@@ -2397,11 +2349,7 @@ public class LocationManager {
*/
public void unregisterGnssNavigationMessageCallback(
@NonNull GnssNavigationMessage.Callback callback) {
try {
mGnssNavigationMessageListenerTransport.removeListener(callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mGnssNavigationMessageListenerTransport.removeListener(callback);
}
/**
@@ -2451,12 +2399,11 @@ public class LocationManager {
}
synchronized (mBatchedLocationCallbackManager) {
mBatchedLocationCallbackManager.addListener(callback, new HandlerExecutor(handler));
try {
if (mBatchedLocationCallbackManager.addListener(callback, handler)) {
return mService.startGnssBatch(periodNanos, wakeOnFifoFull,
mService.startGnssBatch(periodNanos, wakeOnFifoFull,
mContext.getPackageName(), mContext.getFeatureId());
}
return false;
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2826,13 +2773,41 @@ public class LocationManager {
}
}
private class GnssStatusListenerManager extends
AbstractListenerManager<Void, GnssStatus.Callback> {
@Nullable
private IGnssStatusListener mListenerTransport;
private static class GpsAdapter extends GnssStatus.Callback {
@Nullable
private volatile GnssStatus mGnssStatus;
private final GpsStatus.Listener mGpsListener;
private GpsAdapter(GpsStatus.Listener gpsListener) {
mGpsListener = gpsListener;
}
@Override
public void onStarted() {
mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STARTED);
}
@Override
public void onStopped() {
mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STOPPED);
}
@Override
public void onFirstFix(int ttffMillis) {
mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_FIRST_FIX);
}
@Override
public void onSatelliteStatusChanged(GnssStatus status) {
mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_SATELLITE_STATUS);
}
}
private class GnssStatusListenerManager extends
ClientListenerManager<Void, GnssStatus.Callback> {
private @Nullable IGnssStatusListener mListenerTransport;
private volatile @Nullable GnssStatus mGnssStatus;
private volatile int mTtff;
public GnssStatus getGnssStatus() {
@@ -2843,95 +2818,58 @@ public class LocationManager {
return mTtff;
}
public boolean addListener(@NonNull GpsStatus.Listener listener, @NonNull Executor executor)
throws RemoteException {
return addInternal(null, listener, executor);
public void addListener(@NonNull OnNmeaMessageListener listener,
@NonNull Executor executor) {
addListener(listener, null, new NmeaAdapter(listener), executor);
}
public boolean addListener(@NonNull OnNmeaMessageListener listener,
@NonNull Handler handler)
throws RemoteException {
return addInternal(null, listener, handler);
}
public boolean addListener(@NonNull OnNmeaMessageListener listener,
@NonNull Executor executor)
throws RemoteException {
return addInternal(null, listener, executor);
public void addListener(@NonNull GpsStatus.Listener listener, @NonNull Executor executor) {
addListener(listener, null, new GpsAdapter(listener), executor);
}
@Override
protected GnssStatus.Callback convertKey(Object listener) {
if (listener instanceof GnssStatus.Callback) {
return (GnssStatus.Callback) listener;
} else if (listener instanceof GpsStatus.Listener) {
return new GnssStatus.Callback() {
private final GpsStatus.Listener mGpsListener = (GpsStatus.Listener) listener;
@Override
public void onStarted() {
mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STARTED);
}
@Override
public void onStopped() {
mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STOPPED);
}
@Override
public void onFirstFix(int ttffMillis) {
mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_FIRST_FIX);
}
@Override
public void onSatelliteStatusChanged(GnssStatus status) {
mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_SATELLITE_STATUS);
}
};
} else if (listener instanceof OnNmeaMessageListener) {
return new NmeaAdapter((OnNmeaMessageListener) listener);
} else {
throw new IllegalStateException();
}
}
@Override
protected boolean registerService(Void ignored) throws RemoteException {
protected boolean registerService(Void ignored) {
Preconditions.checkState(mListenerTransport == null);
GnssStatusListener transport = new GnssStatusListener();
if (mService.registerGnssStatusCallback(transport, mContext.getPackageName(),
mContext.getFeatureId())) {
try {
mService.registerGnssStatusCallback(transport, mContext.getPackageName(),
mContext.getFeatureId());
mListenerTransport = transport;
return true;
} else {
return false;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Override
protected void unregisterService() throws RemoteException {
Preconditions.checkState(mListenerTransport != null);
mService.unregisterGnssStatusCallback(mListenerTransport);
mListenerTransport = null;
protected void unregisterService() {
if (mListenerTransport != null) {
try {
IGnssStatusListener transport = mListenerTransport;
mListenerTransport = null;
mService.unregisterGnssStatusCallback(transport);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
private class GnssStatusListener extends IGnssStatusListener.Stub {
@Override
public void onGnssStarted() {
execute(GnssStatus.Callback::onStarted);
deliverToListeners(GnssStatus.Callback::onStarted);
}
@Override
public void onGnssStopped() {
execute(GnssStatus.Callback::onStopped);
deliverToListeners(GnssStatus.Callback::onStopped);
}
@Override
public void onFirstFix(int ttff) {
mTtff = ttff;
execute((callback) -> callback.onFirstFix(ttff));
deliverToListeners(callback -> callback.onFirstFix(ttff));
}
@Override
@@ -2941,12 +2879,12 @@ public class LocationManager {
GnssStatus localStatus = GnssStatus.wrap(svCount, svidWithFlags, cn0s,
elevations, azimuths, carrierFreqs, basebandCn0s);
mGnssStatus = localStatus;
execute((callback) -> callback.onSatelliteStatusChanged(localStatus));
deliverToListeners(callback -> callback.onSatelliteStatusChanged(localStatus));
}
@Override
public void onNmeaReceived(long timestamp, String nmea) {
execute((callback) -> {
deliverToListeners((callback) -> {
if (callback instanceof NmeaAdapter) {
((NmeaAdapter) callback).onNmeaMessage(nmea, timestamp);
}
@@ -2956,168 +2894,192 @@ public class LocationManager {
}
private class GnssMeasurementsListenerManager extends
AbstractListenerManager<GnssRequest, GnssMeasurementsEvent.Callback> {
ClientListenerManager<GnssRequest, GnssMeasurementsEvent.Callback> {
@Nullable
private IGnssMeasurementsListener mListenerTransport;
@Override
protected boolean registerService(GnssRequest request) throws RemoteException {
protected boolean registerService(GnssRequest request) {
Preconditions.checkState(mListenerTransport == null);
GnssMeasurementsListener transport = new GnssMeasurementsListener();
if (mService.addGnssMeasurementsListener(request, transport, mContext.getPackageName(),
mContext.getFeatureId())) {
try {
mService.addGnssMeasurementsListener(request, transport, mContext.getPackageName(),
mContext.getFeatureId());
mListenerTransport = transport;
return true;
} else {
return false;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Override
protected void unregisterService() throws RemoteException {
Preconditions.checkState(mListenerTransport != null);
mService.removeGnssMeasurementsListener(mListenerTransport);
mListenerTransport = null;
}
@Override
@Nullable
protected GnssRequest merge(@NonNull GnssRequest[] requests) {
Preconditions.checkArgument(requests.length > 0);
for (GnssRequest request : requests) {
if (request.isFullTracking()) {
return request;
protected void unregisterService() {
if (mListenerTransport != null) {
try {
IGnssMeasurementsListener transport = mListenerTransport;
mListenerTransport = null;
mService.removeGnssMeasurementsListener(transport);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return requests[0];
}
@Override
protected GnssRequest mergeRequests(
List<Registration<GnssRequest, GnssMeasurementsEvent.Callback>> registrations) {
for (Registration<GnssRequest, GnssMeasurementsEvent.Callback> registration :
registrations) {
if (registration.getRequest().isFullTracking()) {
return registration.getRequest();
}
}
return registrations.get(0).getRequest();
}
private class GnssMeasurementsListener extends IGnssMeasurementsListener.Stub {
@Override
public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) {
execute((callback) -> callback.onGnssMeasurementsReceived(event));
deliverToListeners((callback) -> callback.onGnssMeasurementsReceived(event));
}
@Override
public void onStatusChanged(int status) {
execute((callback) -> callback.onStatusChanged(status));
deliverToListeners((callback) -> callback.onStatusChanged(status));
}
}
}
private class GnssNavigationMessageListenerManager extends
AbstractListenerManager<Void, GnssNavigationMessage.Callback> {
ClientListenerManager<Void, GnssNavigationMessage.Callback> {
@Nullable
private IGnssNavigationMessageListener mListenerTransport;
@Override
protected boolean registerService(Void ignored) throws RemoteException {
protected boolean registerService(Void ignored) {
Preconditions.checkState(mListenerTransport == null);
GnssNavigationMessageListener transport = new GnssNavigationMessageListener();
if (mService.addGnssNavigationMessageListener(transport, mContext.getPackageName(),
mContext.getFeatureId())) {
try {
mService.addGnssNavigationMessageListener(transport, mContext.getPackageName(),
mContext.getFeatureId());
mListenerTransport = transport;
return true;
} else {
return false;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Override
protected void unregisterService() throws RemoteException {
Preconditions.checkState(mListenerTransport != null);
mService.removeGnssNavigationMessageListener(mListenerTransport);
mListenerTransport = null;
protected void unregisterService() {
if (mListenerTransport != null) {
try {
IGnssNavigationMessageListener transport = mListenerTransport;
mListenerTransport = null;
mService.removeGnssNavigationMessageListener(transport);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
private class GnssNavigationMessageListener extends IGnssNavigationMessageListener.Stub {
@Override
public void onGnssNavigationMessageReceived(GnssNavigationMessage event) {
execute((listener) -> listener.onGnssNavigationMessageReceived(event));
deliverToListeners((listener) -> listener.onGnssNavigationMessageReceived(event));
}
@Override
public void onStatusChanged(int status) {
execute((listener) -> listener.onStatusChanged(status));
deliverToListeners((listener) -> listener.onStatusChanged(status));
}
}
}
private class GnssAntennaInfoListenerManager extends
AbstractListenerManager<Void, GnssAntennaInfo.Listener> {
ClientListenerManager<Void, GnssAntennaInfo.Listener> {
@Nullable
private IGnssAntennaInfoListener mListenerTransport;
@Override
protected boolean registerService(Void ignored) throws RemoteException {
protected boolean registerService(Void ignored) {
Preconditions.checkState(mListenerTransport == null);
GnssAntennaInfoListener transport = new GnssAntennaInfoListener();
if (mService.addGnssAntennaInfoListener(transport, mContext.getPackageName(),
mContext.getFeatureId())) {
try {
mService.addGnssAntennaInfoListener(transport, mContext.getPackageName(),
mContext.getFeatureId());
mListenerTransport = transport;
return true;
} else {
return false;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Override
protected void unregisterService() throws RemoteException {
Preconditions.checkState(mListenerTransport != null);
mService.removeGnssAntennaInfoListener(mListenerTransport);
mListenerTransport = null;
protected void unregisterService() {
if (mListenerTransport != null) {
try {
IGnssAntennaInfoListener transport = mListenerTransport;
mListenerTransport = null;
mService.removeGnssAntennaInfoListener(transport);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
private class GnssAntennaInfoListener extends IGnssAntennaInfoListener.Stub {
@Override
public void onGnssAntennaInfoReceived(final List<GnssAntennaInfo> gnssAntennaInfos) {
execute((callback) -> callback.onGnssAntennaInfoReceived(gnssAntennaInfos));
public void onGnssAntennaInfoReceived(List<GnssAntennaInfo> infos) {
deliverToListeners(callback -> callback.onGnssAntennaInfoReceived(infos));
}
}
}
private class BatchedLocationCallbackManager extends
AbstractListenerManager<Void, BatchedLocationCallback> {
ClientListenerManager<Void, BatchedLocationCallback> {
@Nullable
private IBatchedLocationCallback mListenerTransport;
@Override
protected boolean registerService(Void ignored) throws RemoteException {
protected boolean registerService(Void ignored) {
Preconditions.checkState(mListenerTransport == null);
BatchedLocationCallback transport = new BatchedLocationCallback();
if (mService.addGnssBatchingCallback(transport, mContext.getPackageName(),
mContext.getFeatureId())) {
try {
mService.addGnssBatchingCallback(transport, mContext.getPackageName(),
mContext.getFeatureId());
mListenerTransport = transport;
return true;
} else {
return false;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Override
protected void unregisterService() throws RemoteException {
Preconditions.checkState(mListenerTransport != null);
mService.removeGnssBatchingCallback();
mListenerTransport = null;
protected void unregisterService() {
if (mListenerTransport != null) {
try {
mListenerTransport = null;
mService.removeGnssBatchingCallback();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
private class BatchedLocationCallback extends IBatchedLocationCallback.Stub {
@Override
public void onLocationBatch(List<Location> locations) {
execute((listener) -> listener.onLocationBatch(locations));
deliverToListeners((listener) -> listener.onLocationBatch(locations));
}
}
@@ -3126,7 +3088,7 @@ public class LocationManager {
/**
* @hide
*/
public static final String CACHE_KEY_LOCATION_ENABLED_PROPERTY =
private static final String CACHE_KEY_LOCATION_ENABLED_PROPERTY =
"cache_key.location_enabled";
/**

View File

@@ -0,0 +1,411 @@
/*
* 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 android.location.util.listeners;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Binder;
import android.os.Build;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* A base class to manage listeners multiplexed from some source.
*
* @param <TKey> key type
* @param <TRequest> request type
* @param <TListener> listener type
* @param <TRegistration> registration type
* @param <TMergedRequest> merged request type
* @hide
*/
public abstract class AbstractListenerManager<TKey, TRequest, TListener,
TRegistration extends AbstractListenerManager.Registration<TRequest, TListener>,
TMergedRequest> {
/**
* A listener registration object which holds data associated with the listener.
*
* @param <TRequest> request type
* @param <TListener> listener type
*/
public static class Registration<TRequest, TListener> {
private final Executor mExecutor;
private volatile @Nullable TRequest mRequest;
private volatile @Nullable TListener mListener;
protected Registration(@Nullable TRequest request, @NonNull Executor executor,
@NonNull TListener listener) {
Preconditions.checkArgument(executor != null, "invalid null executor");
Preconditions.checkArgument(listener != null, "invalid null listener/callback");
mExecutor = executor;
mRequest = request;
mListener = listener;
}
/**
* Returns the request associated with this listener, or null if one wasn't supplied.
*/
public @Nullable TRequest getRequest() {
return mRequest;
}
/**
* Returns the listener, or null if this registration is no longer registered.
*/
protected @Nullable TListener getListener() {
return mListener;
}
boolean register() {
Preconditions.checkState(mListener != null);
return onRegister();
}
protected final void unregister() {
if (mListener != null) {
onUnregister();
mRequest = null;
mListener = null;
}
}
/**
* May be overridden by subclasses. Invoked when registration is occurring. If this returns
* true, then registration will complete successfully. If this returns false, registration
* will fail.
*/
protected boolean onRegister() {
return true;
}
/**
* May be overridden by subclasses. Invoked before unregistration occurs.
*/
protected void onUnregister() {}
void execute(Consumer<TListener> operation) {
mExecutor.execute(() -> {
TListener listener = mListener;
if (listener == null) {
return;
}
// we may be under the binder identity if a direct executor is used
long identity = Binder.clearCallingIdentity();
try {
operation.accept(listener);
} finally {
Binder.restoreCallingIdentity(identity);
}
});
}
@Override
public String toString() {
if (mRequest == null) {
return "[]";
} else {
return mRequest.toString();
}
}
}
@GuardedBy("mRegistrations")
private final ArrayMap<TKey, TRegistration> mRegistrations = new ArrayMap<>();
@GuardedBy("mRegistrations")
private final ReentrancyGuard mReentrancyGuard = new ReentrancyGuard();
@GuardedBy("mRegistrations")
private boolean mServiceRegistered = false;
@GuardedBy("mRegistrations")
private TMergedRequest mCurrentRequest;
/**
* Adds a new registration with the given key. If the registration succeeds,
* {@link #onRegistrationAdded(Object, Registration)} will be invoked.
*/
protected final void addRegistration(@NonNull TKey key, @NonNull TRegistration registration) {
synchronized (mRegistrations) {
// this class does not support adding listeners reentrantly
Preconditions.checkState(!mReentrancyGuard.isReentrant());
if (!registration.register()) {
registration.unregister();
return;
}
TRegistration old = mRegistrations.put(Objects.requireNonNull(key), registration);
if (old != null) {
onRegistrationRemoved(key, old);
old.unregister();
}
onRegistrationAdded(key, registration);
updateService();
}
}
/**
* Removes the given registration with the given key. If unregistration occurs,
* {@link #onRegistrationRemoved(Object, Registration)} will be called.
*/
protected final void removeRegistration(@NonNull TKey key,
@NonNull TRegistration registration) {
synchronized (mRegistrations) {
if (mRegistrations.remove(key, registration)) {
unregisterRegistration(key, registration);
}
}
}
/**
* Removes the registration with the given key. If unregistration occurs,
* {@link #onRegistrationRemoved(Object, Registration)} will be called.
*/
protected final void removeRegistration(@NonNull TKey key) {
synchronized (mRegistrations) {
TRegistration registration = mRegistrations.remove(key);
if (registration != null) {
unregisterRegistration(key, registration);
}
}
}
@GuardedBy("mRegistrations")
private void unregisterRegistration(TKey key, TRegistration registration) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mRegistrations));
}
// this class does not support removing listeners reentrantly
Preconditions.checkState(!mReentrancyGuard.isReentrant());
onRegistrationRemoved(key, registration);
registration.unregister();
updateService();
}
@GuardedBy("mRegistrations")
private void updateService() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mRegistrations));
}
ArrayList<TRegistration> actives = new ArrayList<>(mRegistrations.size());
for (int i = 0; i < mRegistrations.size(); i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (isActive(registration)) {
actives.add(registration);
}
}
if (actives.isEmpty()) {
if (mServiceRegistered) {
unregisterService();
mServiceRegistered = false;
}
mCurrentRequest = null;
return;
}
TMergedRequest merged = mergeRequests(actives);
if (!mServiceRegistered || !Objects.equals(merged, mCurrentRequest)) {
if (mServiceRegistered) {
unregisterService();
}
mCurrentRequest = merged;
mServiceRegistered = registerService(mCurrentRequest);
}
}
/**
* Defines whether a registration is currently active or not. Only active registrations will be
* considered within {@link #mergeRequests(List)} to calculate the merged request, and listener
* invocations will only be delivered to active requests. If a registration's active state
* changes, {@link #updateRegistrations(Function)} should be invoked with a function that
* returns true for any registrations that have changed their active state in order to inform
* this manager of the active status change.
*/
protected boolean isActive(@NonNull TRegistration registration) {
return true;
}
/**
* Performs some function on all (not just active) registrations. The function should return
* true if the active state of the registration has changed, or if the change to the
* registration may have changed the result of {@link #mergeRequests(List)}.
*/
protected final void updateRegistrations(@NonNull Function<TRegistration, Boolean> function) {
synchronized (mRegistrations) {
boolean changed = false;
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
for (int i = 0; i < mRegistrations.size(); i++) {
changed |= function.apply(mRegistrations.valueAt(i));
}
}
if (changed) {
updateService();
}
}
}
/**
* Called in order to generate a merged request from all active requests. The list of
* registrations will never be empty.
*/
@Nullable
protected TMergedRequest mergeRequests(@NonNull List<TRegistration> registrations) {
if (Build.IS_DEBUGGABLE) {
for (TRegistration registration : registrations) {
// if using non-null requests then implementations must override this method
Preconditions.checkState(registration.getRequest() == null);
}
}
return null;
}
/**
* Should be implemented to register the service with the given request, and should return true
* if registration succeeds.
*/
protected abstract boolean registerService(@Nullable TMergedRequest mergedRequest);
/**
* Should be implemented to unregister the service.
*/
protected abstract void unregisterService();
/**
* Invoked when a registration is added.
*/
protected void onRegistrationAdded(@NonNull TKey key, @NonNull TRegistration registration) {}
/**
* Invoked when a registration is removed.
*/
protected void onRegistrationRemoved(@NonNull TKey key, @NonNull TRegistration registration) {}
/**
* Executes the given delivery operation for all active listeners.
*/
protected final void deliverToListeners(@NonNull Consumer<TListener> operation) {
deliverToListeners(operation, registration -> true);
}
/**
* Executes the given delivery operation for all active listeners which pass the given
* predicate.
*/
protected final void deliverToListeners(@NonNull Consumer<TListener> operation,
@NonNull Predicate<TRegistration> deliveryPredicate) {
synchronized (mRegistrations) {
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
for (int i = 0; i < mRegistrations.size(); i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (isActive(registration) && deliveryPredicate.test(registration)) {
registration.execute(operation);
}
}
}
}
}
/**
* Dumps debug information.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
synchronized (mRegistrations) {
ipw.print("service: ");
ipw.println(serviceStateToString());
if (!mRegistrations.isEmpty()) {
ipw.println("listeners:");
ipw.increaseIndent();
for (int i = 0; i < mRegistrations.size(); i++) {
TRegistration registration = mRegistrations.valueAt(i);
ipw.print(registration);
if (!isActive(registration)) {
ipw.println(" (inactive)");
} else {
ipw.println();
}
}
ipw.decreaseIndent();
}
}
}
/**
* May be override to provide additional details on service state when dumping the manager
* state.
*/
protected String serviceStateToString() {
if (mServiceRegistered) {
if (mCurrentRequest == null) {
return "registered";
} else {
return "registered with " + mCurrentRequest;
}
} else {
return "unregistered";
}
}
// this class does not have an idempotent close(), always use via try-with-resources
private static class ReentrancyGuard implements AutoCloseable {
private int mGuard = 0;
ReentrancyGuard() {
}
public boolean isReentrant() {
return mGuard != 0;
}
public ReentrancyGuard acquire() {
++mGuard;
return this;
}
@Override
public void close() {
--mGuard;
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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 android.location.util.listeners;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.location.util.listeners.AbstractListenerManager.Registration;
import java.util.concurrent.Executor;
/**
* A listener manager for client side implementations, where there is no need to deal with any
* complications other than muxing listeners. Listeners without associated requests are supported
* without any further work, and listeners with requests must produce muxed requests of the same
* type.
*
* @param <TRequest> request type
* @param <TListener> listener type
* @hide
*/
public abstract class ClientListenerManager<TRequest, TListener> extends
AbstractListenerManager<Object, TRequest, TListener, Registration<TRequest, TListener>,
TRequest> {
/**
* Adds a new listener with no request.
*/
public void addListener(@NonNull TListener listener, @NonNull Executor executor) {
addListener(listener, null, listener, executor);
}
/**
* Adds a new listener with the given request.
*/
public void addListener(@Nullable TRequest request, @NonNull TListener listener,
@NonNull Executor executor) {
addListener(listener, request, listener, executor);
}
/**
* Adds a new listener with the given request using a custom key, rather than using the listener
* as the key.
*/
protected void addListener(@NonNull Object key, @Nullable TRequest request,
@NonNull TListener listener, @NonNull Executor executor) {
addRegistration(key, new Registration<>(request, executor, listener));
}
/**
* Removes the listener with the given key.
*/
public void removeListener(@NonNull Object key) {
removeRegistration(key);
}
}

View File

@@ -152,12 +152,12 @@ public class LocationManagerService extends ILocationManager.Stub {
@Override
public void onStart() {
// enable client caches by doing the first invalidate
LocationManager.invalidateLocalLocationEnabledCaches();
publishBinderService(Context.LOCATION_SERVICE, mService);
// disable caching for whatever process contains LocationManagerService
((LocationManager) mService.mContext.getSystemService(LocationManager.class))
// client caching behavior is only enabled after seeing the first invalidate
LocationManager.invalidateLocalLocationEnabledCaches();
// disable caching for our own process
Objects.requireNonNull(mService.mContext.getSystemService(LocationManager.class))
.disableLocalLocationEnabledCaches();
}
@@ -588,8 +588,8 @@ public class LocationManagerService extends ILocationManager.Stub {
// initialize gnss last because it has no awareness of boot phases and blindly assumes that
// all other location providers are loaded at initialization
if (GnssManagerService.isGnssSupported()) {
mGnssManagerService = new GnssManagerService(mContext, mAppOpsHelper, mSettingsHelper,
mAppForegroundHelper, mLocationUsageLogger);
mGnssManagerService = new GnssManagerService(mContext, mUserInfoHelper, mSettingsHelper,
mAppOpsHelper, mAppForegroundHelper, mLocationUsageLogger);
mGnssManagerService.onSystemReady();
LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER);
@@ -1358,32 +1358,40 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
public void addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
String featureId) {
return mGnssManagerService != null && mGnssManagerService.addGnssBatchingCallback(
callback, packageName, featureId);
if (mGnssManagerService != null) {
mGnssManagerService.addGnssBatchingCallback(callback, packageName, featureId);
}
}
@Override
public void removeGnssBatchingCallback() {
if (mGnssManagerService != null) mGnssManagerService.removeGnssBatchingCallback();
if (mGnssManagerService != null) {
mGnssManagerService.removeGnssBatchingCallback();
}
}
@Override
public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName,
public void startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName,
String featureId) {
return mGnssManagerService != null && mGnssManagerService.startGnssBatch(periodNanos,
wakeOnFifoFull, packageName, featureId);
if (mGnssManagerService != null) {
mGnssManagerService.startGnssBatch(periodNanos, wakeOnFifoFull, packageName, featureId);
}
}
@Override
public void flushGnssBatch(String packageName) {
if (mGnssManagerService != null) mGnssManagerService.flushGnssBatch(packageName);
if (mGnssManagerService != null) {
mGnssManagerService.flushGnssBatch(packageName);
}
}
@Override
public boolean stopGnssBatch() {
return mGnssManagerService != null && mGnssManagerService.stopGnssBatch();
public void stopGnssBatch() {
if (mGnssManagerService != null) {
mGnssManagerService.stopGnssBatch();
}
}
@Nullable
@@ -1725,7 +1733,7 @@ public class LocationManagerService extends ILocationManager.Stub {
mReceiver.isListener(),
mReceiver.isPendingIntent(),
/* geofence= */ null,
mAppForegroundHelper.getImportance(mReceiver.mCallerIdentity.uid));
mAppForegroundHelper.isAppForeground(mReceiver.mCallerIdentity.uid));
// remove from mRecordsByProvider
ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider);
@@ -1877,7 +1885,7 @@ public class LocationManagerService extends ILocationManager.Stub {
LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
packageName, request, listener != null, intent != null,
/* geofence= */ null,
mAppForegroundHelper.getImportance(identity.uid));
mAppForegroundHelper.isAppForeground(identity.uid));
synchronized (mLock) {
Receiver receiver;
@@ -2134,7 +2142,7 @@ public class LocationManagerService extends ILocationManager.Stub {
/* hasListener= */ false,
true,
geofence,
mAppForegroundHelper.getImportance(identity.uid));
mAppForegroundHelper.isAppForeground(identity.uid));
mGeofenceManager.addFence(sanitizedRequest, geofence, intent, identity);
}
@@ -2155,7 +2163,7 @@ public class LocationManagerService extends ILocationManager.Stub {
/* hasListener= */ false,
true,
geofence,
mAppForegroundHelper.getImportance(Binder.getCallingUid()));
mAppForegroundHelper.isAppForeground(Binder.getCallingUid()));
// geo-fence manager uses the public location API, need to clear identity
long identity = Binder.clearCallingIdentity();
@@ -2167,22 +2175,27 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
public void registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
String featureId) {
return mGnssManagerService != null && mGnssManagerService.registerGnssStatusCallback(
listener, packageName, featureId);
if (mGnssManagerService != null) {
mGnssManagerService.registerGnssStatusCallback(listener, packageName, featureId);
}
}
@Override
public void unregisterGnssStatusCallback(IGnssStatusListener listener) {
if (mGnssManagerService != null) mGnssManagerService.unregisterGnssStatusCallback(listener);
if (mGnssManagerService != null) {
mGnssManagerService.unregisterGnssStatusCallback(listener);
}
}
@Override
public boolean addGnssMeasurementsListener(@Nullable GnssRequest request,
public void addGnssMeasurementsListener(@Nullable GnssRequest request,
IGnssMeasurementsListener listener, String packageName, String featureId) {
return mGnssManagerService != null && mGnssManagerService.addGnssMeasurementsListener(
request, listener, packageName, featureId);
if (mGnssManagerService != null) {
mGnssManagerService.addGnssMeasurementsListener(request, listener, packageName,
featureId);
}
}
@Override
@@ -2209,11 +2222,11 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
public boolean addGnssAntennaInfoListener(IGnssAntennaInfoListener listener,
public void addGnssAntennaInfoListener(IGnssAntennaInfoListener listener,
String packageName, String featureId) {
return mGnssManagerService != null && mGnssManagerService.addGnssAntennaInfoListener(
listener, packageName, featureId);
if (mGnssManagerService != null) {
mGnssManagerService.addGnssAntennaInfoListener(listener, packageName, featureId);
}
}
@Override
@@ -2224,10 +2237,11 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
public boolean addGnssNavigationMessageListener(IGnssNavigationMessageListener listener,
public void addGnssNavigationMessageListener(IGnssNavigationMessageListener listener,
String packageName, String featureId) {
return mGnssManagerService != null && mGnssManagerService.addGnssNavigationMessageListener(
listener, packageName, featureId);
if (mGnssManagerService != null) {
mGnssManagerService.addGnssNavigationMessageListener(listener, packageName, featureId);
}
}
@Override
@@ -2339,9 +2353,6 @@ public class LocationManagerService extends ILocationManager.Stub {
@Override
public boolean isProviderEnabledForUser(String provider, int userId) {
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, false, "isProviderEnabledForUser", null);
// Fused provider is accessed indirectly via criteria rather than the provider-based APIs,
// so we discourage its use
if (FUSED_PROVIDER.equals(provider)) return false;
@@ -2616,12 +2627,12 @@ public class LocationManagerService extends ILocationManager.Stub {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
synchronized (mLock) {
if (mGnssManagerService != null && args.length > 0 && args[0].equals("--gnssmetrics")) {
mGnssManagerService.dump(fd, pw, args);
return;
}
if (mGnssManagerService != null && args.length > 0 && args[0].equals("--gnssmetrics")) {
mGnssManagerService.dump(fd, pw, args);
return;
}
synchronized (mLock) {
ipw.println("Location Manager State:");
ipw.increaseIndent();
ipw.print("Current System Time: "
@@ -2694,13 +2705,11 @@ public class LocationManagerService extends ILocationManager.Stub {
}
ipw.decreaseIndent();
synchronized (mLock) {
if (mGnssManagerService != null) {
ipw.println("GNSS:");
ipw.increaseIndent();
mGnssManagerService.dump(fd, ipw, args);
ipw.decreaseIndent();
}
if (mGnssManagerService != null) {
ipw.println("GNSS Manager:");
ipw.increaseIndent();
mGnssManagerService.dump(fd, ipw, args);
ipw.decreaseIndent();
}
}
@@ -2708,14 +2717,15 @@ public class LocationManagerService extends ILocationManager.Stub {
@Override
public boolean isProviderEnabledForUser(@NonNull String provider, int userId) {
synchronized (mLock) {
LocationProviderManager manager = getLocationProviderManager(provider);
if (manager == null) {
return false;
}
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, false, "isProviderEnabledForUser", null);
return manager.isEnabled(userId);
LocationProviderManager manager = getLocationProviderManager(provider);
if (manager == null) {
return false;
}
return manager.isEnabled(userId);
}
@Override

View File

@@ -17,53 +17,18 @@
package com.android.server;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
import android.os.RemoteException;
import com.android.server.location.CallerIdentity;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
/**
* Shared utilities for LocationManagerService and GnssManager.
*/
public class LocationManagerServiceUtils {
/**
* Listener that can be linked to a binder.
* @param <TListener> listener type
* @param <TRequest> request type
*/
public static class LinkedListener<TRequest, TListener> extends
LinkedListenerBase {
@Nullable protected final TRequest mRequest;
private final TListener mListener;
private final Consumer<TListener> mBinderDeathCallback;
public LinkedListener(
@Nullable TRequest request,
@NonNull TListener listener,
@NonNull CallerIdentity callerIdentity,
@NonNull Consumer<TListener> binderDeathCallback) {
super(callerIdentity);
mListener = listener;
mRequest = request;
mBinderDeathCallback = binderDeathCallback;
}
@Nullable
public TRequest getRequest() {
return mRequest;
}
@Override
public void binderDied() {
mBinderDeathCallback.accept(mListener);
}
}
/**
* Skeleton class of listener that can be linked to a binder.
*/

View File

@@ -106,24 +106,13 @@ public class AppForegroundHelper {
* Whether the given uid is currently foreground.
*/
public boolean isAppForeground(int uid) {
return isForeground(getImportance(uid));
}
/**
* Retrieves the current importance of the given uid.
*
* @deprecated Prefer {@link #isAppForeground(int)}.
*/
@Deprecated
@Importance
public int getImportance(int uid) {
synchronized (this) {
Preconditions.checkState(mActivityManager != null);
}
long identity = Binder.clearCallingIdentity();
try {
return mActivityManager.getUidImportance(uid);
return isForeground(mActivityManager.getUidImportance(uid));
} finally {
Binder.restoreCallingIdentity(identity);
}

View File

@@ -16,107 +16,88 @@
package com.android.server.location;
import android.content.Context;
import android.location.GnssAntennaInfo;
import android.location.IGnssAntennaInfoListener;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.location.gnss.GnssManagerService;
import com.android.server.location.util.listeners.GnssListenerManager;
import java.util.List;
/**
* An base implementation for GNSS antenna info provider. It abstracts out the responsibility of
* handling listeners, while still allowing technology specific implementations to be built.
*
* @hide
* Provides GNSS antenna information to clients.
*/
public abstract class GnssAntennaInfoProvider
extends RemoteListenerHelper<Void, IGnssAntennaInfoListener> {
private static final String TAG = "GnssAntennaInfoProvider";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public class GnssAntennaInfoProvider extends
GnssListenerManager<Void, IGnssAntennaInfoListener, Void> {
private final GnssAntennaInfoProviderNative mNative;
private boolean mIsListeningStarted;
protected GnssAntennaInfoProvider(Context context, Handler handler) {
this(context, handler, new GnssAntennaInfoProviderNative());
public GnssAntennaInfoProvider(UserInfoHelper userInfoHelper, SettingsHelper settingsHelper,
AppOpsHelper appOpsHelper, AppForegroundHelper appForegroundHelper) {
this(userInfoHelper, settingsHelper, appOpsHelper, appForegroundHelper,
new GnssAntennaInfoProviderNative());
}
@VisibleForTesting
public GnssAntennaInfoProvider(
Context context, Handler handler, GnssAntennaInfoProviderNative aNative) {
super(context, handler, TAG);
public GnssAntennaInfoProvider(UserInfoHelper userInfoHelper, SettingsHelper settingsHelper,
AppOpsHelper appOpsHelper, AppForegroundHelper appForegroundHelper,
GnssAntennaInfoProviderNative aNative) {
super(userInfoHelper, settingsHelper, appOpsHelper, appForegroundHelper);
mNative = aNative;
}
void resumeIfStarted() {
if (DEBUG) {
Log.d(TAG, "resumeIfStarted");
}
if (mIsListeningStarted) {
mNative.startAntennaInfoListening();
}
}
@Override
public boolean isAvailableInPlatform() {
return mNative.isAntennaInfoSupported();
public void addListener(CallerIdentity identity, IGnssAntennaInfoListener listener) {
super.addListener(identity, listener);
}
@Override
protected int registerWithService() {
boolean started = mNative.startAntennaInfoListening();
if (started) {
mIsListeningStarted = true;
return RemoteListenerHelper.RESULT_SUCCESS;
}
return RemoteListenerHelper.RESULT_INTERNAL_ERROR;
}
@Override
protected void unregisterFromService() {
boolean stopped = mNative.stopAntennaInfoListening();
if (stopped) {
mIsListeningStarted = false;
}
}
/** Handle GNSS capabilities update from the GNSS HAL implementation. */
public void onCapabilitiesUpdated(boolean isAntennaInfoSupported) {
setSupported(isAntennaInfoSupported);
updateResult();
}
/** Handle GNSS enabled changes.*/
public void onGpsEnabledChanged() {
tryUpdateRegistrationWithService();
updateResult();
}
@Override
protected ListenerOperation<IGnssAntennaInfoListener> getHandlerOperation(int result) {
return (IGnssAntennaInfoListener listener,
CallerIdentity callerIdentity) -> {
// Do nothing, as GnssAntennaInfo.Callback does not have an onStatusChanged method.
};
}
/** Handle Gnss Antenna Info report. */
public void onGnssAntennaInfoAvailable(final List<GnssAntennaInfo> gnssAntennaInfos) {
foreach((IGnssAntennaInfoListener listener, CallerIdentity callerIdentity) -> {
if (!hasPermission(mContext, callerIdentity)) {
logPermissionDisabledEventNotReported(
TAG, callerIdentity.packageName, "GNSS antenna info");
return;
protected boolean registerService(Void ignored) {
if (mNative.isAntennaInfoSupported()) {
if (mNative.startAntennaInfoListening()) {
if (GnssManagerService.D) {
Log.d(GnssManagerService.TAG, "starting gnss antenna info");
}
return true;
}
Log.e(GnssManagerService.TAG, "error starting gnss antenna info");
}
return false;
}
@Override
protected void unregisterService() {
if (mNative.stopAntennaInfoListening()) {
if (GnssManagerService.D) {
Log.d(GnssManagerService.TAG, "stopping gnss antenna info");
}
} else {
Log.e(GnssManagerService.TAG, "error stopping gnss antenna info");
}
}
/**
* Called by GnssLocationProvider.
*/
public void onGnssAntennaInfoAvailable(List<GnssAntennaInfo> gnssAntennaInfos) {
deliverToListeners((listener) -> {
try {
listener.onGnssAntennaInfoReceived(gnssAntennaInfos);
} catch (RemoteException e) {
// ignore - the listener will get cleaned up later anyways
}
listener.onGnssAntennaInfoReceived(gnssAntennaInfos);
});
}
@Override
protected boolean isServiceSupported() {
return mNative.isAntennaInfoSupported();
}
/**
* Wrapper class for native methods. This is mocked for testing.
*/
@@ -138,9 +119,9 @@ public abstract class GnssAntennaInfoProvider
}
}
private static native boolean native_is_antenna_info_supported();
static native boolean native_is_antenna_info_supported();
private static native boolean native_start_antenna_info_listening();
static native boolean native_start_antenna_info_listening();
private static native boolean native_stop_antenna_info_listening();
static native boolean native_stop_antenna_info_listening();
}

View File

@@ -236,17 +236,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
// Update duration extension multiplier for emergency REQUEST_LOCATION.
private static final int EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER = 3;
/** simpler wrapper for ProviderRequest + Worksource */
private static class GpsRequest {
public ProviderRequest request;
public WorkSource source;
public GpsRequest(ProviderRequest request, WorkSource source) {
this.request = request;
this.source = source;
}
}
// Threadsafe class to hold stats reported in the Extras Bundle
private static class LocationExtras {
private int mSvCount;
@@ -370,9 +359,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
// Current request from underlying location clients.
private ProviderRequest mProviderRequest;
// The WorkSource associated with the most recent client request (i.e, most recent call to
// setRequest).
private WorkSource mWorkSource = null;
// True if gps should be disabled because of PowerManager controls
private boolean mDisableGpsForPowerManager = false;
@@ -396,7 +382,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
private final Looper mLooper;
private final LocationExtras mLocationExtras = new LocationExtras();
private final GnssStatusListenerHelper mGnssStatusListenerHelper;
private final GnssStatusProvider mGnssStatusListenerHelper;
private final GnssMeasurementsProvider mGnssMeasurementsProvider;
private final GnssMeasurementCorrectionsProvider mGnssMeasurementCorrectionsProvider;
private final GnssAntennaInfoProvider mGnssAntennaInfoProvider;
@@ -456,7 +442,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
// GNSS Metrics
private GnssMetrics mGnssMetrics;
public GnssStatusListenerHelper getGnssStatusProvider() {
public GnssStatusProvider getGnssStatusProvider() {
return mGnssStatusListenerHelper;
}
@@ -631,7 +617,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
}
}
public GnssLocationProvider(Context context) {
public GnssLocationProvider(Context context, UserInfoHelper userInfoHelper,
SettingsHelper settingsHelper, AppOpsHelper appOpsHelper,
AppForegroundHelper appForegroundHelper, LocationUsageLogger logger) {
super(FgThread.getExecutor(), context);
ensureInitialized();
@@ -679,40 +667,15 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
mSuplEsEnabled);
sendMessage(INITIALIZE_HANDLER, 0, null);
mGnssStatusListenerHelper = new GnssStatusListenerHelper(mContext, mHandler) {
@Override
protected boolean isAvailableInPlatform() {
return isSupported();
}
@Override
protected boolean isGpsEnabled() {
return GnssLocationProvider.this.isGpsEnabled();
}
};
mGnssMeasurementsProvider = new GnssMeasurementsProvider(mContext, mHandler) {
@Override
protected boolean isGpsEnabled() {
return GnssLocationProvider.this.isGpsEnabled();
}
};
mGnssStatusListenerHelper = new GnssStatusProvider(userInfoHelper, settingsHelper,
appOpsHelper, appForegroundHelper, logger);
mGnssMeasurementsProvider = new GnssMeasurementsProvider(userInfoHelper,
settingsHelper, appOpsHelper, appForegroundHelper, logger);
mGnssMeasurementCorrectionsProvider = new GnssMeasurementCorrectionsProvider(mHandler);
mGnssAntennaInfoProvider = new GnssAntennaInfoProvider(mContext, mHandler) {
@Override
protected boolean isGpsEnabled() {
return GnssLocationProvider.this.isGpsEnabled();
}
};
mGnssNavigationMessageProvider = new GnssNavigationMessageProvider(mContext, mHandler) {
@Override
protected boolean isGpsEnabled() {
return GnssLocationProvider.this.isGpsEnabled();
}
};
mGnssAntennaInfoProvider = new GnssAntennaInfoProvider(userInfoHelper, settingsHelper,
appOpsHelper, appForegroundHelper);
mGnssNavigationMessageProvider = new GnssNavigationMessageProvider(userInfoHelper,
settingsHelper, appOpsHelper, appForegroundHelper);
mGnssMetrics = new GnssMetrics(mContext, mBatteryStats);
mNtpTimeHelper = new NtpTimeHelper(mContext, mLooper, this);
@@ -1009,9 +972,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
mC2KServerHost, mC2KServerPort);
}
mGnssMeasurementsProvider.onGpsEnabledChanged();
mGnssNavigationMessageProvider.onGpsEnabledChanged();
mGnssAntennaInfoProvider.onGpsEnabledChanged();
mGnssBatchingProvider.enable();
if (mGnssVisibilityControl != null) {
mGnssVisibilityControl.onGpsEnabledChanged(/* isEnabled= */ true);
@@ -1037,10 +997,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
mGnssBatchingProvider.disable();
// do this before releasing wakelock
native_cleanup();
mGnssAntennaInfoProvider.onGpsEnabledChanged();
mGnssMeasurementsProvider.onGpsEnabledChanged();
mGnssNavigationMessageProvider.onGpsEnabledChanged();
}
private void updateEnabled() {
@@ -1077,26 +1033,25 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
@Override
public void onSetRequest(ProviderRequest request) {
sendMessage(SET_REQUEST, 0, new GpsRequest(request, request.workSource));
sendMessage(SET_REQUEST, 0, request);
}
private void handleSetRequest(ProviderRequest request, WorkSource source) {
private void handleSetRequest(ProviderRequest request) {
mProviderRequest = request;
mWorkSource = source;
updateEnabled();
updateRequirements();
}
// Called when the requirements for GPS may have changed
private void updateRequirements() {
if (mProviderRequest == null || mWorkSource == null) {
if (mProviderRequest == null || mProviderRequest.workSource == null) {
return;
}
if (DEBUG) Log.d(TAG, "setRequest " + mProviderRequest);
if (mProviderRequest.reportLocation && isGpsEnabled()) {
// update client uids
updateClientUids(mWorkSource);
updateClientUids(mProviderRequest.workSource);
mFixInterval = (int) mProviderRequest.interval;
mLowPowerMode = mProviderRequest.lowPowerMode;
@@ -1533,19 +1488,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
meanCn0 += gnssStatus.getCn0DbHz(i);
mGnssMetrics.logConstellationType(gnssStatus.getConstellationType(i));
}
if (VERBOSE) {
Log.v(TAG, "svid: " + gnssStatus.getSvid(i)
+ " cn0: " + gnssStatus.getCn0DbHz(i)
+ " basebandCn0: " + gnssStatus.getBasebandCn0DbHz(i)
+ " elev: " + gnssStatus.getElevationDegrees(i)
+ " azimuth: " + gnssStatus.getAzimuthDegrees(i)
+ " carrier frequency: " + gnssStatus.getCn0DbHz(i)
+ (gnssStatus.hasEphemerisData(i) ? " E" : " ")
+ (gnssStatus.hasAlmanacData(i) ? " A" : " ")
+ (gnssStatus.usedInFix(i) ? "U" : "")
+ (gnssStatus.hasCarrierFrequencyHz(i) ? "F" : "")
+ (gnssStatus.hasBasebandCn0DbHz(i) ? "B" : ""));
}
}
if (usedInFixCount > 0) {
meanCn0 /= usedInFixCount;
@@ -1601,13 +1543,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
requestUtcTime();
}
mGnssMeasurementsProvider.onCapabilitiesUpdated(
hasCapability(GPS_CAPABILITY_MEASUREMENTS));
mGnssNavigationMessageProvider.onCapabilitiesUpdated(
hasCapability(GPS_CAPABILITY_NAV_MESSAGES));
restartRequests();
mGnssAntennaInfoProvider.onCapabilitiesUpdated(
hasCapability(GPS_CAPABILITY_ANTENNA_INFO));
mGnssCapabilitiesProvider.setTopHalCapabilities(mTopHalCapabilities);
});
@@ -1629,9 +1565,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
Log.i(TAG, "restartRequests");
restartLocationRequest();
mGnssAntennaInfoProvider.resumeIfStarted();
mGnssMeasurementsProvider.resumeIfStarted();
mGnssNavigationMessageProvider.resumeIfStarted();
mGnssBatchingProvider.resumeIfStarted();
mGnssGeofenceProvider.resumeIfStarted();
}
@@ -2062,8 +1995,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
int message = msg.what;
switch (message) {
case SET_REQUEST:
GpsRequest gpsRequest = (GpsRequest) msg.obj;
handleSetRequest(gpsRequest.request, gpsRequest.source);
handleSetRequest((ProviderRequest) msg.obj);
break;
case INJECT_NTP_TIME:
mNtpTimeHelper.retrieveAndInjectNtpTime();
@@ -2152,20 +2084,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
}
}
private abstract class LocationChangeListener implements LocationListener {
private abstract static class LocationChangeListener implements LocationListener {
private int mNumLocationUpdateRequest;
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}
private final class NetworkLocationListener extends LocationChangeListener {
@@ -2222,12 +2142,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
s.append(" ago)").append('\n');
s.append("mFixInterval=").append(mFixInterval).append('\n');
s.append("mLowPowerMode=").append(mLowPowerMode).append('\n');
s.append("mGnssAntennaInfoProvider.isRegistered()=")
.append(mGnssAntennaInfoProvider.isRegistered()).append('\n');
s.append("mGnssMeasurementsProvider.isRegistered()=")
.append(mGnssMeasurementsProvider.isRegistered()).append('\n');
s.append("mGnssNavigationMessageProvider.isRegistered()=")
.append(mGnssNavigationMessageProvider.isRegistered()).append('\n');
s.append("mDisableGpsForPowerManager=").append(mDisableGpsForPowerManager).append('\n');
s.append("mTopHalCapabilities=0x").append(Integer.toHexString(mTopHalCapabilities));
s.append(" ( ");

View File

@@ -144,8 +144,8 @@ public class GnssMeasurementCorrectionsProvider {
}
}
private static native boolean native_is_measurement_corrections_supported();
static native boolean native_is_measurement_corrections_supported();
private static native boolean native_inject_gnss_measurement_corrections(
static native boolean native_inject_gnss_measurement_corrections(
GnssMeasurementCorrections measurementCorrections);
}

View File

@@ -16,163 +16,141 @@
package com.android.server.location;
import android.content.Context;
import android.location.GnssMeasurementsEvent;
import android.location.GnssRequest;
import android.location.IGnssMeasurementsListener;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.Settings;
import android.stats.location.LocationStatsEnums;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.location.gnss.GnssManagerService;
import com.android.server.location.util.listeners.GnssListenerManager;
import java.util.List;
/**
* An base implementation for GPS measurements provider. It abstracts out the responsibility of
* 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
*/
public abstract class GnssMeasurementsProvider
extends RemoteListenerHelper<GnssRequest, IGnssMeasurementsListener> {
private static final String TAG = "GnssMeasProvider";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public class GnssMeasurementsProvider extends
GnssListenerManager<GnssRequest, IGnssMeasurementsListener, Boolean> {
private final LocationUsageLogger mLogger;
private final GnssMeasurementProviderNative mNative;
private boolean mStartedCollection;
private boolean mStartedFullTracking;
protected GnssMeasurementsProvider(Context context, Handler handler) {
this(context, handler, new GnssMeasurementProviderNative());
public GnssMeasurementsProvider(UserInfoHelper userInfoHelper,
SettingsHelper settingsHelper, AppOpsHelper appOpsHelper,
AppForegroundHelper appForegroundHelper, LocationUsageLogger logger) {
this(userInfoHelper, settingsHelper, appOpsHelper, appForegroundHelper, logger,
new GnssMeasurementProviderNative());
}
@VisibleForTesting
public GnssMeasurementsProvider(
Context context, Handler handler, GnssMeasurementProviderNative aNative) {
super(context, handler, TAG);
public GnssMeasurementsProvider(UserInfoHelper userInfoHelper,
SettingsHelper settingsHelper, AppOpsHelper appOpsHelper,
AppForegroundHelper appForegroundHelper, LocationUsageLogger logger,
GnssMeasurementProviderNative aNative) {
super(userInfoHelper, settingsHelper, appOpsHelper, appForegroundHelper);
mLogger = logger;
mNative = aNative;
}
void resumeIfStarted() {
if (DEBUG) {
Log.d(TAG, "resumeIfStarted");
}
if (mStartedCollection) {
mNative.startMeasurementCollection(mStartedFullTracking);
}
@Override
public void addListener(GnssRequest request, CallerIdentity identity,
IGnssMeasurementsListener listener) {
super.addListener(request, identity, listener);
}
@Override
public boolean isAvailableInPlatform() {
return mNative.isMeasurementSupported();
}
private boolean getMergedFullTracking() {
int devOptions = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
int enableFullTracking = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, 0);
boolean enableFullTrackingBySetting = (devOptions == 1 /* Developer Mode enabled */)
&& (enableFullTracking == 1 /* Raw Measurements Full Tracking enabled */);
if (enableFullTrackingBySetting) {
return true;
}
synchronized (mListenerMap) {
for (IdentifiedListener identifiedListener : mListenerMap.values()) {
GnssRequest request = identifiedListener.getRequest();
if (request != null && request.isFullTracking()) {
return true;
protected boolean registerService(Boolean fullTrackingRequest) {
if (mNative.isMeasurementSupported()) {
if (mNative.startMeasurementCollection(fullTrackingRequest)) {
if (GnssManagerService.D) {
Log.d(GnssManagerService.TAG,
"starting gnss measurements (" + fullTrackingRequest + ")");
}
return true;
}
Log.e(GnssManagerService.TAG, "error starting gnss measurements");
}
return false;
}
@Override
protected int registerWithService() {
boolean enableFullTracking = getMergedFullTracking();
boolean result = mNative.startMeasurementCollection(enableFullTracking);
if (result) {
mStartedCollection = true;
mStartedFullTracking = enableFullTracking;
return RemoteListenerHelper.RESULT_SUCCESS;
} else {
return RemoteListenerHelper.RESULT_INTERNAL_ERROR;
}
}
@Override
protected void unregisterFromService() {
boolean stopped = mNative.stopMeasurementCollection();
if (stopped) {
mStartedCollection = false;
}
}
public void onMeasurementsAvailable(final GnssMeasurementsEvent event) {
foreach((IGnssMeasurementsListener listener, CallerIdentity callerIdentity) -> {
if (!hasPermission(mContext, callerIdentity)) {
logPermissionDisabledEventNotReported(
TAG, callerIdentity.packageName, "GNSS measurements");
return;
protected void unregisterService() {
if (mNative.isMeasurementSupported()) {
if (mNative.stopMeasurementCollection()) {
if (GnssManagerService.D) {
Log.d(GnssManagerService.TAG, "stopping gnss measurements");
}
} else {
Log.e(GnssManagerService.TAG, "error stopping gnss measurements");
}
listener.onGnssMeasurementsReceived(event);
});
}
/** Handle GNSS capabilities update from the GNSS HAL implementation. */
public void onCapabilitiesUpdated(boolean isGnssMeasurementsSupported) {
setSupported(isGnssMeasurementsSupported);
updateResult();
}
public void onGpsEnabledChanged() {
tryUpdateRegistrationWithService();
updateResult();
}
}
@Override
protected ListenerOperation<IGnssMeasurementsListener> getHandlerOperation(int result) {
int status;
switch (result) {
case RESULT_SUCCESS:
status = GnssMeasurementsEvent.Callback.STATUS_READY;
break;
case RESULT_NOT_AVAILABLE:
case RESULT_NOT_SUPPORTED:
case RESULT_INTERNAL_ERROR:
status = GnssMeasurementsEvent.Callback.STATUS_NOT_SUPPORTED;
break;
case RESULT_NOT_ALLOWED:
status = GnssMeasurementsEvent.Callback.STATUS_NOT_ALLOWED;
break;
case RESULT_GPS_LOCATION_DISABLED:
status = GnssMeasurementsEvent.Callback.STATUS_LOCATION_DISABLED;
break;
case RESULT_UNKNOWN:
return null;
default:
Log.v(TAG, "Unhandled addListener result: " + result);
return null;
protected Boolean mergeRequests(List<GnssRegistration> registrations) {
if (mSettingsHelper.isGnssMeasurementsFullTrackingEnabled()) {
return true;
}
return new StatusChangedOperation(status);
for (GnssRegistration registration : registrations) {
if (registration.getRequest().isFullTracking()) {
return true;
}
}
return false;
}
private static class StatusChangedOperation
implements ListenerOperation<IGnssMeasurementsListener> {
private final int mStatus;
@Override
protected void onRegistrationAdded(IBinder key, GnssRegistration registration) {
mLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_STARTED,
LocationStatsEnums.API_ADD_GNSS_MEASUREMENTS_LISTENER,
registration.getIdentity().packageName,
/* LocationRequest= */ null,
/* hasListener= */ true,
/* hasIntent= */ false,
/* geofence= */ null,
registration.isForeground());
}
public StatusChangedOperation(int status) {
mStatus = status;
}
@Override
protected void onRegistrationRemoved(IBinder key, GnssRegistration registration) {
mLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_ENDED,
LocationStatsEnums.API_ADD_GNSS_MEASUREMENTS_LISTENER,
registration.getIdentity().packageName,
/* LocationRequest= */ null,
/* hasListener= */ true,
/* hasIntent= */ false,
/* geofence= */ null,
registration.isForeground());
}
@Override
public void execute(IGnssMeasurementsListener listener,
CallerIdentity callerIdentity) throws RemoteException {
listener.onStatusChanged(mStatus);
}
/**
* Called by GnssLocationProvider.
*/
public void onMeasurementsAvailable(GnssMeasurementsEvent event) {
deliverToListeners(listener -> {
try {
listener.onGnssMeasurementsReceived(event);
} catch (RemoteException e) {
// ignore - the listener will get cleaned up later anyways
}
}, registration -> mAppOpsHelper.noteLocationAccess(registration.getIdentity()));
}
@Override
protected boolean isServiceSupported() {
return mNative.isMeasurementSupported();
}
@VisibleForTesting
@@ -190,9 +168,9 @@ public abstract class GnssMeasurementsProvider
}
}
private static native boolean native_is_measurement_supported();
static native boolean native_is_measurement_supported();
private static native boolean native_start_measurement_collection(boolean enableFullTracking);
static native boolean native_start_measurement_collection(boolean enableFullTracking);
private static native boolean native_stop_measurement_collection();
static native boolean native_stop_measurement_collection();
}

View File

@@ -16,14 +16,14 @@
package com.android.server.location;
import android.content.Context;
import android.location.GnssNavigationMessage;
import android.location.IGnssNavigationMessageListener;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.location.gnss.GnssManagerService;
import com.android.server.location.util.listeners.GnssListenerManager;
/**
* An base implementation for GPS navigation messages provider.
@@ -32,113 +32,75 @@ import com.android.internal.annotations.VisibleForTesting;
*
* @hide
*/
public abstract class GnssNavigationMessageProvider
extends RemoteListenerHelper<Void, IGnssNavigationMessageListener> {
private static final String TAG = "GnssNavigationMessageProvider";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public class GnssNavigationMessageProvider extends
GnssListenerManager<Void, IGnssNavigationMessageListener, Void> {
private final GnssNavigationMessageProviderNative mNative;
private boolean mCollectionStarted;
protected GnssNavigationMessageProvider(Context context, Handler handler) {
this(context, handler, new GnssNavigationMessageProviderNative());
public GnssNavigationMessageProvider(UserInfoHelper userInfoHelper,
SettingsHelper settingsHelper, AppOpsHelper appOpsHelper,
AppForegroundHelper appForegroundHelper) {
this(userInfoHelper, settingsHelper, appOpsHelper, appForegroundHelper,
new GnssNavigationMessageProviderNative());
}
@VisibleForTesting
public GnssNavigationMessageProvider(Context context, Handler handler,
GnssNavigationMessageProviderNative aNative) {
super(context, handler, TAG);
public GnssNavigationMessageProvider(UserInfoHelper userInfoHelper,
SettingsHelper settingsHelper, AppOpsHelper appOpsHelper,
AppForegroundHelper appForegroundHelper, GnssNavigationMessageProviderNative aNative) {
super(userInfoHelper, settingsHelper, appOpsHelper, appForegroundHelper);
mNative = aNative;
}
void resumeIfStarted() {
if (DEBUG) {
Log.d(TAG, "resumeIfStarted");
}
if (mCollectionStarted) {
mNative.startNavigationMessageCollection();
}
@Override
public void addListener(CallerIdentity identity, IGnssNavigationMessageListener listener) {
super.addListener(identity, listener);
}
@Override
protected boolean isAvailableInPlatform() {
return mNative.isNavigationMessageSupported();
}
@Override
protected int registerWithService() {
boolean result = mNative.startNavigationMessageCollection();
if (result) {
mCollectionStarted = true;
return RemoteListenerHelper.RESULT_SUCCESS;
} else {
return RemoteListenerHelper.RESULT_INTERNAL_ERROR;
}
}
@Override
protected void unregisterFromService() {
boolean stopped = mNative.stopNavigationMessageCollection();
if (stopped) {
mCollectionStarted = false;
}
}
public void onNavigationMessageAvailable(final GnssNavigationMessage event) {
foreach((IGnssNavigationMessageListener listener, CallerIdentity callerIdentity) -> {
listener.onGnssNavigationMessageReceived(event);
protected boolean registerService(Void ignored) {
if (mNative.isNavigationMessageSupported()) {
if (mNative.startNavigationMessageCollection()) {
if (GnssManagerService.D) {
Log.d(GnssManagerService.TAG, "starting gnss navigation messages");
}
);
}
return true;
}
/** Handle GNSS capabilities update from the GNSS HAL implementation */
public void onCapabilitiesUpdated(boolean isGnssNavigationMessageSupported) {
setSupported(isGnssNavigationMessageSupported);
updateResult();
}
public void onGpsEnabledChanged() {
tryUpdateRegistrationWithService();
updateResult();
Log.e(GnssManagerService.TAG, "error starting gnss navigation messages");
}
return false;
}
@Override
protected ListenerOperation<IGnssNavigationMessageListener> getHandlerOperation(int result) {
int status;
switch (result) {
case RESULT_SUCCESS:
status = GnssNavigationMessage.Callback.STATUS_READY;
break;
case RESULT_NOT_AVAILABLE:
case RESULT_NOT_SUPPORTED:
case RESULT_INTERNAL_ERROR:
status = GnssNavigationMessage.Callback.STATUS_NOT_SUPPORTED;
break;
case RESULT_GPS_LOCATION_DISABLED:
status = GnssNavigationMessage.Callback.STATUS_LOCATION_DISABLED;
break;
case RESULT_UNKNOWN:
return null;
default:
Log.v(TAG, "Unhandled addListener result: " + result);
return null;
protected void unregisterService() {
if (mNative.isNavigationMessageSupported()) {
if (mNative.stopNavigationMessageCollection()) {
if (GnssManagerService.D) {
Log.d(GnssManagerService.TAG, "stopping gnss navigation messages");
}
} else {
Log.e(GnssManagerService.TAG, "error stopping gnss navigation messages");
}
}
return new StatusChangedOperation(status);
}
private static class StatusChangedOperation
implements ListenerOperation<IGnssNavigationMessageListener> {
private final int mStatus;
/**
* Called by GnssLocationProvider.
*/
public void onNavigationMessageAvailable(GnssNavigationMessage event) {
deliverToListeners((listener) -> {
try {
listener.onGnssNavigationMessageReceived(event);
} catch (RemoteException e) {
// ignore - the listener will get cleaned up later anyways
}
}, registration -> mAppOpsHelper.noteLocationAccess(registration.getIdentity()));
}
public StatusChangedOperation(int status) {
mStatus = status;
}
@Override
public void execute(IGnssNavigationMessageListener listener,
CallerIdentity callerIdentity) throws RemoteException {
listener.onStatusChanged(mStatus);
}
@Override
protected boolean isServiceSupported() {
return mNative.isNavigationMessageSupported();
}
@VisibleForTesting
@@ -156,9 +118,9 @@ public abstract class GnssNavigationMessageProvider
}
}
private static native boolean native_is_navigation_message_supported();
static native boolean native_is_navigation_message_supported();
private static native boolean native_start_navigation_message_collection();
static native boolean native_start_navigation_message_collection();
private static native boolean native_stop_navigation_message_collection();
static native boolean native_stop_navigation_message_collection();
}

View File

@@ -1,97 +0,0 @@
/*
* Copyright (C) 2014 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;
import android.content.Context;
import android.location.IGnssStatusListener;
import android.os.Handler;
import android.util.Log;
/**
* Implementation of a handler for {@link IGnssStatusListener}.
*/
public abstract class GnssStatusListenerHelper extends
RemoteListenerHelper<Void, IGnssStatusListener> {
private static final String TAG = "GnssStatusListenerHelper";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
protected GnssStatusListenerHelper(Context context, Handler handler) {
super(context, handler, TAG);
setSupported(GnssLocationProvider.isSupported());
}
@Override
protected int registerWithService() {
return RemoteListenerHelper.RESULT_SUCCESS;
}
@Override
protected void unregisterFromService() {}
@Override
protected ListenerOperation<IGnssStatusListener> getHandlerOperation(int result) {
return null;
}
public void onStatusChanged(boolean isNavigating) {
if (isNavigating) {
foreach((IGnssStatusListener listener, CallerIdentity callerIdentity) -> {
listener.onGnssStarted();
});
} else {
foreach((IGnssStatusListener listener, CallerIdentity callerIdentity) -> {
listener.onGnssStopped();
});
}
}
public void onFirstFix(final int timeToFirstFix) {
foreach((IGnssStatusListener listener, CallerIdentity callerIdentity) -> {
listener.onFirstFix(timeToFirstFix);
}
);
}
public void onSvStatusChanged(
final int svCount,
final int[] prnWithFlags,
final float[] cn0s,
final float[] elevations,
final float[] azimuths,
final float[] carrierFreqs,
final float[] basebandCn0s) {
foreach((IGnssStatusListener listener, CallerIdentity callerIdentity) -> {
if (!hasPermission(mContext, callerIdentity)) {
logPermissionDisabledEventNotReported(TAG, callerIdentity.packageName,
"GNSS status");
return;
}
listener.onSvStatusChanged(svCount, prnWithFlags, cn0s, elevations, azimuths,
carrierFreqs, basebandCn0s);
});
}
public void onNmeaReceived(final long timestamp, final String nmea) {
foreach((IGnssStatusListener listener, CallerIdentity callerIdentity) -> {
if (!hasPermission(mContext, callerIdentity)) {
logPermissionDisabledEventNotReported(TAG, callerIdentity.packageName, "NMEA");
return;
}
listener.onNmeaReceived(timestamp, nmea);
});
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright (C) 2014 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;
import android.location.IGnssStatusListener;
import android.os.IBinder;
import android.os.RemoteException;
import android.stats.location.LocationStatsEnums;
import android.util.Log;
import com.android.server.location.gnss.GnssManagerService;
import com.android.server.location.util.listeners.GnssListenerManager;
/**
* Implementation of a handler for {@link IGnssStatusListener}.
*/
public class GnssStatusProvider extends GnssListenerManager<Void, IGnssStatusListener, Void> {
private final LocationUsageLogger mLogger;
public GnssStatusProvider(UserInfoHelper userInfoHelper, SettingsHelper settingsHelper,
AppOpsHelper appOpsHelper, AppForegroundHelper appForegroundHelper,
LocationUsageLogger logger) {
super(userInfoHelper, settingsHelper, appOpsHelper, appForegroundHelper);
mLogger = logger;
}
@Override
public void addListener(CallerIdentity identity, IGnssStatusListener listener) {
super.addListener(identity, listener);
}
@Override
protected boolean registerService(Void ignored) {
if (GnssManagerService.D) {
Log.d(GnssManagerService.TAG, "starting gnss status");
}
return true;
}
@Override
protected void unregisterService() {
if (GnssManagerService.D) {
Log.d(GnssManagerService.TAG, "stopping gnss status");
}
}
@Override
protected void onRegistrationAdded(IBinder key, GnssRegistration registration) {
mLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_STARTED,
LocationStatsEnums.API_REGISTER_GNSS_STATUS_CALLBACK,
registration.getIdentity().packageName,
/* LocationRequest= */ null,
/* hasListener= */ true,
/* hasIntent= */ false,
/* geofence= */ null,
registration.isForeground());
}
@Override
protected void onRegistrationRemoved(IBinder key, GnssRegistration registration) {
mLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_ENDED,
LocationStatsEnums.API_REGISTER_GNSS_STATUS_CALLBACK,
registration.getIdentity().packageName,
/* LocationRequest= */ null,
/* hasListener= */ true,
/* hasIntent= */ false,
/* geofence= */ null,
registration.isForeground());
}
/**
* Called by GnssLocationProvider.
*/
public void onStatusChanged(boolean isNavigating) {
if (isNavigating) {
deliverToListeners((listener) -> {
try {
listener.onGnssStarted();
} catch (RemoteException e) {
// ignore - the listener will get cleaned up later anyways
}
});
} else {
deliverToListeners((listener) -> {
try {
listener.onGnssStopped();
} catch (RemoteException e) {
// ignore - the listener will get cleaned up later anyways
}
});
}
}
/**
* Called by GnssLocationProvider.
*/
public void onFirstFix(int ttff) {
deliverToListeners((listener) -> {
try {
listener.onFirstFix(ttff);
} catch (RemoteException e) {
// ignore - the listener will get cleaned up later anyways
}
});
}
/**
* Called by GnssLocationProvider.
*/
public void onSvStatusChanged(
final int svCount,
final int[] prnWithFlags,
final float[] cn0s,
final float[] elevations,
final float[] azimuths,
final float[] carrierFreqs,
final float[] basebandCn0s) {
deliverToListeners((listener) -> {
try {
listener.onSvStatusChanged(svCount, prnWithFlags, cn0s, elevations, azimuths,
carrierFreqs, basebandCn0s);
} catch (RemoteException e) {
// ignore - the listener will get cleaned up later anyways
}
}, registration -> mAppOpsHelper.noteLocationAccess(registration.getIdentity()));
}
/**
* Called by GnssLocationProvider.
*/
public void onNmeaReceived(long timestamp, String nmea) {
deliverToListeners((listener) -> {
try {
listener.onNmeaReceived(timestamp, nmea);
} catch (RemoteException e) {
// ignore - the listener will get cleaned up later anyways
}
}, registration -> mAppOpsHelper.noteLocationAccess(registration.getIdentity()));
}
}

View File

@@ -18,7 +18,6 @@ package com.android.server.location;
import static com.android.server.LocationManagerService.TAG;
import android.app.ActivityManager;
import android.location.Geofence;
import android.location.LocationManager;
import android.location.LocationRequest;
@@ -52,7 +51,7 @@ public class LocationUsageLogger {
public void logLocationApiUsage(int usageType, int apiInUse,
String packageName, LocationRequest locationRequest,
boolean hasListener, boolean hasIntent,
Geofence geofence, int activityImportance) {
Geofence geofence, boolean foreground) {
try {
if (hitApiUsageLogCap()) {
return;
@@ -85,7 +84,7 @@ public class LocationUsageLogger {
isGeofenceNull
? LocationStatsEnums.RADIUS_UNKNOWN
: bucketizeRadius(geofence.getRadius()),
categorizeActivityImportance(activityImportance));
categorizeActivityImportance(foreground));
} catch (Exception e) {
// Swallow exceptions to avoid crashing LMS.
Log.w(TAG, "Failed to log API usage to statsd.", e);
@@ -197,13 +196,9 @@ public class LocationUsageLogger {
}
}
private static int categorizeActivityImportance(int importance) {
if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
private static int categorizeActivityImportance(boolean foreground) {
if (foreground) {
return LocationStatsEnums.IMPORTANCE_TOP;
} else if (importance == ActivityManager
.RunningAppProcessInfo
.IMPORTANCE_FOREGROUND_SERVICE) {
return LocationStatsEnums.IMPORTANCE_FORGROUND_SERVICE;
} else {
return LocationStatsEnums.IMPORTANCE_BACKGROUND;
}

View File

@@ -1,300 +0,0 @@
/*
* Copyright (C) 2014 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;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* A helper class that handles operations in remote listeners.
*
* @param <TRequest> the type of request.
* @param <TListener> the type of GNSS data listener.
*/
public abstract class RemoteListenerHelper<TRequest, TListener extends IInterface> {
protected static final int RESULT_SUCCESS = 0;
protected static final int RESULT_NOT_AVAILABLE = 1;
protected static final int RESULT_NOT_SUPPORTED = 2;
protected static final int RESULT_GPS_LOCATION_DISABLED = 3;
protected static final int RESULT_INTERNAL_ERROR = 4;
protected static final int RESULT_UNKNOWN = 5;
protected static final int RESULT_NOT_ALLOWED = 6;
protected final Handler mHandler;
private final String mTag;
protected final Map<IBinder, IdentifiedListener> mListenerMap = new HashMap<>();
protected final Context mContext;
protected final AppOpsManager mAppOps;
private volatile boolean mIsRegistered; // must access only on handler thread, or read-only
private boolean mHasIsSupported;
private boolean mIsSupported;
private int mLastReportedResult = RESULT_UNKNOWN;
protected RemoteListenerHelper(Context context, Handler handler, String name) {
Objects.requireNonNull(name);
mHandler = handler;
mTag = name;
mContext = context;
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
}
// read-only access for a dump() thread assured via volatile
public boolean isRegistered() {
return mIsRegistered;
}
/**
* Adds GNSS data listener {@code listener} with caller identify {@code callerIdentify}.
*/
public void addListener(@Nullable TRequest request, @NonNull TListener listener,
CallerIdentity callerIdentity) {
Objects.requireNonNull(listener, "Attempted to register a 'null' listener.");
IBinder binder = listener.asBinder();
synchronized (mListenerMap) {
if (mListenerMap.containsKey(binder)) {
// listener already added
return;
}
IdentifiedListener identifiedListener = new IdentifiedListener(request, listener,
callerIdentity);
mListenerMap.put(binder, identifiedListener);
// update statuses we already know about, starting from the ones that will never change
int result;
if (!isAvailableInPlatform()) {
result = RESULT_NOT_AVAILABLE;
} else if (mHasIsSupported && !mIsSupported) {
result = RESULT_NOT_SUPPORTED;
} else if (!isGpsEnabled()) {
// only attempt to register if GPS is enabled, otherwise we will register once GPS
// becomes available
result = RESULT_GPS_LOCATION_DISABLED;
} else if (mHasIsSupported && mIsSupported) {
tryRegister();
// initially presume success, possible internal error could follow asynchornously
result = RESULT_SUCCESS;
} else {
// at this point if the supported flag is not set, the notification will be sent
// asynchronously in the future
return;
}
post(identifiedListener, getHandlerOperation(result));
}
}
/**
* Remove GNSS data listener {@code listener}.
*/
public void removeListener(@NonNull TListener listener) {
Objects.requireNonNull(listener, "Attempted to remove a 'null' listener.");
synchronized (mListenerMap) {
mListenerMap.remove(listener.asBinder());
if (mListenerMap.isEmpty()) {
tryUnregister();
}
}
}
protected abstract boolean isAvailableInPlatform();
protected abstract boolean isGpsEnabled();
// must access only on handler thread
protected abstract int registerWithService();
protected abstract void unregisterFromService(); // must access only on handler thread
protected abstract ListenerOperation<TListener> getHandlerOperation(int result);
protected interface ListenerOperation<TListener extends IInterface> {
void execute(TListener listener, CallerIdentity callerIdentity) throws RemoteException;
}
protected void foreach(ListenerOperation<TListener> operation) {
synchronized (mListenerMap) {
foreachUnsafe(operation);
}
}
protected void setSupported(boolean value) {
synchronized (mListenerMap) {
mHasIsSupported = true;
mIsSupported = value;
}
}
protected void tryUpdateRegistrationWithService() {
synchronized (mListenerMap) {
if (!isGpsEnabled()) {
tryUnregister();
return;
}
if (mListenerMap.isEmpty()) {
return;
}
tryRegister();
}
}
protected void updateResult() {
synchronized (mListenerMap) {
int newResult = calculateCurrentResultUnsafe();
if (mLastReportedResult == newResult) {
return;
}
foreachUnsafe(getHandlerOperation(newResult));
mLastReportedResult = newResult;
}
}
protected boolean hasPermission(Context context, CallerIdentity callerIdentity) {
if (LocationPermissionUtil.doesCallerReportToAppOps(context, callerIdentity)) {
// The caller is identified as a location provider that will report location
// access to AppOps. Skip noteOp but do checkOp to check for location permission.
return mAppOps.checkOpNoThrow(AppOpsManager.OP_FINE_LOCATION, callerIdentity.uid,
callerIdentity.packageName) == AppOpsManager.MODE_ALLOWED;
}
return mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, callerIdentity.uid,
callerIdentity.packageName, callerIdentity.featureId, null)
== AppOpsManager.MODE_ALLOWED;
}
protected void logPermissionDisabledEventNotReported(String tag, String packageName,
String event) {
if (Log.isLoggable(tag, Log.DEBUG)) {
Log.d(tag, "Location permission disabled. Skipping " + event + " reporting for app: "
+ packageName);
}
}
private void foreachUnsafe(ListenerOperation<TListener> operation) {
for (IdentifiedListener identifiedListener : mListenerMap.values()) {
post(identifiedListener, operation);
}
}
private void post(IdentifiedListener identifiedListener,
ListenerOperation<TListener> operation) {
if (operation != null) {
mHandler.post(new HandlerRunnable(identifiedListener, operation));
}
}
private void tryRegister() {
mHandler.post(new Runnable() {
int registrationState = RESULT_INTERNAL_ERROR;
@Override
public void run() {
if (!mIsRegistered) {
registrationState = registerWithService();
mIsRegistered = registrationState == RESULT_SUCCESS;
}
if (!mIsRegistered) {
// post back a failure
mHandler.post(() -> {
synchronized (mListenerMap) {
foreachUnsafe(getHandlerOperation(registrationState));
}
});
}
}
});
}
private void tryUnregister() {
mHandler.post(() -> {
if (!mIsRegistered) {
return;
}
unregisterFromService();
mIsRegistered = false;
}
);
}
private int calculateCurrentResultUnsafe() {
// update statuses we already know about, starting from the ones that will never change
if (!isAvailableInPlatform()) {
return RESULT_NOT_AVAILABLE;
}
if (!mHasIsSupported || mListenerMap.isEmpty()) {
// we'll update once we have a supported status available
return RESULT_UNKNOWN;
}
if (!mIsSupported) {
return RESULT_NOT_SUPPORTED;
}
if (!isGpsEnabled()) {
return RESULT_GPS_LOCATION_DISABLED;
}
return RESULT_SUCCESS;
}
protected class IdentifiedListener {
@Nullable private final TRequest mRequest;
private final TListener mListener;
private final CallerIdentity mCallerIdentity;
private IdentifiedListener(@Nullable TRequest request, @NonNull TListener listener,
CallerIdentity callerIdentity) {
mListener = listener;
mRequest = request;
mCallerIdentity = callerIdentity;
}
@Nullable
protected TRequest getRequest() {
return mRequest;
}
}
private class HandlerRunnable implements Runnable {
private final IdentifiedListener mIdentifiedListener;
private final ListenerOperation<TListener> mOperation;
private HandlerRunnable(IdentifiedListener identifiedListener,
ListenerOperation<TListener> operation) {
mIdentifiedListener = identifiedListener;
mOperation = operation;
}
@Override
public void run() {
try {
mOperation.execute(mIdentifiedListener.mListener,
mIdentifiedListener.mCallerIdentity);
} catch (RemoteException e) {
Log.v(mTag, "Error in monitored listener.", e);
}
}
}
}

View File

@@ -18,6 +18,7 @@ package com.android.server.location;
import static android.location.LocationManager.FUSED_PROVIDER;
import static android.location.LocationManager.PASSIVE_PROVIDER;
import static android.provider.Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING;
import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS;
import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST;
import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS;
@@ -282,6 +283,19 @@ public class SettingsHelper {
}
}
/**
* Retrieve the gnss measurements full tracking enabled setting.
*/
public boolean isGnssMeasurementsFullTrackingEnabled() {
long identity = Binder.clearCallingIdentity();
try {
return Settings.Global.getInt(mContext.getContentResolver(),
ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, 0) == 1;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Retrieve the accuracy for coarsening location, ie, the grid size used for snap-to-grid
* coarsening.

View File

@@ -33,12 +33,7 @@ import android.location.INetInitiatedListener;
import android.location.Location;
import android.location.LocationManagerInternal;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Process;
import android.os.RemoteException;
import android.stats.location.LocationStatsEnums;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -46,8 +41,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.LocationManagerServiceUtils.LinkedListener;
import com.android.server.LocationManagerServiceUtils.LinkedListenerBase;
import com.android.server.location.AppForegroundHelper;
import com.android.server.location.AppOpsHelper;
import com.android.server.location.CallerIdentity;
@@ -58,35 +51,33 @@ import com.android.server.location.GnssLocationProvider;
import com.android.server.location.GnssMeasurementCorrectionsProvider;
import com.android.server.location.GnssMeasurementsProvider;
import com.android.server.location.GnssNavigationMessageProvider;
import com.android.server.location.GnssStatusListenerHelper;
import com.android.server.location.GnssStatusProvider;
import com.android.server.location.LocationUsageLogger;
import com.android.server.location.RemoteListenerHelper;
import com.android.server.location.SettingsHelper;
import com.android.server.location.UserInfoHelper;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
/** Manages Gnss providers and related Gnss functions for LocationManagerService. */
public class GnssManagerService {
private static final String TAG = "GnssManagerService";
public static final String TAG = "GnssManager";
public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
public static boolean isGnssSupported() {
return GnssLocationProvider.isSupported();
}
private final Context mContext;
private final AppOpsHelper mAppOpsHelper;
private final SettingsHelper mSettingsHelper;
private final AppOpsHelper mAppOpsHelper;
private final AppForegroundHelper mAppForegroundHelper;
private final LocationUsageLogger mLocationUsageLogger;
private final LocationManagerInternal mLocationManagerInternal;
private final GnssLocationProvider mGnssLocationProvider;
private final GnssStatusListenerHelper mGnssStatusProvider;
private final GnssStatusProvider mGnssStatusProvider;
private final GnssMeasurementsProvider mGnssMeasurementsProvider;
private final GnssMeasurementCorrectionsProvider mGnssMeasurementCorrectionsProvider;
private final GnssAntennaInfoProvider mGnssAntennaInfoProvider;
@@ -98,59 +89,40 @@ public class GnssManagerService {
private final INetInitiatedListener mNetInitiatedListener;
private final IGpsGeofenceHardware mGpsGeofenceProxy;
@GuardedBy("mGnssMeasurementsListeners")
private final ArrayMap<IBinder, LinkedListener<GnssRequest, IGnssMeasurementsListener>>
mGnssMeasurementsListeners = new ArrayMap<>();
@GuardedBy("mGnssAntennaInfoListeners")
private final ArrayMap<IBinder,
LinkedListener<Void, IGnssAntennaInfoListener>>
mGnssAntennaInfoListeners = new ArrayMap<>();
@GuardedBy("mGnssNavigationMessageListeners")
private final ArrayMap<IBinder, LinkedListener<Void, IGnssNavigationMessageListener>>
mGnssNavigationMessageListeners = new ArrayMap<>();
@GuardedBy("mGnssStatusListeners")
private final ArrayMap<IBinder, LinkedListener<Void, IGnssStatusListener>>
mGnssStatusListeners = new ArrayMap<>();
@GuardedBy("this")
@Nullable private LocationManagerInternal mLocationManagerInternal;
private final Object mGnssBatchingLock = new Object();
@GuardedBy("mGnssBatchingLock")
@Nullable private IBatchedLocationCallback mGnssBatchingCallback;
private @Nullable IBatchedLocationCallback mGnssBatchingCallback;
@GuardedBy("mGnssBatchingLock")
@Nullable
private LinkedListener<Void, IBatchedLocationCallback> mGnssBatchingDeathCallback;
private @Nullable CallerIdentity mGnssBatchingIdentity;
@GuardedBy("mGnssBatchingLock")
private @Nullable Binder.DeathRecipient mGnssBatchingDeathRecipient;
@GuardedBy("mGnssBatchingLock")
private boolean mGnssBatchingInProgress = false;
public GnssManagerService(Context context, AppOpsHelper appOpsHelper,
SettingsHelper settingsHelper, AppForegroundHelper appForegroundHelper,
LocationUsageLogger locationUsageLogger) {
this(context, appOpsHelper, settingsHelper, appForegroundHelper, locationUsageLogger, null);
public GnssManagerService(Context context, UserInfoHelper userInfoHelper,
SettingsHelper settingsHelper, AppOpsHelper appOpsHelper,
AppForegroundHelper appForegroundHelper, LocationUsageLogger locationUsageLogger) {
this(context, userInfoHelper, settingsHelper, appOpsHelper, appForegroundHelper,
locationUsageLogger, null);
}
// Can use this constructor to inject GnssLocationProvider for testing
@VisibleForTesting
GnssManagerService(Context context, AppOpsHelper appOpsHelper, SettingsHelper settingsHelper,
GnssManagerService(Context context, UserInfoHelper userInfoHelper,
SettingsHelper settingsHelper, AppOpsHelper appOpsHelper,
AppForegroundHelper appForegroundHelper, LocationUsageLogger locationUsageLogger,
GnssLocationProvider gnssLocationProvider) {
Preconditions.checkState(isGnssSupported());
mContext = context;
mAppOpsHelper = appOpsHelper;
mSettingsHelper = settingsHelper;
mAppOpsHelper = appOpsHelper;
mAppForegroundHelper = appForegroundHelper;
mLocationUsageLogger = locationUsageLogger;
mLocationManagerInternal = LocalServices.getService(LocationManagerInternal.class);
if (gnssLocationProvider == null) {
gnssLocationProvider = new GnssLocationProvider(mContext);
gnssLocationProvider = new GnssLocationProvider(mContext, userInfoHelper,
mSettingsHelper, mAppOpsHelper, mAppForegroundHelper, locationUsageLogger);
}
mGnssLocationProvider = gnssLocationProvider;
@@ -170,17 +142,9 @@ public class GnssManagerService {
/** Called when system is ready. */
public synchronized void onSystemReady() {
if (mLocationManagerInternal != null) {
return;
}
mAppOpsHelper.onSystemReady();
mSettingsHelper.onSystemReady();
mAppForegroundHelper.onSystemReady();
mLocationManagerInternal = LocalServices.getService(LocationManagerInternal.class);
mAppForegroundHelper.addListener(this::onAppForegroundChanged);
}
/** Retrieve the GnssLocationProvider. */
@@ -220,8 +184,8 @@ public class GnssManagerService {
* Get size of GNSS batch (GNSS location results are batched together for power savings).
*/
public int getGnssBatchSize(String packageName) {
mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE, null);
mContext.enforceCallingPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null);
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
synchronized (mGnssBatchingLock) {
return mGnssBatchingProvider.getBatchSize();
@@ -234,8 +198,8 @@ public class GnssManagerService {
*/
public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName,
String featureId) {
mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE, null);
mContext.enforceCallingPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null);
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId);
if (!mAppOpsHelper.checkLocationAccess(identity)) {
@@ -259,25 +223,28 @@ public class GnssManagerService {
*/
public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
@Nullable String featureId) {
mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE, null);
mContext.enforceCallingPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null);
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId);
synchronized (mGnssBatchingLock) {
mGnssBatchingCallback = callback;
mGnssBatchingDeathCallback =
new LinkedListener<>(
/* request= */ null,
callback,
identity,
(IBatchedLocationCallback listener) -> {
stopGnssBatch();
removeGnssBatchingCallback();
});
Binder.DeathRecipient deathRecipient = () -> {
synchronized (mGnssBatchingLock) {
stopGnssBatch();
removeGnssBatchingCallback();
}
};
return mGnssBatchingDeathCallback.linkToListenerDeathNotificationLocked(
callback.asBinder());
try {
callback.asBinder().linkToDeath(mGnssBatchingDeathRecipient, 0);
mGnssBatchingCallback = callback;
mGnssBatchingIdentity = identity;
mGnssBatchingDeathRecipient = deathRecipient;
return true;
} catch (RemoteException e) {
return false;
}
}
}
@@ -287,8 +254,8 @@ public class GnssManagerService {
* @param packageName name of requesting package
*/
public void flushGnssBatch(String packageName) {
mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE, null);
mContext.enforceCallingPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null);
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
synchronized (mGnssBatchingLock) {
mGnssBatchingProvider.flush();
@@ -299,13 +266,17 @@ public class GnssManagerService {
* Removes GNSS batching callback.
*/
public void removeGnssBatchingCallback() {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, null);
mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null);
synchronized (mGnssBatchingLock) {
mGnssBatchingDeathCallback.unlinkFromListenerDeathNotificationLocked(
mGnssBatchingCallback.asBinder());
if (mGnssBatchingCallback == null) {
return;
}
mGnssBatchingCallback.asBinder().unlinkToDeath(mGnssBatchingDeathRecipient, 0);
mGnssBatchingCallback = null;
mGnssBatchingDeathCallback = null;
mGnssBatchingIdentity = null;
mGnssBatchingDeathRecipient = null;
}
}
@@ -313,7 +284,7 @@ public class GnssManagerService {
* Stop GNSS batch collection.
*/
public boolean stopGnssBatch() {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, null);
mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null);
synchronized (mGnssBatchingLock) {
mGnssBatchingInProgress = false;
@@ -321,192 +292,37 @@ public class GnssManagerService {
}
}
private void onAppForegroundChanged(int uid, boolean foreground) {
synchronized (mGnssMeasurementsListeners) {
updateListenersOnForegroundChangedLocked(
mGnssMeasurementsListeners,
mGnssMeasurementsProvider,
IGnssMeasurementsListener.Stub::asInterface,
uid,
foreground);
}
synchronized (mGnssNavigationMessageListeners) {
updateListenersOnForegroundChangedLocked(
mGnssNavigationMessageListeners,
mGnssNavigationMessageProvider,
IGnssNavigationMessageListener.Stub::asInterface,
uid,
foreground);
}
synchronized (mGnssStatusListeners) {
updateListenersOnForegroundChangedLocked(
mGnssStatusListeners,
mGnssStatusProvider,
IGnssStatusListener.Stub::asInterface,
uid,
foreground);
}
synchronized (mGnssAntennaInfoListeners) {
updateListenersOnForegroundChangedLocked(
mGnssAntennaInfoListeners,
mGnssAntennaInfoProvider,
IGnssAntennaInfoListener.Stub::asInterface,
uid,
foreground);
}
}
private <TRequest, TListener extends IInterface> void updateListenersOnForegroundChangedLocked(
Map<IBinder, LinkedListener<TRequest, TListener>> gnssDataListeners,
RemoteListenerHelper<TRequest, TListener> gnssDataProvider,
Function<IBinder, TListener> mapBinderToListener,
int uid,
boolean foreground) {
for (Map.Entry<IBinder, LinkedListener<TRequest, TListener>> entry :
gnssDataListeners.entrySet()) {
LinkedListener<TRequest, TListener> linkedListener = entry.getValue();
CallerIdentity callerIdentity = linkedListener.getCallerIdentity();
TRequest request = linkedListener.getRequest();
if (callerIdentity.uid != uid) {
continue;
}
TListener listener = mapBinderToListener.apply(entry.getKey());
if (foreground || isThrottlingExempt(callerIdentity)) {
gnssDataProvider.addListener(request, listener, callerIdentity);
} else {
gnssDataProvider.removeListener(listener);
}
}
}
private <TListener extends IInterface, TRequest> boolean addGnssDataListenerLocked(
@Nullable TRequest request,
TListener listener,
String packageName,
@Nullable String featureId,
RemoteListenerHelper<TRequest, TListener> gnssDataProvider,
ArrayMap<IBinder,
LinkedListener<TRequest, TListener>> gnssDataListeners,
Consumer<TListener> binderDeathCallback) {
mContext.enforceCallingPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId);
if (!mAppOpsHelper.checkLocationAccess(identity)) {
return false;
}
LinkedListener<TRequest, TListener> linkedListener = new LinkedListener<>(request, listener,
identity, binderDeathCallback);
IBinder binder = listener.asBinder();
if (!linkedListener.linkToListenerDeathNotificationLocked(binder)) {
return false;
}
gnssDataListeners.put(binder, linkedListener);
if (gnssDataProvider == mGnssMeasurementsProvider
|| gnssDataProvider == mGnssStatusProvider) {
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_STARTED,
gnssDataProvider == mGnssMeasurementsProvider
? LocationStatsEnums.API_ADD_GNSS_MEASUREMENTS_LISTENER
: LocationStatsEnums.API_REGISTER_GNSS_STATUS_CALLBACK,
packageName,
/* LocationRequest= */ null,
/* hasListener= */ true,
/* hasIntent= */ false,
/* geofence= */ null,
mAppForegroundHelper.getImportance(identity.uid));
}
if (mAppForegroundHelper.isAppForeground(identity.uid)
|| isThrottlingExempt(identity)) {
gnssDataProvider.addListener(request, listener, identity);
}
return true;
}
private <TRequest, TListener extends IInterface> void removeGnssDataListenerLocked(
TListener listener,
RemoteListenerHelper<TRequest, TListener> gnssDataProvider,
ArrayMap<IBinder, LinkedListener<TRequest, TListener>> gnssDataListeners) {
if (gnssDataProvider == null) {
Log.e(
TAG,
"Can not remove GNSS data listener. GNSS data provider "
+ "not available.");
return;
}
IBinder binder = listener.asBinder();
LinkedListener<TRequest, TListener> linkedListener =
gnssDataListeners.remove(binder);
if (linkedListener == null) {
return;
}
if (gnssDataProvider == mGnssMeasurementsProvider
|| gnssDataProvider == mGnssStatusProvider) {
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_ENDED,
gnssDataProvider == mGnssMeasurementsProvider
? LocationStatsEnums.API_ADD_GNSS_MEASUREMENTS_LISTENER
: LocationStatsEnums.API_REGISTER_GNSS_STATUS_CALLBACK,
linkedListener.getCallerIdentity().packageName,
/* LocationRequest= */ null,
/* hasListener= */ true,
/* hasIntent= */ false,
/* geofence= */ null,
mAppForegroundHelper.getImportance(Binder.getCallingUid()));
}
linkedListener.unlinkFromListenerDeathNotificationLocked(binder);
gnssDataProvider.removeListener(listener);
}
/**
* Registers listener for GNSS status changes.
*/
public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
public void registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
@Nullable String featureId) {
synchronized (mGnssStatusListeners) {
return addGnssDataListenerLocked(
/* request= */ null,
listener,
packageName,
featureId,
mGnssStatusProvider,
mGnssStatusListeners,
this::unregisterGnssStatusCallback);
}
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId);
mGnssStatusProvider.addListener(identity, listener);
}
/**
* Unregisters listener for GNSS status changes.
*/
public void unregisterGnssStatusCallback(IGnssStatusListener listener) {
synchronized (mGnssStatusListeners) {
removeGnssDataListenerLocked(listener, mGnssStatusProvider, mGnssStatusListeners);
}
mGnssStatusProvider.removeListener(listener);
}
/**
* Adds a GNSS measurements listener.
*/
public boolean addGnssMeasurementsListener(@Nullable GnssRequest request,
public void addGnssMeasurementsListener(@Nullable GnssRequest request,
IGnssMeasurementsListener listener, String packageName,
@Nullable String featureId) {
if (request != null && request.isFullTracking()) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.LOCATION_HARDWARE,
null);
}
synchronized (mGnssMeasurementsListeners) {
return addGnssDataListenerLocked(
request,
listener,
packageName,
featureId,
mGnssMeasurementsProvider,
mGnssMeasurementsListeners,
this::removeGnssMeasurementsListener);
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
if (request.isFullTracking()) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null);
}
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId);
mGnssMeasurementsProvider.addListener(request, identity, listener);
}
/**
@@ -514,8 +330,8 @@ public class GnssManagerService {
*/
public void injectGnssMeasurementCorrections(
GnssMeasurementCorrections measurementCorrections, String packageName) {
mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE, null);
mContext.enforceCallingPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null);
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
mGnssMeasurementCorrectionsProvider.injectGnssMeasurementCorrections(
measurementCorrections);
@@ -525,10 +341,7 @@ public class GnssManagerService {
* Removes a GNSS measurements listener.
*/
public void removeGnssMeasurementsListener(IGnssMeasurementsListener listener) {
synchronized (mGnssMeasurementsListeners) {
removeGnssDataListenerLocked(listener, mGnssMeasurementsProvider,
mGnssMeasurementsListeners);
}
mGnssMeasurementsProvider.removeListener(listener);
}
/**
@@ -537,19 +350,12 @@ public class GnssManagerService {
* @param listener called when GNSS antenna info is received
* @param packageName name of requesting package
*/
public boolean addGnssAntennaInfoListener(
IGnssAntennaInfoListener listener, String packageName,
public void addGnssAntennaInfoListener(IGnssAntennaInfoListener listener, String packageName,
@Nullable String featureId) {
synchronized (mGnssAntennaInfoListeners) {
return addGnssDataListenerLocked(
/* request= */ null,
listener,
packageName,
featureId,
mGnssAntennaInfoProvider,
mGnssAntennaInfoListeners,
this::removeGnssAntennaInfoListener);
}
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId);
mGnssAntennaInfoProvider.addListener(identity, listener);
}
/**
@@ -558,38 +364,25 @@ public class GnssManagerService {
* @param listener called when GNSS antenna info is received
*/
public void removeGnssAntennaInfoListener(IGnssAntennaInfoListener listener) {
synchronized (mGnssAntennaInfoListeners) {
removeGnssDataListenerLocked(
listener, mGnssAntennaInfoProvider, mGnssAntennaInfoListeners);
}
mGnssAntennaInfoProvider.removeListener(listener);
}
/**
* Adds a GNSS navigation message listener.
*/
public boolean addGnssNavigationMessageListener(
IGnssNavigationMessageListener listener, String packageName,
@Nullable String featureId) {
synchronized (mGnssNavigationMessageListeners) {
return addGnssDataListenerLocked(
/* request= */ null,
listener,
packageName,
featureId,
mGnssNavigationMessageProvider,
mGnssNavigationMessageListeners,
this::removeGnssNavigationMessageListener);
}
public void addGnssNavigationMessageListener(IGnssNavigationMessageListener listener,
String packageName, @Nullable String featureId) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId);
mGnssNavigationMessageProvider.addListener(identity, listener);
}
/**
* Removes a GNSS navigation message listener.
*/
public void removeGnssNavigationMessageListener(IGnssNavigationMessageListener listener) {
synchronized (mGnssNavigationMessageListeners) {
removeGnssDataListenerLocked(
listener, mGnssNavigationMessageProvider, mGnssNavigationMessageListeners);
}
mGnssNavigationMessageProvider.removeListener(listener);
}
/**
@@ -607,46 +400,29 @@ public class GnssManagerService {
* Report location results to GNSS batching listener.
*/
public void onReportLocation(List<Location> locations) {
IBatchedLocationCallback gnssBatchingCallback;
LinkedListener<Void, IBatchedLocationCallback> gnssBatchingDeathCallback;
IBatchedLocationCallback callback;
CallerIdentity identity;
synchronized (mGnssBatchingLock) {
gnssBatchingCallback = mGnssBatchingCallback;
gnssBatchingDeathCallback = mGnssBatchingDeathCallback;
callback = mGnssBatchingCallback;
identity = mGnssBatchingIdentity;
}
if (gnssBatchingCallback == null || gnssBatchingDeathCallback == null) {
if (callback == null || identity == null) {
return;
}
int userId = gnssBatchingDeathCallback.getCallerIdentity().userId;
if (!mLocationManagerInternal.isProviderEnabledForUser(GPS_PROVIDER, userId)) {
if (!mLocationManagerInternal.isProviderEnabledForUser(GPS_PROVIDER, identity.userId)) {
Log.w(TAG, "reportLocationBatch() called without user permission");
return;
}
try {
gnssBatchingCallback.onLocationBatch(locations);
callback.onLocationBatch(locations);
} catch (RemoteException e) {
Log.e(TAG, "reportLocationBatch() failed", e);
// ignore
}
}
private boolean isThrottlingExempt(CallerIdentity callerIdentity) {
if (callerIdentity.uid == Process.SYSTEM_UID) {
return true;
}
if (mSettingsHelper.getBackgroundThrottlePackageWhitelist().contains(
callerIdentity.packageName)) {
return true;
}
synchronized (this) {
Preconditions.checkState(mLocationManagerInternal != null);
}
return mLocationManagerInternal.isProviderPackage(callerIdentity.packageName);
}
/**
* Dump info for debugging.
*/
@@ -660,31 +436,24 @@ public class GnssManagerService {
return;
}
ipw.println("GnssMeasurement Listeners:");
ipw.println("Antenna Info Provider:");
ipw.increaseIndent();
synchronized (mGnssMeasurementsListeners) {
for (LinkedListenerBase listener : mGnssMeasurementsListeners.values()) {
ipw.println(listener);
}
}
mGnssAntennaInfoProvider.dump(fd, ipw, args);
ipw.decreaseIndent();
ipw.println("GnssNavigationMessage Listeners:");
ipw.println("Measurement Provider:");
ipw.increaseIndent();
synchronized (mGnssNavigationMessageListeners) {
for (LinkedListenerBase listener : mGnssNavigationMessageListeners.values()) {
ipw.println(listener);
}
}
mGnssMeasurementsProvider.dump(fd, ipw, args);
ipw.decreaseIndent();
ipw.println("GnssStatus Listeners:");
ipw.println("Navigation Message Provider:");
ipw.increaseIndent();
synchronized (mGnssStatusListeners) {
for (LinkedListenerBase listener : mGnssStatusListeners.values()) {
ipw.println(listener);
}
}
mGnssNavigationMessageProvider.dump(fd, ipw, args);
ipw.decreaseIndent();
ipw.println("Status Provider:");
ipw.increaseIndent();
mGnssStatusProvider.dump(fd, ipw, args);
ipw.decreaseIndent();
synchronized (mGnssBatchingLock) {

View File

@@ -0,0 +1,285 @@
/*
* 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.util.listeners;
import android.annotation.Nullable;
import android.location.LocationManager;
import android.location.LocationManagerInternal;
import android.location.util.listeners.AbstractListenerManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Process;
import android.os.RemoteException;
import android.util.ArraySet;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.location.AppForegroundHelper;
import com.android.server.location.AppOpsHelper;
import com.android.server.location.CallerIdentity;
import com.android.server.location.SettingsHelper;
import com.android.server.location.UserInfoHelper;
import java.util.Objects;
/**
* Manager for all GNSS related listeners. This class handles deactivating listeners that do not
* belong to the current user, that do not have the appropriate permissions, or that are not
* currently in the foreground. It will also disable listeners if the GNSS provider is disabled.
* Listeners must be registered with the associated IBinder as the key, if the IBinder dies, the
* registration will automatically be removed.
*
* @param <TRequest> request type
* @param <TListener> listener type
* @param <TMergedRequest> merged request type
*/
public abstract class GnssListenerManager<TRequest, TListener extends IInterface, TMergedRequest>
extends AbstractListenerManager<IBinder, TRequest, TListener, GnssListenerManager<TRequest,
TListener, TMergedRequest>.GnssRegistration, TMergedRequest> {
/**
* Registration object for GNSS listeners.
*/
protected class GnssRegistration extends
IdentifiedRegistration<TRequest, TListener> implements Binder.DeathRecipient {
private volatile @Nullable IBinder mKey;
@GuardedBy("this")
private boolean mAppOpsAllowed;
@GuardedBy("this")
private boolean mForeground;
@GuardedBy("this")
private boolean mActive;
protected GnssRegistration(@Nullable TRequest request, CallerIdentity callerIdentity,
TListener listener) {
super(request, callerIdentity, listener);
mKey = listener.asBinder();
}
/**
* Returns true if this registration is currently in the foreground.
*/
public synchronized boolean isForeground() {
return mForeground;
}
synchronized boolean isActive() {
return mActive;
}
@Override
protected final boolean onRegister() {
try {
Objects.requireNonNull(mKey).linkToDeath(this, 0);
} catch (RemoteException e) {
mKey = null;
return false;
}
mAppOpsAllowed = mAppOpsHelper.checkLocationAccess(getIdentity());
mForeground = mAppForegroundHelper.isAppForeground(getIdentity().uid);
onActiveChanged();
return true;
}
@Override
protected void onUnregister() {
IBinder key = mKey;
if (key != null) {
mKey = null;
key.unlinkToDeath(this, 0);
}
}
boolean onUserChanged(int userId) {
return getIdentity().userId == userId;
}
boolean onAppOpsChanged(String packageName) {
if (getIdentity().packageName.equals(packageName)) {
boolean appOpsAllowed = mAppOpsHelper.checkLocationAccess(getIdentity());
synchronized (this) {
if (appOpsAllowed != mAppOpsAllowed) {
mAppOpsAllowed = appOpsAllowed;
return onActiveChanged();
}
}
}
return false;
}
boolean onForegroundChanged(int uid, boolean foreground) {
if (getIdentity().uid == uid) {
synchronized (this) {
if (foreground != mForeground) {
mForeground = foreground;
return onActiveChanged();
}
}
}
return false;
}
private boolean onActiveChanged() {
synchronized (this) {
boolean active = mAppOpsAllowed
&& (mForeground || isBackgroundRestrictionExempt(getIdentity()))
&& mUserInfoHelper.isCurrentUserId(getIdentity().userId);
if (active != mActive) {
mActive = active;
return true;
}
}
return false;
}
@Override
public void binderDied() {
IBinder key = mKey;
if (key != null) {
removeListener(key, this);
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(getIdentity());
ArraySet<String> flags = new ArraySet<>(2);
if (!mForeground) {
flags.add("bg");
}
if (!mAppOpsAllowed) {
flags.add("na");
}
if (!flags.isEmpty()) {
builder.append(" ").append(flags);
}
if (getRequest() != null) {
builder.append(" ").append(getRequest());
}
return builder.toString();
}
}
protected final UserInfoHelper mUserInfoHelper;
protected final SettingsHelper mSettingsHelper;
protected final AppOpsHelper mAppOpsHelper;
protected final AppForegroundHelper mAppForegroundHelper;
protected final LocationManagerInternal mLocationManagerInternal;
protected GnssListenerManager(UserInfoHelper userInfoHelper, SettingsHelper settingsHelper,
AppOpsHelper appOpsHelper, AppForegroundHelper appForegroundHelper) {
mUserInfoHelper = userInfoHelper;
mSettingsHelper = settingsHelper;
mAppOpsHelper = appOpsHelper;
mAppForegroundHelper = appForegroundHelper;
mLocationManagerInternal = Objects.requireNonNull(
LocalServices.getService(LocationManagerInternal.class));
mUserInfoHelper.addListener(this::onUserChanged);
mAppOpsHelper.addListener(this::onAppOpsChanged);
mAppForegroundHelper.addListener(this::onAppForegroundChanged);
}
/**
* Adds a listener with the given identity.
*/
protected void addListener(CallerIdentity identity, TListener listener) {
addListener(null, identity, listener);
}
/**
* Adds a listener with the given identity and request.
*/
protected void addListener(TRequest request, CallerIdentity identity, TListener listener) {
addRegistration(listener.asBinder(), new GnssRegistration(request, identity, listener));
}
/**
* Removes the given listener.
*/
public void removeListener(TListener listener) {
removeRegistration(listener.asBinder());
}
void removeListener(IBinder key, GnssRegistration registration) {
removeRegistration(key, registration);
}
@Override
protected boolean isActive(GnssRegistration registration) {
// we don't have an easy listener for provider enabled status changes available, so we
// check it every time, which should be pretty cheap
return registration.isActive() && mLocationManagerInternal.isProviderEnabledForUser(
LocationManager.GPS_PROVIDER, registration.getIdentity().userId);
}
private void onUserChanged(int userId, int change) {
if (change == UserInfoHelper.UserListener.USER_SWITCHED) {
updateRegistrations(registration -> registration.onUserChanged(userId));
}
}
private void onAppOpsChanged(String packageName) {
updateRegistrations(registration -> registration.onAppOpsChanged(packageName));
}
private void onAppForegroundChanged(int uid, boolean foreground) {
updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground));
}
boolean isBackgroundRestrictionExempt(CallerIdentity callerIdentity) {
if (callerIdentity.uid == Process.SYSTEM_UID) {
return true;
}
if (mSettingsHelper.getBackgroundThrottlePackageWhitelist().contains(
callerIdentity.packageName)) {
return true;
}
return mLocationManagerInternal.isProviderPackage(callerIdentity.packageName);
}
/**
* May be overridden by subclasses to provide extra debug information.
*/
protected boolean isServiceSupported() {
return true;
}
@Override
protected String serviceStateToString() {
if (!isServiceSupported()) {
return "unsupported";
}
return super.serviceStateToString();
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.util.listeners;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import android.annotation.Nullable;
import android.location.util.listeners.AbstractListenerManager;
import android.os.Process;
import com.android.server.FgThread;
import com.android.server.location.CallerIdentity;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* A listener manager for system server side implementations where client callers are identified via
* {@link CallerIdentity}. Listener callbacks going to other processes will be run on a direct
* executor, listener callbacks going to the same process will be run asynchronously.
*
* @param <TRequest> request type
* @param <TListener> listener type
*/
public class IdentifiedRegistration<TRequest, TListener> extends
AbstractListenerManager.Registration<TRequest, TListener> {
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.pid == 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 FgThread.getExecutor();
} else {
return DIRECT_EXECUTOR;
}
}
private final CallerIdentity mCallerIdentity;
protected IdentifiedRegistration(@Nullable TRequest request, CallerIdentity callerIdentity,
TListener listener) {
super(request, chooseExecutor(callerIdentity), listener);
mCallerIdentity = Objects.requireNonNull(callerIdentity);
}
/**
* Returns the listener identity.
*/
public CallerIdentity getIdentity() {
return mCallerIdentity;
}
}

View File

@@ -1,94 +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;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.os.Handler;
import android.os.Looper;
import android.platform.test.annotations.Presubmit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
/**
* Unit tests for {@link GnssAntennaInfoProvider}.
*/
@RunWith(RobolectricTestRunner.class)
@Presubmit
public class GnssAntennaInfoProviderTest {
@Mock
private GnssAntennaInfoProvider.GnssAntennaInfoProviderNative mMockNative;
private GnssAntennaInfoProvider mTestProvider;
/** Setup. */
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMockNative.startAntennaInfoListening()).thenReturn(true);
when(mMockNative.stopAntennaInfoListening()).thenReturn(true);
mTestProvider = new GnssAntennaInfoProvider(RuntimeEnvironment.application,
new Handler(Looper.myLooper()), mMockNative) {
@Override
public boolean isGpsEnabled() {
return true;
}
};
}
/**
* Test that registerWithService calls the native startAntennaInfoListening method.
*/
@Test
public void register_nativeStarted() {
mTestProvider.registerWithService();
verify(mMockNative, times(1)).startAntennaInfoListening();
}
/**
* Test that unregisterFromService calls the native stopAntennaInfoListening method.
*/
@Test
public void unregister_nativeStopped() {
mTestProvider.registerWithService();
mTestProvider.unregisterFromService();
verify(mMockNative, times(1)).stopAntennaInfoListening();
}
/**
* Test that GnssAntennaInfoProvider.isAntennaInfoSupported() returns the result of the
* native isAntennaInfoSupported method.
*/
@Test
public void isSupported_nativeIsSupported() {
when(mMockNative.isAntennaInfoSupported()).thenReturn(true);
assertThat(mTestProvider.isAvailableInPlatform()).isTrue();
when(mMockNative.isAntennaInfoSupported()).thenReturn(false);
assertThat(mTestProvider.isAvailableInPlatform()).isFalse();
}
}

View File

@@ -1,83 +0,0 @@
package com.android.server.location;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.os.Handler;
import android.os.Looper;
import android.platform.test.annotations.Presubmit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
/**
* Unit tests for {@link GnssMeasurementsProvider}.
*/
@RunWith(RobolectricTestRunner.class)
@Presubmit
public class GnssMeasurementsProviderTest {
@Mock
private GnssMeasurementsProvider.GnssMeasurementProviderNative mMockNative;
private GnssMeasurementsProvider mTestProvider;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMockNative.startMeasurementCollection(anyBoolean())).thenReturn(true);
when(mMockNative.stopMeasurementCollection()).thenReturn(true);
mTestProvider = new GnssMeasurementsProvider(RuntimeEnvironment.application,
new Handler(Looper.myLooper()), mMockNative) {
@Override
public boolean isGpsEnabled() {
return true;
}
};
}
@Test
public void register_nativeStarted() {
mTestProvider.registerWithService();
verify(mMockNative).startMeasurementCollection(anyBoolean());
}
@Test
public void unregister_nativeStopped() {
mTestProvider.registerWithService();
mTestProvider.unregisterFromService();
verify(mMockNative).stopMeasurementCollection();
}
@Test
public void isSupported_nativeIsSupported() {
when(mMockNative.isMeasurementSupported()).thenReturn(true);
assertThat(mTestProvider.isAvailableInPlatform()).isTrue();
when(mMockNative.isMeasurementSupported()).thenReturn(false);
assertThat(mTestProvider.isAvailableInPlatform()).isFalse();
}
@Test
public void register_resume_started() {
mTestProvider.registerWithService();
mTestProvider.resumeIfStarted();
verify(mMockNative, times(2)).startMeasurementCollection(anyBoolean());
}
@Test
public void unregister_resume_notStarted() {
mTestProvider.registerWithService();
mTestProvider.unregisterFromService();
mTestProvider.resumeIfStarted();
verify(mMockNative, times(1)).startMeasurementCollection(anyBoolean());
}
}

View File

@@ -1,82 +0,0 @@
package com.android.server.location;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.os.Handler;
import android.os.Looper;
import android.platform.test.annotations.Presubmit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
/**
* Unit tests for {@link GnssNavigationMessageProvider}.
*/
@RunWith(RobolectricTestRunner.class)
@Presubmit
public class GnssNavigationMessageProviderTest {
@Mock
private GnssNavigationMessageProvider.GnssNavigationMessageProviderNative mMockNative;
private GnssNavigationMessageProvider mTestProvider;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMockNative.startNavigationMessageCollection()).thenReturn(true);
when(mMockNative.stopNavigationMessageCollection()).thenReturn(true);
mTestProvider = new GnssNavigationMessageProvider(RuntimeEnvironment.application,
new Handler(Looper.myLooper()), mMockNative) {
@Override
public boolean isGpsEnabled() {
return true;
}
};
}
@Test
public void register_nativeStarted() {
mTestProvider.registerWithService();
verify(mMockNative).startNavigationMessageCollection();
}
@Test
public void unregister_nativeStopped() {
mTestProvider.registerWithService();
mTestProvider.unregisterFromService();
verify(mMockNative).stopNavigationMessageCollection();
}
@Test
public void isSupported_nativeIsSupported() {
when(mMockNative.isNavigationMessageSupported()).thenReturn(true);
assertThat(mTestProvider.isAvailableInPlatform()).isTrue();
when(mMockNative.isNavigationMessageSupported()).thenReturn(false);
assertThat(mTestProvider.isAvailableInPlatform()).isFalse();
}
@Test
public void register_resume_started() {
mTestProvider.registerWithService();
mTestProvider.resumeIfStarted();
verify(mMockNative, times(2)).startNavigationMessageCollection();
}
@Test
public void unregister_resume_notStarted() {
mTestProvider.registerWithService();
mTestProvider.unregisterFromService();
mTestProvider.resumeIfStarted();
verify(mMockNative, times(1)).startNavigationMessageCollection();
}
}

View File

@@ -29,6 +29,8 @@ android_test {
"platform-test-annotations",
"truth-prebuilt",
"testables",
// TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows
"testng",
],
libs: [

View File

@@ -27,7 +27,9 @@ import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -62,6 +64,7 @@ import android.os.RemoteException;
import com.android.server.LocalServices;
import com.android.server.location.AppForegroundHelper;
import com.android.server.location.AppOpsHelper;
import com.android.server.location.CallerIdentity;
import com.android.server.location.GnssAntennaInfoProvider;
import com.android.server.location.GnssAntennaInfoProvider.GnssAntennaInfoProviderNative;
import com.android.server.location.GnssBatchingProvider;
@@ -72,9 +75,10 @@ import com.android.server.location.GnssMeasurementsProvider;
import com.android.server.location.GnssMeasurementsProvider.GnssMeasurementProviderNative;
import com.android.server.location.GnssNavigationMessageProvider;
import com.android.server.location.GnssNavigationMessageProvider.GnssNavigationMessageProviderNative;
import com.android.server.location.GnssStatusListenerHelper;
import com.android.server.location.GnssStatusProvider;
import com.android.server.location.LocationUsageLogger;
import com.android.server.location.SettingsHelper;
import com.android.server.location.UserInfoHelper;
import org.junit.After;
import org.junit.Before;
@@ -94,6 +98,9 @@ import java.util.List;
*/
public class GnssManagerServiceTest {
private static final long TIMEOUT_MS = 5000;
private static final long FAILURE_TIMEOUT_MS = 200;
// Gnss Providers
@Mock
private GnssLocationProvider mMockGnssLocationProvider;
@@ -108,15 +115,19 @@ public class GnssManagerServiceTest {
@Mock
private INetInitiatedListener mNetInitiatedListener;
private GnssMeasurementsProvider mTestGnssMeasurementsProvider;
private GnssStatusListenerHelper mTestGnssStatusProvider;
private GnssStatusProvider mTestGnssStatusProvider;
private GnssNavigationMessageProvider mTestGnssNavigationMessageProvider;
private GnssAntennaInfoProvider mTestGnssAntennaInfoProvider;
@Mock
private PackageManager mPackageManager;
@Mock
private AppOpsHelper mAppOpsHelper;
@Mock
private AppOpsManager mAppOpsManager;
@Mock
private UserInfoHelper mUserInfoHelper;
@Mock
private SettingsHelper mSettingsHelper;
@Mock
private AppForegroundHelper mAppForegroundHelper;
@@ -146,6 +157,9 @@ public class GnssManagerServiceTest {
new String[]{"com.android.server"});
enableLocationPermissions();
when(mUserInfoHelper.isCurrentUserId(anyInt())).thenReturn(true);
when(mAppOpsHelper.checkLocationAccess(any(CallerIdentity.class))).thenReturn(true);
when(mAppOpsHelper.noteLocationAccess(any(CallerIdentity.class))).thenReturn(true);
when(mAppForegroundHelper.isAppForeground(anyInt())).thenReturn(true);
LocalServices.addService(LocationManagerInternal.class, mLocationManagerInternal);
@@ -159,14 +173,10 @@ public class GnssManagerServiceTest {
});
// Setup providers
mTestGnssMeasurementsProvider = createGnssMeasurementsProvider(
mMockContext, mMockHandler);
mTestGnssStatusProvider = createGnssStatusListenerHelper(
mMockContext, mMockHandler);
mTestGnssNavigationMessageProvider = createGnssNavigationMessageProvider(
mMockContext, mMockHandler);
mTestGnssAntennaInfoProvider = createGnssAntennaInfoProvider(
mMockContext, mMockHandler);
mTestGnssMeasurementsProvider = createGnssMeasurementsProvider();
mTestGnssStatusProvider = createGnssStatusListenerHelper();
mTestGnssNavigationMessageProvider = createGnssNavigationMessageProvider();
mTestGnssAntennaInfoProvider = createGnssAntennaInfoProvider();
// Setup GnssLocationProvider to return providers
when(mMockGnssLocationProvider.getGnssStatusProvider()).thenReturn(
@@ -192,12 +202,9 @@ public class GnssManagerServiceTest {
when(mMockGnssBatchingProvider.start(anyLong(), anyBoolean())).thenReturn(true);
when(mMockGnssBatchingProvider.stop()).thenReturn(true);
// Managers and services
AppOpsHelper appOpsHelper = new AppOpsHelper(mMockContext);
// Create GnssManagerService
mGnssManagerService = new GnssManagerService(mMockContext, appOpsHelper, mSettingsHelper,
mAppForegroundHelper, new LocationUsageLogger(),
mGnssManagerService = new GnssManagerService(mMockContext, mUserInfoHelper, mSettingsHelper,
mAppOpsHelper, mAppForegroundHelper, new LocationUsageLogger(),
mMockGnssLocationProvider);
mGnssManagerService.onSystemReady();
}
@@ -315,9 +322,7 @@ public class GnssManagerServiceTest {
private void disableLocationPermissions() {
Mockito.doThrow(new SecurityException()).when(
mMockContext).enforceCallingPermission(anyString(), nullable(String.class));
Mockito.doThrow(new SecurityException()).when(
mMockContext).checkPermission(anyString(), anyInt(), anyInt());
mMockContext).enforceCallingOrSelfPermission(anyString(), nullable(String.class));
when(mAppOpsManager.checkOpNoThrow(anyInt(), anyInt(),
anyString())).thenReturn(AppOpsManager.MODE_ERRORED);
@@ -326,58 +331,31 @@ public class GnssManagerServiceTest {
.thenReturn(false);
}
private GnssStatusListenerHelper createGnssStatusListenerHelper(Context context,
Handler handler) {
return new GnssStatusListenerHelper(
context, handler) {
@Override
protected boolean isAvailableInPlatform() {
return true;
}
@Override
protected boolean isGpsEnabled() {
return true;
}
};
private GnssStatusProvider createGnssStatusListenerHelper() {
return new GnssStatusProvider(mUserInfoHelper, mSettingsHelper, mAppOpsHelper,
mAppForegroundHelper, new LocationUsageLogger());
}
private GnssMeasurementsProvider createGnssMeasurementsProvider(Context context,
Handler handler) {
private GnssMeasurementsProvider createGnssMeasurementsProvider() {
GnssMeasurementProviderNative
mockGnssMeasurementProviderNative = mock(GnssMeasurementProviderNative.class);
return new GnssMeasurementsProvider(
context, handler, mockGnssMeasurementProviderNative) {
@Override
protected boolean isGpsEnabled() {
return true;
}
};
return new GnssMeasurementsProvider(mUserInfoHelper, mSettingsHelper,
mAppOpsHelper, mAppForegroundHelper, new LocationUsageLogger(),
mockGnssMeasurementProviderNative);
}
private GnssNavigationMessageProvider createGnssNavigationMessageProvider(Context context,
Handler handler) {
private GnssNavigationMessageProvider createGnssNavigationMessageProvider() {
GnssNavigationMessageProviderNative mockGnssNavigationMessageProviderNative = mock(
GnssNavigationMessageProviderNative.class);
return new GnssNavigationMessageProvider(context, handler,
mockGnssNavigationMessageProviderNative) {
@Override
protected boolean isGpsEnabled() {
return true;
}
};
return new GnssNavigationMessageProvider(mUserInfoHelper, mSettingsHelper, mAppOpsHelper,
mAppForegroundHelper, mockGnssNavigationMessageProviderNative);
}
private GnssAntennaInfoProvider createGnssAntennaInfoProvider(Context context,
Handler handler) {
private GnssAntennaInfoProvider createGnssAntennaInfoProvider() {
GnssAntennaInfoProviderNative mockGnssAntenaInfoProviderNative = mock(
GnssAntennaInfoProviderNative.class);
return new GnssAntennaInfoProvider(context, handler, mockGnssAntenaInfoProviderNative) {
@Override
protected boolean isGpsEnabled() {
return true;
}
};
return new GnssAntennaInfoProvider(mUserInfoHelper, mSettingsHelper, mAppOpsHelper,
mAppForegroundHelper, mockGnssAntenaInfoProviderNative);
}
@Test
@@ -590,7 +568,8 @@ public class GnssManagerServiceTest {
mTestGnssStatusProvider.onFirstFix(timeToFirstFix);
verify(mockGnssStatusListener, times(0)).onFirstFix(timeToFirstFix);
verify(mockGnssStatusListener, after(FAILURE_TIMEOUT_MS).times(0)).onFirstFix(
timeToFirstFix);
}
@Test
@@ -600,12 +579,12 @@ public class GnssManagerServiceTest {
enableLocationPermissions();
assertThat(mGnssManagerService.registerGnssStatusCallback(
mockGnssStatusListener, "com.android.server", "abcd123")).isEqualTo(true);
mGnssManagerService.registerGnssStatusCallback(
mockGnssStatusListener, "com.android.server", "abcd123");
mTestGnssStatusProvider.onFirstFix(timeToFirstFix);
verify(mockGnssStatusListener, times(1)).onFirstFix(timeToFirstFix);
verify(mockGnssStatusListener, timeout(TIMEOUT_MS).times(1)).onFirstFix(timeToFirstFix);
}
@Test
@@ -622,7 +601,8 @@ public class GnssManagerServiceTest {
mTestGnssStatusProvider.onFirstFix(timeToFirstFix);
verify(mockGnssStatusListener, times(0)).onFirstFix(timeToFirstFix);
verify(mockGnssStatusListener, after(FAILURE_TIMEOUT_MS).times(0)).onFirstFix(
timeToFirstFix);
}
@Test
@@ -640,7 +620,8 @@ public class GnssManagerServiceTest {
"com.android.server", null));
mTestGnssMeasurementsProvider.onMeasurementsAvailable(gnssMeasurementsEvent);
verify(mockGnssMeasurementsListener, times(0)).onGnssMeasurementsReceived(
verify(mockGnssMeasurementsListener,
after(FAILURE_TIMEOUT_MS).times(0)).onGnssMeasurementsReceived(
gnssMeasurementsEvent);
}
@@ -653,13 +634,14 @@ public class GnssManagerServiceTest {
enableLocationPermissions();
assertThat(mGnssManagerService.addGnssMeasurementsListener(
mGnssManagerService.addGnssMeasurementsListener(
new GnssRequest.Builder().build(),
mockGnssMeasurementsListener,
"com.android.server", null)).isEqualTo(true);
"com.android.server", null);
mTestGnssMeasurementsProvider.onMeasurementsAvailable(gnssMeasurementsEvent);
verify(mockGnssMeasurementsListener, times(1)).onGnssMeasurementsReceived(
verify(mockGnssMeasurementsListener,
timeout(TIMEOUT_MS).times(1)).onGnssMeasurementsReceived(
gnssMeasurementsEvent);
}
@@ -711,7 +693,8 @@ public class GnssManagerServiceTest {
mockGnssMeasurementsListener);
mTestGnssMeasurementsProvider.onMeasurementsAvailable(gnssMeasurementsEvent);
verify(mockGnssMeasurementsListener, times(0)).onGnssMeasurementsReceived(
verify(mockGnssMeasurementsListener,
after(FAILURE_TIMEOUT_MS).times(0)).onGnssMeasurementsReceived(
gnssMeasurementsEvent);
}
@@ -734,7 +717,8 @@ public class GnssManagerServiceTest {
mockGnssMeasurementsListener);
mTestGnssMeasurementsProvider.onMeasurementsAvailable(gnssMeasurementsEvent);
verify(mockGnssMeasurementsListener, times(0)).onGnssMeasurementsReceived(
verify(mockGnssMeasurementsListener,
after(FAILURE_TIMEOUT_MS).times(0)).onGnssMeasurementsReceived(
gnssMeasurementsEvent);
}
@@ -752,7 +736,7 @@ public class GnssManagerServiceTest {
"com.android.server", null));
mTestGnssAntennaInfoProvider.onGnssAntennaInfoAvailable(gnssAntennaInfos);
verify(mockGnssAntennaInfoListener, times(0))
verify(mockGnssAntennaInfoListener, after(FAILURE_TIMEOUT_MS).times(0))
.onGnssAntennaInfoReceived(gnssAntennaInfos);
}
@@ -764,11 +748,11 @@ public class GnssManagerServiceTest {
enableLocationPermissions();
assertThat(mGnssManagerService.addGnssAntennaInfoListener(mockGnssAntennaInfoListener,
"com.android.server", null)).isEqualTo(true);
mGnssManagerService.addGnssAntennaInfoListener(mockGnssAntennaInfoListener,
"com.android.server", null);
mTestGnssAntennaInfoProvider.onGnssAntennaInfoAvailable(gnssAntennaInfos);
verify(mockGnssAntennaInfoListener, times(1))
verify(mockGnssAntennaInfoListener, timeout(TIMEOUT_MS).times(1))
.onGnssAntennaInfoReceived(gnssAntennaInfos);
}
@@ -790,8 +774,8 @@ public class GnssManagerServiceTest {
mockGnssAntennaInfoListener);
mTestGnssAntennaInfoProvider.onGnssAntennaInfoAvailable(gnssAntennaInfos);
verify(mockGnssAntennaInfoListener, times(0)).onGnssAntennaInfoReceived(
gnssAntennaInfos);
verify(mockGnssAntennaInfoListener, after(FAILURE_TIMEOUT_MS).times(0))
.onGnssAntennaInfoReceived(gnssAntennaInfos);
}
@Test
@@ -810,7 +794,8 @@ public class GnssManagerServiceTest {
mockGnssAntennaInfoListener);
mTestGnssAntennaInfoProvider.onGnssAntennaInfoAvailable(gnssAntennaInfos);
verify(mockGnssAntennaInfoListener, times(0)).onGnssAntennaInfoReceived(
verify(mockGnssAntennaInfoListener,
after(FAILURE_TIMEOUT_MS).times(0)).onGnssAntennaInfoReceived(
gnssAntennaInfos);
}
@@ -828,7 +813,8 @@ public class GnssManagerServiceTest {
mTestGnssNavigationMessageProvider.onNavigationMessageAvailable(gnssNavigationMessage);
verify(mockGnssNavigationMessageListener, times(0)).onGnssNavigationMessageReceived(
verify(mockGnssNavigationMessageListener,
after(FAILURE_TIMEOUT_MS).times(0)).onGnssNavigationMessageReceived(
gnssNavigationMessage);
}
@@ -840,13 +826,13 @@ public class GnssManagerServiceTest {
enableLocationPermissions();
assertThat(mGnssManagerService.addGnssNavigationMessageListener(
mockGnssNavigationMessageListener, "com.android.server", null))
.isEqualTo(true);
mGnssManagerService.addGnssNavigationMessageListener(
mockGnssNavigationMessageListener, "com.android.server", null);
mTestGnssNavigationMessageProvider.onNavigationMessageAvailable(gnssNavigationMessage);
verify(mockGnssNavigationMessageListener, times(1)).onGnssNavigationMessageReceived(
verify(mockGnssNavigationMessageListener,
timeout(TIMEOUT_MS).times(1)).onGnssNavigationMessageReceived(
gnssNavigationMessage);
}
@@ -868,7 +854,8 @@ public class GnssManagerServiceTest {
mTestGnssNavigationMessageProvider.onNavigationMessageAvailable(gnssNavigationMessage);
verify(mockGnssNavigationMessageListener, times(0)).onGnssNavigationMessageReceived(
verify(mockGnssNavigationMessageListener,
after(FAILURE_TIMEOUT_MS).times(0)).onGnssNavigationMessageReceived(
gnssNavigationMessage);
}
@@ -888,7 +875,8 @@ public class GnssManagerServiceTest {
mTestGnssNavigationMessageProvider.onNavigationMessageAvailable(gnssNavigationMessage);
verify(mockGnssNavigationMessageListener, times(0)).onGnssNavigationMessageReceived(
verify(mockGnssNavigationMessageListener,
after(FAILURE_TIMEOUT_MS).times(0)).onGnssNavigationMessageReceived(
gnssNavigationMessage);
}

View File

@@ -0,0 +1,259 @@
/*
* 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.util.listeners;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.location.util.listeners.AbstractListenerManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import java.util.function.Predicate;
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AbstractListenerManagerTest {
private TestListenerManager mListenerManager;
@Before
public void setUp() {
mListenerManager = new TestListenerManager();
}
@Test
public void testAdd() {
Runnable listener = mock(Runnable.class);
mListenerManager.addListener(0, listener);
assertThat(mListenerManager.mRegistered).isTrue();
assertThat(mListenerManager.mMergedRequest).isEqualTo(0);
mListenerManager.notifyListeners();
verify(listener).run();
}
@Test
public void testRemove() {
Runnable listener = mock(Runnable.class);
mListenerManager.addListener(0, listener);
mListenerManager.removeListener(listener);
assertThat(mListenerManager.mRegistered).isFalse();
mListenerManager.notifyListeners();
verify(listener, never()).run();
}
@Test
public void testMergeMultiple() {
Runnable listener1 = mock(Runnable.class);
Runnable listener2 = mock(Runnable.class);
Runnable listener3 = mock(Runnable.class);
mListenerManager.addListener(0, listener1);
mListenerManager.addListener(1, listener2);
assertThat(mListenerManager.mRegistered).isTrue();
assertThat(mListenerManager.mMergedRequest).isEqualTo(1);
mListenerManager.notifyListeners();
verify(listener1, times(1)).run();
verify(listener2, times(1)).run();
verify(listener3, times(0)).run();
mListenerManager.addListener(0, listener3);
assertThat(mListenerManager.mRegistered).isTrue();
assertThat(mListenerManager.mMergedRequest).isEqualTo(1);
mListenerManager.notifyListeners();
verify(listener1, times(2)).run();
verify(listener2, times(2)).run();
verify(listener3, times(1)).run();
mListenerManager.removeListener(listener2);
assertThat(mListenerManager.mRegistered).isTrue();
assertThat(mListenerManager.mMergedRequest).isEqualTo(0);
mListenerManager.notifyListeners();
verify(listener1, times(3)).run();
verify(listener2, times(2)).run();
verify(listener3, times(2)).run();
mListenerManager.removeListener(listener1);
mListenerManager.removeListener(listener3);
assertThat(mListenerManager.mRegistered).isFalse();
}
@Test
public void testPredicate() {
Runnable listener = mock(Runnable.class);
mListenerManager.addListener(0, listener);
mListenerManager.notifyListeners(i -> i != 0);
verify(listener, never()).run();
mListenerManager.notifyListeners(i -> i == 0);
verify(listener).run();
}
@Test
public void testInactive() {
Runnable listener = mock(Runnable.class);
mListenerManager.addListener(0, listener);
mListenerManager.setActive(0, false);
assertThat(mListenerManager.mRegistered).isFalse();
mListenerManager.notifyListeners();
verify(listener, never()).run();
mListenerManager.setActive(0, true);
assertThat(mListenerManager.mRegistered).isTrue();
assertThat(mListenerManager.mMergedRequest).isEqualTo(0);
mListenerManager.notifyListeners();
verify(listener).run();
}
@Test
public void testMergeMultiple_Inactive() {
Runnable listener1 = mock(Runnable.class);
Runnable listener2 = mock(Runnable.class);
Runnable listener3 = mock(Runnable.class);
mListenerManager.addListener(0, listener1);
mListenerManager.addListener(2, listener2);
mListenerManager.addListener(1, listener3);
assertThat(mListenerManager.mRegistered).isTrue();
assertThat(mListenerManager.mMergedRequest).isEqualTo(2);
mListenerManager.notifyListeners();
verify(listener1, times(1)).run();
verify(listener2, times(1)).run();
verify(listener3, times(1)).run();
mListenerManager.setActive(2, false);
assertThat(mListenerManager.mRegistered).isTrue();
assertThat(mListenerManager.mMergedRequest).isEqualTo(1);
mListenerManager.notifyListeners();
verify(listener1, times(2)).run();
verify(listener2, times(1)).run();
verify(listener3, times(2)).run();
mListenerManager.setActive(2, true);
assertThat(mListenerManager.mRegistered).isTrue();
assertThat(mListenerManager.mMergedRequest).isEqualTo(2);
mListenerManager.notifyListeners();
verify(listener1, times(3)).run();
verify(listener2, times(2)).run();
verify(listener3, times(3)).run();
mListenerManager.setActive(0, false);
mListenerManager.setActive(1, false);
mListenerManager.setActive(2, false);
assertThat(mListenerManager.mRegistered).isFalse();
}
private static class TestRegistration extends
AbstractListenerManager.Registration<Integer, Runnable> {
private boolean mActive = true;
protected TestRegistration(Integer integer, Runnable runnable) {
super(integer, DIRECT_EXECUTOR, runnable);
}
}
private static class TestListenerManager extends
AbstractListenerManager<Runnable, Integer, Runnable, TestRegistration, Integer> {
private boolean mRegistered;
private int mMergedRequest;
public void addListener(Integer request, Runnable listener) {
addRegistration(listener, new TestRegistration(request, listener));
}
public void removeListener(Runnable listener) {
removeRegistration(listener);
}
public void setActive(Integer request, boolean active) {
updateRegistrations(testRegistration -> {
if (testRegistration.getRequest().equals(request)) {
testRegistration.mActive = active;
return true;
}
return false;
});
}
public void notifyListeners() {
deliverToListeners(Runnable::run);
}
public void notifyListeners(Predicate<Integer> predicate) {
deliverToListeners(Runnable::run, r -> predicate.test(r.getRequest()));
}
@Override
protected boolean registerService(Integer mergedRequest) {
mRegistered = true;
mMergedRequest = mergedRequest;
return true;
}
@Override
protected void unregisterService() {
mRegistered = false;
}
@Override
protected boolean isActive(TestRegistration registration) {
return registration.mActive;
}
@Override
protected Integer mergeRequests(List<TestRegistration> testRegistrations) {
int max = Integer.MIN_VALUE;
for (TestRegistration registration : testRegistrations) {
if (registration.getRequest() > max) {
max = registration.getRequest();
}
}
return max;
}
}
}