summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Yurii Zubrytskyi <zyy@google.com> 2024-05-22 19:01:18 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-05-22 19:01:18 +0000
commit7b3de69245e075ce995faef1598f4826b8cea04e (patch)
tree922fa4d80fbac505691b4e2c6c116e8552b652b3
parent1cf56be6db80f41b2779af4718ef04898d0ad6b5 (diff)
Revert^2 "PackageParser support for flagging manifest entries"
This reverts commit 1cf56be6db80f41b2779af4718ef04898d0ad6b5. Reason for revert: the test that fails is bad, needs to be fixed Change-Id: Ifa225ea5d8a7cf3df1132fb0e3c411ed18c5ea57
-rw-r--r--Android.bp1
-rw-r--r--core/java/com/android/internal/pm/pkg/component/AconfigFlags.java244
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java3
-rw-r--r--core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java4
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java3
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java6
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java4
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java4
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java30
-rw-r--r--services/tests/PackageManagerServiceTests/server/Android.bp1
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java36
-rw-r--r--services/tests/servicestests/test-apps/PackageParserApp/Android.bp17
-rw-r--r--services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml30
13 files changed, 383 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp
index d6b303f62428..af312bf833e5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -425,6 +425,7 @@ java_defaults {
"sounddose-aidl-java",
"modules-utils-expresslog",
"perfetto_trace_javastream_protos_jarjar",
+ "libaconfig_java_proto_nano",
],
}
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
new file mode 100644
index 000000000000..f306b0b02677
--- /dev/null
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.pm.pkg.component;
+
+import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
+
+import android.aconfig.nano.Aconfig;
+import android.aconfig.nano.Aconfig.parsed_flag;
+import android.aconfig.nano.Aconfig.parsed_flags;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Flags;
+import android.content.res.XmlResourceParser;
+import android.os.Environment;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class that manages a cache of all device feature flags and their default + override values.
+ * This class performs a very similar job to the one in {@code SettingsProvider}, with an important
+ * difference: this is a part of system server and is available for the server startup. Package
+ * parsing happens at the startup when {@code SettingsProvider} isn't available yet, so we need an
+ * own copy of the code here.
+ * @hide
+ */
+public class AconfigFlags {
+ private static final String LOG_TAG = "AconfigFlags";
+
+ private static final List<String> sTextProtoFilesOnDevice = List.of(
+ "/system/etc/aconfig_flags.pb",
+ "/system_ext/etc/aconfig_flags.pb",
+ "/product/etc/aconfig_flags.pb",
+ "/vendor/etc/aconfig_flags.pb");
+
+ private final ArrayMap<String, Boolean> mFlagValues = new ArrayMap<>();
+
+ public AconfigFlags() {
+ if (!Flags.manifestFlagging()) {
+ Slog.v(LOG_TAG, "Feature disabled, skipped all loading");
+ return;
+ }
+ for (String fileName : sTextProtoFilesOnDevice) {
+ try (var inputStream = new FileInputStream(fileName)) {
+ loadAconfigDefaultValues(inputStream.readAllBytes());
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Failed to read Aconfig values from " + fileName, e);
+ }
+ }
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ // Server overrides are only accessible to the system, no need to even try loading them
+ // in user processes.
+ loadServerOverrides();
+ }
+ }
+
+ private void loadServerOverrides() {
+ // Reading the proto files is enough for READ_ONLY flags but if it's a READ_WRITE flag
+ // (which you can check with `flag.getPermission() == flag_permission.READ_WRITE`) then we
+ // also need to check if there is a value pushed from the server in the file
+ // `/data/system/users/0/settings_config.xml`. It will be in a <setting> node under the
+ // root <settings> node with "name" attribute == "flag_namespace/flag_package.flag_name".
+ // The "value" attribute will be true or false.
+ //
+ // The "name" attribute could also be "<namespace>/flag_namespace?flag_package.flag_name"
+ // (prefixed with "staged/" or "device_config_overrides/" and a different separator between
+ // namespace and name). This happens when a flag value is overridden either with a pushed
+ // one from the server, or from the local command.
+ // When the device reboots during package parsing, the staged value will still be there and
+ // only later it will become a regular/non-staged value after SettingsProvider is
+ // initialized.
+ //
+ // In all cases, when there is more than one value, the priority is:
+ // device_config_overrides > staged > default
+ //
+
+ final var settingsFile = new File(Environment.getUserSystemDirectory(0),
+ "settings_config.xml");
+ try (var inputStream = new FileInputStream(settingsFile)) {
+ TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
+ if (parser.next() != XmlPullParser.END_TAG && "settings".equals(parser.getName())) {
+ final var flagPriority = new ArrayMap<String, Integer>();
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ if (!"setting".equals(parser.getName())) {
+ continue;
+ }
+ String name = parser.getAttributeValue(null, "name");
+ final String value = parser.getAttributeValue(null, "value");
+ if (name == null || value == null) {
+ continue;
+ }
+ // A non-boolean setting is definitely not an Aconfig flag value.
+ if (!"false".equalsIgnoreCase(value) && !"true".equalsIgnoreCase(value)) {
+ continue;
+ }
+ final var overridePrefix = "device_config_overrides/";
+ final var stagedPrefix = "staged/";
+ String separator = "/";
+ String prefix = "default";
+ int priority = 0;
+ if (name.startsWith(overridePrefix)) {
+ prefix = overridePrefix;
+ name = name.substring(overridePrefix.length());
+ separator = ":";
+ priority = 20;
+ } else if (name.startsWith(stagedPrefix)) {
+ prefix = stagedPrefix;
+ name = name.substring(stagedPrefix.length());
+ separator = "*";
+ priority = 10;
+ }
+ final String flagPackageAndName = parseFlagPackageAndName(name, separator);
+ if (flagPackageAndName == null) {
+ continue;
+ }
+ // We ignore all settings that aren't for flags. We'll know they are for flags
+ // if they correspond to flags read from the proto files.
+ if (!mFlagValues.containsKey(flagPackageAndName)) {
+ continue;
+ }
+ Slog.d(LOG_TAG, "Found " + prefix
+ + " Aconfig flag value for " + flagPackageAndName + " = " + value);
+ final Integer currentPriority = flagPriority.get(flagPackageAndName);
+ if (currentPriority != null && currentPriority >= priority) {
+ Slog.i(LOG_TAG, "Skipping " + prefix + " flag " + flagPackageAndName
+ + " because of the existing one with priority " + currentPriority);
+ continue;
+ }
+ flagPriority.put(flagPackageAndName, priority);
+ mFlagValues.put(flagPackageAndName, Boolean.parseBoolean(value));
+ }
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(LOG_TAG, "Failed to read Aconfig values from settings_config.xml", e);
+ }
+ }
+
+ private static String parseFlagPackageAndName(String fullName, String separator) {
+ int index = fullName.indexOf(separator);
+ if (index < 0) {
+ return null;
+ }
+ return fullName.substring(index + 1);
+ }
+
+ private void loadAconfigDefaultValues(byte[] fileContents) throws IOException {
+ parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents);
+ for (parsed_flag flag : parsedFlags.parsedFlag) {
+ String flagPackageAndName = flag.package_ + "." + flag.name;
+ boolean flagValue = (flag.state == Aconfig.ENABLED);
+ Slog.v(LOG_TAG, "Read Aconfig default flag value "
+ + flagPackageAndName + " = " + flagValue);
+ mFlagValues.put(flagPackageAndName, flagValue);
+ }
+ }
+
+ /**
+ * Get the flag value, or null if the flag doesn't exist.
+ * @param flagPackageAndName Full flag name formatted as 'package.flag'
+ * @return the current value of the given Aconfig flag, or null if there is no such flag
+ */
+ @Nullable
+ public Boolean getFlagValue(@NonNull String flagPackageAndName) {
+ Boolean value = mFlagValues.get(flagPackageAndName);
+ Slog.d(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
+ return value;
+ }
+
+ /**
+ * Check if the element in {@code parser} should be skipped because of the feature flag.
+ * @param parser XML parser object currently parsing an element
+ * @return true if the element is disabled because of its feature flag
+ */
+ public boolean skipCurrentElement(@NonNull XmlResourceParser parser) {
+ if (!Flags.manifestFlagging()) {
+ return false;
+ }
+ String featureFlag = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "featureFlag");
+ if (featureFlag == null) {
+ return false;
+ }
+ featureFlag = featureFlag.strip();
+ boolean negated = false;
+ if (featureFlag.startsWith("!")) {
+ negated = true;
+ featureFlag = featureFlag.substring(1).strip();
+ }
+ final Boolean flagValue = getFlagValue(featureFlag);
+ if (flagValue == null) {
+ Slog.w(LOG_TAG, "Skipping element " + parser.getName()
+ + " due to unknown feature flag " + featureFlag);
+ return true;
+ }
+ // Skip if flag==false && attr=="flag" OR flag==true && attr=="!flag" (negated)
+ if (flagValue == negated) {
+ Slog.v(LOG_TAG, "Skipping element " + parser.getName()
+ + " behind feature flag " + featureFlag + " = " + flagValue);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Add Aconfig flag values for testing flagging of manifest entries.
+ * @param flagValues A map of flag name -> value.
+ */
+ @VisibleForTesting
+ public void addFlagValuesForTesting(@NonNull Map<String, Boolean> flagValues) {
+ mFlagValues.putAll(flagValues);
+ }
+}
diff --git a/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
index db08005c833e..8858f9492890 100644
--- a/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
@@ -61,6 +61,9 @@ public class ComponentParseUtils {
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ continue;
+ }
final ParseResult result;
if ("meta-data".equals(parser.getName())) {
diff --git a/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
index 0b045919fb13..bb015812c225 100644
--- a/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
+++ b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
@@ -27,6 +27,7 @@ import android.util.ArraySet;
import com.android.internal.R;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -80,6 +81,9 @@ public class InstallConstraintsTagParser {
}
return input.success(prefixes);
} else if (type == XmlPullParser.START_TAG) {
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ continue;
+ }
if (parser.getName().equals(TAG_FINGERPRINT_PREFIX)) {
ParseResult<String> parsedPrefix =
readFingerprintPrefixValue(input, res, parser);
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
index 9f71d88c24bc..55baa532b434 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
@@ -393,6 +393,9 @@ public class ParsedActivityUtils {
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ continue;
+ }
final ParseResult result;
if (parser.getName().equals("intent-filter")) {
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
index 05728eee174f..da48b23a2b81 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
@@ -99,6 +99,9 @@ public class ParsedIntentInfoUtils {
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ continue;
+ }
final ParseResult result;
String nodeName = parser.getName();
@@ -197,6 +200,9 @@ public class ParsedIntentInfoUtils {
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ continue;
+ }
final ParseResult result;
String nodeName = parser.getName();
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
index 12aff1c6669f..6af2a29822a2 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
@@ -36,6 +36,7 @@ import android.util.Slog;
import com.android.internal.R;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.pm.pkg.parsing.ParsingUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -173,6 +174,9 @@ public class ParsedProviderUtils {
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ continue;
+ }
String name = parser.getName();
final ParseResult result;
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
index 4ac542f84226..c68ea2dc8516 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
@@ -34,6 +34,7 @@ import android.os.Build;
import com.android.internal.R;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.pm.pkg.parsing.ParsingUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -137,6 +138,9 @@ public class ParsedServiceUtils {
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ continue;
+ }
final ParseResult parseResult;
switch (parser.getName()) {
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 1dcd893eb143..44fedb11b043 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -90,6 +90,7 @@ import com.android.internal.R;
import com.android.internal.os.ClassLoaderFactory;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.permission.CompatibilityPermissionInfo;
+import com.android.internal.pm.pkg.component.AconfigFlags;
import com.android.internal.pm.pkg.component.ComponentMutateUtils;
import com.android.internal.pm.pkg.component.ComponentParseUtils;
import com.android.internal.pm.pkg.component.InstallConstraintsTagParser;
@@ -292,6 +293,7 @@ public class ParsingPackageUtils {
@NonNull
private final List<PermissionManager.SplitPermissionInfo> mSplitPermissionInfos;
private final Callback mCallback;
+ private static final AconfigFlags sAconfigFlags = new AconfigFlags();
public ParsingPackageUtils(String[] separateProcesses, DisplayMetrics displayMetrics,
@NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
@@ -761,6 +763,9 @@ public class ParsingPackageUtils {
if (outerDepth + 1 < parser.getDepth() || type != XmlPullParser.START_TAG) {
continue;
}
+ if (sAconfigFlags.skipCurrentElement(parser)) {
+ continue;
+ }
final ParseResult result;
String tagName = parser.getName();
@@ -837,6 +842,9 @@ public class ParsingPackageUtils {
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (sAconfigFlags.skipCurrentElement(parser)) {
+ continue;
+ }
ParsedMainComponent mainComponent = null;
@@ -980,6 +988,9 @@ public class ParsingPackageUtils {
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (sAconfigFlags.skipCurrentElement(parser)) {
+ continue;
+ }
String tagName = parser.getName();
final ParseResult result;
@@ -1599,6 +1610,9 @@ public class ParsingPackageUtils {
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (sAconfigFlags.skipCurrentElement(parser)) {
+ continue;
+ }
final String innerTagName = parser.getName();
if (innerTagName.equals("uses-feature")) {
@@ -1839,6 +1853,9 @@ public class ParsingPackageUtils {
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (sAconfigFlags.skipCurrentElement(parser)) {
+ continue;
+ }
if (parser.getName().equals("intent")) {
ParseResult<ParsedIntentInfoImpl> result = ParsedIntentInfoUtils.parseIntentInfo(
null /*className*/, pkg, res, parser, true /*allowGlobs*/,
@@ -2185,6 +2202,9 @@ public class ParsingPackageUtils {
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (sAconfigFlags.skipCurrentElement(parser)) {
+ continue;
+ }
final ParseResult result;
String tagName = parser.getName();
@@ -2773,6 +2793,9 @@ public class ParsingPackageUtils {
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (sAconfigFlags.skipCurrentElement(parser)) {
+ continue;
+ }
final String nodeName = parser.getName();
if (nodeName.equals("additional-certificate")) {
@@ -3458,4 +3481,11 @@ public class ParsingPackageUtils {
@NonNull Set<String> getInstallConstraintsAllowlist();
}
+
+ /**
+ * Getter for the flags object
+ */
+ public static AconfigFlags getAconfigFlags() {
+ return sAconfigFlags;
+ }
}
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index ea7bb8b4a1d1..a738acb299c1 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -105,6 +105,7 @@ android_test {
":PackageParserTestApp5",
":PackageParserTestApp6",
":PackageParserTestApp7",
+ ":PackageParserTestApp8",
],
resource_zips: [":PackageManagerServiceServerTests_apks_as_resources"],
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index a0e0e1ef36ee..5da202f109d4 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -101,6 +101,7 @@ import com.android.internal.pm.pkg.component.ParsedServiceImpl;
import com.android.internal.pm.pkg.component.ParsedUsesPermission;
import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.util.ArrayUtils;
import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.parsing.PackageInfoUtils;
@@ -126,6 +127,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -154,6 +156,7 @@ public class PackageParserTest {
private static final String TEST_APP5_APK = "PackageParserTestApp5.apk";
private static final String TEST_APP6_APK = "PackageParserTestApp6.apk";
private static final String TEST_APP7_APK = "PackageParserTestApp7.apk";
+ private static final String TEST_APP8_APK = "PackageParserTestApp8.apk";
private static final String PACKAGE_NAME = "com.android.servicestests.apps.packageparserapp";
@Before
@@ -814,6 +817,39 @@ public class PackageParserTest {
}
}
+ @Test
+ @RequiresFlagsEnabled(android.content.res.Flags.FLAG_MANIFEST_FLAGGING)
+ public void testParseWithFeatureFlagAttributes() throws Exception {
+ final File testFile = extractFile(TEST_APP8_APK);
+ try (PackageParser2 parser = new TestPackageParser2()) {
+ Map<String, Boolean> flagValues = new HashMap<>();
+ flagValues.put("my.flag1", true);
+ flagValues.put("my.flag2", false);
+ flagValues.put("my.flag3", false);
+ flagValues.put("my.flag4", true);
+ ParsingPackageUtils.getAconfigFlags().addFlagValuesForTesting(flagValues);
+
+ // The manifest has:
+ // <permission android:name="PERM1" android:featureFlag="my.flag1 " />
+ // <permission android:name="PERM2" android:featureFlag=" !my.flag2" />
+ // <permission android:name="PERM3" android:featureFlag="my.flag3" />
+ // <permission android:name="PERM4" android:featureFlag="!my.flag4" />
+ // <permission android:name="PERM5" android:featureFlag="unknown.flag" />
+ // Therefore with the above flag values, only PERM1 and PERM2 should be present.
+
+ final ParsedPackage pkg = parser.parsePackage(testFile, 0, false);
+ List<String> permissionNames =
+ pkg.getPermissions().stream().map(ParsedComponent::getName).toList();
+ assertThat(permissionNames).contains(PACKAGE_NAME + ".PERM1");
+ assertThat(permissionNames).contains(PACKAGE_NAME + ".PERM2");
+ assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM3");
+ assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM4");
+ assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM5");
+ } finally {
+ testFile.delete();
+ }
+ }
+
/**
* A subclass of package parser that adds a "cache_" prefix to the package name for the cached
* results. This is used by tests to tell if a ParsedPackage is generated from the cache or not.
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
index 131b380d9215..3def48aefa00 100644
--- a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
+++ b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
@@ -116,3 +116,20 @@ android_test_helper_app {
resource_dirs: ["res"],
manifest: "AndroidManifestApp7.xml",
}
+
+android_test_helper_app {
+ name: "PackageParserTestApp8",
+ sdk_version: "current",
+ srcs: ["**/*.java"],
+ dex_preopt: {
+ enabled: false,
+ },
+ optimize: {
+ enabled: false,
+ },
+ resource_dirs: ["res"],
+ aaptflags: [
+ "--feature-flags my.flag1,my.flag2,my.flag3,my.flag4,unknown.flag",
+ ],
+ manifest: "AndroidManifestApp8.xml",
+}
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml
new file mode 100644
index 000000000000..d489c1bb9e07
--- /dev/null
+++ b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.servicestests.apps.packageparserapp" >
+
+ <application>
+ <activity android:name=".TestActivity"
+ android:exported="true" />
+ </application>
+
+ <permission android:name="PERM1" android:featureFlag="my.flag1 " />
+ <permission android:name="PERM2" android:featureFlag=" !my.flag2" />
+ <permission android:name="PERM3" android:featureFlag="my.flag3" />
+ <permission android:name="PERM4" android:featureFlag="!my.flag4" />
+ <permission android:name="PERM5" android:featureFlag="unknown.flag" />
+</manifest> \ No newline at end of file