summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jeremy Meyer <jakmcbane@google.com> 2025-03-12 10:55:53 -0700
committer Jeremy Meyer <jakmcbane@google.com> 2025-03-20 14:13:28 -0700
commitb76f745bd22d7d3dd00a24a530b07d6870d80eb2 (patch)
tree45299d697d1afc4d671e763c19ca7aa2bb65c586
parentadfd1c77b9ed58b42bd44be8bdc3298a8e1a956c (diff)
Only check for xml flags when we know there are any
Test: Automation Fixes: 377974898 Fixes: 398541237 Fixes: 398086579 Fixes: 396884481 Fixes: 396992602 Flag: android.content.res.layout_readwrite_flags Change-Id: Ibdabb27689a95eba7b53490ea8651c947beca226
-rw-r--r--core/java/android/content/res/ApkAssets.java2
-rw-r--r--core/java/android/content/res/AssetManager.java11
-rw-r--r--core/java/android/content/res/Resources.java23
-rw-r--r--core/java/android/content/res/ResourcesImpl.java17
-rw-r--r--core/java/android/content/res/XmlBlock.java10
-rw-r--r--core/java/android/util/TypedValue.java6
-rw-r--r--core/jni/android_util_AssetManager.cpp4
-rw-r--r--core/tests/coretests/res/xml/flags.xml4
-rw-r--r--core/tests/coretests/src/android/content/res/XmlResourcesFlaggedTest.kt114
-rw-r--r--libs/androidfw/AssetManager2.cpp16
-rw-r--r--libs/androidfw/include/androidfw/AssetManager2.h11
-rw-r--r--libs/androidfw/tests/AssetManager2_test.cpp15
-rw-r--r--libs/androidfw/tests/data/flagged/AndroidManifest.xml20
-rw-r--r--libs/androidfw/tests/data/flagged/R.h35
-rwxr-xr-xlibs/androidfw/tests/data/flagged/build28
-rw-r--r--libs/androidfw/tests/data/flagged/flagged.apkbin0 -> 1837 bytes
-rw-r--r--libs/androidfw/tests/data/flagged/res/xml/flagged.xml18
17 files changed, 309 insertions, 25 deletions
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index f538e9ffffdd..3987f3abff0b 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -408,7 +408,7 @@ public final class ApkAssets {
Objects.requireNonNull(fileName, "fileName");
synchronized (this) {
long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName);
- try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) {
+ try (XmlBlock block = new XmlBlock(null, nativeXmlPtr, true)) {
XmlResourceParser parser = block.newParser();
// If nativeOpenXml doesn't throw, it will always return a valid native pointer,
// which makes newParser always return non-null. But let's be careful.
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index bcb50881d327..008bf2f522c3 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -1190,7 +1190,7 @@ public final class AssetManager implements AutoCloseable {
*/
public @NonNull XmlResourceParser openXmlResourceParser(int cookie, @NonNull String fileName)
throws IOException {
- try (XmlBlock block = openXmlBlockAsset(cookie, fileName)) {
+ try (XmlBlock block = openXmlBlockAsset(cookie, fileName, true)) {
XmlResourceParser parser = block.newParser(ID_NULL, new Validator());
// If openXmlBlockAsset doesn't throw, it will always return an XmlBlock object with
// a valid native pointer, which makes newParser always return non-null. But let's
@@ -1209,7 +1209,7 @@ public final class AssetManager implements AutoCloseable {
* @hide
*/
@NonNull XmlBlock openXmlBlockAsset(@NonNull String fileName) throws IOException {
- return openXmlBlockAsset(0, fileName);
+ return openXmlBlockAsset(0, fileName, true);
}
/**
@@ -1218,9 +1218,11 @@ public final class AssetManager implements AutoCloseable {
*
* @param cookie Identifier of the package to be opened.
* @param fileName Name of the asset to retrieve.
+ * @param usesFeatureFlags Whether the resources uses feature flags
* @hide
*/
- @NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException {
+ @NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName,
+ boolean usesFeatureFlags) throws IOException {
Objects.requireNonNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
@@ -1229,7 +1231,8 @@ public final class AssetManager implements AutoCloseable {
if (xmlBlock == 0) {
throw new FileNotFoundException("Asset XML file: " + fileName);
}
- final XmlBlock block = new XmlBlock(this, xmlBlock);
+
+ final XmlBlock block = new XmlBlock(this, xmlBlock, usesFeatureFlags);
incRefsLocked(block.hashCode());
return block;
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 2658efab0e44..92f8bb4e005e 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -2568,7 +2568,7 @@ public class Resources {
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
return loadXmlResourceParser(value.string.toString(), id,
- value.assetCookie, type);
+ value.assetCookie, type, value.usesFeatureFlags);
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ " type #0x" + Integer.toHexString(value.type) + " is not valid");
@@ -2591,7 +2591,26 @@ public class Resources {
@UnsupportedAppUsage
XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie,
String type) throws NotFoundException {
- return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type);
+ return loadXmlResourceParser(file, id, assetCookie, type, true);
+ }
+
+ /**
+ * Loads an XML parser for the specified file.
+ *
+ * @param file the path for the XML file to parse
+ * @param id the resource identifier for the file
+ * @param assetCookie the asset cookie for the file
+ * @param type the type of resource (used for logging)
+ * @param usesFeatureFlags whether the xml has read/write feature flags
+ * @return a parser for the specified XML file
+ * @throws NotFoundException if the file could not be loaded
+ * @hide
+ */
+ @NonNull
+ @VisibleForTesting
+ public XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie,
+ String type, boolean usesFeatureFlags) throws NotFoundException {
+ return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type, usesFeatureFlags);
}
/**
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 8c76fd70afd9..6cbad2f0909b 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -276,7 +276,8 @@ public class ResourcesImpl {
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
+ @VisibleForTesting
+ public void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
if (found) {
@@ -1057,8 +1058,8 @@ public class ResourcesImpl {
int id, int density, String file)
throws IOException, XmlPullParserException {
try (
- XmlResourceParser rp =
- loadXmlResourceParser(file, id, value.assetCookie, "drawable")
+ XmlResourceParser rp = loadXmlResourceParser(
+ file, id, value.assetCookie, "drawable", value.usesFeatureFlags)
) {
return Drawable.createFromXmlForDensity(wrapper, rp, density, null);
}
@@ -1092,7 +1093,7 @@ public class ResourcesImpl {
try {
if (file.endsWith("xml")) {
final XmlResourceParser rp = loadXmlResourceParser(
- file, id, value.assetCookie, "font");
+ file, id, value.assetCookie, "font", value.usesFeatureFlags);
final FontResourcesParser.FamilyResourceEntry familyEntry =
FontResourcesParser.parse(rp, wrapper);
if (familyEntry == null) {
@@ -1286,7 +1287,7 @@ public class ResourcesImpl {
if (file.endsWith(".xml")) {
try {
final XmlResourceParser parser = loadXmlResourceParser(
- file, id, value.assetCookie, "ComplexColor");
+ file, id, value.assetCookie, "ComplexColor", value.usesFeatureFlags);
final AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
@@ -1331,12 +1332,13 @@ public class ResourcesImpl {
* @param id the resource identifier for the file
* @param assetCookie the asset cookie for the file
* @param type the type of resource (used for logging)
+ * @param usesFeatureFlags whether the xml has read/write feature flags
* @return a parser for the specified XML file
* @throws NotFoundException if the file could not be loaded
*/
@NonNull
XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
- @NonNull String type)
+ @NonNull String type, boolean usesFeatureFlags)
throws NotFoundException {
if (id != 0) {
try {
@@ -1355,7 +1357,8 @@ public class ResourcesImpl {
// Not in the cache, create a new block and put it at
// the next slot in the cache.
- final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
+ final XmlBlock block =
+ mAssets.openXmlBlockAsset(assetCookie, file, usesFeatureFlags);
if (block != null) {
final int pos = (mLastCachedXmlBlockIndex + 1) % num;
mLastCachedXmlBlockIndex = pos;
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index 36fa05905814..b27150b7171f 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -59,12 +59,14 @@ public final class XmlBlock implements AutoCloseable {
mAssets = null;
mNative = nativeCreate(data, 0, data.length);
mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
+ mUsesFeatureFlags = true;
}
public XmlBlock(byte[] data, int offset, int size) {
mAssets = null;
mNative = nativeCreate(data, offset, size);
mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
+ mUsesFeatureFlags = true;
}
@Override
@@ -346,7 +348,8 @@ public final class XmlBlock implements AutoCloseable {
if (ev == ERROR_BAD_DOCUMENT) {
throw new XmlPullParserException("Corrupt XML binary file");
}
- if (useLayoutReadwrite() && ev == START_TAG) {
+
+ if (useLayoutReadwrite() && mUsesFeatureFlags && ev == START_TAG) {
AconfigFlags flags = ParsingPackageUtils.getAconfigFlags();
if (flags.skipCurrentElement(/* pkg= */ null, this)) {
int depth = 1;
@@ -678,10 +681,11 @@ public final class XmlBlock implements AutoCloseable {
* are doing! The given native object must exist for the entire lifetime
* of this newly creating XmlBlock.
*/
- XmlBlock(@Nullable AssetManager assets, long xmlBlock) {
+ XmlBlock(@Nullable AssetManager assets, long xmlBlock, boolean usesFeatureFlags) {
mAssets = assets;
mNative = xmlBlock;
mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false);
+ mUsesFeatureFlags = usesFeatureFlags;
}
private @Nullable final AssetManager mAssets;
@@ -690,6 +694,8 @@ public final class XmlBlock implements AutoCloseable {
private boolean mOpen = true;
private int mOpenCount = 1;
+ private final boolean mUsesFeatureFlags;
+
private static final native long nativeCreate(byte[] data,
int offset,
int size);
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
index 26ab5885c9ea..11f3f8f68dd6 100644
--- a/core/java/android/util/TypedValue.java
+++ b/core/java/android/util/TypedValue.java
@@ -247,6 +247,12 @@ public class TypedValue {
*/
public int sourceResourceId;
+ /**
+ * Whether the value uses feature flags that need to be evaluated at runtime.
+ * @hide
+ */
+ public boolean usesFeatureFlags = false;
+
/* ------------------------------------------------------------ */
/** Return the data for this value as a float. Only use for values
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 1394b9f8781a..d3d838a1675b 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -67,6 +67,7 @@ static struct typedvalue_offsets_t {
jfieldID mResourceId;
jfieldID mChangingConfigurations;
jfieldID mDensity;
+ jfieldID mUsesFeatureFlags;
} gTypedValueOffsets;
// This is also used by asset_manager.cpp.
@@ -137,6 +138,8 @@ static jint CopyValue(JNIEnv* env, const AssetManager2::SelectedValue& value,
env->SetIntField(out_typed_value, gTypedValueOffsets.mResourceId, value.resid);
env->SetIntField(out_typed_value, gTypedValueOffsets.mChangingConfigurations, value.flags);
env->SetIntField(out_typed_value, gTypedValueOffsets.mDensity, value.config.density);
+ env->SetBooleanField(out_typed_value, gTypedValueOffsets.mUsesFeatureFlags,
+ value.entry_flags & ResTable_entry::FLAG_USES_FEATURE_FLAGS);
return static_cast<jint>(ApkAssetsCookieToJavaCookie(value.cookie));
}
@@ -1664,6 +1667,7 @@ int register_android_content_AssetManager(JNIEnv* env) {
gTypedValueOffsets.mChangingConfigurations =
GetFieldIDOrDie(env, typedValue, "changingConfigurations", "I");
gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I");
+ gTypedValueOffsets.mUsesFeatureFlags = GetFieldIDOrDie(env, typedValue, "usesFeatureFlags", "Z");
jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager");
gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J");
diff --git a/core/tests/coretests/res/xml/flags.xml b/core/tests/coretests/res/xml/flags.xml
new file mode 100644
index 000000000000..e580ea5dea00
--- /dev/null
+++ b/core/tests/coretests/res/xml/flags.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<first xmlns:android="http://schemas.android.com/apk/res/android">
+ <second android:featureFlag="android.content.res.always_false"/>
+</first> \ No newline at end of file
diff --git a/core/tests/coretests/src/android/content/res/XmlResourcesFlaggedTest.kt b/core/tests/coretests/src/android/content/res/XmlResourcesFlaggedTest.kt
new file mode 100644
index 000000000000..8c20ba0d7fbe
--- /dev/null
+++ b/core/tests/coretests/src/android/content/res/XmlResourcesFlaggedTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2025 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 android.content.res
+
+import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.util.TypedValue
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+
+import com.android.frameworks.coretests.R
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils
+
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+
+
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+
+import java.io.IOException
+
+/**
+* Tests for flag handling within Resources.loadXmlResourceParser() and methods that call it.
+*/
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class XmlResourcesFlaggedTest {
+ @get:Rule
+ val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ private var mResources: Resources = Resources(null)
+
+ @Before
+ fun setup() {
+ mResources = InstrumentationRegistry.getInstrumentation().getContext().getResources()
+ mResources.getImpl().flushLayoutCache()
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_LAYOUT_READWRITE_FLAGS)
+ fun flaggedXmlTypedValueMarkedAsSuch() {
+ val tv = TypedValue()
+ mResources.getImpl().getValue(R.xml.flags, tv, false)
+ assertTrue(tv.usesFeatureFlags)
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_LAYOUT_READWRITE_FLAGS)
+ @Throws(IOException::class, XmlPullParserException::class)
+ fun parsedFlaggedXmlWithTrueOneElement() {
+ ParsingPackageUtils.getAconfigFlags()
+ .addFlagValuesForTesting(mapOf("android.content.res.always_false" to false))
+ val tv = TypedValue()
+ mResources.getImpl().getValue(R.xml.flags, tv, false)
+ val parser = mResources.loadXmlResourceParser(
+ tv.string.toString(),
+ R.xml.flags,
+ tv.assetCookie,
+ "xml",
+ true
+ )
+ assertEquals(XmlPullParser.START_DOCUMENT, parser.next())
+ assertEquals(XmlPullParser.START_TAG, parser.next())
+ assertEquals("first", parser.getName())
+ assertEquals(XmlPullParser.END_TAG, parser.next())
+ assertEquals(XmlPullParser.END_DOCUMENT, parser.next())
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_LAYOUT_READWRITE_FLAGS)
+ @Throws(IOException::class, XmlPullParserException::class)
+ fun parsedFlaggedXmlWithFalseTwoElements() {
+ val tv = TypedValue()
+ mResources.getImpl().getValue(R.xml.flags, tv, false)
+ val parser = mResources.loadXmlResourceParser(
+ tv.string.toString(),
+ R.xml.flags,
+ tv.assetCookie,
+ "xml",
+ false
+ )
+ assertEquals(XmlPullParser.START_DOCUMENT, parser.next())
+ assertEquals(XmlPullParser.START_TAG, parser.next())
+ assertEquals("first", parser.getName())
+ assertEquals(XmlPullParser.START_TAG, parser.next())
+ assertEquals("second", parser.getName())
+ assertEquals(XmlPullParser.END_TAG, parser.next())
+ assertEquals(XmlPullParser.END_TAG, parser.next())
+ assertEquals(XmlPullParser.END_DOCUMENT, parser.next())
+ }
+} \ No newline at end of file
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index e09ab5fd1643..6caae4c7623e 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -82,6 +82,9 @@ struct FindEntryResult {
// The bitmask of configuration axis with which the resource value varies.
uint32_t type_flags;
+ // The bitmask of ResTable_entry flags
+ uint16_t entry_flags;
+
// The dynamic package ID map for the package from which this resource came from.
const DynamicRefTable* dynamic_ref_table;
@@ -1031,6 +1034,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
.entry = *entry,
.config = *best_config,
.type_flags = type_flags,
+ .entry_flags = (*best_entry_verified)->flags(),
.dynamic_ref_table = package_group.dynamic_ref_table.get(),
.package_name = &best_package->GetPackageName(),
.type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1),
@@ -1185,16 +1189,16 @@ base::expected<AssetManager2::SelectedValue, NullOrIOError> AssetManager2::GetRe
}
// Create a reference since we can't represent this complex type as a Res_value.
- return SelectedValue(Res_value::TYPE_REFERENCE, resid, result->cookie, result->type_flags,
- resid, result->config);
+ return SelectedValue(Res_value::TYPE_REFERENCE, resid, result->cookie, result->entry_flags,
+ result->type_flags, resid, result->config);
}
// Convert the package ID to the runtime assigned package ID.
Res_value value = std::get<Res_value>(result->entry);
result->dynamic_ref_table->lookupResourceValue(&value);
- return SelectedValue(value.dataType, value.data, result->cookie, result->type_flags,
- resid, result->config);
+ return SelectedValue(value.dataType, value.data, result->cookie, result->entry_flags,
+ result->type_flags, resid, result->config);
}
base::expected<std::monostate, NullOrIOError> AssetManager2::ResolveReference(
@@ -1847,8 +1851,8 @@ std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid)
}
return AssetManager2::SelectedValue(entry_it->value.dataType, entry_it->value.data,
- entry_it->cookie, type_spec_flags, 0U /* resid */,
- {} /* config */);
+ entry_it->cookie, 0U /* entry flags*/, type_spec_flags,
+ 0U /* resid */, {} /* config */);
}
return std::nullopt;
}
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index b0179524f6cd..ffcef944a6ba 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -257,6 +257,7 @@ class AssetManager2 {
: cookie(entry.cookie),
data(entry.value.data),
type(entry.value.dataType),
+ entry_flags(0U),
flags(bag->type_spec_flags),
resid(0U),
config() {
@@ -271,6 +272,9 @@ class AssetManager2 {
// Type of the data value.
uint8_t type;
+ // The bitmask of ResTable_entry flags
+ uint16_t entry_flags;
+
// The bitmask of configuration axis that this resource varies with.
// See ResTable_config::CONFIG_*.
uint32_t flags;
@@ -283,9 +287,10 @@ class AssetManager2 {
private:
SelectedValue(uint8_t value_type, Res_value::data_type value_data, ApkAssetsCookie cookie,
- uint32_t type_flags, uint32_t resid, ResTable_config config) :
- cookie(cookie), data(value_data), type(value_type), flags(type_flags),
- resid(resid), config(std::move(config)) {}
+ uint16_t entry_flags, uint32_t type_flags, uint32_t resid, ResTable_config config)
+ :
+ cookie(cookie), data(value_data), type(value_type), entry_flags(entry_flags),
+ flags(type_flags), resid(resid), config(std::move(config)) {}
};
// Retrieves the best matching resource value with ID `resid`.
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 3f228841f6ba..948437230ecc 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -23,6 +23,7 @@
#include "androidfw/ResourceUtils.h"
#include "data/appaslib/R.h"
#include "data/basic/R.h"
+#include "data/flagged/R.h"
#include "data/lib_one/R.h"
#include "data/lib_two/R.h"
#include "data/libclient/R.h"
@@ -32,6 +33,7 @@
namespace app = com::android::app;
namespace appaslib = com::android::appaslib::app;
namespace basic = com::android::basic;
+namespace flagged = com::android::flagged;
namespace lib_one = com::android::lib_one;
namespace lib_two = com::android::lib_two;
namespace libclient = com::android::libclient;
@@ -87,6 +89,10 @@ class AssetManager2Test : public ::testing::Test {
overlayable_assets_ = ApkAssets::Load("overlayable/overlayable.apk");
ASSERT_THAT(overlayable_assets_, NotNull());
+
+ flagged_assets_ = ApkAssets::Load("flagged/flagged.apk");
+ ASSERT_THAT(app_assets_, NotNull());
+
chdir(original_path.c_str());
}
@@ -104,6 +110,7 @@ class AssetManager2Test : public ::testing::Test {
AssetManager2::ApkAssetsPtr app_assets_;
AssetManager2::ApkAssetsPtr overlay_assets_;
AssetManager2::ApkAssetsPtr overlayable_assets_;
+ AssetManager2::ApkAssetsPtr flagged_assets_;
};
TEST_F(AssetManager2Test, FindsResourceFromSingleApkAssets) {
@@ -856,4 +863,12 @@ TEST_F(AssetManager2Test, GetApkAssets) {
EXPECT_EQ(1, lib_one_assets_->getStrongCount());
}
+TEST_F(AssetManager2Test, GetFlaggedAssets) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({flagged_assets_});
+ auto value = assetmanager.GetResource(flagged::R::xml::flagged, false, 0);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_TRUE(value->entry_flags & ResTable_entry::FLAG_USES_FEATURE_FLAGS);
+}
+
} // namespace android
diff --git a/libs/androidfw/tests/data/flagged/AndroidManifest.xml b/libs/androidfw/tests/data/flagged/AndroidManifest.xml
new file mode 100644
index 000000000000..cc1394328797
--- /dev/null
+++ b/libs/androidfw/tests/data/flagged/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 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.basic">
+ <application />
+</manifest>
diff --git a/libs/androidfw/tests/data/flagged/R.h b/libs/androidfw/tests/data/flagged/R.h
new file mode 100644
index 000000000000..33ccab28cdd3
--- /dev/null
+++ b/libs/androidfw/tests/data/flagged/R.h
@@ -0,0 +1,35 @@
+/*
+* Copyright (C) 2025 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.
+*/
+
+#pragma once
+
+#include <cstdint>
+
+namespace com {
+namespace android {
+namespace flagged {
+
+struct R {
+ struct xml {
+ enum : uint32_t {
+ flagged = 0x7f010000,
+ };
+ };
+};
+
+} // namespace flagged
+} // namespace android
+} // namespace com \ No newline at end of file
diff --git a/libs/androidfw/tests/data/flagged/build b/libs/androidfw/tests/data/flagged/build
new file mode 100755
index 000000000000..9e5d21ba1833
--- /dev/null
+++ b/libs/androidfw/tests/data/flagged/build
@@ -0,0 +1,28 @@
+#!/bin/bash
+#
+# Copyright (C) 2025 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.
+#
+
+set -e
+
+PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar
+
+aapt2 compile --dir res -o compiled.flata
+aapt2 link -o flagged.apk \
+ --manifest AndroidManifest.xml \
+ -I $PATH_TO_FRAMEWORK_RES \
+ -I ../basic/basic.apk \
+ compiled.flata
+rm compiled.flata
diff --git a/libs/androidfw/tests/data/flagged/flagged.apk b/libs/androidfw/tests/data/flagged/flagged.apk
new file mode 100644
index 000000000000..94b8f4d9fcf0
--- /dev/null
+++ b/libs/androidfw/tests/data/flagged/flagged.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/flagged/res/xml/flagged.xml b/libs/androidfw/tests/data/flagged/res/xml/flagged.xml
new file mode 100644
index 000000000000..5fe8d1b3ac27
--- /dev/null
+++ b/libs/androidfw/tests/data/flagged/res/xml/flagged.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 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.
+-->
+<first xmlns:android="http://schemas.android.com/apk/res/android">
+ <second android:featureFlag="android.content.res.always_false"/>
+</first> \ No newline at end of file