From 6257ba865cd6cacbf1ed5cb79cdbb472c77db28a Mon Sep 17 00:00:00 2001 From: Soonil Nagarkar Date: Wed, 26 Feb 2020 11:57:42 -0800 Subject: [PATCH] Refactor GNSS listeners Gnss listeners currently simply ignore requests that are not made in the foreground or with appropriate permissions, even though these are transitory attributes. In addition, some gnss listener use appops APIs incorrectly. The RemoteListenerHelper pattern is not great either, and makes it extremely difficult to write clear code, as well as duplicates much of the functionality provided by AbstractListenerManager on the client side. This CL unifies logic for multiplexing listeners so it can be used across both the client and server side, and updates all GNSS listeners to use the new model, which properly respects foreground, appops, permissions, gnss enablement, and capabilities. Test: presubmits + manual Change-Id: I8f158e860e10c3ed43b246f6f3687169ac134343 --- .../location/AbstractListenerManager.java | 220 --------- .../java/android/location/GnssRequest.java | 4 +- .../android/location/ILocationManager.aidl | 39 +- .../android/location/LocationManager.java | 410 ++++++++--------- .../listeners/AbstractListenerManager.java | 411 +++++++++++++++++ .../util/listeners/ClientListenerManager.java | 69 +++ .../server/LocationManagerService.java | 122 ++--- .../server/LocationManagerServiceUtils.java | 35 -- .../server/location/AppForegroundHelper.java | 13 +- .../location/GnssAntennaInfoProvider.java | 135 +++--- .../server/location/GnssLocationProvider.java | 124 +---- .../GnssMeasurementCorrectionsProvider.java | 4 +- .../location/GnssMeasurementsProvider.java | 216 ++++----- .../GnssNavigationMessageProvider.java | 144 +++--- .../location/GnssStatusListenerHelper.java | 97 ---- .../server/location/GnssStatusProvider.java | 157 +++++++ .../server/location/LocationUsageLogger.java | 13 +- .../server/location/RemoteListenerHelper.java | 300 ------------ .../server/location/SettingsHelper.java | 14 + .../location/gnss/GnssManagerService.java | 435 ++++-------------- .../util/listeners/GnssListenerManager.java | 285 ++++++++++++ .../listeners/IdentifiedRegistration.java | 69 +++ .../location/GnssAntennaInfoProviderTest.java | 94 ---- .../GnssMeasurementsProviderTest.java | 83 ---- .../GnssNavigationMessageProviderTest.java | 82 ---- .../tests/mockingservicestests/Android.bp | 2 + .../location/gnss/GnssManagerServiceTest.java | 154 +++---- .../AbstractListenerManagerTest.java | 259 +++++++++++ 28 files changed, 1945 insertions(+), 2045 deletions(-) delete mode 100644 location/java/android/location/AbstractListenerManager.java create mode 100644 location/java/android/location/util/listeners/AbstractListenerManager.java create mode 100644 location/java/android/location/util/listeners/ClientListenerManager.java delete mode 100644 services/core/java/com/android/server/location/GnssStatusListenerHelper.java create mode 100644 services/core/java/com/android/server/location/GnssStatusProvider.java delete mode 100644 services/core/java/com/android/server/location/RemoteListenerHelper.java create mode 100644 services/core/java/com/android/server/location/util/listeners/GnssListenerManager.java create mode 100644 services/core/java/com/android/server/location/util/listeners/IdentifiedRegistration.java delete mode 100644 services/robotests/src/com/android/server/location/GnssAntennaInfoProviderTest.java delete mode 100644 services/robotests/src/com/android/server/location/GnssMeasurementsProviderTest.java delete mode 100644 services/robotests/src/com/android/server/location/GnssNavigationMessageProviderTest.java rename services/tests/{servicestests => mockingservicestests}/src/com/android/server/location/gnss/GnssManagerServiceTest.java (89%) create mode 100644 services/tests/mockingservicestests/src/com/android/server/location/util/listeners/AbstractListenerManagerTest.java 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 5c2bf26329207..20fcc4f9a8bef 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); @@ -1004,9 +967,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mC2KServerHost, mC2KServerPort); } - mGnssMeasurementsProvider.onGpsEnabledChanged(); - mGnssNavigationMessageProvider.onGpsEnabledChanged(); - mGnssAntennaInfoProvider.onGpsEnabledChanged(); mGnssBatchingProvider.enable(); if (mGnssVisibilityControl != null) { mGnssVisibilityControl.onGpsEnabledChanged(/* isEnabled= */ true); @@ -1032,10 +992,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mGnssBatchingProvider.disable(); // do this before releasing wakelock native_cleanup(); - - mGnssAntennaInfoProvider.onGpsEnabledChanged(); - mGnssMeasurementsProvider.onGpsEnabledChanged(); - mGnssNavigationMessageProvider.onGpsEnabledChanged(); } private void updateEnabled() { @@ -1072,26 +1028,25 @@ public class GnssLocationProvider extends AbstractLocationProvider implements @Override public void onSetRequest(ProviderRequest request) { - sendMessage(SET_REQUEST, 0, new GpsRequest(request, request.workSource)); + sendMessage(SET_REQUEST, 0, request); } - private void handleSetRequest(ProviderRequest request, WorkSource source) { + private void handleSetRequest(ProviderRequest request) { mProviderRequest = request; - mWorkSource = source; updateEnabled(); updateRequirements(); } // Called when the requirements for GPS may have changed private void updateRequirements() { - if (mProviderRequest == null || mWorkSource == null) { + if (mProviderRequest == null || mProviderRequest.workSource == null) { return; } if (DEBUG) Log.d(TAG, "setRequest " + mProviderRequest); if (mProviderRequest.reportLocation && isGpsEnabled()) { // update client uids - updateClientUids(mWorkSource); + updateClientUids(mProviderRequest.workSource); mFixInterval = (int) mProviderRequest.interval; mLowPowerMode = mProviderRequest.lowPowerMode; @@ -1528,19 +1483,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements meanCn0 += gnssStatus.getCn0DbHz(i); mGnssMetrics.logConstellationType(gnssStatus.getConstellationType(i)); } - if (VERBOSE) { - Log.v(TAG, "svid: " + gnssStatus.getSvid(i) - + " cn0: " + gnssStatus.getCn0DbHz(i) - + " basebandCn0: " + gnssStatus.getBasebandCn0DbHz(i) - + " elev: " + gnssStatus.getElevationDegrees(i) - + " azimuth: " + gnssStatus.getAzimuthDegrees(i) - + " carrier frequency: " + gnssStatus.getCn0DbHz(i) - + (gnssStatus.hasEphemerisData(i) ? " E" : " ") - + (gnssStatus.hasAlmanacData(i) ? " A" : " ") - + (gnssStatus.usedInFix(i) ? "U" : "") - + (gnssStatus.hasCarrierFrequencyHz(i) ? "F" : "") - + (gnssStatus.hasBasebandCn0DbHz(i) ? "B" : "")); - } } if (usedInFixCount > 0) { meanCn0 /= usedInFixCount; @@ -1596,13 +1538,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements requestUtcTime(); } - mGnssMeasurementsProvider.onCapabilitiesUpdated( - hasCapability(GPS_CAPABILITY_MEASUREMENTS)); - mGnssNavigationMessageProvider.onCapabilitiesUpdated( - hasCapability(GPS_CAPABILITY_NAV_MESSAGES)); restartRequests(); - mGnssAntennaInfoProvider.onCapabilitiesUpdated( - hasCapability(GPS_CAPABILITY_ANTENNA_INFO)); mGnssCapabilitiesProvider.setTopHalCapabilities(mTopHalCapabilities); }); @@ -1624,9 +1560,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements Log.i(TAG, "restartRequests"); restartLocationRequest(); - mGnssAntennaInfoProvider.resumeIfStarted(); - mGnssMeasurementsProvider.resumeIfStarted(); - mGnssNavigationMessageProvider.resumeIfStarted(); mGnssBatchingProvider.resumeIfStarted(); mGnssGeofenceProvider.resumeIfStarted(); } @@ -2057,8 +1990,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements int message = msg.what; switch (message) { case SET_REQUEST: - GpsRequest gpsRequest = (GpsRequest) msg.obj; - handleSetRequest(gpsRequest.request, gpsRequest.source); + handleSetRequest((ProviderRequest) msg.obj); break; case INJECT_NTP_TIME: mNtpTimeHelper.retrieveAndInjectNtpTime(); @@ -2147,20 +2079,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } } - private abstract class LocationChangeListener implements LocationListener { + private abstract static class LocationChangeListener implements LocationListener { private int mNumLocationUpdateRequest; - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - } - - @Override - public void onProviderEnabled(String provider) { - } - - @Override - public void onProviderDisabled(String provider) { - } } private final class NetworkLocationListener extends LocationChangeListener { @@ -2217,12 +2137,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements s.append(" ago)").append('\n'); s.append("mFixInterval=").append(mFixInterval).append('\n'); s.append("mLowPowerMode=").append(mLowPowerMode).append('\n'); - s.append("mGnssAntennaInfoProvider.isRegistered()=") - .append(mGnssAntennaInfoProvider.isRegistered()).append('\n'); - s.append("mGnssMeasurementsProvider.isRegistered()=") - .append(mGnssMeasurementsProvider.isRegistered()).append('\n'); - s.append("mGnssNavigationMessageProvider.isRegistered()=") - .append(mGnssNavigationMessageProvider.isRegistered()).append('\n'); s.append("mDisableGpsForPowerManager=").append(mDisableGpsForPowerManager).append('\n'); s.append("mTopHalCapabilities=0x").append(Integer.toHexString(mTopHalCapabilities)); s.append(" ( "); 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; + } + } +}