Add DeviceConfigUtils version caching and tests

Querying the APK version can be relatively expensive, so cache it with a
static. Package version cannot change without restarting the process.

Also add some testing for flags being equal to the min/max values.

Bug: 17454103
Test: atest NetworkStaticLibTests
Change-Id: I18c298beb843e0a9f76162d353623bb15ab155f1
diff --git a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
index 5fec0dd..271cc6e 100644
--- a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
+++ b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
@@ -25,6 +25,7 @@
 import androidx.annotation.BoolRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 /**
  * Utilities for modules to query {@link DeviceConfig} and flags.
@@ -34,6 +35,21 @@
 
     private static final String TAG = DeviceConfigUtils.class.getSimpleName();
 
+    @VisibleForTesting
+    protected static volatile long sPackageVersion = -1;
+    private static long getPackageVersion(@NonNull final Context context)
+            throws PackageManager.NameNotFoundException {
+        // sPackageVersion may be set by another thread just after this check, but querying the
+        // package version several times on rare occasions is fine.
+        if (sPackageVersion >= 0) {
+            return sPackageVersion;
+        }
+        final long version = context.getPackageManager().getPackageInfo(
+                context.getPackageName(), 0).getLongVersionCode();
+        sPackageVersion = version;
+        return version;
+    }
+
     /**
      * Look up the value of a property for a particular namespace from {@link DeviceConfig}.
      * @param namespace The namespace containing the property to look up.
@@ -137,8 +153,7 @@
         try {
             final int propertyVersion = getDeviceConfigPropertyInt(namespace, name,
                     0 /* default value */);
-            final long packageVersion = context.getPackageManager().getPackageInfo(
-                    context.getPackageName(), 0).getLongVersionCode();
+            final long packageVersion = getPackageVersion(context);
             return (propertyVersion == 0 && defaultEnabled)
                     || (propertyVersion != 0 && packageVersion >= (long) propertyVersion);
         } catch (PackageManager.NameNotFoundException e) {
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
index bb78854..982dbe7 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
@@ -26,6 +26,8 @@
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.content.pm.PackageInfo;
@@ -86,6 +88,7 @@
     @After
     public void tearDown() {
         mSession.finishMocking();
+        DeviceConfigUtils.sPackageVersion = -1L;
     }
 
     @Test
@@ -137,6 +140,16 @@
     }
 
     @Test
+    public void testGetDeviceConfigPropertyInt_EqualsMaximumValue() {
+        doReturn(Integer.toString(TEST_MAX_FLAG_VALUE)).when(() -> DeviceConfig.getProperty(
+                eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG)));
+        assertEquals(TEST_MAX_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_MIN_FLAG_VALUE /* minimum value */,
+                TEST_MAX_FLAG_VALUE /* maximum value */,
+                TEST_DEFAULT_FLAG_VALUE /* default value */));
+    }
+
+    @Test
     public void testGetDeviceConfigPropertyInt_BelowMinimumValue() {
         doReturn(Integer.toString(TEST_MIN_FLAG_VALUE - 10)).when(() -> DeviceConfig.getProperty(
                 eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG)));
@@ -147,6 +160,16 @@
     }
 
     @Test
+    public void testGetDeviceConfigPropertyInt_EqualsMinimumValue() {
+        doReturn(Integer.toString(TEST_MIN_FLAG_VALUE)).when(() -> DeviceConfig.getProperty(
+                eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG)));
+        assertEquals(TEST_MIN_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_MIN_FLAG_VALUE /* minimum value */,
+                TEST_MAX_FLAG_VALUE /* maximum value */,
+                TEST_DEFAULT_FLAG_VALUE /* default value */));
+    }
+
+    @Test
     public void testGetDeviceConfigPropertyBoolean_Null() {
         doReturn(null).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
                 eq(TEST_EXPERIMENT_FLAG)));
@@ -165,7 +188,7 @@
     }
 
     @Test
-    public void testFeatureIsEnabledWithExceptionsEnabled() {
+    public void testFeatureIsEnabled() {
         doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
                 eq(TEST_EXPERIMENT_FLAG)));
         assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
@@ -190,6 +213,21 @@
     }
 
     @Test
+    public void testFeatureIsEnabledCaching() throws Exception {
+        doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
+        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG));
+        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG));
+
+        // Package info is only queried once
+        verify(mContext, times(1)).getPackageManager();
+        verify(mContext, times(1)).getPackageName();
+        verify(mPm, times(1)).getPackageInfo(anyString(), anyInt());
+    }
+
+    @Test
     public void testGetResBooleanConfig() {
         final int someResId = 1234;
         doReturn(true).when(mResources).getBoolean(someResId);