diff --git a/location/java/android/location/AbstractListenerManager.java b/location/java/android/location/AbstractListenerManager.java deleted file mode 100644 index 3dc7cfce2d921..0000000000000 --- a/location/java/android/location/AbstractListenerManager.java +++ /dev/null @@ -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 { - - private static class Registration { - 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 operation) { - mExecutor.execute( - obtainRunnable(Registration::accept, this, operation) - .recycleOnUse()); - } - - private void accept(Consumer 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> 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 registration) - throws RemoteException { - Preconditions.checkNotNull(registration); - - synchronized (mListeners) { - boolean initialRequest = mListeners.isEmpty(); - - Registration 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 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 operation) { - synchronized (mListeners) { - for (Registration 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); - } -} diff --git a/location/java/android/location/GnssRequest.java b/location/java/android/location/GnssRequest.java index 2afb265f638c1..5fc91615fd0ee 100644 --- a/location/java/android/location/GnssRequest.java +++ b/location/java/android/location/GnssRequest.java @@ -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(); } diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 4150926235311..6be770ea2c7b0 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -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
addrs); @@ -69,31 +65,30 @@ interface ILocationManager double upperRightLatitude, double upperRightLongitude, int maxResults, in GeocoderParams params, out List
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 getAllProviders(); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 4b0ad74b7af50..d275337e8ee21 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -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 { - @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 { + + 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 { + ClientListenerManager { @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> registrations) { + for (Registration 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 { + ClientListenerManager { @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 { + ClientListenerManager { @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 gnssAntennaInfos) { - execute((callback) -> callback.onGnssAntennaInfoReceived(gnssAntennaInfos)); + public void onGnssAntennaInfoReceived(List infos) { + deliverToListeners(callback -> callback.onGnssAntennaInfoReceived(infos)); } } } private class BatchedLocationCallbackManager extends - AbstractListenerManager { + ClientListenerManager { @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 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"; /** diff --git a/location/java/android/location/util/listeners/AbstractListenerManager.java b/location/java/android/location/util/listeners/AbstractListenerManager.java new file mode 100644 index 0000000000000..d2adab0aa9d91 --- /dev/null +++ b/location/java/android/location/util/listeners/AbstractListenerManager.java @@ -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 key type + * @param request type + * @param listener type + * @param registration type + * @param merged request type + * @hide + */ +public abstract class AbstractListenerManager, + TMergedRequest> { + + /** + * A listener registration object which holds data associated with the listener. + * + * @param request type + * @param listener type + */ + public static class Registration { + + 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 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 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 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 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 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 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 operation, + @NonNull Predicate 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; + } + } +} diff --git a/location/java/android/location/util/listeners/ClientListenerManager.java b/location/java/android/location/util/listeners/ClientListenerManager.java new file mode 100644 index 0000000000000..35dd07b420da6 --- /dev/null +++ b/location/java/android/location/util/listeners/ClientListenerManager.java @@ -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 request type + * @param listener type + * @hide + */ +public abstract class ClientListenerManager extends + AbstractListenerManager, + 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); + } +} diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 79d8ab2d9866f..55ccc8a2ad4b0 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -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 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 diff --git a/services/core/java/com/android/server/LocationManagerServiceUtils.java b/services/core/java/com/android/server/LocationManagerServiceUtils.java index 9d0fe5e936bbd..f50a885dd20c1 100644 --- a/services/core/java/com/android/server/LocationManagerServiceUtils.java +++ b/services/core/java/com/android/server/LocationManagerServiceUtils.java @@ -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 listener type - * @param request type - */ - public static class LinkedListener extends - LinkedListenerBase { - @Nullable protected final TRequest mRequest; - private final TListener mListener; - private final Consumer mBinderDeathCallback; - - public LinkedListener( - @Nullable TRequest request, - @NonNull TListener listener, - @NonNull CallerIdentity callerIdentity, - @NonNull Consumer 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. */ diff --git a/services/core/java/com/android/server/location/AppForegroundHelper.java b/services/core/java/com/android/server/location/AppForegroundHelper.java index 8ddfc652d333c..ac77e84e4d291 100644 --- a/services/core/java/com/android/server/location/AppForegroundHelper.java +++ b/services/core/java/com/android/server/location/AppForegroundHelper.java @@ -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); } diff --git a/services/core/java/com/android/server/location/GnssAntennaInfoProvider.java b/services/core/java/com/android/server/location/GnssAntennaInfoProvider.java index 41690b4cd157a..4926a1c7f6e05 100644 --- a/services/core/java/com/android/server/location/GnssAntennaInfoProvider.java +++ b/services/core/java/com/android/server/location/GnssAntennaInfoProvider.java @@ -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 { - private static final String TAG = "GnssAntennaInfoProvider"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); +public class GnssAntennaInfoProvider extends + GnssListenerManager { 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 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 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 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(); } diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 58e332ab6d1da..8984ba8e034b7 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -236,17 +236,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements // Update duration extension multiplier for emergency REQUEST_LOCATION. private static final int EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER = 3; - /** simpler wrapper for ProviderRequest + Worksource */ - private static class GpsRequest { - public ProviderRequest request; - public WorkSource source; - - public GpsRequest(ProviderRequest request, WorkSource source) { - this.request = request; - this.source = source; - } - } - // Threadsafe class to hold stats reported in the Extras Bundle private static class LocationExtras { private int mSvCount; @@ -370,9 +359,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements // Current request from underlying location clients. private ProviderRequest mProviderRequest; - // The WorkSource associated with the most recent client request (i.e, most recent call to - // setRequest). - private WorkSource mWorkSource = null; // True if gps should be disabled because of PowerManager controls private boolean mDisableGpsForPowerManager = false; @@ -396,7 +382,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private final Looper mLooper; private final LocationExtras mLocationExtras = new LocationExtras(); - private final GnssStatusListenerHelper mGnssStatusListenerHelper; + private final GnssStatusProvider mGnssStatusListenerHelper; private final GnssMeasurementsProvider mGnssMeasurementsProvider; private final GnssMeasurementCorrectionsProvider mGnssMeasurementCorrectionsProvider; private final GnssAntennaInfoProvider mGnssAntennaInfoProvider; @@ -456,7 +442,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements // GNSS Metrics private GnssMetrics mGnssMetrics; - public GnssStatusListenerHelper getGnssStatusProvider() { + public GnssStatusProvider getGnssStatusProvider() { return mGnssStatusListenerHelper; } @@ -631,7 +617,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } } - public GnssLocationProvider(Context context) { + public GnssLocationProvider(Context context, UserInfoHelper userInfoHelper, + SettingsHelper settingsHelper, AppOpsHelper appOpsHelper, + AppForegroundHelper appForegroundHelper, LocationUsageLogger logger) { super(FgThread.getExecutor(), context); ensureInitialized(); @@ -679,40 +667,15 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mSuplEsEnabled); sendMessage(INITIALIZE_HANDLER, 0, null); - mGnssStatusListenerHelper = new GnssStatusListenerHelper(mContext, mHandler) { - @Override - protected boolean isAvailableInPlatform() { - return isSupported(); - } - - @Override - protected boolean isGpsEnabled() { - return GnssLocationProvider.this.isGpsEnabled(); - } - }; - - mGnssMeasurementsProvider = new GnssMeasurementsProvider(mContext, mHandler) { - @Override - protected boolean isGpsEnabled() { - return GnssLocationProvider.this.isGpsEnabled(); - } - }; - + mGnssStatusListenerHelper = new GnssStatusProvider(userInfoHelper, settingsHelper, + appOpsHelper, appForegroundHelper, logger); + mGnssMeasurementsProvider = new GnssMeasurementsProvider(userInfoHelper, + settingsHelper, appOpsHelper, appForegroundHelper, logger); mGnssMeasurementCorrectionsProvider = new GnssMeasurementCorrectionsProvider(mHandler); - - mGnssAntennaInfoProvider = new GnssAntennaInfoProvider(mContext, mHandler) { - @Override - protected boolean isGpsEnabled() { - return GnssLocationProvider.this.isGpsEnabled(); - } - }; - - mGnssNavigationMessageProvider = new GnssNavigationMessageProvider(mContext, mHandler) { - @Override - protected boolean isGpsEnabled() { - return GnssLocationProvider.this.isGpsEnabled(); - } - }; + mGnssAntennaInfoProvider = new GnssAntennaInfoProvider(userInfoHelper, settingsHelper, + appOpsHelper, appForegroundHelper); + mGnssNavigationMessageProvider = new GnssNavigationMessageProvider(userInfoHelper, + settingsHelper, appOpsHelper, appForegroundHelper); mGnssMetrics = new GnssMetrics(mContext, mBatteryStats); mNtpTimeHelper = new NtpTimeHelper(mContext, mLooper, this); @@ -1009,9 +972,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mC2KServerHost, mC2KServerPort); } - mGnssMeasurementsProvider.onGpsEnabledChanged(); - mGnssNavigationMessageProvider.onGpsEnabledChanged(); - mGnssAntennaInfoProvider.onGpsEnabledChanged(); mGnssBatchingProvider.enable(); if (mGnssVisibilityControl != null) { mGnssVisibilityControl.onGpsEnabledChanged(/* isEnabled= */ true); @@ -1037,10 +997,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mGnssBatchingProvider.disable(); // do this before releasing wakelock native_cleanup(); - - mGnssAntennaInfoProvider.onGpsEnabledChanged(); - mGnssMeasurementsProvider.onGpsEnabledChanged(); - mGnssNavigationMessageProvider.onGpsEnabledChanged(); } private void updateEnabled() { @@ -1077,26 +1033,25 @@ public class GnssLocationProvider extends AbstractLocationProvider implements @Override public void onSetRequest(ProviderRequest request) { - sendMessage(SET_REQUEST, 0, new GpsRequest(request, request.workSource)); + sendMessage(SET_REQUEST, 0, request); } - private void handleSetRequest(ProviderRequest request, WorkSource source) { + private void handleSetRequest(ProviderRequest request) { mProviderRequest = request; - mWorkSource = source; updateEnabled(); updateRequirements(); } // Called when the requirements for GPS may have changed private void updateRequirements() { - if (mProviderRequest == null || mWorkSource == null) { + if (mProviderRequest == null || mProviderRequest.workSource == null) { return; } if (DEBUG) Log.d(TAG, "setRequest " + mProviderRequest); if (mProviderRequest.reportLocation && isGpsEnabled()) { // update client uids - updateClientUids(mWorkSource); + updateClientUids(mProviderRequest.workSource); mFixInterval = (int) mProviderRequest.interval; mLowPowerMode = mProviderRequest.lowPowerMode; @@ -1533,19 +1488,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements meanCn0 += gnssStatus.getCn0DbHz(i); mGnssMetrics.logConstellationType(gnssStatus.getConstellationType(i)); } - if (VERBOSE) { - Log.v(TAG, "svid: " + gnssStatus.getSvid(i) - + " cn0: " + gnssStatus.getCn0DbHz(i) - + " basebandCn0: " + gnssStatus.getBasebandCn0DbHz(i) - + " elev: " + gnssStatus.getElevationDegrees(i) - + " azimuth: " + gnssStatus.getAzimuthDegrees(i) - + " carrier frequency: " + gnssStatus.getCn0DbHz(i) - + (gnssStatus.hasEphemerisData(i) ? " E" : " ") - + (gnssStatus.hasAlmanacData(i) ? " A" : " ") - + (gnssStatus.usedInFix(i) ? "U" : "") - + (gnssStatus.hasCarrierFrequencyHz(i) ? "F" : "") - + (gnssStatus.hasBasebandCn0DbHz(i) ? "B" : "")); - } } if (usedInFixCount > 0) { meanCn0 /= usedInFixCount; @@ -1601,13 +1543,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements requestUtcTime(); } - mGnssMeasurementsProvider.onCapabilitiesUpdated( - hasCapability(GPS_CAPABILITY_MEASUREMENTS)); - mGnssNavigationMessageProvider.onCapabilitiesUpdated( - hasCapability(GPS_CAPABILITY_NAV_MESSAGES)); restartRequests(); - mGnssAntennaInfoProvider.onCapabilitiesUpdated( - hasCapability(GPS_CAPABILITY_ANTENNA_INFO)); mGnssCapabilitiesProvider.setTopHalCapabilities(mTopHalCapabilities); }); @@ -1629,9 +1565,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements Log.i(TAG, "restartRequests"); restartLocationRequest(); - mGnssAntennaInfoProvider.resumeIfStarted(); - mGnssMeasurementsProvider.resumeIfStarted(); - mGnssNavigationMessageProvider.resumeIfStarted(); mGnssBatchingProvider.resumeIfStarted(); mGnssGeofenceProvider.resumeIfStarted(); } @@ -2062,8 +1995,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements int message = msg.what; switch (message) { case SET_REQUEST: - GpsRequest gpsRequest = (GpsRequest) msg.obj; - handleSetRequest(gpsRequest.request, gpsRequest.source); + handleSetRequest((ProviderRequest) msg.obj); break; case INJECT_NTP_TIME: mNtpTimeHelper.retrieveAndInjectNtpTime(); @@ -2152,20 +2084,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } } - private abstract class LocationChangeListener implements LocationListener { + private abstract static class LocationChangeListener implements LocationListener { private int mNumLocationUpdateRequest; - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - } - - @Override - public void onProviderEnabled(String provider) { - } - - @Override - public void onProviderDisabled(String provider) { - } } private final class NetworkLocationListener extends LocationChangeListener { @@ -2222,12 +2142,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements s.append(" ago)").append('\n'); s.append("mFixInterval=").append(mFixInterval).append('\n'); s.append("mLowPowerMode=").append(mLowPowerMode).append('\n'); - s.append("mGnssAntennaInfoProvider.isRegistered()=") - .append(mGnssAntennaInfoProvider.isRegistered()).append('\n'); - s.append("mGnssMeasurementsProvider.isRegistered()=") - .append(mGnssMeasurementsProvider.isRegistered()).append('\n'); - s.append("mGnssNavigationMessageProvider.isRegistered()=") - .append(mGnssNavigationMessageProvider.isRegistered()).append('\n'); s.append("mDisableGpsForPowerManager=").append(mDisableGpsForPowerManager).append('\n'); s.append("mTopHalCapabilities=0x").append(Integer.toHexString(mTopHalCapabilities)); s.append(" ( "); diff --git a/services/core/java/com/android/server/location/GnssMeasurementCorrectionsProvider.java b/services/core/java/com/android/server/location/GnssMeasurementCorrectionsProvider.java index 82528caa0b4e0..1a0a3db237d06 100644 --- a/services/core/java/com/android/server/location/GnssMeasurementCorrectionsProvider.java +++ b/services/core/java/com/android/server/location/GnssMeasurementCorrectionsProvider.java @@ -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); } diff --git a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java index 7ea81c01f210f..e774bd95f3baf 100644 --- a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java @@ -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 { - private static final String TAG = "GnssMeasProvider"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); +public class GnssMeasurementsProvider extends + GnssListenerManager { + 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 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 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 { - 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(); } diff --git a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java index fb901e86f494f..10f7c4cb34eb2 100644 --- a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java +++ b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java @@ -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 { - private static final String TAG = "GnssNavigationMessageProvider"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); +public class GnssNavigationMessageProvider extends + GnssListenerManager { 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 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 { - 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(); } diff --git a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java b/services/core/java/com/android/server/location/GnssStatusListenerHelper.java deleted file mode 100644 index fad26ac1cc1e1..0000000000000 --- a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java +++ /dev/null @@ -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 { - 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 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); - }); - } -} diff --git a/services/core/java/com/android/server/location/GnssStatusProvider.java b/services/core/java/com/android/server/location/GnssStatusProvider.java new file mode 100644 index 0000000000000..7d9ff2c55907d --- /dev/null +++ b/services/core/java/com/android/server/location/GnssStatusProvider.java @@ -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 { + + 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())); + } +} diff --git a/services/core/java/com/android/server/location/LocationUsageLogger.java b/services/core/java/com/android/server/location/LocationUsageLogger.java index 93e19df01cf35..dad884d12b0e8 100644 --- a/services/core/java/com/android/server/location/LocationUsageLogger.java +++ b/services/core/java/com/android/server/location/LocationUsageLogger.java @@ -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; } diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java deleted file mode 100644 index 0da865e9cca45..0000000000000 --- a/services/core/java/com/android/server/location/RemoteListenerHelper.java +++ /dev/null @@ -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 the type of request. - * @param the type of GNSS data listener. - */ -public abstract class RemoteListenerHelper { - - 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 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 getHandlerOperation(int result); - - protected interface ListenerOperation { - void execute(TListener listener, CallerIdentity callerIdentity) throws RemoteException; - } - - protected void foreach(ListenerOperation 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 operation) { - for (IdentifiedListener identifiedListener : mListenerMap.values()) { - post(identifiedListener, operation); - } - } - - private void post(IdentifiedListener identifiedListener, - ListenerOperation 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 mOperation; - - private HandlerRunnable(IdentifiedListener identifiedListener, - ListenerOperation 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); - } - } - } -} diff --git a/services/core/java/com/android/server/location/SettingsHelper.java b/services/core/java/com/android/server/location/SettingsHelper.java index 7ab258c29b462..5ca83f9981072 100644 --- a/services/core/java/com/android/server/location/SettingsHelper.java +++ b/services/core/java/com/android/server/location/SettingsHelper.java @@ -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. diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java index 85eeb81ddf0dc..2c7ad97be100c 100644 --- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java +++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java @@ -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> - mGnssMeasurementsListeners = new ArrayMap<>(); - - @GuardedBy("mGnssAntennaInfoListeners") - private final ArrayMap> - mGnssAntennaInfoListeners = new ArrayMap<>(); - - @GuardedBy("mGnssNavigationMessageListeners") - private final ArrayMap> - mGnssNavigationMessageListeners = new ArrayMap<>(); - - @GuardedBy("mGnssStatusListeners") - private final ArrayMap> - 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 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 void updateListenersOnForegroundChangedLocked( - Map> gnssDataListeners, - RemoteListenerHelper gnssDataProvider, - Function mapBinderToListener, - int uid, - boolean foreground) { - for (Map.Entry> entry : - gnssDataListeners.entrySet()) { - LinkedListener 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 boolean addGnssDataListenerLocked( - @Nullable TRequest request, - TListener listener, - String packageName, - @Nullable String featureId, - RemoteListenerHelper gnssDataProvider, - ArrayMap> gnssDataListeners, - Consumer binderDeathCallback) { - mContext.enforceCallingPermission(Manifest.permission.ACCESS_FINE_LOCATION, null); - - CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId); - if (!mAppOpsHelper.checkLocationAccess(identity)) { - return false; - } - - LinkedListener 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 void removeGnssDataListenerLocked( - TListener listener, - RemoteListenerHelper gnssDataProvider, - ArrayMap> gnssDataListeners) { - if (gnssDataProvider == null) { - Log.e( - TAG, - "Can not remove GNSS data listener. GNSS data provider " - + "not available."); - return; - } - - IBinder binder = listener.asBinder(); - LinkedListener 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 locations) { - IBatchedLocationCallback gnssBatchingCallback; - LinkedListener 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) { diff --git a/services/core/java/com/android/server/location/util/listeners/GnssListenerManager.java b/services/core/java/com/android/server/location/util/listeners/GnssListenerManager.java new file mode 100644 index 0000000000000..5da3542fe6513 --- /dev/null +++ b/services/core/java/com/android/server/location/util/listeners/GnssListenerManager.java @@ -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 request type + * @param listener type + * @param merged request type + */ +public abstract class GnssListenerManager + extends AbstractListenerManager.GnssRegistration, TMergedRequest> { + + /** + * Registration object for GNSS listeners. + */ + protected class GnssRegistration extends + IdentifiedRegistration 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 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(); + } +} diff --git a/services/core/java/com/android/server/location/util/listeners/IdentifiedRegistration.java b/services/core/java/com/android/server/location/util/listeners/IdentifiedRegistration.java new file mode 100644 index 0000000000000..79577c93595b2 --- /dev/null +++ b/services/core/java/com/android/server/location/util/listeners/IdentifiedRegistration.java @@ -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 request type + * @param listener type + */ +public class IdentifiedRegistration extends + AbstractListenerManager.Registration { + + 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; + } +} diff --git a/services/robotests/src/com/android/server/location/GnssAntennaInfoProviderTest.java b/services/robotests/src/com/android/server/location/GnssAntennaInfoProviderTest.java deleted file mode 100644 index 76f7ad646bb12..0000000000000 --- a/services/robotests/src/com/android/server/location/GnssAntennaInfoProviderTest.java +++ /dev/null @@ -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(); - } -} diff --git a/services/robotests/src/com/android/server/location/GnssMeasurementsProviderTest.java b/services/robotests/src/com/android/server/location/GnssMeasurementsProviderTest.java deleted file mode 100644 index b349b67dab0cb..0000000000000 --- a/services/robotests/src/com/android/server/location/GnssMeasurementsProviderTest.java +++ /dev/null @@ -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()); - } -} diff --git a/services/robotests/src/com/android/server/location/GnssNavigationMessageProviderTest.java b/services/robotests/src/com/android/server/location/GnssNavigationMessageProviderTest.java deleted file mode 100644 index aa2a96e6fad4e..0000000000000 --- a/services/robotests/src/com/android/server/location/GnssNavigationMessageProviderTest.java +++ /dev/null @@ -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(); - } -} diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index ff34ebd8aa9de..caa3bdbba7e55 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -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: [ diff --git a/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java similarity index 89% rename from services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java rename to services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java index a9bf39329af24..dd196312f7631 100644 --- a/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java @@ -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); } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/listeners/AbstractListenerManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/util/listeners/AbstractListenerManagerTest.java new file mode 100644 index 0000000000000..cb821eb091e06 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/listeners/AbstractListenerManagerTest.java @@ -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 { + + private boolean mActive = true; + + protected TestRegistration(Integer integer, Runnable runnable) { + super(integer, DIRECT_EXECUTOR, runnable); + } + } + + private static class TestListenerManager extends + AbstractListenerManager { + + 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 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 testRegistrations) { + int max = Integer.MIN_VALUE; + for (TestRegistration registration : testRegistrations) { + if (registration.getRequest() > max) { + max = registration.getRequest(); + } + } + return max; + } + } +}