From 890ca8b508064a7653302ec301e249bf05d32f76 Mon Sep 17 00:00:00 2001 From: Yu-Han Yang Date: Mon, 16 Apr 2018 22:11:31 -0700 Subject: [PATCH] Refactor GnssGeofenceProvider Bug: 77974401 Change-Id: Id377b14f9f6c47e031a863ee9096c9b5c66ebf08 Fixes: 77974401 Test: m -j ROBOTEST_FILTER=GnssGeofenceProviderTest RunFrameworksServicesRoboTests --- .../server/location/GnssGeofenceProvider.java | 188 ++++++++++++++++++ .../server/location/GnssLocationProvider.java | 42 +--- ...d_server_location_GnssLocationProvider.cpp | 47 +++-- .../location/GnssGeofenceProviderTest.java | 121 +++++++++++ 4 files changed, 340 insertions(+), 58 deletions(-) create mode 100644 services/core/java/com/android/server/location/GnssGeofenceProvider.java create mode 100644 services/robotests/src/com/android/server/location/GnssGeofenceProviderTest.java diff --git a/services/core/java/com/android/server/location/GnssGeofenceProvider.java b/services/core/java/com/android/server/location/GnssGeofenceProvider.java new file mode 100644 index 0000000000000..6ac4aeb7f9ea7 --- /dev/null +++ b/services/core/java/com/android/server/location/GnssGeofenceProvider.java @@ -0,0 +1,188 @@ +package com.android.server.location; + +import android.location.IGpsGeofenceHardware; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +/** + * Manages GNSS Geofence operations. + */ +class GnssGeofenceProvider extends IGpsGeofenceHardware.Stub { + + private static final String TAG = "GnssGeofenceProvider"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + /** Holds the parameters of a geofence. */ + private static class GeofenceEntry { + public int geofenceId; + public double latitude; + public double longitude; + public double radius; + public int lastTransition; + public int monitorTransitions; + public int notificationResponsiveness; + public int unknownTimer; + public boolean paused; + } + + private final GnssGeofenceProviderNative mNative; + private final SparseArray mGeofenceEntries = new SparseArray<>(); + private final Handler mHandler; + + GnssGeofenceProvider(Looper looper) { + this(looper, new GnssGeofenceProviderNative()); + } + + @VisibleForTesting + GnssGeofenceProvider(Looper looper, GnssGeofenceProviderNative gnssGeofenceProviderNative) { + mHandler = new Handler(looper); + mNative = gnssGeofenceProviderNative; + } + + // TODO(b/37460011): use this method in HAL death recovery. + void resumeIfStarted() { + if (DEBUG) { + Log.d(TAG, "resumeIfStarted"); + } + mHandler.post(() -> { + for (int i = 0; i < mGeofenceEntries.size(); i++) { + GeofenceEntry entry = mGeofenceEntries.valueAt(i); + boolean added = mNative.addGeofence(entry.geofenceId, entry.latitude, + entry.longitude, + entry.radius, + entry.lastTransition, entry.monitorTransitions, + entry.notificationResponsiveness, entry.unknownTimer); + if (added && entry.paused) { + mNative.pauseGeofence(entry.geofenceId); + } + } + }); + } + + private boolean runOnHandlerThread(Callable callable) { + FutureTask futureTask = new FutureTask<>(callable); + mHandler.post(futureTask); + try { + return futureTask.get(); + } catch (InterruptedException | ExecutionException e) { + Log.e(TAG, "Failed running callable.", e); + } + return false; + } + + @Override + public boolean isHardwareGeofenceSupported() { + return runOnHandlerThread(mNative::isGeofenceSupported); + } + + @Override + public boolean addCircularHardwareGeofence(int geofenceId, double latitude, + double longitude, double radius, int lastTransition, int monitorTransitions, + int notificationResponsiveness, int unknownTimer) { + return runOnHandlerThread(() -> { + boolean added = mNative.addGeofence(geofenceId, latitude, longitude, radius, + lastTransition, monitorTransitions, notificationResponsiveness, + unknownTimer); + if (added) { + GeofenceEntry entry = new GeofenceEntry(); + entry.geofenceId = geofenceId; + entry.latitude = latitude; + entry.longitude = longitude; + entry.radius = radius; + entry.lastTransition = lastTransition; + entry.monitorTransitions = monitorTransitions; + entry.notificationResponsiveness = notificationResponsiveness; + entry.unknownTimer = unknownTimer; + mGeofenceEntries.put(geofenceId, entry); + } + return added; + }); + } + + @Override + public boolean removeHardwareGeofence(int geofenceId) { + return runOnHandlerThread(() -> { + boolean removed = mNative.removeGeofence(geofenceId); + if (removed) { + mGeofenceEntries.remove(geofenceId); + } + return removed; + }); + } + + @Override + public boolean pauseHardwareGeofence(int geofenceId) { + return runOnHandlerThread(() -> { + boolean paused = mNative.pauseGeofence(geofenceId); + if (paused) { + GeofenceEntry entry = mGeofenceEntries.get(geofenceId); + if (entry != null) { + entry.paused = true; + } + } + return paused; + }); + } + + @Override + public boolean resumeHardwareGeofence(int geofenceId, int monitorTransitions) { + return runOnHandlerThread(() -> { + boolean resumed = mNative.resumeGeofence(geofenceId, monitorTransitions); + if (resumed) { + GeofenceEntry entry = mGeofenceEntries.get(geofenceId); + if (entry != null) { + entry.paused = false; + entry.monitorTransitions = monitorTransitions; + } + } + return resumed; + }); + } + + @VisibleForTesting + static class GnssGeofenceProviderNative { + public boolean isGeofenceSupported() { + return native_is_geofence_supported(); + } + + public boolean addGeofence(int geofenceId, double latitude, double longitude, double radius, + int lastTransition, int monitorTransitions, int notificationResponsiveness, + int unknownTimer) { + return native_add_geofence(geofenceId, latitude, longitude, radius, lastTransition, + monitorTransitions, notificationResponsiveness, unknownTimer); + } + + public boolean removeGeofence(int geofenceId) { + return native_remove_geofence(geofenceId); + } + + public boolean resumeGeofence(int geofenceId, int transitions) { + return native_resume_geofence(geofenceId, transitions); + } + + public boolean pauseGeofence(int geofenceId) { + return native_pause_geofence(geofenceId); + } + } + + private static native boolean native_is_geofence_supported(); + + private static native boolean native_add_geofence(int geofenceId, double latitude, + double longitude, double radius, int lastTransition, int monitorTransitions, + int notificationResponsivenes, int unknownTimer); + + private static native boolean native_remove_geofence(int geofenceId); + + private static native boolean native_resume_geofence(int geofenceId, int transitions); + + private static native boolean native_pause_geofence(int geofenceId); +} diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index ec5182dc35e10..3e0227c6544c6 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -419,6 +419,7 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt private final LocationChangeListener mFusedLocationListener = new FusedLocationListener(); private final NtpTimeHelper mNtpTimeHelper; private final GnssBatchingProvider mGnssBatchingProvider; + private final GnssGeofenceProvider mGnssGeofenceProvider; // Handler for processing events private Handler mHandler; @@ -493,7 +494,7 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt } public IGpsGeofenceHardware getGpsGeofenceProxy() { - return mGpsGeofenceBinder; + return mGnssGeofenceProvider; } public GnssMeasurementsProvider getGnssMeasurementsProvider() { @@ -887,6 +888,7 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt looper, this); mHandler.post(mGnssSatelliteBlacklistHelper::updateSatelliteBlacklist); mGnssBatchingProvider = new GnssBatchingProvider(); + mGnssGeofenceProvider = new GnssGeofenceProvider(looper); } /** @@ -1495,31 +1497,6 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt } } - private IGpsGeofenceHardware mGpsGeofenceBinder = new IGpsGeofenceHardware.Stub() { - public boolean isHardwareGeofenceSupported() { - return native_is_geofence_supported(); - } - - public boolean addCircularHardwareGeofence(int geofenceId, double latitude, - double longitude, double radius, int lastTransition, int monitorTransitions, - int notificationResponsiveness, int unknownTimer) { - return native_add_geofence(geofenceId, latitude, longitude, radius, - lastTransition, monitorTransitions, notificationResponsiveness, unknownTimer); - } - - public boolean removeHardwareGeofence(int geofenceId) { - return native_remove_geofence(geofenceId); - } - - public boolean pauseHardwareGeofence(int geofenceId) { - return native_pause_geofence(geofenceId); - } - - public boolean resumeHardwareGeofence(int geofenceId, int monitorTransition) { - return native_resume_geofence(geofenceId, monitorTransition); - } - }; - private boolean deleteAidingData(Bundle extras) { int flags; @@ -2807,19 +2784,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); - // Hardware Geofence support. - private static native boolean native_is_geofence_supported(); - - private static native boolean native_add_geofence(int geofenceId, double latitude, - double longitude, double radius, int lastTransition, int monitorTransitions, - int notificationResponsivenes, int unknownTimer); - - private static native boolean native_remove_geofence(int geofenceId); - - private static native boolean native_resume_geofence(int geofenceId, int transitions); - - private static native boolean native_pause_geofence(int geofenceId); - // Gps Hal measurements support. private static native boolean native_is_measurement_supported(); diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 92ef9f16ca935..3a9bbe4d976ea 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -1745,12 +1745,12 @@ static void android_location_GnssLocationProvider_update_network_state(JNIEnv* e } } -static jboolean android_location_GnssLocationProvider_is_geofence_supported( +static jboolean android_location_GnssGeofenceProvider_is_geofence_supported( JNIEnv* /* env */, jobject /* obj */) { return (gnssGeofencingIface != nullptr) ? JNI_TRUE : JNI_FALSE; } -static jboolean android_location_GnssLocationProvider_add_geofence(JNIEnv* /* env */, +static jboolean android_location_GnssGeofenceProvider_add_geofence(JNIEnv* /* env */, jobject /* obj */, jint geofenceId, jdouble latitude, jdouble longitude, jdouble radius, jint last_transition, jint monitor_transition, jint notification_responsiveness, jint unknown_timer) { @@ -1766,7 +1766,7 @@ static jboolean android_location_GnssLocationProvider_add_geofence(JNIEnv* /* en return JNI_FALSE; } -static jboolean android_location_GnssLocationProvider_remove_geofence(JNIEnv* /* env */, +static jboolean android_location_GnssGeofenceProvider_remove_geofence(JNIEnv* /* env */, jobject /* obj */, jint geofenceId) { if (gnssGeofencingIface != nullptr) { auto result = gnssGeofencingIface->removeGeofence(geofenceId); @@ -1777,7 +1777,7 @@ static jboolean android_location_GnssLocationProvider_remove_geofence(JNIEnv* /* return JNI_FALSE; } -static jboolean android_location_GnssLocationProvider_pause_geofence(JNIEnv* /* env */, +static jboolean android_location_GnssGeofenceProvider_pause_geofence(JNIEnv* /* env */, jobject /* obj */, jint geofenceId) { if (gnssGeofencingIface != nullptr) { auto result = gnssGeofencingIface->pauseGeofence(geofenceId); @@ -1788,7 +1788,7 @@ static jboolean android_location_GnssLocationProvider_pause_geofence(JNIEnv* /* return JNI_FALSE; } -static jboolean android_location_GnssLocationProvider_resume_geofence(JNIEnv* /* env */, +static jboolean android_location_GnssGeofenceProvider_resume_geofence(JNIEnv* /* env */, jobject /* obj */, jint geofenceId, jint monitor_transition) { if (gnssGeofencingIface != nullptr) { auto result = gnssGeofencingIface->resumeGeofence(geofenceId, monitor_transition); @@ -2178,20 +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_geofence_supported", - "()Z", - reinterpret_cast(android_location_GnssLocationProvider_is_geofence_supported)}, - {"native_add_geofence", - "(IDDDIIII)Z", - reinterpret_cast(android_location_GnssLocationProvider_add_geofence)}, - {"native_remove_geofence", - "(I)Z", - reinterpret_cast(android_location_GnssLocationProvider_remove_geofence)}, - {"native_pause_geofence", "(I)Z", reinterpret_cast( - android_location_GnssLocationProvider_pause_geofence)}, - {"native_resume_geofence", - "(II)Z", - reinterpret_cast(android_location_GnssLocationProvider_resume_geofence)}, {"native_is_measurement_supported", "()Z", reinterpret_cast( @@ -2265,12 +2251,35 @@ static const JNINativeMethod sMethodsBatching[] = { reinterpret_cast(android_location_GnssBatchingProvider_cleanup_batching)}, }; +static const JNINativeMethod sGeofenceMethods[] = { + /* name, signature, funcPtr */ + {"native_is_geofence_supported", + "()Z", + reinterpret_cast(android_location_GnssGeofenceProvider_is_geofence_supported)}, + {"native_add_geofence", + "(IDDDIIII)Z", + reinterpret_cast(android_location_GnssGeofenceProvider_add_geofence)}, + {"native_remove_geofence", + "(I)Z", + reinterpret_cast(android_location_GnssGeofenceProvider_remove_geofence)}, + {"native_pause_geofence", "(I)Z", reinterpret_cast( + android_location_GnssGeofenceProvider_pause_geofence)}, + {"native_resume_geofence", + "(II)Z", + reinterpret_cast(android_location_GnssGeofenceProvider_resume_geofence)}, +}; + int register_android_server_location_GnssLocationProvider(JNIEnv* env) { jniRegisterNativeMethods( env, "com/android/server/location/GnssBatchingProvider", sMethodsBatching, NELEM(sMethodsBatching)); + jniRegisterNativeMethods( + env, + "com/android/server/location/GnssGeofenceProvider", + sGeofenceMethods, + NELEM(sGeofenceMethods)); return jniRegisterNativeMethods( env, "com/android/server/location/GnssLocationProvider", diff --git a/services/robotests/src/com/android/server/location/GnssGeofenceProviderTest.java b/services/robotests/src/com/android/server/location/GnssGeofenceProviderTest.java new file mode 100644 index 0000000000000..187303cc93d6b --- /dev/null +++ b/services/robotests/src/com/android/server/location/GnssGeofenceProviderTest.java @@ -0,0 +1,121 @@ +package com.android.server.location; + +import static org.mockito.Matchers.anyDouble; +import static org.mockito.Matchers.anyInt; +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.os.Looper; +import android.os.RemoteException; +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.annotation.Config; + +/** + * Unit tests for {@link GnssGeofenceProvider}. + */ +@RunWith(FrameworkRobolectricTestRunner.class) +@Config( + manifest = Config.NONE, + sdk = 27 +) +@SystemLoaderPackages({"com.android.server.location"}) +@Presubmit +public class GnssGeofenceProviderTest { + private static final int GEOFENCE_ID = 12345; + private static final double LATITUDE = 10.0; + private static final double LONGITUDE = 20.0; + private static final double RADIUS = 5.0; + private static final int LAST_TRANSITION = 0; + private static final int MONITOR_TRANSITIONS = 0; + private static final int NOTIFICATION_RESPONSIVENESS = 0; + private static final int UNKNOWN_TIMER = 0; + @Mock + private GnssGeofenceProvider.GnssGeofenceProviderNative mMockNative; + private GnssGeofenceProvider mTestProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mMockNative.addGeofence(anyInt(), anyDouble(), anyDouble(), anyDouble(), anyInt(), + anyInt(), anyInt(), anyInt())).thenReturn(true); + when(mMockNative.pauseGeofence(anyInt())).thenReturn(true); + when(mMockNative.removeGeofence(anyInt())).thenReturn(true); + when(mMockNative.resumeGeofence(anyInt(), anyInt())).thenReturn(true); + mTestProvider = new GnssGeofenceProvider(Looper.myLooper(), mMockNative); + mTestProvider.addCircularHardwareGeofence(GEOFENCE_ID, LATITUDE, + LONGITUDE, RADIUS, LAST_TRANSITION, MONITOR_TRANSITIONS, + NOTIFICATION_RESPONSIVENESS, + UNKNOWN_TIMER); + } + + @Test + public void addGeofence_nativeAdded() { + verify(mMockNative).addGeofence(eq(GEOFENCE_ID), eq(LATITUDE), eq(LONGITUDE), + eq(RADIUS), eq(LAST_TRANSITION), eq(MONITOR_TRANSITIONS), + eq(NOTIFICATION_RESPONSIVENESS), + eq(UNKNOWN_TIMER)); + } + + @Test + public void pauseGeofence_nativePaused() { + mTestProvider.pauseHardwareGeofence(GEOFENCE_ID); + verify(mMockNative).pauseGeofence(eq(GEOFENCE_ID)); + } + + @Test + public void removeGeofence_nativeRemoved() { + mTestProvider.removeHardwareGeofence(GEOFENCE_ID); + verify(mMockNative).removeGeofence(eq(GEOFENCE_ID)); + } + + @Test + public void resumeGeofence_nativeResumed() { + mTestProvider.pauseHardwareGeofence(GEOFENCE_ID); + mTestProvider.resumeHardwareGeofence(GEOFENCE_ID, MONITOR_TRANSITIONS); + verify(mMockNative).resumeGeofence(eq(GEOFENCE_ID), eq(MONITOR_TRANSITIONS)); + } + + @Test + public void addGeofence_restart_added() throws RemoteException { + mTestProvider.resumeIfStarted(); + + verify(mMockNative, times(2)).addGeofence(eq(GEOFENCE_ID), eq(LATITUDE), eq(LONGITUDE), + eq(RADIUS), eq(LAST_TRANSITION), eq(MONITOR_TRANSITIONS), + eq(NOTIFICATION_RESPONSIVENESS), + eq(UNKNOWN_TIMER)); + } + + @Test + public void removeGeofence_restart_notAdded() throws RemoteException { + mTestProvider.removeHardwareGeofence(GEOFENCE_ID); + mTestProvider.resumeIfStarted(); + + verify(mMockNative, times(1)).addGeofence(eq(GEOFENCE_ID), eq(LATITUDE), eq(LONGITUDE), + eq(RADIUS), eq(LAST_TRANSITION), eq(MONITOR_TRANSITIONS), + eq(NOTIFICATION_RESPONSIVENESS), + eq(UNKNOWN_TIMER)); + } + + @Test + public void pauseGeofence_restart_paused() throws RemoteException { + mTestProvider.pauseHardwareGeofence(GEOFENCE_ID); + mTestProvider.resumeIfStarted(); + + verify(mMockNative, times(2)).addGeofence(eq(GEOFENCE_ID), eq(LATITUDE), eq(LONGITUDE), + eq(RADIUS), eq(LAST_TRANSITION), eq(MONITOR_TRANSITIONS), + eq(NOTIFICATION_RESPONSIVENESS), + eq(UNKNOWN_TIMER)); + verify(mMockNative, times(2)).pauseGeofence(eq(GEOFENCE_ID)); + } +}