diff --git a/Android.bp b/Android.bp index d6b303f624286b316af5fdb1f94e4cb01bafaf0e..af312bf833e526d7772e78d614d91331c05d3088 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 0000000000000000000000000000000000000000..f306b0b0267767f852b6d7243b36d05b6967b528 --- /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 db08005c833e602a0a05d0d4fbd5abe247775519..8858f94928904e124128612219751a9921985197 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 0b045919fb13e1f1a159cabfa570be36f24027cc..bb015812c2251ef451f00214c041ce55456b606a 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 9f71d88c24bc8e2e55e126a65cf32514367abf98..55baa532b43477165971cbbaf5b2f175d7e1af3d 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 05728eee174fa6a014c471e6cf1e86269447235f..da48b23a2b81121992c8df8ec9db6d08b7924fae 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 12aff1c6669fa5f2f7c4264fd831da879b5b4e4d..6af2a29822a2ea5de71be6b835d46dc9550fa03f 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 4ac542f8422666b6a15264c44b1cadbb35e7d900..c68ea2dc8516b5feb88aedd44b650f0658c25f9c 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 1dcd893eb143ce95bb6cc2763ac1598e8a401a52..44fedb11b043bede71113c48c9f596c153d65e8f 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 ea7bb8b4a1d1d78ab474b27272fd7e32c0c103ef..a738acb299c14db5288d14dbdb3064342950ef73 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 a0e0e1ef36ee3fc637174508059d5b3176ba5d42..5da202f109d4e5dde502d18eaf99a6c00a53a550 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 131b380d92154cebb4362ff2831b07c5ab11aa75..3def48aefa004ef8e4f5d35911dedbaeba3d40da 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 0000000000000000000000000000000000000000..d489c1bb9e076b885c84a627814841ca9c6bebbf --- /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