diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 312b21ce30faa..03046b6b4eb0b 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -818,37 +818,7 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt } }; - mGnssMeasurementsProvider = new GnssMeasurementsProvider(mHandler) { - @Override - public boolean isAvailableInPlatform() { - return native_is_measurement_supported(); - } - - @Override - protected int registerWithService() { - int devOptions = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Global.DEVELOPMENT_SETTINGS_ENABLED , 0); - int fullTrackingToggled = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING , 0); - boolean result = false; - if (devOptions == 1 /* Developer Mode enabled */ - && fullTrackingToggled == 1 /* Raw Measurements Full Tracking enabled */) { - result = native_start_measurement_collection(true /* enableFullTracking */); - } else { - result = native_start_measurement_collection(false /* enableFullTracking */); - } - if (result) { - return RemoteListenerHelper.RESULT_SUCCESS; - } else { - return RemoteListenerHelper.RESULT_INTERNAL_ERROR; - } - } - - @Override - protected void unregisterFromService() { - native_stop_measurement_collection(); - } - + mGnssMeasurementsProvider = new GnssMeasurementsProvider(mContext, mHandler) { @Override protected boolean isGpsEnabled() { return isEnabled(); @@ -1032,7 +1002,7 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt Log.e(TAG, "Invalid status to release SUPL connection: " + agpsDataConnStatus); } } - + private void handleRequestLocation(boolean independentFromGnss) { if (isRequestLocationRateLimited()) { if (DEBUG) { @@ -2790,13 +2760,6 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt private native void native_update_network_state(boolean connected, int type, boolean roaming, boolean available, String extraInfo, String defaultAPN); - // Gps Hal measurements support. - private static native boolean native_is_measurement_supported(); - - private native boolean native_start_measurement_collection(boolean enableFullTracking); - - private native boolean native_stop_measurement_collection(); - // Gps Navigation message support. private static native boolean native_is_navigation_message_supported(); diff --git a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java index 477dae65c6519..0add86315487b 100644 --- a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java @@ -16,12 +16,16 @@ package com.android.server.location; +import android.content.Context; import android.location.GnssMeasurementsEvent; import android.location.IGnssMeasurementsListener; import android.os.Handler; import android.os.RemoteException; +import android.provider.Settings; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + /** * An base implementation for GPS measurements provider. * It abstracts out the responsibility of handling listeners, while still allowing technology @@ -29,22 +33,73 @@ import android.util.Log; * * @hide */ -public abstract class GnssMeasurementsProvider - extends RemoteListenerHelper { +public abstract class GnssMeasurementsProvider extends + RemoteListenerHelper { private static final String TAG = "GnssMeasurementsProvider"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - protected GnssMeasurementsProvider(Handler handler) { + private final Context mContext; + private final GnssMeasurementProviderNative mNative; + + private boolean mIsCollectionStarted; + private boolean mEnableFullTracking; + + protected GnssMeasurementsProvider(Context context, Handler handler) { + this(context, handler, new GnssMeasurementProviderNative()); + } + + @VisibleForTesting + GnssMeasurementsProvider(Context context, Handler handler, + GnssMeasurementProviderNative aNative) { super(handler, TAG); + mContext = context; + mNative = aNative; + } + + // TODO(b/37460011): Use this with death recovery logic. + void resumeIfStarted() { + if (DEBUG) { + Log.d(TAG, "resumeIfStarted"); + } + if (mIsCollectionStarted) { + mNative.startMeasurementCollection(mEnableFullTracking); + } + } + + @Override + public boolean isAvailableInPlatform() { + return mNative.isMeasurementSupported(); + } + + @Override + protected int registerWithService() { + int devOptions = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0); + int fullTrackingToggled = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, 0); + boolean enableFullTracking = (devOptions == 1 /* Developer Mode enabled */) + && (fullTrackingToggled == 1 /* Raw Measurements Full Tracking enabled */); + boolean result = mNative.startMeasurementCollection(enableFullTracking); + if (result) { + mIsCollectionStarted = true; + mEnableFullTracking = enableFullTracking; + return RemoteListenerHelper.RESULT_SUCCESS; + } else { + return RemoteListenerHelper.RESULT_INTERNAL_ERROR; + } + } + + @Override + protected void unregisterFromService() { + boolean stopped = mNative.stopMeasurementCollection(); + if (stopped) { + mIsCollectionStarted = false; + } } public void onMeasurementsAvailable(final GnssMeasurementsEvent event) { ListenerOperation operation = - new ListenerOperation() { - @Override - public void execute(IGnssMeasurementsListener listener) throws RemoteException { - listener.onGnssMeasurementsReceived(event); - } - }; + listener -> listener.onGnssMeasurementsReceived(event); foreach(operation); } @@ -98,4 +153,25 @@ public abstract class GnssMeasurementsProvider listener.onStatusChanged(mStatus); } } + + @VisibleForTesting + static class GnssMeasurementProviderNative { + public boolean isMeasurementSupported() { + return native_is_measurement_supported(); + } + + public boolean startMeasurementCollection(boolean enableFullTracking) { + return native_start_measurement_collection(enableFullTracking); + } + + public boolean stopMeasurementCollection() { + return native_stop_measurement_collection(); + } + } + + private static native boolean native_is_measurement_supported(); + + private static native boolean native_start_measurement_collection(boolean enableFullTracking); + + private static native boolean native_stop_measurement_collection(); } diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 3a9bbe4d976ea..b3b37d6de5643 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -1799,7 +1799,7 @@ static jboolean android_location_GnssGeofenceProvider_resume_geofence(JNIEnv* /* return JNI_FALSE; } -static jboolean android_location_GnssLocationProvider_is_measurement_supported( +static jboolean android_location_GnssMeasurementsProvider_is_measurement_supported( JNIEnv* env, jclass clazz) { if (gnssMeasurementIface != nullptr) { return JNI_TRUE; @@ -1808,7 +1808,7 @@ static jboolean android_location_GnssLocationProvider_is_measurement_supported( return JNI_FALSE; } -static jboolean android_location_GnssLocationProvider_start_measurement_collection( +static jboolean android_location_GnssMeasurementsProvider_start_measurement_collection( JNIEnv* /* env */, jobject /* obj */, jboolean enableFullTracking) { @@ -1842,7 +1842,7 @@ static jboolean android_location_GnssLocationProvider_start_measurement_collecti return JNI_TRUE; } -static jboolean android_location_GnssLocationProvider_stop_measurement_collection( +static jboolean android_location_GnssMeasurementsProvider_stop_measurement_collection( JNIEnv* env, jobject obj) { if (gnssMeasurementIface == nullptr) { @@ -2178,18 +2178,6 @@ static const JNINativeMethod sMethods[] = { {"native_update_network_state", "(ZIZZLjava/lang/String;Ljava/lang/String;)V", reinterpret_cast(android_location_GnssLocationProvider_update_network_state)}, - {"native_is_measurement_supported", - "()Z", - reinterpret_cast( - android_location_GnssLocationProvider_is_measurement_supported)}, - {"native_start_measurement_collection", - "(Z)Z", - reinterpret_cast( - android_location_GnssLocationProvider_start_measurement_collection)}, - {"native_stop_measurement_collection", - "()Z", - reinterpret_cast( - android_location_GnssLocationProvider_stop_measurement_collection)}, {"native_is_navigation_message_supported", "()Z", reinterpret_cast( @@ -2269,6 +2257,22 @@ static const JNINativeMethod sGeofenceMethods[] = { reinterpret_cast(android_location_GnssGeofenceProvider_resume_geofence)}, }; +static const JNINativeMethod sMeasurementMethods[] = { + /* name, signature, funcPtr */ + {"native_is_measurement_supported", + "()Z", + reinterpret_cast( + android_location_GnssMeasurementsProvider_is_measurement_supported)}, + {"native_start_measurement_collection", + "(Z)Z", + reinterpret_cast( + android_location_GnssMeasurementsProvider_start_measurement_collection)}, + {"native_stop_measurement_collection", + "()Z", + reinterpret_cast( + android_location_GnssMeasurementsProvider_stop_measurement_collection)}, +}; + int register_android_server_location_GnssLocationProvider(JNIEnv* env) { jniRegisterNativeMethods( env, @@ -2280,6 +2284,11 @@ int register_android_server_location_GnssLocationProvider(JNIEnv* env) { "com/android/server/location/GnssGeofenceProvider", sGeofenceMethods, NELEM(sGeofenceMethods)); + jniRegisterNativeMethods( + env, + "com/android/server/location/GnssMeasurementsProvider", + sMeasurementMethods, + NELEM(sMeasurementMethods)); return jniRegisterNativeMethods( env, "com/android/server/location/GnssLocationProvider", diff --git a/services/robotests/src/com/android/server/location/GnssMeasurementsProviderTest.java b/services/robotests/src/com/android/server/location/GnssMeasurementsProviderTest.java new file mode 100644 index 0000000000000..23d6cf69edc65 --- /dev/null +++ b/services/robotests/src/com/android/server/location/GnssMeasurementsProviderTest.java @@ -0,0 +1,90 @@ +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 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.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +/** + * Unit tests for {@link GnssMeasurementsProvider}. + */ +@RunWith(FrameworkRobolectricTestRunner.class) +@Config( + manifest = Config.NONE, + sdk = 27 +) +@SystemLoaderPackages({"com.android.server.location"}) +@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()); + } +}