Add isFeatureEnabled by checking with apex module version

A variable of #isFeatureEnabled which check apex module version
instead of apk version.

Bug: 187946226
Test: NetworkStaticLibTests

Change-Id: I4cfc0a68f6ecb7eb9e127b3445b261e0108f6481
diff --git a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
index 5d03dfd..77b7835 100644
--- a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
+++ b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
@@ -17,6 +17,7 @@
 package com.android.net.module.util;
 
 import android.content.Context;
+import android.content.pm.ModuleInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.provider.DeviceConfig;
@@ -38,6 +39,7 @@
     @VisibleForTesting
     public static void resetPackageVersionCacheForTest() {
         sPackageVersion = -1;
+        sModuleVersion = -1;
     }
 
     private static volatile long sPackageVersion = -1;
@@ -155,11 +157,8 @@
     public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
             @NonNull String name, boolean defaultEnabled) {
         try {
-            final int propertyVersion = getDeviceConfigPropertyInt(namespace, name,
-                    0 /* default value */);
             final long packageVersion = getPackageVersion(context);
-            return (propertyVersion == 0 && defaultEnabled)
-                    || (propertyVersion != 0 && packageVersion >= (long) propertyVersion);
+            return isFeatureEnabled(context, packageVersion, namespace, name, defaultEnabled);
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Could not find the package name", e);
             return false;
@@ -167,6 +166,82 @@
     }
 
     /**
+     * Check whether or not one specific experimental feature for a particular namespace from
+     * {@link DeviceConfig} is enabled by comparing module package version
+     * with current version of property. If this property version is valid, the corresponding
+     * experimental feature would be enabled, otherwise disabled.
+     *
+     * This is useful to ensure that if a module install is rolled back, flags are not left fully
+     * rolled out on a version where they have not been well tested.
+     * @param context The global context information about an app environment.
+     * @param namespace The namespace containing the property to look up.
+     * @param name The name of the property to look up.
+     * @param moduleName The mainline module name which is released as apex.
+     * @param defaultEnabled The value to return if the property does not exist or its value is
+     *                       null.
+     * @return true if this feature is enabled, or false if disabled.
+     */
+    public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
+            @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) {
+        try {
+            final long packageVersion = getModuleVersion(context, moduleName);
+            return isFeatureEnabled(context, packageVersion, namespace, name, defaultEnabled);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Could not find the module name", e);
+            return false;
+        }
+    }
+
+    private static boolean maybeUseFixedPackageVersion(@NonNull Context context) {
+        final String packageName = context.getPackageName();
+        if (packageName == null) return false;
+
+        return packageName.equals("com.android.networkstack.tethering")
+                || packageName.equals("com.android.networkstack.tethering.inprocess");
+    }
+
+    private static boolean isFeatureEnabled(@NonNull Context context, long packageVersion,
+            @NonNull String namespace, String name, boolean defaultEnabled)
+            throws PackageManager.NameNotFoundException {
+        final int propertyVersion = getDeviceConfigPropertyInt(namespace, name,
+                0 /* default value */);
+        return (propertyVersion == 0 && defaultEnabled)
+                || (propertyVersion != 0 && packageVersion >= (long) propertyVersion);
+    }
+
+    private static volatile long sModuleVersion = -1;
+    @VisibleForTesting public static long FIXED_PACKAGE_VERSION = 10;
+    private static long getModuleVersion(@NonNull Context context, @NonNull String moduleName)
+            throws PackageManager.NameNotFoundException {
+        if (sModuleVersion >= 0) return sModuleVersion;
+
+        final PackageManager packageManager = context.getPackageManager();
+        ModuleInfo module;
+        try {
+            module = packageManager.getModuleInfo(
+                    moduleName, PackageManager.MODULE_APEX_NAME);
+        } catch (PackageManager.NameNotFoundException e) {
+            // The error may happen if mainline module meta data is not installed e.g. there are
+            // no meta data configuration in AOSP build. To be able to enable a feature in AOSP
+            // by setting a flag via ADB for example. set a small non-zero fixed number for
+            // comparing.
+            if (maybeUseFixedPackageVersion(context)) {
+                sModuleVersion = FIXED_PACKAGE_VERSION;
+                return FIXED_PACKAGE_VERSION;
+            } else {
+                throw e;
+            }
+        }
+        String modulePackageName = module.getPackageName();
+        if (modulePackageName == null) throw new PackageManager.NameNotFoundException(moduleName);
+        final long version = packageManager.getPackageInfo(modulePackageName,
+                PackageManager.MATCH_APEX).getLongVersionCode();
+        sModuleVersion = version;
+
+        return version;
+    }
+
+    /**
      * Gets boolean config from resources.
      */
     public static boolean getResBooleanConfig(@NonNull final Context context,
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 57316b2..c65f793 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
@@ -18,6 +18,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.net.module.util.DeviceConfigUtils.FIXED_PACKAGE_VERSION;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -30,6 +31,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
+import android.content.pm.ModuleInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -64,10 +66,13 @@
     private static final int TEST_MIN_FLAG_VALUE = 100;
     private static final long TEST_PACKAGE_VERSION = 290000000;
     private static final String TEST_PACKAGE_NAME = "test.package.name";
+    private static final String TETHERING_AOSP_PACKAGE_NAME = "com.android.networkstack.tethering";
+    private static final String TEST_APEX_NAME = "test.apex.name";
     private MockitoSession mSession;
 
     @Mock private Context mContext;
     @Mock private PackageManager mPm;
+    @Mock private ModuleInfo mMi;
     @Mock private PackageInfo mPi;
     @Mock private Resources mResources;
 
@@ -81,6 +86,8 @@
 
         doReturn(mPm).when(mContext).getPackageManager();
         doReturn(TEST_PACKAGE_NAME).when(mContext).getPackageName();
+        doReturn(mMi).when(mPm).getModuleInfo(eq(TEST_APEX_NAME), anyInt());
+        doReturn(TEST_PACKAGE_NAME).when(mMi).getPackageName();
         doReturn(pi).when(mPm).getPackageInfo(anyString(), anyInt());
         doReturn(mResources).when(mContext).getResources();
     }
