Merge "Allow using isFeatureEnabled on APEX module init" into udc-dev am: c80be403c0 am: 80777defaf
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/libs/net/+/23126677
Change-Id: Icb90590a6faadb53b2ea7a6f5581e8e348a7b8dc
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/common/device/com/android/net/module/util/DeviceConfigUtils.java b/common/device/com/android/net/module/util/DeviceConfigUtils.java
index f8f250d..dae4eb9 100644
--- a/common/device/com/android/net/module/util/DeviceConfigUtils.java
+++ b/common/device/com/android/net/module/util/DeviceConfigUtils.java
@@ -16,9 +16,12 @@
package com.android.net.module.util;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+
import android.content.Context;
-import android.content.pm.ModuleInfo;
+import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.provider.DeviceConfig;
import android.util.Log;
@@ -28,6 +31,9 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Utilities for modules to query {@link DeviceConfig} and flags.
*/
@@ -43,6 +49,11 @@
public static final String TETHERING_MODULE_NAME = "com.android.tethering";
@VisibleForTesting
+ public static final String RESOURCES_APK_INTENT =
+ "com.android.server.connectivity.intent.action.SERVICE_CONNECTIVITY_RESOURCES_APK";
+ private static final String CONNECTIVITY_RES_PKG_DIR = "/apex/" + TETHERING_MODULE_NAME + "/";
+
+ @VisibleForTesting
public static void resetPackageVersionCacheForTest() {
sPackageVersion = -1;
sModuleVersion = -1;
@@ -189,8 +200,13 @@
*/
public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
@NonNull String name, @NonNull String moduleName, boolean defaultEnabled) {
+ // TODO: migrate callers to a non-generic isTetheringFeatureEnabled method.
+ if (!TETHERING_MODULE_NAME.equals(moduleName)) {
+ throw new IllegalArgumentException(
+ "This method is only usable by the tethering module");
+ }
try {
- final long packageVersion = getModuleVersion(context, moduleName);
+ final long packageVersion = getTetheringModuleVersion(context);
return isFeatureEnabled(context, packageVersion, namespace, name, defaultEnabled);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Could not find the module name", e);
@@ -198,14 +214,6 @@
}
}
- 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 {
@@ -215,36 +223,40 @@
|| (propertyVersion != 0 && packageVersion >= (long) propertyVersion);
}
+ // Guess the tethering module name based on the package prefix of the connectivity resources
+ // Take the resource package name, cut it before "connectivity" and append "tethering".
+ // Then resolve that package version number with packageManager.
+ // If that fails retry by appending "go.tethering" instead
+ private static long resolveTetheringModuleVersion(@NonNull Context context)
+ throws PackageManager.NameNotFoundException {
+ final String connResourcesPackage = getConnectivityResourcesPackageName(context);
+ final int pkgPrefixLen = connResourcesPackage.indexOf("connectivity");
+ if (pkgPrefixLen < 0) {
+ throw new IllegalStateException(
+ "Invalid connectivity resources package: " + connResourcesPackage);
+ }
+
+ final String pkgPrefix = connResourcesPackage.substring(0, pkgPrefixLen);
+ final PackageManager packageManager = context.getPackageManager();
+ try {
+ return packageManager.getPackageInfo(pkgPrefix + "tethering",
+ PackageManager.MATCH_APEX).getLongVersionCode();
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.d(TAG, "Device is using go modules");
+ // fall through
+ }
+
+ return packageManager.getPackageInfo(pkgPrefix + "go.tethering",
+ PackageManager.MATCH_APEX).getLongVersionCode();
+ }
+
private static volatile long sModuleVersion = -1;
- @VisibleForTesting public static long FIXED_PACKAGE_VERSION = 10;
- private static long getModuleVersion(@NonNull Context context, @NonNull String moduleName)
+ private static long getTetheringModuleVersion(@NonNull Context context)
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;
+ sModuleVersion = resolveTetheringModuleVersion(context);
+ return sModuleVersion;
}
/**
@@ -272,4 +284,24 @@
return defaultValue;
}
}
+
+ /**
+ * Get the package name of the ServiceConnectivityResources package, used to provide resources
+ * for service-connectivity.
+ */
+ @NonNull
+ public static String getConnectivityResourcesPackageName(@NonNull Context context) {
+ final List<ResolveInfo> pkgs = new ArrayList<>(context.getPackageManager()
+ .queryIntentActivities(new Intent(RESOURCES_APK_INTENT), MATCH_SYSTEM_ONLY));
+ pkgs.removeIf(pkg -> !pkg.activityInfo.applicationInfo.sourceDir.startsWith(
+ CONNECTIVITY_RES_PKG_DIR));
+ if (pkgs.size() > 1) {
+ Log.wtf(TAG, "More than one connectivity resources package found: " + pkgs);
+ }
+ if (pkgs.isEmpty()) {
+ throw new IllegalStateException("No connectivity resource package found");
+ }
+
+ return pkgs.get(0).activityInfo.applicationInfo.packageName;
+ }
}
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 a8e7993..b75939b 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
@@ -16,25 +16,30 @@
package com.android.net.module.util;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+
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;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
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.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.provider.DeviceConfig;
@@ -49,6 +54,8 @@
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
+import java.util.Arrays;
+
/**
* Tests for DeviceConfigUtils.
@@ -66,14 +73,23 @@
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";
+ // The APEX name is the name of the APEX module, as in android.content.pm.ModuleInfo, and is
+ // used for its mount point in /apex. APEX packages are actually APKs with a different
+ // file extension, so they have an AndroidManifest: the APEX package name is the package name in
+ // that manifest, and is reflected in android.content.pm.ApplicationInfo. Contrary to the APEX
+ // (module) name, different package names are typically used to identify the organization that
+ // built and signed the APEX modules.
+ private static final String TEST_APEX_NAME = "com.android.tethering";
+ private static final String TEST_APEX_PACKAGE_NAME = "com.prefix.android.tethering";
+ private static final String TEST_GO_APEX_PACKAGE_NAME = "com.prefix.android.go.tethering";
+ private static final String TEST_CONNRES_PACKAGE_NAME =
+ "com.prefix.android.connectivity.resources";
+ private final PackageInfo mPackageInfo = new PackageInfo();
+ private final PackageInfo mApexPackageInfo = new PackageInfo();
private MockitoSession mSession;
@Mock private Context mContext;
@Mock private PackageManager mPm;
- @Mock private ModuleInfo mMi;
- @Mock private PackageInfo mPi;
@Mock private Resources mResources;
@Before
@@ -81,15 +97,26 @@
MockitoAnnotations.initMocks(this);
mSession = mockitoSession().spyStatic(DeviceConfig.class).startMocking();
- final PackageInfo pi = new PackageInfo();
- pi.setLongVersionCode(TEST_PACKAGE_VERSION);
+ mPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+ mApexPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
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());
+ doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(anyString(), anyInt());
+ doReturn(mPackageInfo).when(mPm).getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt());
+ doReturn(mApexPackageInfo).when(mPm).getPackageInfo(eq(TEST_APEX_PACKAGE_NAME), anyInt());
+
doReturn(mResources).when(mContext).getResources();
+
+ final ResolveInfo ri = new ResolveInfo();
+ ri.activityInfo = new ActivityInfo();
+ ri.activityInfo.applicationInfo = new ApplicationInfo();
+ ri.activityInfo.applicationInfo.packageName = TEST_CONNRES_PACKAGE_NAME;
+ ri.activityInfo.applicationInfo.sourceDir =
+ "/apex/com.android.tethering/priv-app/ServiceConnectivityResources@version";
+ doReturn(Arrays.asList(ri)).when(mPm).queryIntentActivities(argThat(
+ intent -> intent.getAction().equals(DeviceConfigUtils.RESOURCES_APK_INTENT)),
+ eq(MATCH_SYSTEM_ONLY));
}
@After
@@ -223,35 +250,32 @@
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(
+ public void testFeatureIsEnabledOnGo() throws Exception {
+ doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(
+ eq(TEST_APEX_PACKAGE_NAME), anyInt());
+ doReturn(mApexPackageInfo).when(mPm).getPackageInfo(
+ eq(TEST_GO_APEX_PACKAGE_NAME), anyInt());
+ doReturn("0").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));
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 */));
- doReturn(Long.toString(FIXED_PACKAGE_VERSION - 1)).when(() -> DeviceConfig.getProperty(
- eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG)));
+ 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, TEST_APEX_NAME, false /* defaultEnabled */));
}
@Test
- public void testFeatureIsEnabledCaching() throws Exception {
+ public void testFeatureIsEnabledCaching_APK() 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,
@@ -263,14 +287,20 @@
verify(mContext, times(1)).getPackageManager();
verify(mContext, times(1)).getPackageName();
verify(mPm, times(1)).getPackageInfo(anyString(), anyInt());
+ }
+ @Test
+ public void testFeatureIsEnabledCaching_APEX() 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, 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());
+ // Package info is only queried once
+ verify(mPm, times(1)).getPackageInfo(anyString(), anyInt());
+ verify(mContext, never()).getPackageName();
}
@Test