diff options
-rw-r--r-- | core/java/android/content/res/ApkAssets.java | 2 | ||||
-rw-r--r-- | core/java/android/content/res/AssetManager.java | 11 | ||||
-rw-r--r-- | core/java/android/content/res/Resources.java | 23 | ||||
-rw-r--r-- | core/java/android/content/res/ResourcesImpl.java | 17 | ||||
-rw-r--r-- | core/java/android/content/res/XmlBlock.java | 10 | ||||
-rw-r--r-- | core/java/android/util/TypedValue.java | 6 | ||||
-rw-r--r-- | core/jni/android_util_AssetManager.cpp | 4 | ||||
-rw-r--r-- | core/tests/coretests/res/xml/flags.xml | 4 | ||||
-rw-r--r-- | core/tests/coretests/src/android/content/res/XmlResourcesFlaggedTest.kt | 114 | ||||
-rw-r--r-- | libs/androidfw/AssetManager2.cpp | 16 | ||||
-rw-r--r-- | libs/androidfw/include/androidfw/AssetManager2.h | 11 | ||||
-rw-r--r-- | libs/androidfw/tests/AssetManager2_test.cpp | 15 | ||||
-rw-r--r-- | libs/androidfw/tests/data/flagged/AndroidManifest.xml | 20 | ||||
-rw-r--r-- | libs/androidfw/tests/data/flagged/R.h | 35 | ||||
-rwxr-xr-x | libs/androidfw/tests/data/flagged/build | 28 | ||||
-rw-r--r-- | libs/androidfw/tests/data/flagged/flagged.apk | bin | 0 -> 1837 bytes | |||
-rw-r--r-- | libs/androidfw/tests/data/flagged/res/xml/flagged.xml | 18 |
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 Binary files differnew file mode 100644 index 000000000000..94b8f4d9fcf0 --- /dev/null +++ b/libs/androidfw/tests/data/flagged/flagged.apk 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 |