Refactor GNSS listeners

Gnss listeners currently simply ignore requests that are not made in the
foreground or with appropriate permissions, even though these are
transitory attributes. In addition, some gnss listener use appops APIs
incorrectly. The RemoteListenerHelper pattern is not great either, and
makes it extremely difficult to write clear code, as well as duplicates
much of the functionality provided by AbstractListenerManager on the
client side. This CL unifies logic for multiplexing listeners so it can
be used across both the client and server side, and updates all GNSS
listeners to use the new model, which properly respects foreground,
appops, permissions, gnss enablement, and capabilities.

Test: presubmits + manual
Change-Id: I8f158e860e10c3ed43b246f6f3687169ac134343
This commit is contained in:
Soonil Nagarkar
2020-02-26 11:57:42 -08:00
parent 9880ac9883
commit 6257ba865c
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);
@@ -1004,9 +967,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
mC2KServerHost, mC2KServerPort);
}
mGnssMeasurementsProvider.onGpsEnabledChanged();
mGnssNavigationMessageProvider.onGpsEnabledChanged();
mGnssAntennaInfoProvider.onGpsEnabledChanged();
mGnssBatchingProvider.enable();
if (mGnssVisibilityControl != null) {
mGnssVisibilityControl.onGpsEnabledChanged(/* isEnabled= */ true);
@@ -1032,10 +992,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() {
@@ -1072,26 +1028,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;
@@ -1528,19 +1483,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;
@@ -1596,13 +1538,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);
});
@@ -1624,9 +1560,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
Log.i(TAG, "restartRequests");
restartLocationRequest();
mGnssAntennaInfoProvider.resumeIfStarted();
mGnssMeasurementsProvider.resumeIfStarted();
mGnssNavigationMessageProvider.resumeIfStarted();
mGnssBatchingProvider.resumeIfStarted();
mGnssGeofenceProvider.resumeIfStarted();
}
@@ -2057,8 +1990,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();
@@ -2147,20 +2079,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 {
@@ -2217,12 +2137,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;
}
}
}