summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/aapt2/Android.bp1
-rw-r--r--tools/aapt2/Debug.cpp10
-rw-r--r--tools/aapt2/LoadedApk.h14
-rw-r--r--tools/aapt2/ResourceParser.cpp2
-rw-r--r--tools/aapt2/ResourceTable.h7
-rw-r--r--tools/aapt2/ResourceUtils.cpp32
-rw-r--r--tools/aapt2/ResourceUtils.h4
-rw-r--r--tools/aapt2/ResourceUtils_test.cpp35
-rw-r--r--tools/aapt2/ResourceValues.cpp19
-rw-r--r--tools/aapt2/ResourceValues.h1
-rw-r--r--tools/aapt2/SdkConstants.cpp46
-rw-r--r--tools/aapt2/SdkConstants_test.cpp20
-rw-r--r--tools/aapt2/cmd/Compile.cpp16
-rw-r--r--tools/aapt2/cmd/Compile.h11
-rw-r--r--tools/aapt2/cmd/Compile_test.cpp21
-rw-r--r--tools/aapt2/cmd/Convert.cpp1
-rw-r--r--tools/aapt2/cmd/Convert.h5
-rw-r--r--tools/aapt2/cmd/Dump.cpp1
-rw-r--r--tools/aapt2/cmd/Link.cpp1
-rw-r--r--tools/aapt2/cmd/Link_test.cpp18
-rw-r--r--tools/aapt2/cmd/Optimize.cpp1
-rw-r--r--tools/aapt2/compile/IdAssigner.cpp39
-rw-r--r--tools/aapt2/compile/IdAssigner_test.cpp30
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator.cpp203
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator.h15
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator_test.cpp215
-rw-r--r--tools/aapt2/dump/DumpManifest.cpp4
-rw-r--r--tools/aapt2/format/binary/TableFlattener.cpp14
-rw-r--r--tools/aapt2/format/proto/ProtoDeserialize.cpp2
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize_test.cpp5
-rw-r--r--tools/aapt2/integration-tests/DumpTest/built_with_aapt_expected.txt2
-rw-r--r--tools/aapt2/integration-tests/DumpTest/components_expected.txt2
-rw-r--r--tools/aapt2/integration-tests/DumpTest/minimal_expected.txt2
-rw-r--r--tools/aapt2/integration-tests/DumpTest/multiple_uses_sdk_expected.txt2
-rw-r--r--tools/aapt2/link/ReferenceLinker.cpp4
-rw-r--r--tools/aapt2/link/XmlReferenceLinker.cpp3
-rw-r--r--tools/aapt2/optimize/Obfuscator.cpp42
-rw-r--r--tools/aapt2/optimize/Obfuscator.h7
-rw-r--r--tools/aapt2/optimize/Obfuscator_test.cpp114
-rw-r--r--tools/aapt2/process/SymbolTable.cpp15
-rw-r--r--tools/aapt2/process/SymbolTable.h2
-rw-r--r--tools/aapt2/trace/TraceBuffer.cpp140
-rw-r--r--tools/aapt2/trace/TraceBuffer.h37
-rw-r--r--tools/aapt2/util/Util.cpp30
-rw-r--r--tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt1
-rw-r--r--tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt16
-rw-r--r--tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt6
-rw-r--r--tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt7
-rw-r--r--tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt71
-rw-r--r--tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt32
-rw-r--r--tools/streaming_proto/OWNERS1
-rw-r--r--tools/streaming_proto/cpp/main.cpp10
-rw-r--r--tools/xmlpersistence/Android.bp20
-rw-r--r--tools/xmlpersistence/OWNERS1
-rw-r--r--tools/xmlpersistence/manifest.txt1
-rw-r--r--tools/xmlpersistence/src/main/kotlin/Generator.kt577
-rw-r--r--tools/xmlpersistence/src/main/kotlin/Main.kt45
-rw-r--r--tools/xmlpersistence/src/main/kotlin/Parser.kt248
-rw-r--r--tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt44
59 files changed, 1099 insertions, 1176 deletions
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 0d6dc3522d24..7323b0f4c14f 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -47,6 +47,7 @@ cc_defaults {
"-Wno-missing-field-initializers",
"-fno-exceptions",
"-fno-rtti",
+ "-Wno-deprecated-declarations",
],
target: {
windows: {
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 9cfb85d7cd73..6a17ef85a755 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -265,6 +265,16 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions&
ValueHeadlinePrinter headline_printer(package.name, printer);
ValueBodyPrinter body_printer(package.name, printer);
+ auto& dynamicRefTable = table.GetReferencedPackages();
+ if (!dynamicRefTable.empty()) {
+ printer->Println(StringPrintf("DynamicRefTable entryCount=%d", int(dynamicRefTable.size())));
+ printer->Indent();
+ for (auto&& [id, name] : dynamicRefTable) {
+ printer->Println(StringPrintf("0x%02x -> %s", id, name.c_str()));
+ }
+ printer->Undent();
+ }
+
printer->Print("Package name=");
printer->Print(package.name);
if (package.id) {
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index 4cd7eae0a5e2..27c354a7b08e 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -40,10 +40,8 @@ enum ApkFormat {
};
// Info about an APK loaded in memory.
-class LoadedApk {
+class LoadedApk final {
public:
- virtual ~LoadedApk() = default;
-
// Loads both binary and proto APKs from disk.
static std::unique_ptr<LoadedApk> LoadApkFromPath(android::StringPiece path,
android::IDiagnostics* diag);
@@ -96,8 +94,8 @@ class LoadedApk {
* Writes the APK on disk at the given path, while also removing the resource
* files that are not referenced in the resource table.
*/
- virtual bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
- IArchiveWriter* writer);
+ bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
+ IArchiveWriter* writer);
/**
* Writes the APK on disk at the given path, while also removing the resource files that are not
@@ -108,9 +106,9 @@ class LoadedApk {
* original manifest will be written. The manifest is only required if the contents of the new APK
* have been modified in a way that require the AndroidManifest.xml to also be modified.
*/
- virtual bool WriteToArchive(IAaptContext* context, ResourceTable* split_table,
- const TableFlattenerOptions& options, FilterChain* filters,
- IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
+ bool WriteToArchive(IAaptContext* context, ResourceTable* split_table,
+ const TableFlattenerOptions& options, FilterChain* filters,
+ IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
/** Loads the file as an xml document. */
std::unique_ptr<xml::XmlResource> LoadXml(const std::string& file_path,
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index fa9a98f136cb..6af39b739e9b 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -800,7 +800,7 @@ std::unique_ptr<Item> ResourceParser::ParseXml(const FlattenedXmlSubTree& xmlsub
// Process the raw value.
std::unique_ptr<Item> processed_item = ResourceUtils::TryParseItemForAttribute(
- xmlsub_tree.raw_value, type_mask, on_create_reference);
+ &diag, xmlsub_tree.raw_value, type_mask, on_create_reference);
if (processed_item) {
// Fix up the reference.
if (auto ref = ValueCast<Reference>(processed_item.get())) {
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index bb286a8abdaa..61e399c7ab68 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -307,6 +307,11 @@ class ResourceTable {
// order.
ResourceTableView GetPartitionedView(const ResourceTableViewOptions& options = {}) const;
+ using ReferencedPackages = std::map<uint8_t, std::string>;
+ const ReferencedPackages& GetReferencedPackages() const {
+ return included_packages_;
+ }
+
struct SearchResult {
ResourceTablePackage* package;
ResourceTableType* type;
@@ -342,7 +347,7 @@ class ResourceTable {
// Set of dynamic packages that this table may reference. Their package names get encoded
// into the resources.arsc along with their compile-time assigned IDs.
- std::map<size_t, std::string> included_packages_;
+ ReferencedPackages included_packages_;
private:
DISALLOW_COPY_AND_ASSIGN(ResourceTable);
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 5a118a902963..d358df98ada6 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -619,7 +619,7 @@ uint32_t AndroidTypeToAttributeTypeMask(uint16_t type) {
}
std::unique_ptr<Item> TryParseItemForAttribute(
- StringPiece value, uint32_t type_mask,
+ android::IDiagnostics* diag, StringPiece value, uint32_t type_mask,
const std::function<bool(const ResourceName&)>& on_create_reference) {
using android::ResTable_map;
@@ -670,8 +670,32 @@ std::unique_ptr<Item> TryParseItemForAttribute(
// Try parsing this as a float.
auto floating_point = TryParseFloat(value);
if (floating_point) {
+ // Only check if the parsed result lost precision when the parsed item is
+ // android::Res_value::TYPE_FLOAT and there is other possible types saved in type_mask, like
+ // ResTable_map::TYPE_INTEGER.
if (type_mask & AndroidTypeToAttributeTypeMask(floating_point->value.dataType)) {
- return std::move(floating_point);
+ const bool mayOnlyBeFloat = (type_mask & ~float_mask) == 0;
+ const bool parsedAsFloat = floating_point->value.dataType == android::Res_value::TYPE_FLOAT;
+ if (!mayOnlyBeFloat && parsedAsFloat) {
+ float f = reinterpret_cast<float&>(floating_point->value.data);
+ std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(value));
+ double d;
+ if (android::ResTable::stringToDouble(str16.data(), str16.size(), d)) {
+ // Parse as a float only if the difference between float and double parsed from the
+ // same string is smaller than 1, otherwise return as raw string.
+ if (fabs(f - d) < 1) {
+ return std::move(floating_point);
+ } else {
+ if (diag->IsVerbose()) {
+ diag->Note(android::DiagMessage()
+ << "precision lost greater than 1 while parsing float " << value
+ << ", return a raw string");
+ }
+ }
+ }
+ } else {
+ return std::move(floating_point);
+ }
}
}
}
@@ -683,12 +707,12 @@ std::unique_ptr<Item> TryParseItemForAttribute(
* allows.
*/
std::unique_ptr<Item> TryParseItemForAttribute(
- StringPiece str, const Attribute* attr,
+ android::IDiagnostics* diag, StringPiece str, const Attribute* attr,
const std::function<bool(const ResourceName&)>& on_create_reference) {
using android::ResTable_map;
const uint32_t type_mask = attr->type_mask;
- auto value = TryParseItemForAttribute(str, type_mask, on_create_reference);
+ auto value = TryParseItemForAttribute(diag, str, type_mask, on_create_reference);
if (value) {
return value;
}
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index f30f4acfec7a..50fc87900162 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -200,11 +200,11 @@ std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr,
* reference to an ID that must be created (@+id/foo).
*/
std::unique_ptr<Item> TryParseItemForAttribute(
- android::StringPiece value, const Attribute* attr,
+ android::IDiagnostics* diag, android::StringPiece value, const Attribute* attr,
const std::function<bool(const ResourceName&)>& on_create_reference = {});
std::unique_ptr<Item> TryParseItemForAttribute(
- android::StringPiece value, uint32_t type_mask,
+ android::IDiagnostics* diag, android::StringPiece value, uint32_t type_mask,
const std::function<bool(const ResourceName&)>& on_create_reference = {});
uint32_t AndroidTypeToAttributeTypeMask(uint16_t type);
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index 568871a4d66e..4cba04de72ee 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -217,17 +217,46 @@ TEST(ResourceUtilsTest, EmptyIsBinaryPrimitive) {
}
TEST(ResourceUtilsTest, ItemsWithWhitespaceAreParsedCorrectly) {
- EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(" 12\n ", ResTable_map::TYPE_INTEGER),
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), " 12\n ",
+ ResTable_map::TYPE_INTEGER),
Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_INT_DEC, 12u))));
- EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(" true\n ", ResTable_map::TYPE_BOOLEAN),
+ EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), " true\n ",
+ ResTable_map::TYPE_BOOLEAN),
Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_INT_BOOLEAN, 0xffffffffu))));
const float expected_float = 12.0f;
const uint32_t expected_float_flattened = *(uint32_t*)&expected_float;
- EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(" 12.0\n ", ResTable_map::TYPE_FLOAT),
+ EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), " 12.0\n ",
+ ResTable_map::TYPE_FLOAT),
Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, expected_float_flattened))));
}
+TEST(ResourceUtilsTest, FloatAndBigIntegerParsedCorrectly) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ const float expected_float = 0.125f;
+ const uint32_t expected_float_flattened = *(uint32_t*)&expected_float;
+ EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), "0.125",
+ ResTable_map::TYPE_FLOAT),
+ Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, expected_float_flattened))));
+
+ const float special_float = 1.0f;
+ const uint32_t special_float_flattened = *(uint32_t*)&special_float;
+ EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), "1.0",
+ ResTable_map::TYPE_FLOAT),
+ Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, special_float_flattened))));
+
+ EXPECT_EQ(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), "1099511627776",
+ ResTable_map::TYPE_INTEGER),
+ std::unique_ptr<Item>(nullptr));
+
+ const float big_float = 1099511627776.0f;
+ const uint32_t big_flattened = *(uint32_t*)&big_float;
+ EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), "1099511627776",
+ ResTable_map::TYPE_FLOAT),
+ Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, big_flattened))));
+}
+
TEST(ResourceUtilsTest, ParseSdkVersionWithCodename) {
EXPECT_THAT(ResourceUtils::ParseSdkVersion("Q"), Eq(std::optional<int>(10000)));
EXPECT_THAT(ResourceUtils::ParseSdkVersion("Q.fingerprint"), Eq(std::optional<int>(10000)));
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index a5754e0d168f..166b01bd9154 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -439,6 +439,21 @@ static std::string ComplexToString(uint32_t complex_value, bool fraction) {
return str;
}
+// This function is designed to using different specifier to print different floats,
+// which can print more accurate format rather than using %g only.
+const char* BinaryPrimitive::DecideFormat(float f) {
+ // if the float is either too big or too tiny, print it in scientific notation.
+ // eg: "10995116277760000000000" to 1.099512e+22, "0.00000000001" to 1.000000e-11
+ if (fabs(f) > std::numeric_limits<int64_t>::max() || fabs(f) < 1e-10) {
+ return "%e";
+ // Else if the number is an integer exactly, print it without trailing zeros.
+ // eg: "1099511627776" to 1099511627776
+ } else if (int64_t(f) == f) {
+ return "%.0f";
+ }
+ return "%g";
+}
+
void BinaryPrimitive::PrettyPrint(Printer* printer) const {
using ::android::Res_value;
switch (value.dataType) {
@@ -470,7 +485,9 @@ void BinaryPrimitive::PrettyPrint(Printer* printer) const {
break;
case Res_value::TYPE_FLOAT:
- printer->Print(StringPrintf("%g", *reinterpret_cast<const float*>(&value.data)));
+ float f;
+ f = *reinterpret_cast<const float*>(&value.data);
+ printer->Print(StringPrintf(DecideFormat(f), f));
break;
case Res_value::TYPE_DIMENSION:
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 6f9dccbd3bcc..5192c2be1f98 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -284,6 +284,7 @@ struct BinaryPrimitive : public TransformableItem<BinaryPrimitive, BaseItem<Bina
bool Equals(const Value* value) const override;
bool Flatten(android::Res_value* out_value) const override;
void Print(std::ostream* out) const override;
+ static const char* DecideFormat(float f);
void PrettyPrint(text::Printer* printer) const override;
};
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index a766bd437120..83f2eb31aa57 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -16,20 +16,24 @@
#include "SdkConstants.h"
+#include <stdint.h>
+
#include <algorithm>
#include <string>
-#include <unordered_set>
-#include <vector>
+#include <string_view>
using android::StringPiece;
+using namespace std::literals;
namespace aapt {
-static ApiVersion sDevelopmentSdkLevel = 10000;
-static const auto sDevelopmentSdkCodeNames = std::unordered_set<StringPiece>(
- {"Q", "R", "S", "Sv2", "Tiramisu", "UpsideDownCake", "VanillaIceCream"});
+static constexpr ApiVersion sDevelopmentSdkLevel = 10000;
+static constexpr StringPiece sDevelopmentSdkCodeNames[] = {
+ "Q"sv, "R"sv, "S"sv, "Sv2"sv, "Tiramisu"sv, "UpsideDownCake"sv, "VanillaIceCream"sv};
+
+static constexpr auto sPrivacySandboxSuffix = "PrivacySandbox"sv;
-static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = {
+static constexpr std::pair<uint16_t, ApiVersion> sAttrIdMap[] = {
{0x021c, 1},
{0x021d, 2},
{0x0269, SDK_CUPCAKE},
@@ -62,25 +66,37 @@ static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = {
{0x064c, SDK_S_V2},
};
-static bool less_entry_id(const std::pair<uint16_t, ApiVersion>& p, uint16_t entryId) {
- return p.first < entryId;
-}
+static_assert(std::is_sorted(std::begin(sAttrIdMap), std::end(sAttrIdMap),
+ [](auto&& l, auto&& r) { return l.first < r.first; }));
ApiVersion FindAttributeSdkLevel(const ResourceId& id) {
if (id.package_id() != 0x01 || id.type_id() != 0x01) {
return 0;
}
- auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(), id.entry_id(), less_entry_id);
- if (iter == sAttrIdMap.end()) {
+ const auto it =
+ std::lower_bound(std::begin(sAttrIdMap), std::end(sAttrIdMap), id.entry_id(),
+ [](const auto& pair, uint16_t entryId) { return pair.first < entryId; });
+ if (it == std::end(sAttrIdMap)) {
return SDK_LOLLIPOP_MR1;
}
- return iter->second;
+ return it->second;
}
std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(StringPiece code_name) {
- return (sDevelopmentSdkCodeNames.find(code_name) == sDevelopmentSdkCodeNames.end())
- ? std::optional<ApiVersion>()
- : sDevelopmentSdkLevel;
+ const auto it =
+ std::find_if(std::begin(sDevelopmentSdkCodeNames), std::end(sDevelopmentSdkCodeNames),
+ [code_name](const auto& item) { return code_name.starts_with(item); });
+ if (it == std::end(sDevelopmentSdkCodeNames)) {
+ return {};
+ }
+ if (code_name.size() == it->size()) {
+ return sDevelopmentSdkLevel;
+ }
+ if (code_name.size() == it->size() + sPrivacySandboxSuffix.size() &&
+ code_name.ends_with(sPrivacySandboxSuffix)) {
+ return sDevelopmentSdkLevel;
+ }
+ return {};
}
} // namespace aapt
diff --git a/tools/aapt2/SdkConstants_test.cpp b/tools/aapt2/SdkConstants_test.cpp
index 61f4d71b7fb2..0f645ffc1e8b 100644
--- a/tools/aapt2/SdkConstants_test.cpp
+++ b/tools/aapt2/SdkConstants_test.cpp
@@ -28,4 +28,24 @@ TEST(SdkConstantsTest, NonFrameworkAttributeIsSdk0) {
EXPECT_EQ(0, FindAttributeSdkLevel(ResourceId(0x7f010345)));
}
+TEST(SdkConstantsTest, GetDevelopmentSdkCodeNameVersionValid) {
+ EXPECT_EQ(std::optional<ApiVersion>(10000), GetDevelopmentSdkCodeNameVersion("Q"));
+ EXPECT_EQ(std::optional<ApiVersion>(10000), GetDevelopmentSdkCodeNameVersion("VanillaIceCream"));
+}
+
+TEST(SdkConstantsTest, GetDevelopmentSdkCodeNameVersionPrivacySandbox) {
+ EXPECT_EQ(std::optional<ApiVersion>(10000), GetDevelopmentSdkCodeNameVersion("QPrivacySandbox"));
+ EXPECT_EQ(std::optional<ApiVersion>(10000),
+ GetDevelopmentSdkCodeNameVersion("VanillaIceCreamPrivacySandbox"));
+}
+
+TEST(SdkConstantsTest, GetDevelopmentSdkCodeNameVersionInvalid) {
+ EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("A"));
+ EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("Sv3"));
+ EXPECT_EQ(std::optional<ApiVersion>(),
+ GetDevelopmentSdkCodeNameVersion("VanillaIceCream_PrivacySandbox"));
+ EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("PrivacySandbox"));
+ EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("QQQQQQQQQQQQQQQ"));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 03f9715fb265..b5c290ec8dad 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -186,7 +186,20 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options,
// These are created as weak symbols, and are only generated from default
// configuration
// strings and plurals.
- PseudolocaleGenerator pseudolocale_generator;
+ std::string grammatical_gender_values;
+ std::string grammatical_gender_ratio;
+ if (options.pseudo_localize_gender_values) {
+ grammatical_gender_values = options.pseudo_localize_gender_values.value();
+ } else {
+ grammatical_gender_values = "f,m,n";
+ }
+ if (options.pseudo_localize_gender_ratio) {
+ grammatical_gender_ratio = options.pseudo_localize_gender_ratio.value();
+ } else {
+ grammatical_gender_ratio = "1.0";
+ }
+ PseudolocaleGenerator pseudolocale_generator(grammatical_gender_values,
+ grammatical_gender_ratio);
if (!pseudolocale_generator.Consume(context, &table)) {
return false;
}
@@ -597,6 +610,7 @@ class CompileContext : public IAaptContext {
void SetVerbose(bool val) {
verbose_ = val;
+ diagnostics_->SetVerbose(val);
}
bool IsVerbose() override {
diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h
index 14a730a1b1a0..22890fc53d5c 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -35,6 +35,8 @@ struct CompileOptions {
std::optional<std::string> res_dir;
std::optional<std::string> res_zip;
std::optional<std::string> generate_text_symbols_path;
+ std::optional<std::string> pseudo_localize_gender_values;
+ std::optional<std::string> pseudo_localize_gender_ratio;
std::optional<Visibility::Level> visibility;
bool pseudolocalize = false;
bool no_png_crunch = false;
@@ -76,6 +78,15 @@ class CompileCommand : public Command {
AddOptionalFlag("--source-path",
"Sets the compiled resource file source file path to the given string.",
&options_.source_path);
+ AddOptionalFlag("--pseudo-localize-gender-values",
+ "Sets the gender values to pick up for generating grammatical gender strings, "
+ "gender values should be f, m, or n, which are shortcuts for feminine, "
+ "masculine and neuter, and split with comma.",
+ &options_.pseudo_localize_gender_values);
+ AddOptionalFlag("--pseudo-localize-gender-ratio",
+ "Sets the ratio of resources to generate grammatical gender strings for. The "
+ "ratio has to be a float number between 0 and 1.",
+ &options_.pseudo_localize_gender_ratio);
}
int Action(const std::vector<std::string>& args) override;
diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp
index 3464a7662c60..8880089d0e20 100644
--- a/tools/aapt2/cmd/Compile_test.cpp
+++ b/tools/aapt2/cmd/Compile_test.cpp
@@ -236,9 +236,24 @@ TEST_F(CompilerTest, DoNotTranslateTest) {
// The first string (000) is translatable, the second is not
// ar-XB uses "\u200F\u202E...\u202C\u200F"
std::vector<std::string> expected_translatable = {
- "000", "111", // default locale
- "[000 one]", // en-XA
- "\xE2\x80\x8F\xE2\x80\xAE" "000" "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB
+ "(F)[000 one]", // en-XA-feminine
+ "(F)\xE2\x80\x8F\xE2\x80\xAE"
+ "000"
+ "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB-feminine
+ "(M)[000 one]", // en-XA-masculine
+ "(M)\xE2\x80\x8F\xE2\x80\xAE"
+ "000"
+ "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB-masculine
+ "(N)[000 one]", // en-XA-neuter
+ "(N)\xE2\x80\x8F\xE2\x80\xAE"
+ "000"
+ "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB-neuter
+ "000", // default locale
+ "111", // default locale
+ "[000 one]", // en-XA
+ "\xE2\x80\x8F\xE2\x80\xAE"
+ "000"
+ "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB
};
AssertTranslations(this, "foo", expected_translatable);
AssertTranslations(this, "foo_donottranslate", expected_translatable);
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 7381a85f4339..387dcfe2ddf3 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -425,6 +425,7 @@ int ConvertCommand::Action(const std::vector<std::string>& args) {
if (force_sparse_encoding_) {
table_flattener_options_.sparse_entries = SparseEntriesMode::Forced;
}
+ table_flattener_options_.use_compact_entries = enable_compact_entries_;
if (resources_config_path_) {
if (!ExtractResourceConfig(*resources_config_path_, &context, table_flattener_options_)) {
return 1;
diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h
index 15fe11fd91cc..9452e588953e 100644
--- a/tools/aapt2/cmd/Convert.h
+++ b/tools/aapt2/cmd/Convert.h
@@ -46,6 +46,10 @@ class ConvertCommand : public Command {
"This decreases APK size at the cost of resource retrieval performance.\n"
"Applies sparse encoding to all resources regardless of minSdk.",
&force_sparse_encoding_);
+ AddOptionalSwitch(
+ "--enable-compact-entries",
+ "This decreases APK size by using compact resource entries for simple data types.",
+ &enable_compact_entries_);
AddOptionalSwitch("--keep-raw-values",
android::base::StringPrintf("Preserve raw attribute values in xml files when using the"
" '%s' output format", kOutputFormatBinary),
@@ -85,6 +89,7 @@ class ConvertCommand : public Command {
bool verbose_ = false;
bool enable_sparse_encoding_ = false;
bool force_sparse_encoding_ = false;
+ bool enable_compact_entries_ = false;
std::optional<std::string> resources_config_path_;
};
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 71b08022f688..864af06f187e 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -112,6 +112,7 @@ class DumpContext : public IAaptContext {
void SetVerbose(bool val) {
verbose_ = val;
+ diagnostics_.SetVerbose(val);
}
int GetMinSdkVersion() override {
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 97404fc69af2..eb4e38c5f35f 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -148,6 +148,7 @@ class LinkContext : public IAaptContext {
void SetVerbose(bool val) {
verbose_ = val;
+ diagnostics_->SetVerbose(val);
}
int GetMinSdkVersion() override {
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 28fcc1a4800e..7096f5cc54e3 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -441,8 +441,8 @@ static void BuildNonFinalizedSDK(const std::string& apk_path, const std::string&
R"(<resources>
<public type="attr" name="finalized_res" id="0x01010001"/>
- <!-- S staged attributes (support staged resources in the same type id) -->
- <staging-public-group type="attr" first-id="0x01010050">
+ <!-- S staged attributes (Not support staged resources in the same type id) -->
+ <staging-public-group type="attr" first-id="0x01fc0050">
<public name="staged_s_res" />
</staging-public-group>
@@ -480,8 +480,8 @@ static void BuildFinalizedSDK(const std::string& apk_path, const std::string& ja
<public type="attr" name="staged_s2_res" id="0x01010003"/>
<public type="string" name="staged_s_string" id="0x01020000"/>
- <!-- S staged attributes (support staged resources in the same type id) -->
- <staging-public-group-final type="attr" first-id="0x01010050">
+ <!-- S staged attributes (Not support staged resources in the same type id) -->
+ <staging-public-group-final type="attr" first-id="0x01fc0050">
<public name="staged_s_res" />
</staging-public-group-final>
@@ -551,7 +551,7 @@ TEST_F(LinkTest, StagedAndroidApi) {
EXPECT_THAT(android_r_contents, HasSubstr("public static final int finalized_res=0x01010001;"));
EXPECT_THAT(
android_r_contents,
- HasSubstr("public static final int staged_s_res; static { staged_s_res=0x01010050; }"));
+ HasSubstr("public static final int staged_s_res; static { staged_s_res=0x01fc0050; }"));
EXPECT_THAT(
android_r_contents,
HasSubstr("public static final int staged_s_string; static { staged_s_string=0x01fd0080; }"));
@@ -575,7 +575,7 @@ TEST_F(LinkTest, StagedAndroidApi) {
android::AssetManager2 am;
auto android_asset = android::ApkAssets::Load(android_apk);
ASSERT_THAT(android_asset, NotNull());
- ASSERT_TRUE(am.SetApkAssets({android_asset.get()}));
+ ASSERT_TRUE(am.SetApkAssets({android_asset}));
auto result = am.GetResourceId("android:attr/finalized_res");
ASSERT_TRUE(result.has_value());
@@ -583,7 +583,7 @@ TEST_F(LinkTest, StagedAndroidApi) {
result = am.GetResourceId("android:attr/staged_s_res");
ASSERT_TRUE(result.has_value());
- EXPECT_THAT(*result, Eq(0x01010050));
+ EXPECT_THAT(*result, Eq(0x01fc0050));
result = am.GetResourceId("android:string/staged_s_string");
ASSERT_TRUE(result.has_value());
@@ -631,7 +631,7 @@ TEST_F(LinkTest, FinalizedAndroidApi) {
auto app_against_non_final = android::ApkAssets::Load(app_apk);
ASSERT_THAT(android_asset, NotNull());
ASSERT_THAT(app_against_non_final, NotNull());
- ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_non_final.get()}));
+ ASSERT_TRUE(am.SetApkAssets({android_asset, app_against_non_final}));
auto result = am.GetResourceId("android:attr/finalized_res");
ASSERT_TRUE(result.has_value());
@@ -667,7 +667,7 @@ TEST_F(LinkTest, FinalizedAndroidApi) {
auto app_against_final = android::ApkAssets::Load(app_apk_respin);
ASSERT_THAT(app_against_final, NotNull());
- ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_final.get()}));
+ ASSERT_TRUE(am.SetApkAssets({android_asset, app_against_final}));
{
auto style = am.GetBag(0x7f020000);
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index dbe79701bf5c..f045dad6d11a 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -101,6 +101,7 @@ class OptimizeContext : public IAaptContext {
void SetVerbose(bool val) {
verbose_ = val;
+ diagnostics_.SetVerbose(val);
}
void SetMinSdkVersion(int sdk_version) {
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
index b3f98a9d3e30..5421abde3689 100644
--- a/tools/aapt2/compile/IdAssigner.cpp
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -37,6 +37,7 @@ using Result = expected<T, std::string>;
template <typename Id, typename Key>
struct NextIdFinder {
+ std::map<Id, Key> pre_assigned_ids_;
explicit NextIdFinder(Id start_id = 0u) : next_id_(start_id){};
// Attempts to reserve an identifier for the specified key.
@@ -55,7 +56,6 @@ struct NextIdFinder {
Id next_id_;
bool next_id_called_ = false;
bool exhausted_ = false;
- std::map<Id, Key> pre_assigned_ids_;
typename std::map<Id, Key>::iterator next_preassigned_id_;
};
@@ -158,7 +158,7 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) {
}
if (assigned_id_map_) {
- // Reserve all the IDs mentioned in the stable ID map. That way we won't assig IDs that were
+ // Reserve all the IDs mentioned in the stable ID map. That way we won't assign IDs that were
// listed in the map if they don't exist in the table.
for (const auto& stable_id_entry : *assigned_id_map_) {
const ResourceName& pre_assigned_name = stable_id_entry.first;
@@ -191,6 +191,11 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) {
}
namespace {
+static const std::string_view staged_type_overlap_error =
+ "Staged public resource type IDs have conflict with non staged public resources type "
+ "IDs, please restart staged resource type ID assignment at 0xff in public-staging.xml "
+ "and also delete all the overlapping groups in public-final.xml";
+
template <typename Id, typename Key>
Result<Id> NextIdFinder<Id, Key>::ReserveId(Key key, Id id) {
CHECK(!next_id_called_) << "ReserveId cannot be called after NextId";
@@ -282,8 +287,20 @@ bool IdAssignerContext::ReserveId(const ResourceName& name, ResourceId id,
// another type.
auto assign_result = type_id_finder_.ReserveId(key, id.type_id());
if (!assign_result.has_value()) {
- diag->Error(android::DiagMessage() << "can't assign ID " << id << " to resource " << name
- << " because type " << assign_result.error());
+ auto pre_assigned_type = type_id_finder_.pre_assigned_ids_[id.type_id()].type;
+ bool pre_assigned_type_staged =
+ non_staged_type_ids_.find(pre_assigned_type) == non_staged_type_ids_.end();
+ auto hex_type_id = fmt::format("{:#04x}", (int)id.type_id());
+ bool current_type_staged = visibility.staged_api;
+ diag->Error(android::DiagMessage()
+ << "can't assign type ID " << hex_type_id << " to "
+ << (current_type_staged ? "staged type " : "non staged type ") << name.type.type
+ << " because this type ID have been assigned to "
+ << (pre_assigned_type_staged ? "staged type " : "non staged type ")
+ << pre_assigned_type);
+ if (pre_assigned_type_staged || current_type_staged) {
+ diag->Error(android::DiagMessage() << staged_type_overlap_error);
+ }
return false;
}
type = types_.emplace(key, TypeGroup(package_id_, id.type_id())).first;
@@ -298,6 +315,20 @@ bool IdAssignerContext::ReserveId(const ResourceName& name, ResourceId id,
<< " because type already has ID " << std::hex << (int)id.type_id());
return false;
}
+ } else {
+ // Ensure that staged public resources cannot have the same type name and type id with
+ // non staged public resources.
+ auto non_staged_type = non_staged_type_ids_.find(name.type.type);
+ if (non_staged_type != non_staged_type_ids_.end() && non_staged_type->second == id.type_id()) {
+ diag->Error(
+ android::DiagMessage()
+ << "can`t assign type ID " << fmt::format("{:#04x}", (int)id.type_id())
+ << " to staged type " << name.type.type << " because type ID "
+ << fmt::format("{:#04x}", (int)id.type_id())
+ << " already has been assigned to a non staged resource type with the same type name");
+ diag->Error(android::DiagMessage() << staged_type_overlap_error);
+ return false;
+ }
}
auto assign_result = type->second.ReserveId(name, id);
diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp
index 8911dad39470..ce45b7c1df04 100644
--- a/tools/aapt2/compile/IdAssigner_test.cpp
+++ b/tools/aapt2/compile/IdAssigner_test.cpp
@@ -117,14 +117,28 @@ TEST_F(IdAssignerTests, FailWhenTypeHasTwoNonStagedIds) {
}
TEST_F(IdAssignerTests, FailWhenTypeHasTwoNonStagedIdsRegardlessOfStagedId) {
- auto table = test::ResourceTableBuilder()
- .AddSimple("android:attr/foo", ResourceId(0x01050000))
- .AddSimple("android:attr/bar", ResourceId(0x01ff0006))
- .Add(NewResourceBuilder("android:attr/staged_baz")
- .SetId(0x01ff0000)
- .SetVisibility({.staged_api = true})
- .Build())
- .Build();
+ auto table =
+ test::ResourceTableBuilder()
+ .AddSimple("android:attr/foo", ResourceId(0x01050000))
+ .AddSimple("android:attr/bar", ResourceId(0x01ff0006))
+ .Add(NewResourceBuilder("android:attr/staged_baz")
+ .SetId(0x01ff0000)
+ .SetVisibility({.staged_api = true, .level = Visibility::Level::kPublic})
+ .Build())
+ .Build();
+ IdAssigner assigner;
+ ASSERT_FALSE(assigner.Consume(context.get(), table.get()));
+}
+
+TEST_F(IdAssignerTests, FailWhenTypeHaveBothStagedAndNonStagedIds) {
+ auto table =
+ test::ResourceTableBuilder()
+ .AddSimple("android:attr/foo", ResourceId(0x01010000))
+ .Add(NewResourceBuilder("android:bool/staged_baz")
+ .SetId(0x01010001)
+ .SetVisibility({.staged_api = true, .level = Visibility::Level::kPublic})
+ .Build())
+ .Build();
IdAssigner assigner;
ASSERT_FALSE(assigner.Consume(context.get(), table.get()));
}
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index 09a8560f984a..8143052f4376 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -16,11 +16,15 @@
#include "compile/PseudolocaleGenerator.h"
+#include <stdint.h>
+
#include <algorithm>
+#include <random>
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
+#include "androidfw/ResourceTypes.h"
#include "androidfw/Util.h"
#include "compile/Pseudolocalizer.h"
#include "util/Util.h"
@@ -293,8 +297,85 @@ class Visitor : public ValueVisitor {
Pseudolocalizer localizer_;
};
+class GrammaticalGenderVisitor : public ValueVisitor {
+ public:
+ std::unique_ptr<Value> value;
+ std::unique_ptr<Item> item;
+
+ GrammaticalGenderVisitor(android::StringPool* pool, uint8_t grammaticalInflection)
+ : pool_(pool), grammaticalInflection_(grammaticalInflection) {
+ }
+
+ void Visit(Plural* plural) override {
+ CloningValueTransformer cloner(pool_);
+ std::unique_ptr<Plural> grammatical_gendered = util::make_unique<Plural>();
+ for (size_t i = 0; i < plural->values.size(); i++) {
+ if (plural->values[i]) {
+ GrammaticalGenderVisitor sub_visitor(pool_, grammaticalInflection_);
+ plural->values[i]->Accept(&sub_visitor);
+ if (sub_visitor.item) {
+ grammatical_gendered->values[i] = std::move(sub_visitor.item);
+ } else {
+ grammatical_gendered->values[i] = plural->values[i]->Transform(cloner);
+ }
+ }
+ }
+ grammatical_gendered->SetSource(plural->GetSource());
+ grammatical_gendered->SetWeak(true);
+ value = std::move(grammatical_gendered);
+ }
+
+ std::string AddGrammaticalGenderPrefix(const std::string_view& original_string) {
+ std::string result;
+ switch (grammaticalInflection_) {
+ case android::ResTable_config::GRAMMATICAL_GENDER_MASCULINE:
+ result = std::string("(M)") + std::string(original_string);
+ break;
+ case android::ResTable_config::GRAMMATICAL_GENDER_FEMININE:
+ result = std::string("(F)") + std::string(original_string);
+ break;
+ case android::ResTable_config::GRAMMATICAL_GENDER_NEUTER:
+ result = std::string("(N)") + std::string(original_string);
+ break;
+ default:
+ result = std::string(original_string);
+ break;
+ }
+ return result;
+ }
+
+ void Visit(String* string) override {
+ std::string prefixed_string = AddGrammaticalGenderPrefix(std::string(*string->value));
+ std::unique_ptr<String> grammatical_gendered =
+ util::make_unique<String>(pool_->MakeRef(prefixed_string));
+ grammatical_gendered->SetSource(string->GetSource());
+ grammatical_gendered->SetWeak(true);
+ item = std::move(grammatical_gendered);
+ }
+
+ void Visit(StyledString* string) override {
+ std::string prefixed_string = AddGrammaticalGenderPrefix(std::string(string->value->value));
+ android::StyleString new_string;
+ new_string.str = std::move(prefixed_string);
+ for (const android::StringPool::Span& span : string->value->spans) {
+ new_string.spans.emplace_back(android::Span{*span.name, span.first_char, span.last_char});
+ }
+ std::unique_ptr<StyledString> grammatical_gendered =
+ util::make_unique<StyledString>(pool_->MakeRef(new_string));
+ grammatical_gendered->SetSource(string->GetSource());
+ grammatical_gendered->SetWeak(true);
+ item = std::move(grammatical_gendered);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(GrammaticalGenderVisitor);
+ android::StringPool* pool_;
+ uint8_t grammaticalInflection_;
+};
+
ConfigDescription ModifyConfigForPseudoLocale(const ConfigDescription& base,
- Pseudolocalizer::Method m) {
+ Pseudolocalizer::Method m,
+ uint8_t grammaticalInflection) {
ConfigDescription modified = base;
switch (m) {
case Pseudolocalizer::Method::kAccent:
@@ -313,12 +394,64 @@ ConfigDescription ModifyConfigForPseudoLocale(const ConfigDescription& base,
default:
break;
}
+ modified.grammaticalInflection = grammaticalInflection;
return modified;
}
+void GrammaticalGender(ResourceConfigValue* original_value,
+ ResourceConfigValue* localized_config_value, android::StringPool* pool,
+ ResourceEntry* entry, const Pseudolocalizer::Method method,
+ uint8_t grammaticalInflection) {
+ GrammaticalGenderVisitor visitor(pool, grammaticalInflection);
+ localized_config_value->value->Accept(&visitor);
+
+ std::unique_ptr<Value> grammatical_gendered_value;
+ if (visitor.value) {
+ grammatical_gendered_value = std::move(visitor.value);
+ } else if (visitor.item) {
+ grammatical_gendered_value = std::move(visitor.item);
+ }
+ if (!grammatical_gendered_value) {
+ return;
+ }
+
+ ConfigDescription config =
+ ModifyConfigForPseudoLocale(original_value->config, method, grammaticalInflection);
+
+ ResourceConfigValue* grammatical_gendered_config_value =
+ entry->FindOrCreateValue(config, original_value->product);
+ if (!grammatical_gendered_config_value->value) {
+ // Only use auto-generated pseudo-localization if none is defined.
+ grammatical_gendered_config_value->value = std::move(grammatical_gendered_value);
+ }
+}
+
+const uint32_t MASK_MASCULINE = 1; // Bit mask for masculine
+const uint32_t MASK_FEMININE = 2; // Bit mask for feminine
+const uint32_t MASK_NEUTER = 4; // Bit mask for neuter
+
+void GrammaticalGenderIfNeeded(ResourceConfigValue* original_value, ResourceConfigValue* new_value,
+ android::StringPool* pool, ResourceEntry* entry,
+ const Pseudolocalizer::Method method, uint32_t gender_state) {
+ if (gender_state & MASK_FEMININE) {
+ GrammaticalGender(original_value, new_value, pool, entry, method,
+ android::ResTable_config::GRAMMATICAL_GENDER_FEMININE);
+ }
+
+ if (gender_state & MASK_MASCULINE) {
+ GrammaticalGender(original_value, new_value, pool, entry, method,
+ android::ResTable_config::GRAMMATICAL_GENDER_MASCULINE);
+ }
+
+ if (gender_state & MASK_NEUTER) {
+ GrammaticalGender(original_value, new_value, pool, entry, method,
+ android::ResTable_config::GRAMMATICAL_GENDER_NEUTER);
+ }
+}
+
void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
ResourceConfigValue* original_value, android::StringPool* pool,
- ResourceEntry* entry) {
+ ResourceEntry* entry, uint32_t gender_state, bool gender_flag) {
Visitor visitor(pool, method);
original_value->value->Accept(&visitor);
@@ -333,8 +466,8 @@ void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
return;
}
- ConfigDescription config_with_accent =
- ModifyConfigForPseudoLocale(original_value->config, method);
+ ConfigDescription config_with_accent = ModifyConfigForPseudoLocale(
+ original_value->config, method, android::ResTable_config::GRAMMATICAL_GENDER_ANY);
ResourceConfigValue* new_config_value =
entry->FindOrCreateValue(config_with_accent, original_value->product);
@@ -342,6 +475,9 @@ void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
// Only use auto-generated pseudo-localization if none is defined.
new_config_value->value = std::move(localized_value);
}
+ if (gender_flag) {
+ GrammaticalGenderIfNeeded(original_value, new_config_value, pool, entry, method, gender_state);
+ }
}
// A value is pseudolocalizable if it does not define a locale (or is the default locale) and is
@@ -356,16 +492,71 @@ static bool IsPseudolocalizable(ResourceConfigValue* config_value) {
} // namespace
+bool ParseGenderValuesAndSaveState(const std::string& grammatical_gender_values,
+ uint32_t* gender_state, android::IDiagnostics* diag) {
+ std::vector<std::string> values = util::SplitAndLowercase(grammatical_gender_values, ',');
+ for (size_t i = 0; i < values.size(); i++) {
+ if (values[i].length() != 0) {
+ if (values[i] == "f") {
+ *gender_state |= MASK_FEMININE;
+ } else if (values[i] == "m") {
+ *gender_state |= MASK_MASCULINE;
+ } else if (values[i] == "n") {
+ *gender_state |= MASK_NEUTER;
+ } else {
+ diag->Error(android::DiagMessage() << "Invalid grammatical gender value: " << values[i]);
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool ParseGenderRatio(const std::string& grammatical_gender_ratio, float* gender_ratio,
+ android::IDiagnostics* diag) {
+ const char* input = grammatical_gender_ratio.c_str();
+ char* endPtr;
+ errno = 0;
+ *gender_ratio = strtof(input, &endPtr);
+ if (endPtr == input || *endPtr != '\0' || errno == ERANGE || *gender_ratio < 0 ||
+ *gender_ratio > 1) {
+ diag->Error(android::DiagMessage()
+ << "Invalid grammatical gender ratio: " << grammatical_gender_ratio
+ << ", must be a real number between 0 and 1");
+ return false;
+ }
+ return true;
+}
+
bool PseudolocaleGenerator::Consume(IAaptContext* context, ResourceTable* table) {
+ uint32_t gender_state = 0;
+ if (!ParseGenderValuesAndSaveState(grammatical_gender_values_, &gender_state,
+ context->GetDiagnostics())) {
+ return false;
+ }
+
+ float gender_ratio = 0;
+ if (!ParseGenderRatio(grammatical_gender_ratio_, &gender_ratio, context->GetDiagnostics())) {
+ return false;
+ }
+
+ std::random_device rd;
+ std::mt19937 gen(rd());
+ std::uniform_real_distribution<> distrib(0.0, 1.0);
+
for (auto& package : table->packages) {
for (auto& type : package->types) {
for (auto& entry : type->entries) {
+ bool gender_flag = false;
+ if (distrib(gen) < gender_ratio) {
+ gender_flag = true;
+ }
std::vector<ResourceConfigValue*> values = entry->FindValuesIf(IsPseudolocalizable);
for (ResourceConfigValue* value : values) {
PseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, &table->string_pool,
- entry.get());
+ entry.get(), gender_state, gender_flag);
PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, &table->string_pool,
- entry.get());
+ entry.get(), gender_state, gender_flag);
}
}
}
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.h b/tools/aapt2/compile/PseudolocaleGenerator.h
index 44e6e3e86f92..ce92008cdba1 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.h
+++ b/tools/aapt2/compile/PseudolocaleGenerator.h
@@ -27,8 +27,19 @@ std::unique_ptr<StyledString> PseudolocalizeStyledString(StyledString* string,
Pseudolocalizer::Method method,
android::StringPool* pool);
-struct PseudolocaleGenerator : public IResourceTableConsumer {
- bool Consume(IAaptContext* context, ResourceTable* table) override;
+class PseudolocaleGenerator : public IResourceTableConsumer {
+ public:
+ explicit PseudolocaleGenerator(std::string grammatical_gender_values,
+ std::string grammatical_gender_ratio)
+ : grammatical_gender_values_(std::move(grammatical_gender_values)),
+ grammatical_gender_ratio_(std::move(grammatical_gender_ratio)) {
+ }
+
+ bool Consume(IAaptContext* context, ResourceTable* table);
+
+ private:
+ std::string grammatical_gender_values_;
+ std::string grammatical_gender_ratio_;
};
} // namespace aapt
diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
index 2f90cbf722c2..1477ebf8473d 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
@@ -197,7 +197,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) {
val->SetTranslatable(false);
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
- PseudolocaleGenerator generator;
+ PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
ASSERT_TRUE(generator.Consume(context.get(), table.get()));
// Normal pseudolocalization should take place.
@@ -249,7 +249,7 @@ TEST(PseudolocaleGeneratorTest, PluralsArePseudolocalized) {
expected->values = {util::make_unique<String>(table->string_pool.MakeRef("[žéŕö one]")),
util::make_unique<String>(table->string_pool.MakeRef("[öñé one]"))};
- PseudolocaleGenerator generator;
+ PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
ASSERT_TRUE(generator.Consume(context.get(), table.get()));
const auto* actual = test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo",
@@ -287,7 +287,7 @@ TEST(PseudolocaleGeneratorTest, RespectUntranslateableSections) {
context->GetDiagnostics()));
}
- PseudolocaleGenerator generator;
+ PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
ASSERT_TRUE(generator.Consume(context.get(), table.get()));
StyledString* new_styled_string = test::GetValueForConfig<StyledString>(
@@ -305,4 +305,213 @@ TEST(PseudolocaleGeneratorTest, RespectUntranslateableSections) {
EXPECT_NE(std::string::npos, new_string->value->find("world"));
}
+TEST(PseudolocaleGeneratorTest, PseudolocalizeGrammaticalGenderForString) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
+ ASSERT_TRUE(generator.Consume(context.get(), table.get()));
+
+ String* locale = test::GetValueForConfig<String>(table.get(), "android:string/foo",
+ test::ParseConfigOrDie("en-rXA"));
+ ASSERT_NE(nullptr, locale);
+
+ // Grammatical gendered string
+ auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine");
+ config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+ String* feminine =
+ test::GetValueForConfig<String>(table.get(), "android:string/foo", config_feminine);
+ ASSERT_NE(nullptr, feminine);
+ EXPECT_EQ(std::string("(F)") + *locale->value, *feminine->value);
+
+ auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine");
+ config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+ String* masculine =
+ test::GetValueForConfig<String>(table.get(), "android:string/foo", config_masculine);
+ ASSERT_NE(nullptr, masculine);
+ EXPECT_EQ(std::string("(M)") + *locale->value, *masculine->value);
+
+ auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter");
+ config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+ String* neuter =
+ test::GetValueForConfig<String>(table.get(), "android:string/foo", config_neuter);
+ ASSERT_NE(nullptr, neuter);
+ EXPECT_EQ(std::string("(N)") + *locale->value, *neuter->value);
+}
+
+TEST(PseudolocaleGeneratorTest, PseudolocalizeGrammaticalGenderForPlural) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder().Build();
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ plural->values = {util::make_unique<String>(table->string_pool.MakeRef("zero")),
+ util::make_unique<String>(table->string_pool.MakeRef("one"))};
+ ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.pkg:plurals/foo"))
+ .SetValue(std::move(plural))
+ .Build(),
+ context->GetDiagnostics()));
+ PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
+ ASSERT_TRUE(generator.Consume(context.get(), table.get()));
+
+ Plural* actual = test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo",
+ test::ParseConfigOrDie("en-rXA"));
+ ASSERT_NE(nullptr, actual);
+
+ // Grammatical gendered Plural
+ auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine");
+ config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+ Plural* actual_feminine =
+ test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo", config_feminine);
+ for (size_t i = 0; i < actual->values.size(); i++) {
+ if (actual->values[i]) {
+ String* locale = ValueCast<String>(actual->values[i].get());
+ String* feminine = ValueCast<String>(actual_feminine->values[i].get());
+ EXPECT_EQ(std::string("(F)") + *locale->value, *feminine->value);
+ }
+ }
+
+ auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine");
+ config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+ Plural* actual_masculine =
+ test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo", config_masculine);
+ ASSERT_NE(nullptr, actual_masculine);
+ for (size_t i = 0; i < actual->values.size(); i++) {
+ if (actual->values[i]) {
+ String* locale = ValueCast<String>(actual->values[i].get());
+ String* masculine = ValueCast<String>(actual_masculine->values[i].get());
+ EXPECT_EQ(std::string("(M)") + *locale->value, *masculine->value);
+ }
+ }
+
+ auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter");
+ config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+ Plural* actual_neuter =
+ test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo", config_neuter);
+ for (size_t i = 0; i < actual->values.size(); i++) {
+ if (actual->values[i]) {
+ String* locale = ValueCast<String>(actual->values[i].get());
+ String* neuter = ValueCast<String>(actual_neuter->values[i].get());
+ EXPECT_EQ(std::string("(N)") + *locale->value, *neuter->value);
+ }
+ }
+}
+
+TEST(PseudolocaleGeneratorTest, PseudolocalizeGrammaticalGenderForStyledString) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder().Build();
+ android::StyleString original_style;
+ original_style.str = "Hello world!";
+ original_style.spans = {android::Span{"i", 1, 10}};
+
+ std::unique_ptr<StyledString> original =
+ util::make_unique<StyledString>(table->string_pool.MakeRef(original_style));
+ ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/foo"))
+ .SetValue(std::move(original))
+ .Build(),
+ context->GetDiagnostics()));
+ PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
+ ASSERT_TRUE(generator.Consume(context.get(), table.get()));
+
+ StyledString* locale = test::GetValueForConfig<StyledString>(table.get(), "android:string/foo",
+ test::ParseConfigOrDie("en-rXA"));
+ ASSERT_NE(nullptr, locale);
+ EXPECT_EQ(1, locale->value->spans.size());
+ EXPECT_EQ(std::string("i"), *locale->value->spans[0].name);
+
+ // Grammatical gendered StyledString
+ auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine");
+ config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+ StyledString* feminine =
+ test::GetValueForConfig<StyledString>(table.get(), "android:string/foo", config_feminine);
+ ASSERT_NE(nullptr, feminine);
+ EXPECT_EQ(1, feminine->value->spans.size());
+ EXPECT_EQ(std::string("i"), *feminine->value->spans[0].name);
+ EXPECT_EQ(std::string("(F)") + locale->value->value, feminine->value->value);
+
+ auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine");
+ config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+ StyledString* masculine =
+ test::GetValueForConfig<StyledString>(table.get(), "android:string/foo", config_masculine);
+ ASSERT_NE(nullptr, masculine);
+ EXPECT_EQ(1, masculine->value->spans.size());
+ EXPECT_EQ(std::string("i"), *masculine->value->spans[0].name);
+ EXPECT_EQ(std::string("(M)") + locale->value->value, masculine->value->value);
+
+ auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter");
+ config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+ StyledString* neuter =
+ test::GetValueForConfig<StyledString>(table.get(), "android:string/foo", config_neuter);
+ ASSERT_NE(nullptr, neuter);
+ EXPECT_EQ(1, neuter->value->spans.size());
+ EXPECT_EQ(std::string("i"), *neuter->value->spans[0].name);
+ EXPECT_EQ(std::string("(N)") + locale->value->value, neuter->value->value);
+}
+
+TEST(PseudolocaleGeneratorTest, GrammaticalGenderForCertainValues) {
+ // single gender value
+ std::unique_ptr<ResourceTable> table_0 =
+ test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build();
+
+ std::unique_ptr<IAaptContext> context_0 = test::ContextBuilder().Build();
+ PseudolocaleGenerator generator_0(std::string("f"), std::string("1.0"));
+ ASSERT_TRUE(generator_0.Consume(context_0.get(), table_0.get()));
+
+ String* locale_0 = test::GetValueForConfig<String>(table_0.get(), "android:string/foo",
+ test::ParseConfigOrDie("en-rXA"));
+ ASSERT_NE(nullptr, locale_0);
+
+ auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine");
+ config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+ String* feminine_0 =
+ test::GetValueForConfig<String>(table_0.get(), "android:string/foo", config_feminine);
+ ASSERT_NE(nullptr, feminine_0);
+ EXPECT_EQ(std::string("(F)") + *locale_0->value, *feminine_0->value);
+
+ auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine");
+ config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+ String* masculine_0 =
+ test::GetValueForConfig<String>(table_0.get(), "android:string/foo", config_masculine);
+ EXPECT_EQ(nullptr, masculine_0);
+
+ auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter");
+ config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+ String* neuter_0 =
+ test::GetValueForConfig<String>(table_0.get(), "android:string/foo", config_neuter);
+ EXPECT_EQ(nullptr, neuter_0);
+
+ // multiple gender values
+ std::unique_ptr<ResourceTable> table_1 =
+ test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build();
+
+ std::unique_ptr<IAaptContext> context_1 = test::ContextBuilder().Build();
+ PseudolocaleGenerator generator_1(std::string("f,n"), std::string("1.0"));
+ ASSERT_TRUE(generator_1.Consume(context_1.get(), table_1.get()));
+
+ String* locale_1 = test::GetValueForConfig<String>(table_1.get(), "android:string/foo",
+ test::ParseConfigOrDie("en-rXA"));
+ ASSERT_NE(nullptr, locale_1);
+
+ String* feminine_1 =
+ test::GetValueForConfig<String>(table_1.get(), "android:string/foo", config_feminine);
+ ASSERT_NE(nullptr, feminine_1);
+ EXPECT_EQ(std::string("(F)") + *locale_1->value, *feminine_1->value);
+
+ String* masculine_1 =
+ test::GetValueForConfig<String>(table_1.get(), "android:string/foo", config_masculine);
+ EXPECT_EQ(nullptr, masculine_1);
+
+ String* neuter_1 =
+ test::GetValueForConfig<String>(table_1.get(), "android:string/foo", config_neuter);
+ ASSERT_NE(nullptr, neuter_1);
+ EXPECT_EQ(std::string("(N)") + *locale_1->value, *neuter_1->value);
+
+ // invalid gender value
+ std::unique_ptr<ResourceTable> table_2 =
+ test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build();
+
+ std::unique_ptr<IAaptContext> context_2 = test::ContextBuilder().Build();
+ PseudolocaleGenerator generator_2(std::string("invald,"), std::string("1.0"));
+ ASSERT_FALSE(generator_2.Consume(context_2.get(), table_2.get()));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index a43bf1b60f42..6bf265d2e363 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -837,9 +837,9 @@ class UsesSdkBadging : public ManifestExtractor::Element {
void Print(text::Printer* printer) override {
if (min_sdk) {
- printer->Print(StringPrintf("sdkVersion:'%d'\n", *min_sdk));
+ printer->Print(StringPrintf("minSdkVersion:'%d'\n", *min_sdk));
} else if (min_sdk_name) {
- printer->Print(StringPrintf("sdkVersion:'%s'\n", min_sdk_name->data()));
+ printer->Print(StringPrintf("minSdkVersion:'%s'\n", min_sdk_name->data()));
}
if (max_sdk) {
printer->Print(StringPrintf("maxSdkVersion:'%d'\n", *max_sdk));
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 8c594ba553a0..f05611048caa 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -68,9 +68,8 @@ struct OverlayableChunk {
class PackageFlattener {
public:
PackageFlattener(IAaptContext* context, const ResourceTablePackageView& package,
- const std::map<size_t, std::string>* shared_libs,
- SparseEntriesMode sparse_entries,
- bool compact_entries,
+ const ResourceTable::ReferencedPackages* shared_libs,
+ SparseEntriesMode sparse_entries, bool compact_entries,
bool collapse_key_stringpool,
const std::set<ResourceName>& name_collapse_exemptions,
bool deduplicate_entry_values)
@@ -145,10 +144,9 @@ class PackageFlattener {
// 2) the entries will be accessed on platforms U+, and
// 3) all entry keys can be encoded in 16 bits
bool UseCompactEntries(const ConfigDescription& config, std::vector<FlatEntry>* entries) const {
- return compact_entries_ &&
- (context_->GetMinSdkVersion() > SDK_TIRAMISU || config.sdkVersion > SDK_TIRAMISU) &&
- std::none_of(entries->cbegin(), entries->cend(),
- [](const auto& e) { return e.entry_key >= std::numeric_limits<uint16_t>::max(); });
+ return compact_entries_ && context_->GetMinSdkVersion() > SDK_TIRAMISU &&
+ std::none_of(entries->cbegin(), entries->cend(),
+ [](const auto& e) { return e.entry_key >= std::numeric_limits<uint16_t>::max(); });
}
std::unique_ptr<ResEntryWriter> GetResEntryWriter(bool dedup, bool compact, BigBuffer* buffer) {
@@ -548,7 +546,7 @@ class PackageFlattener {
IAaptContext* context_;
android::IDiagnostics* diag_;
const ResourceTablePackageView package_;
- const std::map<size_t, std::string>* shared_libs_;
+ const ResourceTable::ReferencedPackages* shared_libs_;
SparseEntriesMode sparse_entries_;
bool compact_entries_;
android::StringPool type_pool_;
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 09ef9bddd3bd..e1a3013c07bb 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -916,7 +916,7 @@ std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
} break;
case pb::Primitive::kIntDecimalValue: {
val.dataType = android::Res_value::TYPE_INT_DEC;
- val.data = static_cast<uint32_t>(pb_prim.int_decimal_value());
+ val.data = static_cast<int32_t>(pb_prim.int_decimal_value());
} break;
case pb::Primitive::kIntHexadecimalValue: {
val.dataType = android::Res_value::TYPE_INT_HEX;
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index afb83562b129..fa8860ff69eb 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -250,6 +250,7 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
}
TEST(ProtoSerializeTest, SerializeAndDeserializeXml) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
xml::Element element;
element.line_number = 22;
element.column_number = 23;
@@ -269,8 +270,8 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeXml) {
attr.namespace_uri = xml::kSchemaAndroid;
attr.value = "23dp";
attr.compiled_attribute = xml::AaptAttribute(Attribute{}, ResourceId(0x01010000));
- attr.compiled_value =
- ResourceUtils::TryParseItemForAttribute(attr.value, android::ResTable_map::TYPE_DIMENSION);
+ attr.compiled_value = ResourceUtils::TryParseItemForAttribute(
+ context->GetDiagnostics(), attr.value, android::ResTable_map::TYPE_DIMENSION);
attr.compiled_value->SetSource(android::Source().WithLine(25));
element.attributes.push_back(std::move(attr));
diff --git a/tools/aapt2/integration-tests/DumpTest/built_with_aapt_expected.txt b/tools/aapt2/integration-tests/DumpTest/built_with_aapt_expected.txt
index cc0b3bf5d2fb..d14e5fb5c477 100644
--- a/tools/aapt2/integration-tests/DumpTest/built_with_aapt_expected.txt
+++ b/tools/aapt2/integration-tests/DumpTest/built_with_aapt_expected.txt
@@ -1,5 +1,5 @@
package: name='com.aapt.app' versionCode='222' versionName='222' platformBuildVersionName='12' platformBuildVersionCode='32' compileSdkVersion='32' compileSdkVersionCodename='12'
-sdkVersion:'22'
+minSdkVersion:'22'
targetSdkVersion:'32'
application: label='App' icon=''
feature-group: label=''
diff --git a/tools/aapt2/integration-tests/DumpTest/components_expected.txt b/tools/aapt2/integration-tests/DumpTest/components_expected.txt
index 9c81fb83ca15..93cce0a29024 100644
--- a/tools/aapt2/integration-tests/DumpTest/components_expected.txt
+++ b/tools/aapt2/integration-tests/DumpTest/components_expected.txt
@@ -1,5 +1,5 @@
package: name='com.example.bundletool.minimal' versionCode='1' versionName='1.0' platformBuildVersionName='12' platformBuildVersionCode='31' compileSdkVersion='31' compileSdkVersionCodename='12'
-sdkVersion:'21'
+minSdkVersion:'21'
targetSdkVersion:'31'
uses-configuration: reqTouchScreen='3' reqKeyboardType='2' reqHardKeyboard='-1' reqNavigation='3' reqFiveWayNav='-1'
supports-gl-texture:'GL_OES_compressed_paletted_texture'
diff --git a/tools/aapt2/integration-tests/DumpTest/minimal_expected.txt b/tools/aapt2/integration-tests/DumpTest/minimal_expected.txt
index 85ab5d80cd39..aafca68443a9 100644
--- a/tools/aapt2/integration-tests/DumpTest/minimal_expected.txt
+++ b/tools/aapt2/integration-tests/DumpTest/minimal_expected.txt
@@ -1,5 +1,5 @@
package: name='com.lato.bubblegirl' versionCode='33' versionName='1.0.0' platformBuildVersionName='8.1.0' platformBuildVersionCode='27'
-sdkVersion:'19'
+minSdkVersion:'19'
targetSdkVersion:'26'
application-label:'Bubble Girl'
application-label-ar:'Bubble Girl'
diff --git a/tools/aapt2/integration-tests/DumpTest/multiple_uses_sdk_expected.txt b/tools/aapt2/integration-tests/DumpTest/multiple_uses_sdk_expected.txt
index 85e8d0a3cbba..f48f381e3012 100644
--- a/tools/aapt2/integration-tests/DumpTest/multiple_uses_sdk_expected.txt
+++ b/tools/aapt2/integration-tests/DumpTest/multiple_uses_sdk_expected.txt
@@ -1,5 +1,5 @@
package: name='com.test.e17wmultiapknexus' versionCode='107' versionName='14' platformBuildVersionName='2.3.3' platformBuildVersionCode='10'
-sdkVersion:'1'
+minSdkVersion:'1'
application-label:'w45wmultiapknexus_10'
application-icon-120:'res/drawable-ldpi-v4/icon.png'
application-icon-160:'res/drawable-mdpi-v4/icon.png'
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 9dadfb26a3f8..c69b32513167 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -164,8 +164,8 @@ std::unique_ptr<Item> ReferenceLinkerTransformer::TransformItem(const Reference*
std::unique_ptr<Item> ReferenceLinkerTransformer::ParseValueWithAttribute(
std::unique_ptr<Item> value, const Attribute* attr) {
if (RawString* raw_string = ValueCast<RawString>(value.get())) {
- std::unique_ptr<Item> transformed =
- ResourceUtils::TryParseItemForAttribute(*raw_string->value, attr);
+ std::unique_ptr<Item> transformed = ResourceUtils::TryParseItemForAttribute(
+ context_->GetDiagnostics(), *raw_string->value, attr);
// If we could not parse as any specific type, try a basic STRING.
if (!transformed && (attr->type_mask & android::ResTable_map::TYPE_STRING)) {
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index d2e9bd770a31..aec7ceb82c0f 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -90,7 +90,8 @@ class XmlVisitor : public xml::PackageAwareVisitor {
attribute = &attr.compiled_attribute.value().attribute;
}
- attr.compiled_value = ResourceUtils::TryParseItemForAttribute(attr.value, attribute);
+ attr.compiled_value = ResourceUtils::TryParseItemForAttribute(context_->GetDiagnostics(),
+ attr.value, attribute);
if (attr.compiled_value) {
// With a compiledValue, we must resolve the reference and assign it an ID.
attr.compiled_value->SetSource(source);
diff --git a/tools/aapt2/optimize/Obfuscator.cpp b/tools/aapt2/optimize/Obfuscator.cpp
index 8f12f735736e..903cdf852566 100644
--- a/tools/aapt2/optimize/Obfuscator.cpp
+++ b/tools/aapt2/optimize/Obfuscator.cpp
@@ -40,9 +40,9 @@ Obfuscator::Obfuscator(OptimizeOptions& optimizeOptions)
collapse_key_stringpool_(optimizeOptions.table_flattener_options.collapse_key_stringpool) {
}
-std::string ShortenFileName(android::StringPiece file_path, int output_length) {
+std::string Obfuscator::ShortenFileName(android::StringPiece file_path, int output_length) {
std::size_t hash_num = std::hash<android::StringPiece>{}(file_path);
- std::string result = "";
+ std::string result;
// Convert to (modified) base64 so that it is a proper file path.
for (int i = 0; i < output_length; i++) {
uint8_t sextet = hash_num & 0x3f;
@@ -52,10 +52,33 @@ std::string ShortenFileName(android::StringPiece file_path, int output_length) {
return result;
}
+static std::string RenameDisallowedFileNames(const std::string& file_name) {
+ // We are renaming shortened file names to make sure they not a reserved file name in Windows.
+ // See: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file. We are renaming
+ // "COM" and "LPT" too because we are appending a number in case of hash collisions; "COM1",
+ // "COM2", etc. are reserved names.
+ static const char* const reserved_windows_names[] = {"CON", "PRN", "AUX", "NUL", "COM", "LPT"};
+ if (file_name.length() == 3) {
+ // Need to convert the file name to uppercase as Windows is case insensitive. E.g., "NuL",
+ // "nul", and "NUl" are also reserved.
+ std::string result_upper_cased(3, 0);
+ std::transform(file_name.begin(), file_name.end(), result_upper_cased.begin(),
+ [](unsigned char c) { return std::toupper(c); });
+ for (auto reserved_windows_name : reserved_windows_names) {
+ if (result_upper_cased == reserved_windows_name) {
+ // Simple solution to make it a non-reserved name is to add an underscore
+ return "_" + file_name;
+ }
+ }
+ }
+
+ return file_name;
+}
+
// Return the optimal hash length such that at most 10% of resources collide in
// their shortened path.
// Reference: http://matt.might.net/articles/counting-hash-collisions/
-int OptimalShortenedLength(int num_resources) {
+static int OptimalShortenedLength(int num_resources) {
if (num_resources > 4000) {
return 3;
} else {
@@ -63,8 +86,8 @@ int OptimalShortenedLength(int num_resources) {
}
}
-std::string GetShortenedPath(android::StringPiece shortened_filename,
- android::StringPiece extension, int collision_count) {
+static std::string GetShortenedPath(android::StringPiece shortened_filename,
+ android::StringPiece extension, int collision_count) {
std::string shortened_path = std::string("res/") += shortened_filename;
if (collision_count > 0) {
shortened_path += std::to_string(collision_count);
@@ -82,9 +105,9 @@ struct PathComparator {
}
};
-static bool HandleShortenFilePaths(ResourceTable* table,
- std::map<std::string, std::string>& shortened_path_map,
- const std::set<ResourceName>& path_shorten_exemptions) {
+bool Obfuscator::HandleShortenFilePaths(ResourceTable* table,
+ std::map<std::string, std::string>& shortened_path_map,
+ const std::set<ResourceName>& path_shorten_exemptions) {
// used to detect collisions
std::unordered_set<std::string> shortened_paths;
std::set<FileReference*, PathComparator> file_refs;
@@ -112,7 +135,8 @@ static bool HandleShortenFilePaths(ResourceTable* table,
// Android detects ColorStateLists via pathname, skip res/color*
if (util::StartsWith(res_subdir, "res/color")) continue;
- std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars);
+ std::string shortened_filename =
+ RenameDisallowedFileNames(ShortenFileName(*file_ref->path, num_chars));
int collision_count = 0;
std::string shortened_path = GetShortenedPath(shortened_filename, extension, collision_count);
while (shortened_paths.find(shortened_path) != shortened_paths.end()) {
diff --git a/tools/aapt2/optimize/Obfuscator.h b/tools/aapt2/optimize/Obfuscator.h
index 5ccf54383aae..79d7e088d1cc 100644
--- a/tools/aapt2/optimize/Obfuscator.h
+++ b/tools/aapt2/optimize/Obfuscator.h
@@ -53,7 +53,14 @@ class Obfuscator : public IResourceTableConsumer {
const ResourceNamedType& type_name, const ResourceTableEntryView& entry,
const android::base::function_ref<void(Result, const ResourceName&)> onObfuscate);
+ protected:
+ virtual std::string ShortenFileName(android::StringPiece file_path, int output_length);
+
private:
+ bool HandleShortenFilePaths(ResourceTable* table,
+ std::map<std::string, std::string>& shortened_path_map,
+ const std::set<ResourceName>& path_shorten_exemptions);
+
TableFlattenerOptions& options_;
const bool shorten_resource_paths_;
const bool collapse_key_stringpool_;
diff --git a/tools/aapt2/optimize/Obfuscator_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp
index 940cf1096f92..c3429e0fc1d6 100644
--- a/tools/aapt2/optimize/Obfuscator_test.cpp
+++ b/tools/aapt2/optimize/Obfuscator_test.cpp
@@ -19,6 +19,7 @@
#include <map>
#include <memory>
#include <string>
+#include <utility>
#include "ResourceTable.h"
#include "android-base/file.h"
@@ -26,6 +27,7 @@
using ::aapt::test::GetValue;
using ::testing::AnyOf;
+using ::testing::Contains;
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::IsFalse;
@@ -33,6 +35,10 @@ using ::testing::IsTrue;
using ::testing::Not;
using ::testing::NotNull;
+namespace aapt {
+
+namespace {
+
android::StringPiece GetExtension(android::StringPiece path) {
auto iter = std::find(path.begin(), path.end(), '.');
return android::StringPiece(iter, path.end() - iter);
@@ -45,7 +51,22 @@ void FillTable(aapt::test::ResourceTableBuilder& builder, int start, int end) {
}
}
-namespace aapt {
+class FakeObfuscator : public Obfuscator {
+ public:
+ explicit FakeObfuscator(OptimizeOptions& optimize_options,
+ const std::unordered_map<std::string, std::string>& shortened_name_map)
+ : Obfuscator(optimize_options), shortened_name_map_(shortened_name_map) {
+ }
+
+ protected:
+ std::string ShortenFileName(android::StringPiece file_path, int output_length) override {
+ return shortened_name_map_[std::string(file_path)];
+ }
+
+ private:
+ std::unordered_map<std::string, std::string> shortened_name_map_;
+ DISALLOW_COPY_AND_ASSIGN(FakeObfuscator);
+};
TEST(ObfuscatorTest, FileRefPathsChangedInResourceTable) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
@@ -127,7 +148,7 @@ TEST(ObfuscatorTest, SkipPathShortenExemptions) {
EXPECT_THAT(path_map.find("res/drawables/xmlfile2.xml"), Not(Eq(path_map.end())));
FileReference* ref = GetValue<FileReference>(table.get(), "android:drawable/xmlfile");
- EXPECT_THAT(ref, NotNull());
+ ASSERT_THAT(ref, NotNull());
ASSERT_THAT(HasFailure(), IsFalse());
// The path of first drawable in exemption was not changed
EXPECT_THAT("res/drawables/xmlfile.xml", Eq(*ref->path));
@@ -161,13 +182,78 @@ TEST(ObfuscatorTest, KeepExtensions) {
ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end())));
ASSERT_THAT(path_map.find("res/drawable/pngfile.png"), Not(Eq(path_map.end())));
- auto shortend_xml_path = path_map[original_xml_path];
- auto shortend_png_path = path_map[original_png_path];
-
EXPECT_THAT(GetExtension(path_map[original_xml_path]), Eq(android::StringPiece(".xml")));
EXPECT_THAT(GetExtension(path_map[original_png_path]), Eq(android::StringPiece(".png")));
}
+TEST(ObfuscatorTest, ShortenedToReservedWindowsNames) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ std::string original_path_1 = "res/drawable/pngfile_1.png";
+ std::string original_path_2 = "res/drawable/pngfile_2.png";
+ std::string original_path_3 = "res/drawable/pngfile_3.png";
+ std::string original_path_4 = "res/drawable/pngfile_4.png";
+ std::string original_path_5 = "res/drawable/pngfile_5.png";
+ std::string original_path_6 = "res/drawable/pngfile_6.png";
+ std::string original_path_7 = "res/drawable/pngfile_7.png";
+ std::string original_path_8 = "res/drawable/pngfile_8.png";
+ std::string original_path_9 = "res/drawable/pngfile_9.png";
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddFileReference("android:drawable/pngfile_1", original_path_1)
+ .AddFileReference("android:drawable/pngfile_2", original_path_2)
+ .AddFileReference("android:drawable/pngfile_3", original_path_3)
+ .AddFileReference("android:drawable/pngfile_4", original_path_4)
+ .AddFileReference("android:drawable/pngfile_5", original_path_5)
+ .AddFileReference("android:drawable/pngfile_6", original_path_6)
+ .AddFileReference("android:drawable/pngfile_7", original_path_7)
+ .AddFileReference("android:drawable/pngfile_8", original_path_8)
+ .AddFileReference("android:drawable/pngfile_9", original_path_9)
+ .Build();
+
+ OptimizeOptions options{.shorten_resource_paths = true};
+ std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+ auto obfuscator = FakeObfuscator(
+ options,
+ {
+ {original_path_1, "CON"},
+ {original_path_2, "Prn"},
+ {original_path_3, "AuX"},
+ {original_path_4, "nul"},
+ {original_path_5, "cOM"},
+ {original_path_6, "lPt"},
+ {original_path_7, "lPt"},
+ {original_path_8, "lPt"}, // 6, 7, and 8 will be appended with a number to disambiguate
+ {original_path_9, "F0o"}, // This one is not reserved
+ });
+ ASSERT_TRUE(obfuscator.Consume(context.get(), table.get()));
+
+ // Expect that the path map is populated
+ ASSERT_THAT(path_map.find(original_path_1), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_2), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_3), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_4), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_5), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_6), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_7), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_8), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_9), Not(Eq(path_map.end())));
+
+ EXPECT_THAT(path_map[original_path_1], Eq("res/_CON.png"));
+ EXPECT_THAT(path_map[original_path_2], Eq("res/_Prn.png"));
+ EXPECT_THAT(path_map[original_path_3], Eq("res/_AuX.png"));
+ EXPECT_THAT(path_map[original_path_4], Eq("res/_nul.png"));
+ EXPECT_THAT(path_map[original_path_5], Eq("res/_cOM.png"));
+ EXPECT_THAT(path_map[original_path_9], Eq("res/F0o.png"));
+
+ std::set<std::string> lpt_shortened_names{path_map[original_path_6], path_map[original_path_7],
+ path_map[original_path_8]};
+ EXPECT_THAT(lpt_shortened_names, Contains("res/_lPt.png"));
+ EXPECT_THAT(lpt_shortened_names, Contains("res/_lPt1.png"));
+ EXPECT_THAT(lpt_shortened_names, Contains("res/_lPt2.png"));
+}
+
TEST(ObfuscatorTest, DeterministicallyHandleCollisions) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
@@ -247,7 +333,9 @@ TEST(ObfuscatorTest, DumpIdResourceMap) {
ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
// Expect that the id resource name map is populated
+ ASSERT_THAT(id_resource_map.find(0x7f020000), Not(Eq(id_resource_map.end())));
EXPECT_THAT(id_resource_map.at(0x7f020000), Eq("mycolor"));
+ ASSERT_THAT(id_resource_map.find(0x7f030000), Not(Eq(id_resource_map.end())));
EXPECT_THAT(id_resource_map.at(0x7f030000), Eq("mystring"));
EXPECT_THAT(id_resource_map.find(0x7f030001), Eq(id_resource_map.end()));
EXPECT_THAT(id_resource_map.find(0x7f030002), Eq(id_resource_map.end()));
@@ -300,17 +388,18 @@ TEST(ObfuscatorTest, WriteObfuscationMapInProtocolBufferFormat) {
ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(),
getProtocolBufferTableUnderTest().get()));
- obfuscator.WriteObfuscationMap("obfuscated_map.pb");
+ const auto map_path = testing::TempDir() + "/obfuscated_map.pb";
+ ASSERT_TRUE(obfuscator.WriteObfuscationMap(map_path));
std::string pbOut;
- android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */);
+ ASSERT_TRUE(android::base::ReadFileToString(map_path, &pbOut, false /* follow_symlinks */));
EXPECT_THAT(pbOut, HasSubstr("drawable/xmlfile.xml"));
EXPECT_THAT(pbOut, HasSubstr("drawable/pngfile.png"));
EXPECT_THAT(pbOut, HasSubstr("mycolor"));
EXPECT_THAT(pbOut, HasSubstr("mystring"));
pb::ResourceMappings resourceMappings;
- EXPECT_THAT(resourceMappings.ParseFromString(pbOut), IsTrue());
- EXPECT_THAT(resourceMappings.collapsed_names().resource_names_size(), Eq(2));
+ ASSERT_THAT(resourceMappings.ParseFromString(pbOut), IsTrue());
+ ASSERT_THAT(resourceMappings.collapsed_names().resource_names_size(), Eq(2));
auto& resource_names = resourceMappings.collapsed_names().resource_names();
EXPECT_THAT(resource_names.at(0).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
EXPECT_THAT(resource_names.at(1).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
@@ -328,11 +417,14 @@ TEST(ObfuscatorTest, WriteObfuscatingMapWithNonEnabledOption) {
ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(),
getProtocolBufferTableUnderTest().get()));
- obfuscator.WriteObfuscationMap("obfuscated_map.pb");
+ const auto map_path = testing::TempDir() + "/obfuscated_map.pb";
+ ASSERT_TRUE(obfuscator.WriteObfuscationMap(map_path));
std::string pbOut;
- android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */);
+ ASSERT_TRUE(android::base::ReadFileToString(map_path, &pbOut, false /* follow_symlinks */));
ASSERT_THAT(pbOut, Eq(""));
}
+} // namespace
+
} // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index bca62da447b0..d78baf9ffeb4 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -220,15 +220,9 @@ std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName(
bool AssetManagerSymbolSource::AddAssetPath(StringPiece path) {
TRACE_CALL();
- if (std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path.data())) {
+ if (auto apk = ApkAssets::Load(path.data())) {
apk_assets_.push_back(std::move(apk));
-
- std::vector<const ApkAssets*> apk_assets;
- for (const std::unique_ptr<const ApkAssets>& apk_asset : apk_assets_) {
- apk_assets.push_back(apk_asset.get());
- }
-
- asset_manager_.SetApkAssets(apk_assets);
+ asset_manager_.SetApkAssets(apk_assets_);
return true;
}
return false;
@@ -251,7 +245,7 @@ bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId,
return true;
}
- for (const std::unique_ptr<const ApkAssets>& assets : apk_assets_) {
+ for (auto&& assets : apk_assets_) {
for (const std::unique_ptr<const android::LoadedPackage>& loaded_package
: assets->GetLoadedArsc()->GetPackages()) {
if (package_name == loaded_package->GetPackageName() && loaded_package->IsDynamic()) {
@@ -266,10 +260,11 @@ bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId,
static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable(
android::AssetManager2& am, ResourceId id) {
using namespace android;
- if (am.GetApkAssets().empty()) {
+ if (am.GetApkAssetsCount() == 0) {
return {};
}
+ auto op = am.StartOperation();
auto bag_result = am.GetBag(id.id);
if (!bag_result.has_value()) {
return nullptr;
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index b09ff702ca58..36eb0bab6046 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -207,8 +207,8 @@ class AssetManagerSymbolSource : public ISymbolSource {
}
private:
+ std::vector<android::AssetManager2::ApkAssetsPtr> apk_assets_;
android::AssetManager2 asset_manager_;
- std::vector<std::unique_ptr<const android::ApkAssets>> apk_assets_;
DISALLOW_COPY_AND_ASSIGN(AssetManagerSymbolSource);
};
diff --git a/tools/aapt2/trace/TraceBuffer.cpp b/tools/aapt2/trace/TraceBuffer.cpp
index da5373936306..0988c313b65b 100644
--- a/tools/aapt2/trace/TraceBuffer.cpp
+++ b/tools/aapt2/trace/TraceBuffer.cpp
@@ -36,116 +36,142 @@ constexpr char kBegin = 'B';
constexpr char kEnd = 'E';
struct TracePoint {
+ char type;
pid_t tid;
int64_t time;
std::string tag;
- char type;
};
std::vector<TracePoint> traces;
+bool enabled = true;
+constinit std::chrono::steady_clock::time_point startTime = {};
int64_t GetTime() noexcept {
auto now = std::chrono::steady_clock::now();
- return std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()).count();
+ if (startTime == decltype(tracebuffer::startTime){}) {
+ startTime = now;
+ }
+ return std::chrono::duration_cast<std::chrono::microseconds>(now - startTime).count();
}
-} // namespace anonymous
-
-void AddWithTime(const std::string& tag, char type, int64_t time) noexcept {
- TracePoint t = {getpid(), time, tag, type};
- traces.emplace_back(t);
+void AddWithTime(std::string tag, char type, int64_t time) noexcept {
+ TracePoint t = {type, getpid(), time, std::move(tag)};
+ traces.emplace_back(std::move(t));
}
-void Add(const std::string& tag, char type) noexcept {
- AddWithTime(tag, type, GetTime());
+void Add(std::string tag, char type) noexcept {
+ AddWithTime(std::move(tag), type, GetTime());
}
-
-
-
void Flush(const std::string& basePath) {
- TRACE_CALL();
if (basePath.empty()) {
return;
}
+ BeginTrace(__func__); // We can't do much here, only record that it happened.
- std::stringstream s;
+ std::ostringstream s;
s << basePath << aapt::file::sDirSep << "report_aapt2_" << getpid() << ".json";
FILE* f = android::base::utf8::fopen(s.str().c_str(), "a");
if (f == nullptr) {
return;
}
- for(const TracePoint& trace : traces) {
- fprintf(f, "{\"ts\" : \"%" PRIu64 "\", \"ph\" : \"%c\", \"tid\" : \"%d\" , \"pid\" : \"%d\", "
- "\"name\" : \"%s\" },\n", trace.time, trace.type, 0, trace.tid, trace.tag.c_str());
+ // Wrap the trace in a JSON array [] to make Chrome/Perfetto UI handle it.
+ char delimiter = '[';
+ for (const TracePoint& trace : traces) {
+ fprintf(f,
+ "%c{\"ts\" : \"%" PRIu64
+ "\", \"ph\" : \"%c\", \"tid\" : \"%d\" , \"pid\" : \"%d\", \"name\" : \"%s\" }\n",
+ delimiter, trace.time, trace.type, 0, trace.tid, trace.tag.c_str());
+ delimiter = ',';
+ }
+ if (!traces.empty()) {
+ fprintf(f, "]");
}
fclose(f);
traces.clear();
}
+} // namespace
+
} // namespace tracebuffer
-void BeginTrace(const std::string& tag) {
- tracebuffer::Add(tag, tracebuffer::kBegin);
+void BeginTrace(std::string tag) {
+ if (!tracebuffer::enabled) return;
+ tracebuffer::Add(std::move(tag), tracebuffer::kBegin);
+}
+
+void EndTrace(std::string tag) {
+ if (!tracebuffer::enabled) return;
+ tracebuffer::Add(std::move(tag), tracebuffer::kEnd);
+}
+
+bool Trace::enable(bool value) {
+ return tracebuffer::enabled = value;
}
-void EndTrace() {
- tracebuffer::Add("", tracebuffer::kEnd);
+Trace::Trace(const char* tag) {
+ if (!tracebuffer::enabled) return;
+ tag_.assign(tag);
+ tracebuffer::Add(tag_, tracebuffer::kBegin);
}
-Trace::Trace(const std::string& tag) {
- tracebuffer::Add(tag, tracebuffer::kBegin);
+Trace::Trace(std::string tag) : tag_(std::move(tag)) {
+ if (!tracebuffer::enabled) return;
+ tracebuffer::Add(tag_, tracebuffer::kBegin);
}
-Trace::Trace(const std::string& tag, const std::vector<android::StringPiece>& args) {
- std::stringstream s;
+template <class SpanOfStrings>
+std::string makeTag(std::string_view tag, const SpanOfStrings& args) {
+ std::ostringstream s;
s << tag;
- s << " ";
- for (auto& arg : args) {
- s << arg;
- s << " ";
+ if (!args.empty()) {
+ for (const auto& arg : args) {
+ s << ' ';
+ s << arg;
+ }
}
- tracebuffer::Add(s.str(), tracebuffer::kBegin);
+ return std::move(s).str();
+}
+
+Trace::Trace(std::string_view tag, const std::vector<android::StringPiece>& args) {
+ if (!tracebuffer::enabled) return;
+ tag_ = makeTag(tag, args);
+ tracebuffer::Add(tag_, tracebuffer::kBegin);
}
Trace::~Trace() {
- tracebuffer::Add("", tracebuffer::kEnd);
+ if (!tracebuffer::enabled) return;
+ tracebuffer::Add(std::move(tag_), tracebuffer::kEnd);
}
-FlushTrace::FlushTrace(const std::string& basepath, const std::string& tag)
- : basepath_(basepath) {
- tracebuffer::Add(tag, tracebuffer::kBegin);
+FlushTrace::FlushTrace(std::string_view basepath, std::string_view tag) {
+ if (!Trace::enable(!basepath.empty())) return;
+ basepath_.assign(basepath);
+ tag_.assign(tag);
+ tracebuffer::Add(tag_, tracebuffer::kBegin);
}
-FlushTrace::FlushTrace(const std::string& basepath, const std::string& tag,
- const std::vector<android::StringPiece>& args) : basepath_(basepath) {
- std::stringstream s;
- s << tag;
- s << " ";
- for (auto& arg : args) {
- s << arg;
- s << " ";
- }
- tracebuffer::Add(s.str(), tracebuffer::kBegin);
+FlushTrace::FlushTrace(std::string_view basepath, std::string_view tag,
+ const std::vector<android::StringPiece>& args) {
+ if (!Trace::enable(!basepath.empty())) return;
+ basepath_.assign(basepath);
+ tag_ = makeTag(tag, args);
+ tracebuffer::Add(tag_, tracebuffer::kBegin);
}
-FlushTrace::FlushTrace(const std::string& basepath, const std::string& tag,
- const std::vector<std::string>& args) : basepath_(basepath){
- std::stringstream s;
- s << tag;
- s << " ";
- for (auto& arg : args) {
- s << arg;
- s << " ";
- }
- tracebuffer::Add(s.str(), tracebuffer::kBegin);
+FlushTrace::FlushTrace(std::string_view basepath, std::string_view tag,
+ const std::vector<std::string>& args) {
+ if (!Trace::enable(!basepath.empty())) return;
+ basepath_.assign(basepath);
+ tag_ = makeTag(tag, args);
+ tracebuffer::Add(tag_, tracebuffer::kBegin);
}
FlushTrace::~FlushTrace() {
- tracebuffer::Add("", tracebuffer::kEnd);
+ if (!tracebuffer::enabled) return;
+ tracebuffer::Add(std::move(tag_), tracebuffer::kEnd);
tracebuffer::Flush(basepath_);
}
-} // namespace aapt
-
+} // namespace aapt
diff --git a/tools/aapt2/trace/TraceBuffer.h b/tools/aapt2/trace/TraceBuffer.h
index ba751dd72f41..f0333d1fe071 100644
--- a/tools/aapt2/trace/TraceBuffer.h
+++ b/tools/aapt2/trace/TraceBuffer.h
@@ -17,41 +17,50 @@
#ifndef AAPT_TRACEBUFFER_H
#define AAPT_TRACEBUFFER_H
+#include <androidfw/StringPiece.h>
+
#include <string>
+#include <string_view>
#include <vector>
-#include <androidfw/StringPiece.h>
-
namespace aapt {
// Record timestamps for beginning and end of a task and generate systrace json fragments.
// This is an in-process ftrace which has the advantage of being platform independent.
// These methods are NOT thread-safe since aapt2 is not multi-threaded.
-// Convenience RIAA object to automatically finish an event when object goes out of scope.
+// Convenience RAII object to automatically finish an event when object goes out of scope.
class Trace {
public:
- Trace(const std::string& tag);
- Trace(const std::string& tag, const std::vector<android::StringPiece>& args);
- ~Trace();
+ Trace(const char* tag);
+ Trace(std::string tag);
+ Trace(std::string_view tag, const std::vector<android::StringPiece>& args);
+ ~Trace();
+
+ static bool enable(bool value = true);
+
+private:
+ std::string tag_;
};
// Manual markers.
-void BeginTrace(const std::string& tag);
-void EndTrace();
+void BeginTrace(std::string tag);
+void EndTrace(std::string tag);
// A main trace is required to flush events to disk. Events are formatted in systrace
// json format.
class FlushTrace {
public:
- explicit FlushTrace(const std::string& basepath, const std::string& tag);
- explicit FlushTrace(const std::string& basepath, const std::string& tag,
- const std::vector<android::StringPiece>& args);
- explicit FlushTrace(const std::string& basepath, const std::string& tag,
- const std::vector<std::string>& args);
- ~FlushTrace();
+ explicit FlushTrace(std::string_view basepath, std::string_view tag);
+ explicit FlushTrace(std::string_view basepath, std::string_view tag,
+ const std::vector<android::StringPiece>& args);
+ explicit FlushTrace(std::string_view basepath, std::string_view tag,
+ const std::vector<std::string>& args);
+ ~FlushTrace();
+
private:
std::string basepath_;
+ std::string tag_;
};
#define TRACE_CALL() Trace __t(__func__)
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index be877660ef72..3d83caf29bba 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -21,6 +21,7 @@
#include <string>
#include <vector>
+#include "android-base/parseint.h"
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
#include "androidfw/BigBuffer.h"
@@ -229,14 +230,29 @@ std::string GetToolFingerprint() {
static const char* const sMinorVersion = "19";
// The build id of aapt2 binary.
- static std::string sBuildId = android::build::GetBuildNumber();
-
- if (android::base::StartsWith(sBuildId, "eng.")) {
- time_t now = time(0);
- tm* ltm = localtime(&now);
+ static const std::string sBuildId = [] {
+ std::string buildNumber = android::build::GetBuildNumber();
+
+ if (android::base::StartsWith(buildNumber, "eng.")) {
+ // android::build::GetBuildNumber() returns something like "eng.user.20230725.214219" where
+ // the latter two parts are "yyyyMMdd.HHmmss" at build time. Use "yyyyMM" in the fingerprint.
+ std::vector<std::string> parts = util::Split(buildNumber, '.');
+ int buildYear;
+ int buildMonth;
+ if (parts.size() < 3 || parts[2].length() < 6 ||
+ !android::base::ParseInt(parts[2].substr(0, 4), &buildYear) ||
+ !android::base::ParseInt(parts[2].substr(4, 2), &buildMonth)) {
+ // Fallback to localtime() if GetBuildNumber() returns an unexpected output.
+ time_t now = time(0);
+ tm* ltm = localtime(&now);
+ buildYear = 1900 + ltm->tm_year;
+ buildMonth = 1 + ltm->tm_mon;
+ }
- sBuildId = android::base::StringPrintf("eng.%d%d", 1900 + ltm->tm_year, 1 + ltm->tm_mon);
- }
+ buildNumber = android::base::StringPrintf("eng.%04d%02d", buildYear, buildMonth);
+ }
+ return buildNumber;
+ }();
return android::base::StringPrintf("%s.%s-%s", sMajorVersion, sMinorVersion, sBuildId.c_str());
}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
index e03d92ab44a0..f1727b78f135 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
@@ -30,6 +30,7 @@ const val BINDER_CLASS = "android.os.Binder"
const val IINTERFACE_INTERFACE = "android.os.IInterface"
const val AIDL_PERMISSION_HELPER_SUFFIX = "_enforcePermission"
+const val PERMISSION_PREFIX_LITERAL = "android.permission."
/**
* If a non java (e.g. c++) backend is enabled, the @EnforcePermission
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index 0baac2c7aacf..3a95df9b2773 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -40,6 +40,8 @@ import org.jetbrains.uast.UElement
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.toUElement
+import java.util.EnumSet
+
/**
* Lint Detector that ensures that any method overriding a method annotated
* with @EnforcePermission is also annotated with the exact same annotation.
@@ -93,7 +95,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
val v1 = ConstantEvaluator.evaluate(context, value1)
val v2 = ConstantEvaluator.evaluate(context, value2)
if (v1 != null && v2 != null) {
- if (v1 != v2) {
+ if (v1 != v2 && !isOneShortPermissionOfOther(v1, v2)) {
return false
}
} else {
@@ -105,7 +107,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
for (j in children1.indices) {
val c1 = ConstantEvaluator.evaluate(context, children1[j])
val c2 = ConstantEvaluator.evaluate(context, children2[j])
- if (c1 != c2) {
+ if (c1 != c2 && !isOneShortPermissionOfOther(c1, c2)) {
return false
}
}
@@ -114,6 +116,12 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
return true
}
+ private fun isOneShortPermissionOfOther(
+ permission1: Any?,
+ permission2: Any?
+ ): Boolean = permission1 == (permission2 as? String)?.removePrefix(PERMISSION_PREFIX_LITERAL) ||
+ permission2 == (permission1 as? String)?.removePrefix(PERMISSION_PREFIX_LITERAL)
+
private fun compareMethods(
context: JavaContext,
element: UElement,
@@ -206,7 +214,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
severity = Severity.ERROR,
implementation = Implementation(
EnforcePermissionDetector::class.java,
- Scope.JAVA_FILE_SCOPE
+ EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
)
)
@@ -219,7 +227,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
severity = Severity.ERROR,
implementation = Implementation(
EnforcePermissionDetector::class.java,
- Scope.JAVA_FILE_SCOPE
+ EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
)
)
}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
index df13af516514..cbbf91b49ded 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
@@ -34,6 +34,8 @@ import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.skipParenthesizedExprDown
+import java.util.EnumSet
+
class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
override fun getApplicableUastTypes(): List<Class<out UElement?>> =
listOf(UMethod::class.java)
@@ -117,7 +119,7 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
severity = Severity.ERROR,
implementation = Implementation(
EnforcePermissionHelperDetector::class.java,
- Scope.JAVA_FILE_SCOPE
+ EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
)
)
@@ -130,7 +132,7 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
severity = Severity.ERROR,
implementation = Implementation(
EnforcePermissionDetector::class.java,
- Scope.JAVA_FILE_SCOPE
+ EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
)
)
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
index c7be36efd991..22f749eee36b 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
@@ -53,10 +53,9 @@ class SimpleManualPermissionEnforcementDetector : AidlImplementationDetector() {
lintFix
)
- // TODO(b/265014041): turn on errors once all code that would cause one is fixed
- // if (enforcePermissionFix.errorLevel) {
- // incident.overrideSeverity(Severity.ERROR)
- // }
+ if (enforcePermissionFix.errorLevel) {
+ incident.overrideSeverity(Severity.ERROR)
+ }
context.report(incident)
}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
index 75b00737a168..b3dacbdf57a6 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -316,20 +316,55 @@ class EnforcePermissionDetectorTest : LintDetectorTest() {
overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same annotation must be used on Default.testMethod [MissingEnforcePermissionAnnotation]
public void testMethod() {}
~~~~~~~~~~
- 1 errors, 0 warnings
+ 1 errors, 0 warnings
""".addLineContinuation()
)
}
- fun testDoesDetectIssuesShortStringsNotAllowed() {
+ fun testDoesNotDetectIssuesShortStringsAllowedInChildAndParent() {
lint().files(java(
"""
package test.pkg;
import android.annotation.EnforcePermission;
public class TestClass121 extends IFooMethod.Stub {
@Override
+ @EnforcePermission("READ_PHONE_STATE")
+ public void testMethod() {}
+ @Override
+ @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+ public void testMethodParentShortPermission() {}
+ @Override
@EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
public void testMethodAnyLiteral() {}
+ @Override
+ @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+ public void testMethodAnyLiteralParentsShortPermission() {}
+ }
+ """).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ fun testDoesDetectIssuesWrongShortStringsInChildAndParent() {
+ lint().files(java(
+ """
+ package test.pkg;
+ import android.annotation.EnforcePermission;
+ public class TestClass121 extends IFooMethod.Stub {
+ @Override
+ @EnforcePermission("READ_WRONG_PHONE_STATE")
+ public void testMethod() {}
+ @Override
+ @EnforcePermission(android.Manifest.permission.READ_WRONG_PHONE_STATE)
+ public void testMethodParentShortPermission() {}
+ @Override
+ @EnforcePermission(anyOf={"WRONG_INTERNET", "READ_PHONE_STATE"})
+ public void testMethodAnyLiteral() {}
+ @Override
+ @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_WRONG_PHONE_STATE})
+ public void testMethodAnyLiteralParentsShortPermission() {}
}
""").indented(),
*stubs
@@ -337,14 +372,19 @@ class EnforcePermissionDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/test/pkg/TestClass121.java:6: Error: The method \
- TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) \
- which differs from the overridden method Stub.testMethodAnyLiteral: \
- @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
- The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
- public void testMethodAnyLiteral() {}
- ~~~~~~~~~~~~~~~~~~~~
- 1 errors, 0 warnings
+ src/test/pkg/TestClass121.java:6: Error: The method TestClass121.testMethod is annotated with @EnforcePermission("READ_WRONG_PHONE_STATE") which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethod() {}
+ ~~~~~~~~~~
+ src/test/pkg/TestClass121.java:9: Error: The method TestClass121.testMethodParentShortPermission is annotated with @EnforcePermission(android.Manifest.permission.READ_WRONG_PHONE_STATE) which differs from the overridden method Stub.testMethodParentShortPermission: @android.annotation.EnforcePermission("READ_PHONE_STATE"). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethodParentShortPermission() {}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass121.java:12: Error: The method TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"WRONG_INTERNET", "READ_PHONE_STATE"}) which differs from the overridden method Stub.testMethodAnyLiteral: @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethodAnyLiteral() {}
+ ~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass121.java:15: Error: The method TestClass121.testMethodAnyLiteralParentsShortPermission is annotated with @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_WRONG_PHONE_STATE}) which differs from the overridden method Stub.testMethodAnyLiteralParentsShortPermission: @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethodAnyLiteralParentsShortPermission() {}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 4 errors, 0 warnings
""".addLineContinuation()
)
}
@@ -360,12 +400,18 @@ class EnforcePermissionDetectorTest : LintDetectorTest() {
@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
public void testMethod() {}
@Override
+ @android.annotation.EnforcePermission("READ_PHONE_STATE")
+ public void testMethodParentShortPermission() {}
+ @Override
@android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
public void testMethodAny() {}
@Override
@android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
public void testMethodAnyLiteral() {}
@Override
+ @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
+ public void testMethodAnyLiteralParentsShortPermission() {}
+ @Override
@android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
public void testMethodAll() {}
@Override
@@ -374,10 +420,14 @@ class EnforcePermissionDetectorTest : LintDetectorTest() {
}
@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
public void testMethod();
+ @android.annotation.EnforcePermission("READ_PHONE_STATE")
+ public void testMethodParentShortPermission();
@android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
public void testMethodAny() {}
@android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
public void testMethodAnyLiteral() {}
+ @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
+ public void testMethodAnyLiteralParentsShortPermission() {}
@android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
public void testMethodAll() {}
@android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
@@ -404,6 +454,7 @@ class EnforcePermissionDetectorTest : LintDetectorTest() {
package android.Manifest;
class permission {
public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+ public static final String READ_WRONG_PHONE_STATE = "android.permission.READ_WRONG_PHONE_STATE";
public static final String NFC = "android.permission.NFC";
public static final String INTERNET = "android.permission.INTERNET";
}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
index 6b8e72cf9222..9170e752934f 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
@@ -51,10 +51,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:7: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ src/Foo.java:7: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 0 errors, 1 warnings
+ 1 errors, 0 warnings
"""
)
.expectFixDiffs(
@@ -168,10 +168,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:8: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ src/Foo.java:8: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
mContext.enforceCallingOrSelfPermission(
^
- 0 errors, 1 warnings
+ 1 errors, 0 warnings
"""
)
.expectFixDiffs(
@@ -209,10 +209,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:8: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ src/Foo.java:8: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 0 errors, 1 warnings
+ 1 errors, 0 warnings
"""
)
.expectFixDiffs(
@@ -252,10 +252,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:10: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ src/Foo.java:10: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
mContext.enforceCallingOrSelfPermission(
^
- 0 errors, 1 warnings
+ 1 errors, 0 warnings
"""
)
.expectFixDiffs(
@@ -414,10 +414,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:14: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ src/Foo.java:14: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
helper();
~~~~~~~~~
- 0 errors, 1 warnings
+ 1 errors, 0 warnings
"""
)
.expectFixDiffs(
@@ -506,10 +506,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:16: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ src/Foo.java:16: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
mContext.enforceCallingOrSelfPermission("FOO", "foo");
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 0 errors, 1 warnings
+ 1 errors, 0 warnings
"""
)
.expectFixDiffs(
@@ -558,10 +558,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:19: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ src/Foo.java:19: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
helperHelper();
~~~~~~~~~~~~~~~
- 0 errors, 1 warnings
+ 1 errors, 0 warnings
"""
)
.expectFixDiffs(
@@ -599,10 +599,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:7: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ src/Foo.java:7: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
if (mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo")
^
- 0 errors, 1 warnings
+ 1 errors, 0 warnings
"""
)
.expectFixDiffs(
diff --git a/tools/streaming_proto/OWNERS b/tools/streaming_proto/OWNERS
new file mode 100644
index 000000000000..5f6e59f82c8a
--- /dev/null
+++ b/tools/streaming_proto/OWNERS
@@ -0,0 +1 @@
+mwachens@google.com
diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp
index fe9a438d81d7..905ed354049b 100644
--- a/tools/streaming_proto/cpp/main.cpp
+++ b/tools/streaming_proto/cpp/main.cpp
@@ -119,9 +119,8 @@ write_message(stringstream& text, const DescriptorProto& message, const string&
text << endl;
}
-static void
-write_header_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
+static void write_header_file(const string& request_parameter, CodeGeneratorResponse* response,
+ const FileDescriptorProto& file_descriptor) {
stringstream text;
text << "// Generated by protoc-gen-cppstream. DO NOT MODIFY." << endl;
@@ -159,6 +158,9 @@ write_header_file(CodeGeneratorResponse* response, const FileDescriptorProto& fi
text << endl;
text << "#endif // " << header << endl;
+ if (request_parameter.find("experimental_allow_proto3_optional") != string::npos) {
+ response->set_supported_features(CodeGeneratorResponse::FEATURE_PROTO3_OPTIONAL);
+ }
CodeGeneratorResponse::File* file_response = response->add_file();
file_response->set_name(make_filename(file_descriptor));
file_response->set_content(text.str());
@@ -182,7 +184,7 @@ int main(int argc, char const *argv[])
for (int i=0; i<N; i++) {
const FileDescriptorProto& file_descriptor = request.proto_file(i);
if (should_generate_for_file(request, file_descriptor.name())) {
- write_header_file(&response, file_descriptor);
+ write_header_file(request.parameter(), &response, file_descriptor);
}
}
diff --git a/tools/xmlpersistence/Android.bp b/tools/xmlpersistence/Android.bp
deleted file mode 100644
index 0b6dba626794..000000000000
--- a/tools/xmlpersistence/Android.bp
+++ /dev/null
@@ -1,20 +0,0 @@
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-java_binary_host {
- name: "xmlpersistence_cli",
- manifest: "manifest.txt",
- srcs: [
- "src/**/*.kt",
- ],
- static_libs: [
- "javaparser-symbol-solver",
- "javapoet",
- ],
-}
diff --git a/tools/xmlpersistence/OWNERS b/tools/xmlpersistence/OWNERS
deleted file mode 100644
index 4f4d06a32676..000000000000
--- a/tools/xmlpersistence/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-zhanghai@google.com
diff --git a/tools/xmlpersistence/manifest.txt b/tools/xmlpersistence/manifest.txt
deleted file mode 100644
index 6d9771998efc..000000000000
--- a/tools/xmlpersistence/manifest.txt
+++ /dev/null
@@ -1 +0,0 @@
-Main-class: MainKt
diff --git a/tools/xmlpersistence/src/main/kotlin/Generator.kt b/tools/xmlpersistence/src/main/kotlin/Generator.kt
deleted file mode 100644
index 8e62388c860f..000000000000
--- a/tools/xmlpersistence/src/main/kotlin/Generator.kt
+++ /dev/null
@@ -1,577 +0,0 @@
-/*
- * 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.
- */
-
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.JavaFile
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.NameAllocator
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.TypeSpec
-import java.io.File
-import java.io.FileInputStream
-import java.io.FileNotFoundException
-import java.io.FileOutputStream
-import java.io.IOException
-import java.nio.charset.StandardCharsets
-import java.time.Year
-import java.util.Objects
-import javax.lang.model.element.Modifier
-
-// JavaPoet only supports line comments, and can't add a newline after file level comments.
-val FILE_HEADER = """
- /*
- * Copyright (C) ${Year.now().value} 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.
- */
-
- // Generated by xmlpersistence. DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- // @formatter:off
-""".trimIndent() + "\n\n"
-
-private val atomicFileType = ClassName.get("android.util", "AtomicFile")
-
-fun generate(persistence: PersistenceInfo): JavaFile {
- val distinctClassFields = persistence.root.allClassFields.distinctBy { it.type }
- val type = TypeSpec.classBuilder(persistence.name)
- .addJavadoc(
- """
- Generated class implementing XML persistence for${'$'}W{@link $1T}.
- <p>
- This class provides atomicity for persistence via {@link $2T}, however it does not provide
- thread safety, so please bring your own synchronization mechanism.
- """.trimIndent(), persistence.root.type, atomicFileType
- )
- .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
- .addField(generateFileField())
- .addMethod(generateConstructor())
- .addMethod(generateReadMethod(persistence.root))
- .addMethod(generateParseMethod(persistence.root))
- .addMethods(distinctClassFields.map { generateParseClassMethod(it) })
- .addMethod(generateWriteMethod(persistence.root))
- .addMethod(generateSerializeMethod(persistence.root))
- .addMethods(distinctClassFields.map { generateSerializeClassMethod(it) })
- .addMethod(generateDeleteMethod())
- .build()
- return JavaFile.builder(persistence.root.type.packageName(), type)
- .skipJavaLangImports(true)
- .indent(" ")
- .build()
-}
-
-private val nonNullType = ClassName.get("android.annotation", "NonNull")
-
-private fun generateFileField(): FieldSpec =
- FieldSpec.builder(atomicFileType, "mFile", Modifier.PRIVATE, Modifier.FINAL)
- .addAnnotation(nonNullType)
- .build()
-
-private fun generateConstructor(): MethodSpec =
- MethodSpec.constructorBuilder()
- .addJavadoc(
- """
- Create an instance of this class.
-
- @param file the XML file for persistence
- """.trimIndent()
- )
- .addModifiers(Modifier.PUBLIC)
- .addParameter(
- ParameterSpec.builder(File::class.java, "file").addAnnotation(nonNullType).build()
- )
- .addStatement("mFile = new \$1T(file)", atomicFileType)
- .build()
-
-private val nullableType = ClassName.get("android.annotation", "Nullable")
-
-private val xmlPullParserType = ClassName.get("org.xmlpull.v1", "XmlPullParser")
-
-private val xmlType = ClassName.get("android.util", "Xml")
-
-private val xmlPullParserExceptionType = ClassName.get("org.xmlpull.v1", "XmlPullParserException")
-
-private fun generateReadMethod(rootField: ClassFieldInfo): MethodSpec =
- MethodSpec.methodBuilder("read")
- .addJavadoc(
- """
- Read${'$'}W{@link $1T}${'$'}Wfrom${'$'}Wthe${'$'}WXML${'$'}Wfile.
-
- @return the persisted${'$'}W{@link $1T},${'$'}Wor${'$'}W{@code null}${'$'}Wif${'$'}Wthe${'$'}WXML${'$'}Wfile${'$'}Wdoesn't${'$'}Wexist
- @throws IllegalArgumentException if an error occurred while reading
- """.trimIndent(), rootField.type
- )
- .addAnnotation(nullableType)
- .addModifiers(Modifier.PUBLIC)
- .returns(rootField.type)
- .addControlFlow("try (\$1T inputStream = mFile.openRead())", FileInputStream::class.java) {
- addStatement("final \$1T parser = \$2T.newPullParser()", xmlPullParserType, xmlType)
- addStatement("parser.setInput(inputStream, null)")
- addStatement("return parse(parser)")
- nextControlFlow("catch (\$1T e)", FileNotFoundException::class.java)
- addStatement("return null")
- nextControlFlow(
- "catch (\$1T | \$2T e)", IOException::class.java, xmlPullParserExceptionType
- )
- addStatement("throw new IllegalArgumentException(e)")
- }
- .build()
-
-private val ClassFieldInfo.allClassFields: List<ClassFieldInfo>
- get() =
- mutableListOf<ClassFieldInfo>().apply {
- this += this@allClassFields
- for (field in fields) {
- when (field) {
- is ClassFieldInfo -> this += field.allClassFields
- is ListFieldInfo -> this += field.element.allClassFields
- else -> {}
- }
- }
- }
-
-private fun generateParseMethod(rootField: ClassFieldInfo): MethodSpec =
- MethodSpec.methodBuilder("parse")
- .addAnnotation(nonNullType)
- .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
- .returns(rootField.type)
- .addParameter(
- ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build()
- )
- .addExceptions(listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType))
- .apply {
- addStatement("int type")
- addStatement("int depth")
- addStatement("int innerDepth = parser.getDepth() + 1")
- addControlFlow(
- "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W"
- + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))",
- xmlPullParserType
- ) {
- addControlFlow(
- "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType
- ) {
- addStatement("continue")
- }
- addControlFlow(
- "if (\$1T.equals(parser.getName(),\$W\$2S))", Objects::class.java,
- rootField.tagName
- ) {
- addStatement("return \$1L(parser)", rootField.parseMethodName)
- }
- }
- addStatement(
- "throw new IllegalArgumentException(\$1S)",
- "Missing root tag <${rootField.tagName}>"
- )
- }
- .build()
-
-private fun generateParseClassMethod(classField: ClassFieldInfo): MethodSpec =
- MethodSpec.methodBuilder(classField.parseMethodName)
- .addAnnotation(nonNullType)
- .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
- .returns(classField.type)
- .addParameter(
- ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build()
- )
- .apply {
- val (attributeFields, tagFields) = classField.fields
- .partition { it is PrimitiveFieldInfo || it is StringFieldInfo }
- if (tagFields.isNotEmpty()) {
- addExceptions(
- listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType)
- )
- }
- val nameAllocator = NameAllocator().apply {
- newName("parser")
- newName("type")
- newName("depth")
- newName("innerDepth")
- }
- for (field in attributeFields) {
- val variableName = nameAllocator.newName(field.variableName, field)
- when (field) {
- is PrimitiveFieldInfo -> {
- val stringVariableName =
- nameAllocator.newName("${field.variableName}String")
- addStatement(
- "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)",
- stringVariableName, field.attributeName
- )
- if (field.isRequired) {
- addControlFlow("if (\$1L == null)", stringVariableName) {
- addStatement(
- "throw new IllegalArgumentException(\$1S)",
- "Missing attribute \"${field.attributeName}\""
- )
- }
- }
- val boxedType = field.type.box()
- val parseTypeMethodName = if (field.type.isPrimitive) {
- "parse${field.type.toString().capitalize()}"
- } else {
- "valueOf"
- }
- if (field.isRequired) {
- addStatement(
- "final \$1T \$2L =\$W\$3T.\$4L($5L)", field.type, variableName,
- boxedType, parseTypeMethodName, stringVariableName
- )
- } else {
- addStatement(
- "final \$1T \$2L =\$W$3L != null ?\$W\$4T.\$5L($3L)\$W: null",
- field.type, variableName, stringVariableName, boxedType,
- parseTypeMethodName
- )
- }
- }
- is StringFieldInfo ->
- addStatement(
- "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)",
- variableName, field.attributeName
- )
- else -> error(field)
- }
- }
- if (tagFields.isNotEmpty()) {
- for (field in tagFields) {
- val variableName = nameAllocator.newName(field.variableName, field)
- when (field) {
- is ClassFieldInfo ->
- addStatement("\$1T \$2L =\$Wnull", field.type, variableName)
- is ListFieldInfo ->
- addStatement(
- "final \$1T \$2L =\$Wnew \$3T<>()", field.type, variableName,
- ArrayList::class.java
- )
- else -> error(field)
- }
- }
- addStatement("int type")
- addStatement("int depth")
- addStatement("int innerDepth = parser.getDepth() + 1")
- addControlFlow(
- "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W"
- + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))",
- xmlPullParserType
- ) {
- addControlFlow(
- "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType
- ) {
- addStatement("continue")
- }
- addControlFlow("switch (parser.getName())") {
- for (field in tagFields) {
- addControlFlow("case \$1S:", field.tagName) {
- val variableName = nameAllocator.get(field)
- when (field) {
- is ClassFieldInfo -> {
- addControlFlow("if (\$1L != null)", variableName) {
- addStatement(
- "throw new IllegalArgumentException(\$1S)",
- "Duplicate tag \"${field.tagName}\""
- )
- }
- addStatement(
- "\$1L =\$W\$2L(parser)", variableName,
- field.parseMethodName
- )
- addStatement("break")
- }
- is ListFieldInfo -> {
- val elementNameAllocator = nameAllocator.clone()
- val elementVariableName = elementNameAllocator.newName(
- field.element.xmlName!!.toLowerCamelCase()
- )
- addStatement(
- "final \$1T \$2L =\$W\$3L(parser)", field.element.type,
- elementVariableName, field.element.parseMethodName
- )
- addStatement(
- "\$1L.add(\$2L)", variableName, elementVariableName
- )
- addStatement("break")
- }
- else -> error(field)
- }
- }
- }
- }
- }
- }
- for (field in tagFields.filter { it is ClassFieldInfo && it.isRequired }) {
- addControlFlow("if ($1L == null)", nameAllocator.get(field)) {
- addStatement(
- "throw new IllegalArgumentException(\$1S)", "Missing tag <${field.tagName}>"
- )
- }
- }
- addStatement(
- classField.fields.joinToString(",\$W", "return new \$1T(", ")") {
- nameAllocator.get(it)
- }, classField.type
- )
- }
- .build()
-
-private val ClassFieldInfo.parseMethodName: String
- get() = "parse${type.simpleName().toUpperCamelCase()}"
-
-private val xmlSerializerType = ClassName.get("org.xmlpull.v1", "XmlSerializer")
-
-private fun generateWriteMethod(rootField: ClassFieldInfo): MethodSpec =
- MethodSpec.methodBuilder("write")
- .apply {
- val nameAllocator = NameAllocator().apply {
- newName("outputStream")
- newName("serializer")
- }
- val parameterName = nameAllocator.newName(rootField.variableName)
- addJavadoc(
- """
- Write${'$'}W{@link $1T}${'$'}Wto${'$'}Wthe${'$'}WXML${'$'}Wfile.
-
- @param $2L the${'$'}W{@link ${'$'}1T}${'$'}Wto${'$'}Wpersist
- """.trimIndent(), rootField.type, parameterName
- )
- addAnnotation(nullableType)
- addModifiers(Modifier.PUBLIC)
- addParameter(
- ParameterSpec.builder(rootField.type, parameterName)
- .addAnnotation(nonNullType)
- .build()
- )
- addStatement("\$1T outputStream = null", FileOutputStream::class.java)
- addControlFlow("try") {
- addStatement("outputStream = mFile.startWrite()")
- addStatement(
- "final \$1T serializer =\$W\$2T.newSerializer()", xmlSerializerType, xmlType
- )
- addStatement(
- "serializer.setOutput(outputStream, \$1T.UTF_8.name())",
- StandardCharsets::class.java
- )
- addStatement(
- "serializer.setFeature(\$1S, true)",
- "http://xmlpull.org/v1/doc/features.html#indent-output"
- )
- addStatement("serializer.startDocument(null, true)")
- addStatement("serialize(serializer,\$W\$1L)", parameterName)
- addStatement("serializer.endDocument()")
- addStatement("mFile.finishWrite(outputStream)")
- nextControlFlow("catch (Exception e)")
- addStatement("e.printStackTrace()")
- addStatement("mFile.failWrite(outputStream)")
- }
- }
- .build()
-
-private fun generateSerializeMethod(rootField: ClassFieldInfo): MethodSpec =
- MethodSpec.methodBuilder("serialize")
- .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
- .addParameter(
- ParameterSpec.builder(xmlSerializerType, "serializer")
- .addAnnotation(nonNullType)
- .build()
- )
- .apply {
- val nameAllocator = NameAllocator().apply { newName("serializer") }
- val parameterName = nameAllocator.newName(rootField.variableName)
- addParameter(
- ParameterSpec.builder(rootField.type, parameterName)
- .addAnnotation(nonNullType)
- .build()
- )
- addException(IOException::class.java)
- addStatement("serializer.startTag(null, \$1S)", rootField.tagName)
- addStatement("\$1L(serializer, \$2L)", rootField.serializeMethodName, parameterName)
- addStatement("serializer.endTag(null, \$1S)", rootField.tagName)
- }
- .build()
-
-private fun generateSerializeClassMethod(classField: ClassFieldInfo): MethodSpec =
- MethodSpec.methodBuilder(classField.serializeMethodName)
- .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
- .addParameter(
- ParameterSpec.builder(xmlSerializerType, "serializer")
- .addAnnotation(nonNullType)
- .build()
- )
- .apply {
- val nameAllocator = NameAllocator().apply {
- newName("serializer")
- newName("i")
- }
- val parameterName = nameAllocator.newName(classField.serializeParameterName)
- addParameter(
- ParameterSpec.builder(classField.type, parameterName)
- .addAnnotation(nonNullType)
- .build()
- )
- addException(IOException::class.java)
- val (attributeFields, tagFields) = classField.fields
- .partition { it is PrimitiveFieldInfo || it is StringFieldInfo }
- for (field in attributeFields) {
- val variableName = "$parameterName.${field.name}"
- if (!field.isRequired) {
- beginControlFlow("if (\$1L != null)", variableName)
- }
- when (field) {
- is PrimitiveFieldInfo -> {
- if (field.isRequired && !field.type.isPrimitive) {
- addControlFlow("if (\$1L == null)", variableName) {
- addStatement(
- "throw new IllegalArgumentException(\$1S)",
- "Field \"${field.name}\" is null"
- )
- }
- }
- val stringVariableName =
- nameAllocator.newName("${field.variableName}String")
- addStatement(
- "final String \$1L =\$WString.valueOf(\$2L)", stringVariableName,
- variableName
- )
- addStatement(
- "serializer.attribute(null, \$1S, \$2L)", field.attributeName,
- stringVariableName
- )
- }
- is StringFieldInfo -> {
- if (field.isRequired) {
- addControlFlow("if (\$1L == null)", variableName) {
- addStatement(
- "throw new IllegalArgumentException(\$1S)",
- "Field \"${field.name}\" is null"
- )
- }
- }
- addStatement(
- "serializer.attribute(null, \$1S, \$2L)", field.attributeName,
- variableName
- )
- }
- else -> error(field)
- }
- if (!field.isRequired) {
- endControlFlow()
- }
- }
- for (field in tagFields) {
- val variableName = "$parameterName.${field.name}"
- if (field.isRequired) {
- addControlFlow("if (\$1L == null)", variableName) {
- addStatement(
- "throw new IllegalArgumentException(\$1S)",
- "Field \"${field.name}\" is null"
- )
- }
- }
- when (field) {
- is ClassFieldInfo -> {
- addStatement("serializer.startTag(null, \$1S)", field.tagName)
- addStatement(
- "\$1L(serializer, \$2L)", field.serializeMethodName, variableName
- )
- addStatement("serializer.endTag(null, \$1S)", field.tagName)
- }
- is ListFieldInfo -> {
- val sizeVariableName = nameAllocator.newName("${field.variableName}Size")
- addStatement(
- "final int \$1L =\$W\$2L.size()", sizeVariableName, variableName
- )
- addControlFlow("for (int i = 0;\$Wi < \$1L;\$Wi++)", sizeVariableName) {
- val elementNameAllocator = nameAllocator.clone()
- val elementVariableName = elementNameAllocator.newName(
- field.element.xmlName!!.toLowerCamelCase()
- )
- addStatement(
- "final \$1T \$2L =\$W\$3L.get(i)", field.element.type,
- elementVariableName, variableName
- )
- addControlFlow("if (\$1L == null)", elementVariableName) {
- addStatement(
- "throw new IllegalArgumentException(\$1S\$W+ i\$W+ \$2S)",
- "Field element \"${field.name}[", "]\" is null"
- )
- }
- addStatement("serializer.startTag(null, \$1S)", field.element.tagName)
- addStatement(
- "\$1L(serializer,\$W\$2L)", field.element.serializeMethodName,
- elementVariableName
- )
- addStatement("serializer.endTag(null, \$1S)", field.element.tagName)
- }
- }
- else -> error(field)
- }
- }
- }
- .build()
-
-private val ClassFieldInfo.serializeMethodName: String
- get() = "serialize${type.simpleName().toUpperCamelCase()}"
-
-private val ClassFieldInfo.serializeParameterName: String
- get() = type.simpleName().toLowerCamelCase()
-
-private val FieldInfo.variableName: String
- get() = name.toLowerCamelCase()
-
-private val FieldInfo.attributeName: String
- get() {
- check(this is PrimitiveFieldInfo || this is StringFieldInfo)
- return xmlNameOrName.toLowerCamelCase()
- }
-
-private val FieldInfo.tagName: String
- get() {
- check(this is ClassFieldInfo || this is ListFieldInfo)
- return xmlNameOrName.toLowerKebabCase()
- }
-
-private val FieldInfo.xmlNameOrName: String
- get() = xmlName ?: name
-
-private fun generateDeleteMethod(): MethodSpec =
- MethodSpec.methodBuilder("delete")
- .addJavadoc("Delete the XML file, if any.")
- .addModifiers(Modifier.PUBLIC)
- .addStatement("mFile.delete()")
- .build()
-
-private inline fun MethodSpec.Builder.addControlFlow(
- controlFlow: String,
- vararg args: Any,
- block: MethodSpec.Builder.() -> Unit
-): MethodSpec.Builder {
- beginControlFlow(controlFlow, *args)
- block()
- endControlFlow()
- return this
-}
diff --git a/tools/xmlpersistence/src/main/kotlin/Main.kt b/tools/xmlpersistence/src/main/kotlin/Main.kt
deleted file mode 100644
index e271f8cb9361..000000000000
--- a/tools/xmlpersistence/src/main/kotlin/Main.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.
- */
-
-import java.io.File
-import java.nio.file.Files
-
-fun main(args: Array<String>) {
- val showUsage = args.isEmpty() || when (args.singleOrNull()) {
- "-h", "--help" -> true
- else -> false
- }
- if (showUsage) {
- usage()
- return
- }
-
- val files = args.flatMap {
- File(it).walk().filter { it.isFile && it.extension == "java" }.map { it.toPath() }
- }
- val persistences = parse(files)
- for (persistence in persistences) {
- val file = generate(persistence)
- Files.newBufferedWriter(persistence.path).use {
- it.write(FILE_HEADER)
- file.writeTo(it)
- }
- }
-}
-
-private fun usage() {
- println("Usage: xmlpersistence <FILES>")
-}
diff --git a/tools/xmlpersistence/src/main/kotlin/Parser.kt b/tools/xmlpersistence/src/main/kotlin/Parser.kt
deleted file mode 100644
index 3ea12a9aa389..000000000000
--- a/tools/xmlpersistence/src/main/kotlin/Parser.kt
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * 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.
- */
-
-import com.github.javaparser.JavaParser
-import com.github.javaparser.ParseProblemException
-import com.github.javaparser.ParseResult
-import com.github.javaparser.ParserConfiguration
-import com.github.javaparser.ast.Node
-import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
-import com.github.javaparser.ast.body.FieldDeclaration
-import com.github.javaparser.ast.body.TypeDeclaration
-import com.github.javaparser.ast.expr.AnnotationExpr
-import com.github.javaparser.ast.expr.Expression
-import com.github.javaparser.ast.expr.NormalAnnotationExpr
-import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr
-import com.github.javaparser.ast.expr.StringLiteralExpr
-import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration
-import com.github.javaparser.resolution.types.ResolvedPrimitiveType
-import com.github.javaparser.resolution.types.ResolvedReferenceType
-import com.github.javaparser.symbolsolver.JavaSymbolSolver
-import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration
-import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver
-import com.github.javaparser.symbolsolver.resolution.typesolvers.MemoryTypeSolver
-import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import java.nio.file.Path
-import java.util.Optional
-
-class PersistenceInfo(
- val name: String,
- val root: ClassFieldInfo,
- val path: Path
-)
-
-sealed class FieldInfo {
- abstract val name: String
- abstract val xmlName: String?
- abstract val type: TypeName
- abstract val isRequired: Boolean
-}
-
-class PrimitiveFieldInfo(
- override val name: String,
- override val xmlName: String?,
- override val type: TypeName,
- override val isRequired: Boolean
-) : FieldInfo()
-
-class StringFieldInfo(
- override val name: String,
- override val xmlName: String?,
- override val isRequired: Boolean
-) : FieldInfo() {
- override val type: TypeName = ClassName.get(String::class.java)
-}
-
-class ClassFieldInfo(
- override val name: String,
- override val xmlName: String?,
- override val type: ClassName,
- override val isRequired: Boolean,
- val fields: List<FieldInfo>
-) : FieldInfo()
-
-class ListFieldInfo(
- override val name: String,
- override val xmlName: String?,
- override val type: ParameterizedTypeName,
- val element: ClassFieldInfo
-) : FieldInfo() {
- override val isRequired: Boolean = true
-}
-
-fun parse(files: List<Path>): List<PersistenceInfo> {
- val typeSolver = CombinedTypeSolver().apply { add(ReflectionTypeSolver()) }
- val javaParser = JavaParser(ParserConfiguration()
- .setSymbolResolver(JavaSymbolSolver(typeSolver)))
- val compilationUnits = files.map { javaParser.parse(it).getOrThrow() }
- val memoryTypeSolver = MemoryTypeSolver().apply {
- for (compilationUnit in compilationUnits) {
- for (typeDeclaration in compilationUnit.getNodesByClass<TypeDeclaration<*>>()) {
- val name = typeDeclaration.fullyQualifiedName.getOrNull() ?: continue
- addDeclaration(name, typeDeclaration.resolve())
- }
- }
- }
- typeSolver.add(memoryTypeSolver)
- return mutableListOf<PersistenceInfo>().apply {
- for (compilationUnit in compilationUnits) {
- val classDeclarations = compilationUnit
- .getNodesByClass<ClassOrInterfaceDeclaration>()
- .filter { !it.isInterface && (!it.isNestedType || it.isStatic) }
- this += classDeclarations.mapNotNull { parsePersistenceInfo(it) }
- }
- }
-}
-
-private fun parsePersistenceInfo(classDeclaration: ClassOrInterfaceDeclaration): PersistenceInfo? {
- val annotation = classDeclaration.getAnnotationByName("XmlPersistence").getOrNull()
- ?: return null
- val rootClassName = classDeclaration.nameAsString
- val name = annotation.getMemberValue("value")?.stringLiteralValue
- ?: "${rootClassName}Persistence"
- val rootXmlName = classDeclaration.getAnnotationByName("XmlName").getOrNull()
- ?.getMemberValue("value")?.stringLiteralValue
- val root = parseClassFieldInfo(
- rootXmlName ?: rootClassName, rootXmlName, true, classDeclaration
- )
- val path = classDeclaration.findCompilationUnit().get().storage.get().path
- .resolveSibling("$name.java")
- return PersistenceInfo(name, root, path)
-}
-
-private fun parseClassFieldInfo(
- name: String,
- xmlName: String?,
- isRequired: Boolean,
- classDeclaration: ClassOrInterfaceDeclaration
-): ClassFieldInfo {
- val fields = classDeclaration.fields.filterNot { it.isStatic }.map { parseFieldInfo(it) }
- val type = classDeclaration.resolve().typeName
- return ClassFieldInfo(name, xmlName, type, isRequired, fields)
-}
-
-private fun parseFieldInfo(field: FieldDeclaration): FieldInfo {
- require(field.isPublic && field.isFinal)
- val variable = field.variables.single()
- val name = variable.nameAsString
- val annotations = field.annotations + variable.type.annotations
- val annotation = annotations.getByName("XmlName")
- val xmlName = annotation?.getMemberValue("value")?.stringLiteralValue
- val isRequired = annotations.getByName("NonNull") != null
- return when (val type = variable.type.resolve()) {
- is ResolvedPrimitiveType -> {
- val primitiveType = type.typeName
- PrimitiveFieldInfo(name, xmlName, primitiveType, true)
- }
- is ResolvedReferenceType -> {
- when (type.qualifiedName) {
- Boolean::class.javaObjectType.name, Byte::class.javaObjectType.name,
- Short::class.javaObjectType.name, Char::class.javaObjectType.name,
- Integer::class.javaObjectType.name, Long::class.javaObjectType.name,
- Float::class.javaObjectType.name, Double::class.javaObjectType.name ->
- PrimitiveFieldInfo(name, xmlName, type.typeName, isRequired)
- String::class.java.name -> StringFieldInfo(name, xmlName, isRequired)
- List::class.java.name -> {
- requireNotNull(xmlName)
- val elementType = type.typeParametersValues().single()
- require(elementType is ResolvedReferenceType)
- val listType = ParameterizedTypeName.get(
- ClassName.get(List::class.java), elementType.typeName
- )
- val element = parseClassFieldInfo(
- "(element)", xmlName, true, elementType.classDeclaration
- )
- ListFieldInfo(name, xmlName, listType, element)
- }
- else -> parseClassFieldInfo(name, xmlName, isRequired, type.classDeclaration)
- }
- }
- else -> error(type)
- }
-}
-
-private fun <T> ParseResult<T>.getOrThrow(): T =
- if (isSuccessful) {
- result.get()
- } else {
- throw ParseProblemException(problems)
- }
-
-private inline fun <reified T : Node> Node.getNodesByClass(): List<T> =
- getNodesByClass(T::class.java)
-
-private fun <T : Node> Node.getNodesByClass(klass: Class<T>): List<T> = mutableListOf<T>().apply {
- if (klass.isInstance(this@getNodesByClass)) {
- this += klass.cast(this@getNodesByClass)
- }
- for (childNode in childNodes) {
- this += childNode.getNodesByClass(klass)
- }
-}
-
-private fun <T> Optional<T>.getOrNull(): T? = orElse(null)
-
-private fun List<AnnotationExpr>.getByName(name: String): AnnotationExpr? =
- find { it.name.identifier == name }
-
-private fun AnnotationExpr.getMemberValue(name: String): Expression? =
- when (this) {
- is NormalAnnotationExpr -> pairs.find { it.nameAsString == name }?.value
- is SingleMemberAnnotationExpr -> if (name == "value") memberValue else null
- else -> null
- }
-
-private val Expression.stringLiteralValue: String
- get() {
- require(this is StringLiteralExpr)
- return value
- }
-
-private val ResolvedReferenceType.classDeclaration: ClassOrInterfaceDeclaration
- get() {
- val resolvedClassDeclaration = typeDeclaration
- require(resolvedClassDeclaration is JavaParserClassDeclaration)
- return resolvedClassDeclaration.wrappedNode
- }
-
-private val ResolvedPrimitiveType.typeName: TypeName
- get() =
- when (this) {
- ResolvedPrimitiveType.BOOLEAN -> TypeName.BOOLEAN
- ResolvedPrimitiveType.BYTE -> TypeName.BYTE
- ResolvedPrimitiveType.SHORT -> TypeName.SHORT
- ResolvedPrimitiveType.CHAR -> TypeName.CHAR
- ResolvedPrimitiveType.INT -> TypeName.INT
- ResolvedPrimitiveType.LONG -> TypeName.LONG
- ResolvedPrimitiveType.FLOAT -> TypeName.FLOAT
- ResolvedPrimitiveType.DOUBLE -> TypeName.DOUBLE
- }
-
-// This doesn't support type parameters.
-private val ResolvedReferenceType.typeName: TypeName
- get() = typeDeclaration.typeName
-
-private val ResolvedReferenceTypeDeclaration.typeName: ClassName
- get() {
- val packageName = packageName
- val classNames = className.split(".")
- val topLevelClassName = classNames.first()
- val nestedClassNames = classNames.drop(1)
- return ClassName.get(packageName, topLevelClassName, *nestedClassNames.toTypedArray())
- }
diff --git a/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt b/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt
deleted file mode 100644
index b4bdbba7170b..000000000000
--- a/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.
- */
-
-import java.util.Locale
-
-private val camelHumpBoundary = Regex(
- "-"
- + "|_"
- + "|(?<=[0-9])(?=[^0-9])"
- + "|(?<=[A-Z])(?=[^A-Za-z]|[A-Z][a-z])"
- + "|(?<=[a-z])(?=[^a-z])"
-)
-
-private fun String.toCamelHumps(): List<String> = split(camelHumpBoundary)
-
-fun String.toUpperCamelCase(): String =
- toCamelHumps().joinToString("") { it.toLowerCase(Locale.ROOT).capitalize(Locale.ROOT) }
-
-fun String.toLowerCamelCase(): String = toUpperCamelCase().decapitalize(Locale.ROOT)
-
-fun String.toUpperKebabCase(): String =
- toCamelHumps().joinToString("-") { it.toUpperCase(Locale.ROOT) }
-
-fun String.toLowerKebabCase(): String =
- toCamelHumps().joinToString("-") { it.toLowerCase(Locale.ROOT) }
-
-fun String.toUpperSnakeCase(): String =
- toCamelHumps().joinToString("_") { it.toUpperCase(Locale.ROOT) }
-
-fun String.toLowerSnakeCase(): String =
- toCamelHumps().joinToString("_") { it.toLowerCase(Locale.ROOT) }