@@ -193,23 +200,54 @@
                 eq(TEST_EXPERIMENT_FLAG)));
         assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG));
+        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
     }
 
     @Test
-    public void testFeatureIsNotEnabled() {
+    public void testFeatureDefaultEnabled() {
         doReturn(null).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
                 eq(TEST_EXPERIMENT_FLAG)));
         assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG));
+        assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
+        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, true /* defaultEnabled */));
     }
 
     @Test
     public void testFeatureIsEnabledWithException() throws Exception {
-        doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
-                eq(TEST_EXPERIMENT_FLAG)));
         doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(anyString(), anyInt());
         assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG));
+        assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
+        doThrow(NameNotFoundException.class).when(mPm).getModuleInfo(anyString(), anyInt());
+        assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
+    }
+
+
+    @Test
+    public void testFeatureIsEnabledUsingFixedVersion() throws Exception {
+        doReturn(TETHERING_AOSP_PACKAGE_NAME).when(mContext).getPackageName();
+        doThrow(NameNotFoundException.class).when(mPm).getModuleInfo(anyString(), anyInt());
+
+        doReturn(Long.toString(FIXED_PACKAGE_VERSION)).when(() -> DeviceConfig.getProperty(
+                eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG)));
+        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
+
+        doReturn(Long.toString(FIXED_PACKAGE_VERSION + 1)).when(() -> DeviceConfig.getProperty(
+                eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG)));
+        assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
+
+        doReturn(Long.toString(FIXED_PACKAGE_VERSION - 1)).when(() -> DeviceConfig.getProperty(
+                eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG)));
+        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
     }
 
     @Test
@@ -225,6 +263,14 @@
         verify(mContext, times(1)).getPackageManager();
         verify(mContext, times(1)).getPackageName();
         verify(mPm, times(1)).getPackageInfo(anyString(), anyInt());
+
+        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
+        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
+
+        // Module info is only queried once
+        verify(mPm, times(1)).getModuleInfo(anyString(), anyInt());
     }
 
     @Test