Merge "Refactor GnssNavigationMessageProvider" into pi-dev
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 03046b6..64750b0 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -827,26 +827,6 @@
 
         mGnssNavigationMessageProvider = new GnssNavigationMessageProvider(mHandler) {
             @Override
-            protected boolean isAvailableInPlatform() {
-                return native_is_navigation_message_supported();
-            }
-
-            @Override
-            protected int registerWithService() {
-                boolean result = native_start_navigation_message_collection();
-                if (result) {
-                    return RemoteListenerHelper.RESULT_SUCCESS;
-                } else {
-                    return RemoteListenerHelper.RESULT_INTERNAL_ERROR;
-                }
-            }
-
-            @Override
-            protected void unregisterFromService() {
-                native_stop_navigation_message_collection();
-            }
-
-            @Override
             protected boolean isGpsEnabled() {
                 return isEnabled();
             }
@@ -2760,13 +2740,6 @@
     private native void native_update_network_state(boolean connected, int type,
             boolean roaming, boolean available, String extraInfo, String defaultAPN);
 
-    // Gps Navigation message support.
-    private static native boolean native_is_navigation_message_supported();
-
-    private native boolean native_start_navigation_message_collection();
-
-    private native boolean native_stop_navigation_message_collection();
-
     // GNSS Configuration
     private static native boolean native_set_supl_version(int version);
 
diff --git a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java
index df3c49b..1b4fd18 100644
--- a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java
+++ b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java
@@ -22,6 +22,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * An base implementation for GPS navigation messages provider.
  * It abstracts out the responsibility of handling listeners, while still allowing technology
@@ -32,9 +34,53 @@
 public abstract class GnssNavigationMessageProvider
         extends RemoteListenerHelper<IGnssNavigationMessageListener> {
     private static final String TAG = "GnssNavigationMessageProvider";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final GnssNavigationMessageProviderNative mNative;
+    private boolean mCollectionStarted;
 
     protected GnssNavigationMessageProvider(Handler handler) {
+        this(handler, new GnssNavigationMessageProviderNative());
+    }
+
+    @VisibleForTesting
+    GnssNavigationMessageProvider(Handler handler, GnssNavigationMessageProviderNative aNative) {
         super(handler, TAG);
+        mNative = aNative;
+    }
+
+    // TODO(b/37460011): Use this with death recovery logic.
+    void resumeIfStarted() {
+        if (DEBUG) {
+            Log.d(TAG, "resumeIfStarted");
+        }
+        if (mCollectionStarted) {
+            mNative.startNavigationMessageCollection();
+        }
+    }
+
+    @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) {
@@ -96,4 +142,25 @@
             listener.onStatusChanged(mStatus);
         }
     }
+
+    @VisibleForTesting
+    static class GnssNavigationMessageProviderNative {
+        public boolean isNavigationMessageSupported() {
+            return native_is_navigation_message_supported();
+        }
+
+        public boolean startNavigationMessageCollection() {
+            return native_start_navigation_message_collection();
+        }
+
+        public boolean stopNavigationMessageCollection() {
+            return native_stop_navigation_message_collection();
+        }
+    }
+
+    private static native boolean native_is_navigation_message_supported();
+
+    private static native boolean native_start_navigation_message_collection();
+
+    private static native boolean native_stop_navigation_message_collection();
 }
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index b3b37d6..a3a7e1e 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -1854,7 +1854,7 @@
     return boolToJbool(result.isOk());
 }
 
-static jboolean android_location_GnssLocationProvider_is_navigation_message_supported(
+static jboolean android_location_GnssNavigationMessageProvider_is_navigation_message_supported(
         JNIEnv* env,
         jclass clazz) {
     if (gnssNavigationMessageIface != nullptr) {
@@ -1863,7 +1863,7 @@
     return JNI_FALSE;
 }
 
-static jboolean android_location_GnssLocationProvider_start_navigation_message_collection(
+static jboolean android_location_GnssNavigationMessageProvider_start_navigation_message_collection(
         JNIEnv* env,
         jobject obj) {
     if (gnssNavigationMessageIface == nullptr) {
@@ -1884,7 +1884,7 @@
     return JNI_TRUE;
 }
 
-static jboolean android_location_GnssLocationProvider_stop_navigation_message_collection(
+static jboolean android_location_GnssNavigationMessageProvider_stop_navigation_message_collection(
         JNIEnv* env,
         jobject obj) {
     if (gnssNavigationMessageIface == nullptr) {
@@ -2178,18 +2178,6 @@
     {"native_update_network_state",
             "(ZIZZLjava/lang/String;Ljava/lang/String;)V",
             reinterpret_cast<void *>(android_location_GnssLocationProvider_update_network_state)},
-    {"native_is_navigation_message_supported",
-            "()Z",
-            reinterpret_cast<void *>(
-                    android_location_GnssLocationProvider_is_navigation_message_supported)},
-    {"native_start_navigation_message_collection",
-            "()Z",
-            reinterpret_cast<void *>(
-                    android_location_GnssLocationProvider_start_navigation_message_collection)},
-    {"native_stop_navigation_message_collection",
-            "()Z",
-            reinterpret_cast<void *>(
-                    android_location_GnssLocationProvider_stop_navigation_message_collection)},
     {"native_set_supl_es",
             "(I)Z",
             reinterpret_cast<void *>(android_location_GnssLocationProvider_set_supl_es)},
@@ -2273,6 +2261,22 @@
                     android_location_GnssMeasurementsProvider_stop_measurement_collection)},
 };
 
+static const JNINativeMethod sNavigationMessageMethods[] = {
+     /* name, signature, funcPtr */
+    {"native_is_navigation_message_supported",
+            "()Z",
+            reinterpret_cast<void *>(
+                    android_location_GnssNavigationMessageProvider_is_navigation_message_supported)},
+    {"native_start_navigation_message_collection",
+            "()Z",
+            reinterpret_cast<void *>(
+                    android_location_GnssNavigationMessageProvider_start_navigation_message_collection)},
+    {"native_stop_navigation_message_collection",
+            "()Z",
+            reinterpret_cast<void *>(
+                    android_location_GnssNavigationMessageProvider_stop_navigation_message_collection)},
+};
+
 int register_android_server_location_GnssLocationProvider(JNIEnv* env) {
     jniRegisterNativeMethods(
             env,
@@ -2289,6 +2293,11 @@
             "com/android/server/location/GnssMeasurementsProvider",
             sMeasurementMethods,
             NELEM(sMeasurementMethods));
+    jniRegisterNativeMethods(
+            env,
+            "com/android/server/location/GnssNavigationMessageProvider",
+            sNavigationMessageMethods,
+            NELEM(sNavigationMessageMethods));
     return jniRegisterNativeMethods(
             env,
             "com/android/server/location/GnssLocationProvider",
diff --git a/services/robotests/src/com/android/server/location/GnssNavigationMessageProviderTest.java b/services/robotests/src/com/android/server/location/GnssNavigationMessageProviderTest.java
new file mode 100644
index 0000000..8d3de3c
--- /dev/null
+++ b/services/robotests/src/com/android/server/location/GnssNavigationMessageProviderTest.java
@@ -0,0 +1,89 @@
+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 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 GnssNavigationMessageProvider}.
+ */
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(
+        manifest = Config.NONE,
+        sdk = 27
+)
+@SystemLoaderPackages({"com.android.server.location"})
+@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(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();
+    }
+}