From c3fd30f479f173844de7991284cfbc9a6baca67a Mon Sep 17 00:00:00 2001 From: markchien Date: Wed, 12 May 2021 23:09:07 +0800 Subject: 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 --- .../android/net/module/util/DeviceConfigUtils.java | 83 ++++++++++++++++++++-- .../net/module/util/DeviceConfigUtilsTest.java | 52 +++++++++++++- 2 files changed, 128 insertions(+), 7 deletions(-) (limited to 'common') diff --git a/common/device/com/android/net/module/util/DeviceConfigUtils.java b/common/device/com/android/net/module/util/DeviceConfigUtils.java index 5d03dfd2..77b7835b 100644 --- a/common/device/com/android/net/module/util/DeviceConfigUtils.java +++ b/common/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 @@ public final class DeviceConfigUtils { @VisibleForTesting public static void resetPackageVersionCacheForTest() { sPackageVersion = -1; + sModuleVersion = -1; } private static volatile long sPackageVersion = -1; @@ -155,17 +157,90 @@ public final class DeviceConfigUtils { 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; } } + /** + * 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. */ diff --git a/common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java index 57316b2f..c65f7934 100644 --- a/common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java +++ b/common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java @@ -18,6 +18,7 @@ package com.android.net.module.util; 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.times; 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 @@ public class DeviceConfigUtilsTest { 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 @@ public class DeviceConfigUtilsTest { 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 @@ public class DeviceConfigUtilsTest { 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 @@ public class DeviceConfigUtilsTest { 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 -- cgit v1.2.3