diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index fb5fba0d097e5..bea414f69f43d 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -86,6 +86,7 @@ import com.android.server.location.ActivityRecognitionProxy; import com.android.server.location.GeocoderProxy; import com.android.server.location.GeofenceManager; import com.android.server.location.GeofenceProxy; +import com.android.server.location.GnssBatchingProvider; import com.android.server.location.GnssLocationProvider; import com.android.server.location.GnssMeasurementsProvider; import com.android.server.location.GnssNavigationMessageProvider; @@ -244,7 +245,7 @@ public class LocationManagerService extends ILocationManager.Stub { private GnssLocationProvider.GnssMetricsProvider mGnssMetricsProvider; - private GnssLocationProvider.GnssBatchingProvider mGnssBatchingProvider; + private GnssBatchingProvider mGnssBatchingProvider; private IBatchedLocationCallback mGnssBatchingCallback; private LinkedCallback mGnssBatchingDeathCallback; private boolean mGnssBatchingInProgress = false; @@ -1173,7 +1174,7 @@ public class LocationManagerService extends ILocationManager.Stub { "Location Hardware permission not granted to access hardware batching"); if (hasGnssPermissions(packageName) && mGnssBatchingProvider != null) { - return mGnssBatchingProvider.getSize(); + return mGnssBatchingProvider.getBatchSize(); } else { return 0; } diff --git a/services/core/java/com/android/server/location/GnssBatchingProvider.java b/services/core/java/com/android/server/location/GnssBatchingProvider.java new file mode 100644 index 0000000000000..f3918ee9e8ff6 --- /dev/null +++ b/services/core/java/com/android/server/location/GnssBatchingProvider.java @@ -0,0 +1,145 @@ +package com.android.server.location; + +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Manages GNSS Batching operations. + * + *

This class is not thread safe (It's client's responsibility to make sure calls happen on + * the same thread). + */ +public class GnssBatchingProvider { + + private static final String TAG = "GnssBatchingProvider"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final GnssBatchingProviderNative mNative; + private boolean mEnabled; + private boolean mStarted; + private long mPeriodNanos; + private boolean mWakeOnFifoFull; + + GnssBatchingProvider() { + this(new GnssBatchingProviderNative()); + } + + @VisibleForTesting + GnssBatchingProvider(GnssBatchingProviderNative gnssBatchingProviderNative) { + mNative = gnssBatchingProviderNative; + } + + /** + * Returns the GNSS batching size + */ + public int getBatchSize() { + return mNative.getBatchSize(); + } + + /** Enable GNSS batching. */ + public void enable() { + mEnabled = mNative.initBatching(); + if (!mEnabled) { + Log.e(TAG, "Failed to initialize GNSS batching"); + } + } + + /** + * Starts the hardware batching operation + */ + public boolean start(long periodNanos, boolean wakeOnFifoFull) { + if (!mEnabled) { + throw new IllegalStateException(); + } + if (periodNanos <= 0) { + Log.e(TAG, "Invalid periodNanos " + periodNanos + + " in batching request, not started"); + return false; + } + mStarted = mNative.startBatch(periodNanos, wakeOnFifoFull); + if (mStarted) { + mPeriodNanos = periodNanos; + mWakeOnFifoFull = wakeOnFifoFull; + } + return mStarted; + } + + /** + * Forces a flush of existing locations from the hardware batching + */ + public void flush() { + if (!mStarted) { + Log.w(TAG, "Cannot flush since GNSS batching has not started."); + return; + } + mNative.flushBatch(); + } + + /** + * Stops the batching operation + */ + public boolean stop() { + boolean stopped = mNative.stopBatch(); + if (stopped) { + mStarted = false; + } + return stopped; + } + + /** Disable GNSS batching. */ + public void disable() { + stop(); + mNative.cleanupBatching(); + mEnabled = false; + } + + // TODO(b/37460011): Use this with death recovery logic. + void resumeIfStarted() { + if (DEBUG) { + Log.d(TAG, "resumeIfStarted"); + } + if (mStarted) { + mNative.startBatch(mPeriodNanos, mWakeOnFifoFull); + } + } + + @VisibleForTesting + static class GnssBatchingProviderNative { + public int getBatchSize() { + return native_get_batch_size(); + } + + public boolean startBatch(long periodNanos, boolean wakeOnFifoFull) { + return native_start_batch(periodNanos, wakeOnFifoFull); + } + + public void flushBatch() { + native_flush_batch(); + } + + public boolean stopBatch() { + return native_stop_batch(); + } + + public boolean initBatching() { + return native_init_batching(); + } + + public void cleanupBatching() { + native_cleanup_batching(); + } + } + + private static native int native_get_batch_size(); + + private static native boolean native_start_batch(long periodNanos, boolean wakeOnFifoFull); + + private static native void native_flush_batch(); + + private static native boolean native_stop_batch(); + + private static native boolean native_init_batching(); + + private static native void native_cleanup_batching(); +} diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 58bca196ae36d..b09b4571b6225 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -420,6 +420,7 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt private final LocationChangeListener mNetworkLocationListener = new NetworkLocationListener(); private final LocationChangeListener mFusedLocationListener = new FusedLocationListener(); private final NtpTimeHelper mNtpTimeHelper; + private final GnssBatchingProvider mGnssBatchingProvider; // Handler for processing events private Handler mHandler; @@ -887,6 +888,7 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt mGnssSatelliteBlacklistHelper = new GnssSatelliteBlacklistHelper(mContext, looper, this); mHandler.post(mGnssSatelliteBlacklistHelper::updateSatelliteBlacklist); + mGnssBatchingProvider = new GnssBatchingProvider(); } /** @@ -1273,7 +1275,7 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt mGnssMeasurementsProvider.onGpsEnabledChanged(); mGnssNavigationMessageProvider.onGpsEnabledChanged(); - enableBatching(); + mGnssBatchingProvider.enable(); } else { synchronized (mLock) { mEnabled = false; @@ -1305,7 +1307,7 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt mAlarmManager.cancel(mWakeupIntent); mAlarmManager.cancel(mTimeoutIntent); - disableBatching(); + mGnssBatchingProvider.disable(); // do this before releasing wakelock native_cleanup(); @@ -2007,58 +2009,11 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt }; } - public interface GnssBatchingProvider { - /** - * Returns the GNSS batching size - */ - int getSize(); - - /** - * Starts the hardware batching operation - */ - boolean start(long periodNanos, boolean wakeOnFifoFull); - - /** - * Forces a flush of existing locations from the hardware batching - */ - void flush(); - - /** - * Stops the batching operation - */ - boolean stop(); - } - /** * @hide */ public GnssBatchingProvider getGnssBatchingProvider() { - return new GnssBatchingProvider() { - @Override - public int getSize() { - return native_get_batch_size(); - } - - @Override - public boolean start(long periodNanos, boolean wakeOnFifoFull) { - if (periodNanos <= 0) { - Log.e(TAG, "Invalid periodNanos " + periodNanos + - "in batching request, not started"); - return false; - } - return native_start_batch(periodNanos, wakeOnFifoFull); - } - - @Override - public void flush() { - native_flush_batch(); - } - - @Override - public boolean stop() { - return native_stop_batch(); - } - }; + return mGnssBatchingProvider; } public interface GnssMetricsProvider { @@ -2080,23 +2035,6 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt }; } - /** - * Initialize Batching if enabled - */ - private void enableBatching() { - if (!native_init_batching()) { - Log.e(TAG, "Failed to initialize GNSS batching"); - } - } - - /** - * Disable batching - */ - private void disableBatching() { - native_stop_batch(); - native_cleanup_batching(); - } - /** * called from native code - GNSS location batch callback */ @@ -2918,19 +2856,5 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt private static native boolean native_set_emergency_supl_pdn(int emergencySuplPdn); private static native boolean native_set_satellite_blacklist(int[] constellations, int[] svIds); - - // GNSS Batching - private static native int native_get_batch_size(); - - private static native boolean native_start_batch(long periodNanos, boolean wakeOnFifoFull); - - private static native void native_flush_batch(); - - private static native boolean native_stop_batch(); - - private static native boolean native_init_batching(); - - private static native void native_cleanup_batching(); - } diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index e18eee2676106..92ef9f16ca935 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -2050,7 +2050,7 @@ static jboolean android_location_GnssLocationProvider_set_satellite_blacklist( } -static jint android_location_GnssLocationProvider_get_batch_size(JNIEnv*, jclass) { +static jint android_location_GnssBatchingProvider_get_batch_size(JNIEnv*, jclass) { if (gnssBatchingIface == nullptr) { return 0; // batching not supported, size = 0 } @@ -2062,7 +2062,7 @@ static jint android_location_GnssLocationProvider_get_batch_size(JNIEnv*, jclass } } -static jboolean android_location_GnssLocationProvider_init_batching(JNIEnv*, jclass) { +static jboolean android_location_GnssBatchingProvider_init_batching(JNIEnv*, jclass) { if (gnssBatchingIface == nullptr) { return JNI_FALSE; // batching not supported } @@ -2071,14 +2071,14 @@ static jboolean android_location_GnssLocationProvider_init_batching(JNIEnv*, jcl return static_cast(gnssBatchingIface->init(gnssBatchingCbIface)); } -static void android_location_GnssLocationProvider_cleanup_batching(JNIEnv*, jclass) { +static void android_location_GnssBatchingProvider_cleanup_batching(JNIEnv*, jclass) { if (gnssBatchingIface == nullptr) { return; // batching not supported } gnssBatchingIface->cleanup(); } -static jboolean android_location_GnssLocationProvider_start_batch(JNIEnv*, jclass, +static jboolean android_location_GnssBatchingProvider_start_batch(JNIEnv*, jclass, jlong periodNanos, jboolean wakeOnFifoFull) { if (gnssBatchingIface == nullptr) { return JNI_FALSE; // batching not supported @@ -2095,7 +2095,7 @@ static jboolean android_location_GnssLocationProvider_start_batch(JNIEnv*, jclas return static_cast(gnssBatchingIface->start(options)); } -static void android_location_GnssLocationProvider_flush_batch(JNIEnv*, jclass) { +static void android_location_GnssBatchingProvider_flush_batch(JNIEnv*, jclass) { if (gnssBatchingIface == nullptr) { return; // batching not supported } @@ -2103,7 +2103,7 @@ static void android_location_GnssLocationProvider_flush_batch(JNIEnv*, jclass) { gnssBatchingIface->flush(); } -static jboolean android_location_GnssLocationProvider_stop_batch(JNIEnv*, jclass) { +static jboolean android_location_GnssBatchingProvider_stop_batch(JNIEnv*, jclass) { if (gnssBatchingIface == nullptr) { return JNI_FALSE; // batching not supported } @@ -2241,30 +2241,36 @@ static const JNINativeMethod sMethods[] = { {"native_set_satellite_blacklist", "([I[I)Z", reinterpret_cast(android_location_GnssLocationProvider_set_satellite_blacklist)}, +}; + +static const JNINativeMethod sMethodsBatching[] = { + /* name, signature, funcPtr */ {"native_get_batch_size", "()I", - reinterpret_cast(android_location_GnssLocationProvider_get_batch_size)}, - {"native_init_batching", - "()Z", - reinterpret_cast(android_location_GnssLocationProvider_init_batching)}, + reinterpret_cast(android_location_GnssBatchingProvider_get_batch_size)}, {"native_start_batch", "(JZ)Z", - reinterpret_cast(android_location_GnssLocationProvider_start_batch)}, + reinterpret_cast(android_location_GnssBatchingProvider_start_batch)}, {"native_flush_batch", "()V", - reinterpret_cast(android_location_GnssLocationProvider_flush_batch)}, + reinterpret_cast(android_location_GnssBatchingProvider_flush_batch)}, {"native_stop_batch", "()Z", - reinterpret_cast(android_location_GnssLocationProvider_stop_batch)}, + reinterpret_cast(android_location_GnssBatchingProvider_stop_batch)}, {"native_init_batching", "()Z", - reinterpret_cast(android_location_GnssLocationProvider_init_batching)}, + reinterpret_cast(android_location_GnssBatchingProvider_init_batching)}, {"native_cleanup_batching", "()V", - reinterpret_cast(android_location_GnssLocationProvider_cleanup_batching)}, + reinterpret_cast(android_location_GnssBatchingProvider_cleanup_batching)}, }; int register_android_server_location_GnssLocationProvider(JNIEnv* env) { + jniRegisterNativeMethods( + env, + "com/android/server/location/GnssBatchingProvider", + sMethodsBatching, + NELEM(sMethodsBatching)); return jniRegisterNativeMethods( env, "com/android/server/location/GnssLocationProvider", diff --git a/services/robotests/src/com/android/server/location/GnssBatchingProviderTest.java b/services/robotests/src/com/android/server/location/GnssBatchingProviderTest.java new file mode 100644 index 0000000000000..504609472fa6b --- /dev/null +++ b/services/robotests/src/com/android/server/location/GnssBatchingProviderTest.java @@ -0,0 +1,91 @@ +package com.android.server.location; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.platform.test.annotations.Presubmit; + +import com.android.server.location.GnssBatchingProvider.GnssBatchingProviderNative; +import com.android.server.testing.FrameworkRobolectricTestRunner; +import com.android.server.testing.SystemLoaderPackages; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +/** + * Unit tests for {@link GnssBatchingProvider}. + */ +@RunWith(FrameworkRobolectricTestRunner.class) +@Config( + manifest = Config.NONE, + shadows = { + }, + sdk = 27 +) +@SystemLoaderPackages({"com.android.server.location"}) +@Presubmit +public class GnssBatchingProviderTest { + + private static final long PERIOD_NANOS = (long) 1e9; + private static final boolean WAKE_ON_FIFO_FULL = true; + private static final int BATCH_SIZE = 3; + @Mock + private GnssBatchingProviderNative mMockNative; + private GnssBatchingProvider mTestProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mMockNative.initBatching()).thenReturn(true); + when(mMockNative.startBatch(anyLong(), anyBoolean())).thenReturn(true); + when(mMockNative.stopBatch()).thenReturn(true); + when(mMockNative.getBatchSize()).thenReturn(BATCH_SIZE); + mTestProvider = new GnssBatchingProvider(mMockNative); + mTestProvider.enable(); + mTestProvider.start(PERIOD_NANOS, WAKE_ON_FIFO_FULL); + } + + @Test + public void start_nativeStarted() { + verify(mMockNative).startBatch(eq(PERIOD_NANOS), eq(WAKE_ON_FIFO_FULL)); + } + + @Test + public void stop_nativeStopped() { + mTestProvider.stop(); + verify(mMockNative).stopBatch(); + } + + @Test + public void flush_nativeFlushed() { + mTestProvider.flush(); + verify(mMockNative).flushBatch(); + } + + @Test + public void getBatchSize_nativeGetBatchSize() { + assertThat(mTestProvider.getBatchSize()).isEqualTo(BATCH_SIZE); + } + + @Test + public void started_resume_started() { + mTestProvider.resumeIfStarted(); + verify(mMockNative, times(2)).startBatch(eq(PERIOD_NANOS), eq(WAKE_ON_FIFO_FULL)); + } + + @Test + public void stopped_resume_notStarted() { + mTestProvider.stop(); + mTestProvider.resumeIfStarted(); + verify(mMockNative, times(1)).startBatch(eq(PERIOD_NANOS), eq(WAKE_ON_FIFO_FULL)); + } +}