diff options
Diffstat (limited to 'tools')
320 files changed, 36772 insertions, 23886 deletions
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 1dd43f79471d..2e34197e97aa 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -494,7 +494,7 @@ struct ImpliedFeature { struct Feature { Feature() : required(false), version(-1) {} - Feature(bool required, int32_t version = -1) : required(required), version(version) {} + explicit Feature(bool required, int32_t version = -1) : required(required), version(version) {} /** * Whether the feature is required. @@ -775,7 +775,7 @@ int doDump(Bundle* bundle) } // Source for AndroidManifest.xml - const String8 manifestFile = String8::format("%s@AndroidManifest.xml", filename); + const String8 manifestFile("AndroidManifest.xml"); // The dynamicRefTable can be null if there are no resources for this asset cookie. // This fine. @@ -803,7 +803,7 @@ int doDump(Bundle* bundle) ResXMLTree tree(dynamicRefTable); asset = assets.openNonAsset(assetsCookie, resname, Asset::ACCESS_BUFFER); if (asset == NULL) { - fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname); + fprintf(stderr, "ERROR: dump failed because resource %s not found\n", resname); goto bail; } @@ -875,14 +875,16 @@ int doDump(Bundle* bundle) depth++; const char16_t* ctag16 = tree.getElementName(&len); if (ctag16 == NULL) { - fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n"); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR: failed to get XML element name (bad string pool)"); goto bail; } String8 tag(ctag16); //printf("Depth %d tag %s\n", depth, tag.string()); if (depth == 1) { if (tag != "manifest") { - fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n"); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR: manifest does not start with <manifest> tag"); goto bail; } String8 pkg = AaptXml::getAttribute(tree, NULL, "package", NULL); @@ -892,12 +894,14 @@ int doDump(Bundle* bundle) String8 error; String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR: %s\n", error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:name': %s", error.string()); goto bail; } if (name == "") { - fprintf(stderr, "ERROR: missing 'android:name' for permission\n"); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR: missing 'android:name' for permission"); goto bail; } printf("permission: %s\n", @@ -906,12 +910,14 @@ int doDump(Bundle* bundle) String8 error; String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR: %s\n", error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:name' attribute: %s", error.string()); goto bail; } if (name == "") { - fprintf(stderr, "ERROR: missing 'android:name' for uses-permission\n"); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR: missing 'android:name' for uses-permission"); goto bail; } printUsesPermission(name, @@ -921,13 +927,14 @@ int doDump(Bundle* bundle) String8 error; String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR: %s\n", error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:name' attribute: %s", error.string()); goto bail; } if (name == "") { - fprintf(stderr, "ERROR: missing 'android:name' for " - "uses-permission-sdk-23\n"); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR: missing 'android:name' for uses-permission-sdk-23"); goto bail; } printUsesPermissionSdk23( @@ -1188,14 +1195,16 @@ int doDump(Bundle* bundle) const char16_t* ctag16 = tree.getElementName(&len); if (ctag16 == NULL) { - fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n"); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR: failed to get XML element name (bad string pool)"); goto bail; } String8 tag(ctag16); //printf("Depth %d, %s\n", depth, tag.string()); if (depth == 1) { if (tag != "manifest") { - fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n"); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR: manifest does not start with <manifest> tag"); goto bail; } pkg = AaptXml::getAttribute(tree, NULL, "package", NULL); @@ -1204,7 +1213,8 @@ int doDump(Bundle* bundle) int32_t versionCode = AaptXml::getIntegerAttribute(tree, VERSION_CODE_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:versionCode' attribute: %s", error.string()); goto bail; } @@ -1216,7 +1226,8 @@ int doDump(Bundle* bundle) String8 versionName = AaptXml::getResolvedAttribute(res, tree, VERSION_NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:versionName' attribute: %s", error.string()); goto bail; } @@ -1237,7 +1248,8 @@ int doDump(Bundle* bundle) int32_t installLocation = AaptXml::getResolvedIntegerAttribute(res, tree, INSTALL_LOCATION_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:installLocation' attribute: %s\n", + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:installLocation' attribute: %s", error.string()); goto bail; } @@ -1269,7 +1281,7 @@ int doDump(Bundle* bundle) const size_t NL = locales.size(); for (size_t i=0; i<NL; i++) { const char* localeStr = locales[i].string(); - assets.setLocale(localeStr != NULL ? localeStr : ""); + assets.setConfiguration(config, localeStr != NULL ? localeStr : ""); String8 llabel = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR, &error); if (llabel != "") { @@ -1303,14 +1315,15 @@ int doDump(Bundle* bundle) String8 icon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", - error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:icon' attribute: %s", error.string()); goto bail; } int32_t testOnly = AaptXml::getIntegerAttribute(tree, TEST_ONLY_ATTR, 0, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:testOnly' attribute: %s", error.string()); goto bail; } @@ -1318,8 +1331,8 @@ int doDump(Bundle* bundle) String8 banner = AaptXml::getResolvedAttribute(res, tree, BANNER_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n", - error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:banner' attribute: %s", error.string()); goto bail; } printf("application: label='%s' ", @@ -1337,8 +1350,8 @@ int doDump(Bundle* bundle) int32_t isGame = AaptXml::getResolvedIntegerAttribute(res, tree, ISGAME_ATTR, 0, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:isGame' attribute: %s\n", - error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:isGame' attribute: %s", error.string()); goto bail; } if (isGame != 0) { @@ -1348,7 +1361,8 @@ int doDump(Bundle* bundle) int32_t debuggable = AaptXml::getResolvedIntegerAttribute(res, tree, DEBUGGABLE_ATTR, 0, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:debuggable' attribute: %s", error.string()); goto bail; } @@ -1377,8 +1391,8 @@ int doDump(Bundle* bundle) String8 name = AaptXml::getResolvedAttribute(res, tree, MIN_SDK_VERSION_ATTR, &error); if (error != "") { - fprintf(stderr, - "ERROR getting 'android:minSdkVersion' attribute: %s\n", + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:minSdkVersion' attribute: %s", error.string()); goto bail; } @@ -1399,8 +1413,8 @@ int doDump(Bundle* bundle) String8 name = AaptXml::getResolvedAttribute(res, tree, TARGET_SDK_VERSION_ATTR, &error); if (error != "") { - fprintf(stderr, - "ERROR getting 'android:targetSdkVersion' attribute: %s\n", + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:targetSdkVersion' attribute: %s", error.string()); goto bail; } @@ -1465,8 +1479,8 @@ int doDump(Bundle* bundle) FeatureGroup group; group.label = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:label' attribute:" - " %s\n", error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:label' attribute: %s", error.string()); goto bail; } featureGroups.add(group); @@ -1511,13 +1525,14 @@ int doDump(Bundle* bundle) } else if (tag == "uses-permission") { String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", - error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:name' attribute: %s", error.string()); goto bail; } if (name == "") { - fprintf(stderr, "ERROR: missing 'android:name' for uses-permission\n"); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR: missing 'android:name' for uses-permission"); goto bail; } @@ -1546,14 +1561,14 @@ int doDump(Bundle* bundle) } else if (tag == "uses-permission-sdk-23" || tag == "uses-permission-sdk-m") { String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", - error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:name' attribute: %s", error.string()); goto bail; } if (name == "") { - fprintf(stderr, "ERROR: missing 'android:name' for " - "uses-permission-sdk-23\n"); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR: missing 'android:name' for uses-permission-sdk-23"); goto bail; } @@ -1568,9 +1583,9 @@ int doDump(Bundle* bundle) printf("uses-package:'%s'\n", ResTable::normalizeForOutput(name.string()).string()); } else { - fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", - error.string()); - goto bail; + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:name' attribute: %s", error.string()); + goto bail; } } else if (tag == "original-package") { String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); @@ -1578,9 +1593,9 @@ int doDump(Bundle* bundle) printf("original-package:'%s'\n", ResTable::normalizeForOutput(name.string()).string()); } else { - fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", - error.string()); - goto bail; + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:name' attribute: %s", error.string()); + goto bail; } } else if (tag == "supports-gl-texture") { String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); @@ -1588,15 +1603,15 @@ int doDump(Bundle* bundle) printf("supports-gl-texture:'%s'\n", ResTable::normalizeForOutput(name.string()).string()); } else { - fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", - error.string()); - goto bail; + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:name' attribute: %s", error.string()); + goto bail; } } else if (tag == "compatible-screens") { printCompatibleScreens(tree, &error); if (error != "") { - fprintf(stderr, "ERROR getting compatible screens: %s\n", - error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting compatible screens: %s", error.string()); goto bail; } depth--; @@ -1633,7 +1648,8 @@ int doDump(Bundle* bundle) withinActivity = true; activityName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:name' attribute: %s", error.string()); goto bail; } @@ -1641,7 +1657,8 @@ int doDump(Bundle* bundle) activityLabel = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n", + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:label' attribute: %s", error.string()); goto bail; } @@ -1649,7 +1666,8 @@ int doDump(Bundle* bundle) activityIcon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:icon' attribute: %s", error.string()); goto bail; } @@ -1657,7 +1675,8 @@ int doDump(Bundle* bundle) activityBanner = AaptXml::getResolvedAttribute(res, tree, BANNER_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n", + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:banner' attribute: %s", error.string()); goto bail; } @@ -1684,9 +1703,9 @@ int doDump(Bundle* bundle) } else if (tag == "uses-library") { String8 libraryName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, + SourcePos(manifestFile, tree.getLineNumber()).error( "ERROR getting 'android:name' attribute for uses-library" - " %s\n", error.string()); + " %s", error.string()); goto bail; } int req = AaptXml::getIntegerAttribute(tree, @@ -1699,9 +1718,9 @@ int doDump(Bundle* bundle) receiverName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, + SourcePos(manifestFile, tree.getLineNumber()).error( "ERROR getting 'android:name' attribute for receiver:" - " %s\n", error.string()); + " %s", error.string()); goto bail; } @@ -1712,9 +1731,9 @@ int doDump(Bundle* bundle) hasBindDeviceAdminPermission = true; } } else { - fprintf(stderr, + SourcePos(manifestFile, tree.getLineNumber()).error( "ERROR getting 'android:permission' attribute for" - " receiver '%s': %s\n", + " receiver '%s': %s", receiverName.string(), error.string()); } } else if (tag == "service") { @@ -1722,8 +1741,9 @@ int doDump(Bundle* bundle) serviceName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:name' attribute for " - "service:%s\n", error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:name' attribute for " + "service:%s", error.string()); goto bail; } @@ -1748,8 +1768,9 @@ int doDump(Bundle* bundle) hasBindDreamServicePermission = true; } } else { - fprintf(stderr, "ERROR getting 'android:permission' attribute for " - "service '%s': %s\n", serviceName.string(), error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:permission' attribute for " + "service '%s': %s", serviceName.string(), error.string()); } } else if (tag == "provider") { withinProvider = true; @@ -1757,26 +1778,27 @@ int doDump(Bundle* bundle) bool exported = AaptXml::getResolvedIntegerAttribute(res, tree, EXPORTED_ATTR, &error); if (error != "") { - fprintf(stderr, + SourcePos(manifestFile, tree.getLineNumber()).error( "ERROR getting 'android:exported' attribute for provider:" - " %s\n", error.string()); + " %s", error.string()); goto bail; } bool grantUriPermissions = AaptXml::getResolvedIntegerAttribute( res, tree, GRANT_URI_PERMISSIONS_ATTR, &error); if (error != "") { - fprintf(stderr, + SourcePos(manifestFile, tree.getLineNumber()).error( "ERROR getting 'android:grantUriPermissions' attribute for " - "provider: %s\n", error.string()); + "provider: %s", error.string()); goto bail; } String8 permission = AaptXml::getResolvedAttribute(res, tree, PERMISSION_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:permission' attribute for " - "provider: %s\n", error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:permission' attribute for " + "provider: %s", error.string()); goto bail; } @@ -1787,8 +1809,9 @@ int doDump(Bundle* bundle) String8 metaDataName = AaptXml::getResolvedAttribute(res, tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:name' attribute for " - "meta-data:%s\n", error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:name' attribute for " + "meta-data: %s", error.string()); goto bail; } printf("meta-data: name='%s' ", @@ -1801,9 +1824,10 @@ int doDump(Bundle* bundle) printResolvedResourceAttribute(res, tree, RESOURCE_ATTR, String8("resource"), &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:value' or " + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:value' or " "'android:resource' attribute for " - "meta-data:%s\n", error.string()); + "meta-data: %s", error.string()); goto bail; } } @@ -1813,7 +1837,8 @@ int doDump(Bundle* bundle) if (name != "" && error == "") { supportedInput.add(name); } else { - fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:name' attribute: %s", error.string()); goto bail; } @@ -1872,8 +1897,9 @@ int doDump(Bundle* bundle) } else if (withinService && tag == "meta-data") { String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:name' attribute for " - "meta-data tag in service '%s': %s\n", serviceName.string(), + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:name' attribute for " + "meta-data tag in service '%s': %s", serviceName.string(), error.string()); goto bail; } @@ -1888,8 +1914,9 @@ int doDump(Bundle* bundle) String8 xmlPath = AaptXml::getResolvedAttribute(res, tree, RESOURCE_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:resource' attribute for " - "meta-data tag in service '%s': %s\n", + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:resource' attribute for " + "meta-data tag in service '%s': %s", serviceName.string(), error.string()); goto bail; } @@ -1897,7 +1924,8 @@ int doDump(Bundle* bundle) Vector<String8> categories = getNfcAidCategories(assets, xmlPath, offHost, &error); if (error != "") { - fprintf(stderr, "ERROR getting AID category for service '%s'\n", + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting AID category for service '%s'", serviceName.string()); goto bail; } @@ -1918,8 +1946,8 @@ int doDump(Bundle* bundle) if (tag == "action") { action = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", - error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:name' attribute: %s", error.string()); goto bail; } @@ -1974,8 +2002,8 @@ int doDump(Bundle* bundle) if (tag == "category") { String8 category = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'name' attribute: %s\n", - error.string()); + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'name' attribute: %s", error.string()); goto bail; } if (withinActivity) { @@ -2290,6 +2318,10 @@ int doDump(Bundle* bundle) result = NO_ERROR; bail: + if (SourcePos::hasErrors()) { + SourcePos::printErrors(stderr); + } + if (asset) { delete asset; } diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 3e8abb121c42..75a316092a02 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -2930,6 +2930,19 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass if (!keepTag && inApplication && depth == 3) { if (tag == "activity" || tag == "service" || tag == "receiver" || tag == "provider") { keepTag = true; + + if (mainDex) { + String8 componentProcess = AaptXml::getAttribute(tree, + "http://schemas.android.com/apk/res/android", "process", &error); + if (error != "") { + fprintf(stderr, "ERROR: %s\n", error.string()); + return -1; + } + + const String8& process = + componentProcess.length() > 0 ? componentProcess : defaultProcess; + keepTag = process.length() > 0 && process.find(":") != 0; + } } } if (keepTag) { @@ -2942,19 +2955,6 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass keepTag = name.length() > 0; - if (keepTag && mainDex) { - String8 componentProcess = AaptXml::getAttribute(tree, - "http://schemas.android.com/apk/res/android", "process", &error); - if (error != "") { - fprintf(stderr, "ERROR: %s\n", error.string()); - return -1; - } - - const String8& process = - componentProcess.length() > 0 ? componentProcess : defaultProcess; - keepTag = process.length() > 0 && process.find(":") != 0; - } - if (keepTag) { addProguardKeepRule(keep, name, pkg.string(), assFile->getPrintableSource(), tree.getLineNumber()); diff --git a/tools/aapt2/.clang-format b/tools/aapt2/.clang-format new file mode 100644 index 000000000000..545366a9b70b --- /dev/null +++ b/tools/aapt2/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: Google + diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 53225da14569..1efd2ed6446c 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -23,7 +23,11 @@ LOCAL_PATH:= $(call my-dir) main := Main.cpp sources := \ compile/IdAssigner.cpp \ + compile/InlineXmlFormatParser.cpp \ + compile/NinePatch.cpp \ compile/Png.cpp \ + compile/PngChunkFilter.cpp \ + compile/PngCrunch.cpp \ compile/PseudolocaleGenerator.cpp \ compile/Pseudolocalizer.cpp \ compile/XmlIdCollector.cpp \ @@ -31,14 +35,19 @@ sources := \ flatten/Archive.cpp \ flatten/TableFlattener.cpp \ flatten/XmlFlattener.cpp \ + io/File.cpp \ io/FileSystem.cpp \ + io/Io.cpp \ io/ZipArchive.cpp \ link/AutoVersioner.cpp \ link/ManifestFixer.cpp \ link/ProductFilter.cpp \ link/PrivateAttributeMover.cpp \ link/ReferenceLinker.cpp \ + link/ResourceDeduper.cpp \ link/TableMerger.cpp \ + link/VersionCollapser.cpp \ + link/XmlNamespaceRemover.cpp \ link/XmlReferenceLinker.cpp \ process/SymbolTable.cpp \ proto/ProtoHelpers.cpp \ @@ -52,6 +61,7 @@ sources := \ util/Util.cpp \ ConfigDescription.cpp \ Debug.cpp \ + DominatorTree.cpp \ Flags.cpp \ java/AnnotationProcessor.cpp \ java/ClassDefinition.cpp \ @@ -73,8 +83,12 @@ sources := \ sources += Format.proto +sourcesJni := jni/aapt2_jni.cpp + testSources := \ compile/IdAssigner_test.cpp \ + compile/InlineXmlFormatParser_test.cpp \ + compile/NinePatch_test.cpp \ compile/PseudolocaleGenerator_test.cpp \ compile/Pseudolocalizer_test.cpp \ compile/XmlIdCollector_test.cpp \ @@ -86,7 +100,10 @@ testSources := \ link/PrivateAttributeMover_test.cpp \ link/ProductFilter_test.cpp \ link/ReferenceLinker_test.cpp \ + link/ResourceDeduper_test.cpp \ link/TableMerger_test.cpp \ + link/VersionCollapser_test.cpp \ + link/XmlNamespaceRemover_test.cpp \ link/XmlReferenceLinker_test.cpp \ process/SymbolTable_test.cpp \ proto/TableProtoSerializer_test.cpp \ @@ -97,10 +114,12 @@ testSources := \ util/StringPiece_test.cpp \ util/Util_test.cpp \ ConfigDescription_test.cpp \ + DominatorTree_test.cpp \ java/AnnotationProcessor_test.cpp \ java/JavaClassGenerator_test.cpp \ java/ManifestClassGenerator_test.cpp \ Locale_test.cpp \ + NameMangler_test.cpp \ Resource_test.cpp \ ResourceParser_test.cpp \ ResourceTable_test.cpp \ @@ -139,7 +158,7 @@ hostStaticLibs_windows := libz hostLdLibs_linux := -lz hostLdLibs_darwin := -lz -cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG +cFlags := -Wall -Werror -Wno-unused-parameter cFlags_darwin := -D_DARWIN_UNLIMITED_STREAMS cFlags_windows := -Wno-maybe-uninitialized # Incorrectly marking use of Maybe.value() as error. cppFlags := -Wno-missing-field-initializers -fno-exceptions -fno-rtti @@ -168,6 +187,28 @@ LOCAL_STATIC_LIBRARIES := $(hostStaticLibs) LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows) include $(BUILD_HOST_STATIC_LIBRARY) + +# ========================================================== +# Build the host shared library: libaapt2_jni +# ========================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := libaapt2_jni +LOCAL_MODULE_CLASS := SHARED_LIBRARIES +LOCAL_MODULE_HOST_OS := darwin linux windows +LOCAL_CFLAGS := $(cFlags) +LOCAL_CFLAGS_darwin := $(cFlags_darwin) +LOCAL_CFLAGS_windows := $(cFlags_windows) +LOCAL_CPPFLAGS := $(cppFlags) +LOCAL_C_INCLUDES := $(protoIncludes) +LOCAL_SRC_FILES := $(toolSources) $(sourcesJni) +LOCAL_STATIC_LIBRARIES := libaapt2 $(hostStaticLibs) +LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows) +LOCAL_LDLIBS := $(hostLdLibs) +LOCAL_LDLIBS_darwin := $(hostLdLibs_darwin) +LOCAL_LDLIBS_linux := $(hostLdLibs_linux) +include $(BUILD_HOST_SHARED_LIBRARY) + + # ========================================================== # Build the host tests: libaapt2_tests # ========================================================== diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h index 30047f71cc04..1e488f70bd4d 100644 --- a/tools/aapt2/AppInfo.h +++ b/tools/aapt2/AppInfo.h @@ -19,6 +19,8 @@ #include <string> +#include "util/Maybe.h" + namespace aapt { /** @@ -26,12 +28,27 @@ namespace aapt { * will come from the app's AndroidManifest. */ struct AppInfo { - /** - * App's package name. - */ - std::u16string package; + /** + * App's package name. + */ + std::string package; + + /** + * The App's minimum SDK version. + */ + Maybe<std::string> min_sdk_version; + + /** + * The Version code of the app. + */ + Maybe<uint32_t> version_code; + + /** + * The revision code of the app. + */ + Maybe<uint32_t> revision_code; }; -} // namespace aapt +} // namespace aapt -#endif // AAPT_APP_INFO_H +#endif // AAPT_APP_INFO_H diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp index 13f8b3b54f68..289919a39373 100644 --- a/tools/aapt2/ConfigDescription.cpp +++ b/tools/aapt2/ConfigDescription.cpp @@ -15,772 +15,887 @@ */ #include "ConfigDescription.h" + +#include <string> +#include <vector> + +#include "androidfw/ResourceTypes.h" + #include "Locale.h" #include "SdkConstants.h" #include "util/StringPiece.h" #include "util/Util.h" -#include <androidfw/ResourceTypes.h> -#include <string> -#include <vector> - namespace aapt { using android::ResTable_config; static const char* kWildcardName = "any"; -const ConfigDescription& ConfigDescription::defaultConfig() { - static ConfigDescription config = {}; - return config; +const ConfigDescription& ConfigDescription::DefaultConfig() { + static ConfigDescription config = {}; + return config; } static bool parseMcc(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) out->mcc = 0; - return true; - } - const char* c = name; - if (tolower(*c) != 'm') return false; - c++; - if (tolower(*c) != 'c') return false; - c++; - if (tolower(*c) != 'c') return false; + if (strcmp(name, kWildcardName) == 0) { + if (out) out->mcc = 0; + return true; + } + const char* c = name; + if (tolower(*c) != 'm') return false; + c++; + if (tolower(*c) != 'c') return false; + c++; + if (tolower(*c) != 'c') return false; + c++; + + const char* val = c; + + while (*c >= '0' && *c <= '9') { c++; + } + if (*c != 0) return false; + if (c - val != 3) return false; - const char* val = c; - - while (*c >= '0' && *c <= '9') { - c++; - } - if (*c != 0) return false; - if (c-val != 3) return false; - - int d = atoi(val); - if (d != 0) { - if (out) out->mcc = d; - return true; - } + int d = atoi(val); + if (d != 0) { + if (out) out->mcc = d; + return true; + } - return false; + return false; } static bool parseMnc(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) out->mcc = 0; - return true; - } - const char* c = name; - if (tolower(*c) != 'm') return false; - c++; - if (tolower(*c) != 'n') return false; - c++; - if (tolower(*c) != 'c') return false; + if (strcmp(name, kWildcardName) == 0) { + if (out) out->mcc = 0; + return true; + } + const char* c = name; + if (tolower(*c) != 'm') return false; + c++; + if (tolower(*c) != 'n') return false; + c++; + if (tolower(*c) != 'c') return false; + c++; + + const char* val = c; + + while (*c >= '0' && *c <= '9') { c++; + } + if (*c != 0) return false; + if (c - val == 0 || c - val > 3) return false; - const char* val = c; - - while (*c >= '0' && *c <= '9') { - c++; - } - if (*c != 0) return false; - if (c-val == 0 || c-val > 3) return false; - - if (out) { - out->mnc = atoi(val); - if (out->mnc == 0) { - out->mnc = ACONFIGURATION_MNC_ZERO; - } + if (out) { + out->mnc = atoi(val); + if (out->mnc == 0) { + out->mnc = ACONFIGURATION_MNC_ZERO; } + } - return true; + return true; } static bool parseLayoutDirection(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) - | ResTable_config::LAYOUTDIR_ANY; - return true; - } else if (strcmp(name, "ldltr") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) - | ResTable_config::LAYOUTDIR_LTR; - return true; - } else if (strcmp(name, "ldrtl") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) - | ResTable_config::LAYOUTDIR_RTL; - return true; - } + if (strcmp(name, kWildcardName) == 0) { + if (out) + out->screenLayout = + (out->screenLayout & ~ResTable_config::MASK_LAYOUTDIR) | + ResTable_config::LAYOUTDIR_ANY; + return true; + } else if (strcmp(name, "ldltr") == 0) { + if (out) + out->screenLayout = + (out->screenLayout & ~ResTable_config::MASK_LAYOUTDIR) | + ResTable_config::LAYOUTDIR_LTR; + return true; + } else if (strcmp(name, "ldrtl") == 0) { + if (out) + out->screenLayout = + (out->screenLayout & ~ResTable_config::MASK_LAYOUTDIR) | + ResTable_config::LAYOUTDIR_RTL; + return true; + } - return false; + return false; } static bool parseScreenLayoutSize(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) - | ResTable_config::SCREENSIZE_ANY; - return true; - } else if (strcmp(name, "small") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) - | ResTable_config::SCREENSIZE_SMALL; - return true; - } else if (strcmp(name, "normal") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) - | ResTable_config::SCREENSIZE_NORMAL; - return true; - } else if (strcmp(name, "large") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) - | ResTable_config::SCREENSIZE_LARGE; - return true; - } else if (strcmp(name, "xlarge") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) - | ResTable_config::SCREENSIZE_XLARGE; - return true; - } + if (strcmp(name, kWildcardName) == 0) { + if (out) + out->screenLayout = + (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) | + ResTable_config::SCREENSIZE_ANY; + return true; + } else if (strcmp(name, "small") == 0) { + if (out) + out->screenLayout = + (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) | + ResTable_config::SCREENSIZE_SMALL; + return true; + } else if (strcmp(name, "normal") == 0) { + if (out) + out->screenLayout = + (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) | + ResTable_config::SCREENSIZE_NORMAL; + return true; + } else if (strcmp(name, "large") == 0) { + if (out) + out->screenLayout = + (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) | + ResTable_config::SCREENSIZE_LARGE; + return true; + } else if (strcmp(name, "xlarge") == 0) { + if (out) + out->screenLayout = + (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) | + ResTable_config::SCREENSIZE_XLARGE; + return true; + } - return false; + return false; } static bool parseScreenLayoutLong(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENLONG) - | ResTable_config::SCREENLONG_ANY; - return true; - } else if (strcmp(name, "long") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENLONG) - | ResTable_config::SCREENLONG_YES; - return true; - } else if (strcmp(name, "notlong") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENLONG) - | ResTable_config::SCREENLONG_NO; - return true; - } + if (strcmp(name, kWildcardName) == 0) { + if (out) + out->screenLayout = + (out->screenLayout & ~ResTable_config::MASK_SCREENLONG) | + ResTable_config::SCREENLONG_ANY; + return true; + } else if (strcmp(name, "long") == 0) { + if (out) + out->screenLayout = + (out->screenLayout & ~ResTable_config::MASK_SCREENLONG) | + ResTable_config::SCREENLONG_YES; + return true; + } else if (strcmp(name, "notlong") == 0) { + if (out) + out->screenLayout = + (out->screenLayout & ~ResTable_config::MASK_SCREENLONG) | + ResTable_config::SCREENLONG_NO; + return true; + } - return false; + return false; } static bool parseScreenRound(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) out->screenLayout2 = - (out->screenLayout2&~ResTable_config::MASK_SCREENROUND) - | ResTable_config::SCREENROUND_ANY; - return true; - } else if (strcmp(name, "round") == 0) { - if (out) out->screenLayout2 = - (out->screenLayout2&~ResTable_config::MASK_SCREENROUND) - | ResTable_config::SCREENROUND_YES; - return true; - } else if (strcmp(name, "notround") == 0) { - if (out) out->screenLayout2 = - (out->screenLayout2&~ResTable_config::MASK_SCREENROUND) - | ResTable_config::SCREENROUND_NO; - return true; - } - return false; + if (strcmp(name, kWildcardName) == 0) { + if (out) + out->screenLayout2 = + (out->screenLayout2 & ~ResTable_config::MASK_SCREENROUND) | + ResTable_config::SCREENROUND_ANY; + return true; + } else if (strcmp(name, "round") == 0) { + if (out) + out->screenLayout2 = + (out->screenLayout2 & ~ResTable_config::MASK_SCREENROUND) | + ResTable_config::SCREENROUND_YES; + return true; + } else if (strcmp(name, "notround") == 0) { + if (out) + out->screenLayout2 = + (out->screenLayout2 & ~ResTable_config::MASK_SCREENROUND) | + ResTable_config::SCREENROUND_NO; + return true; + } + return false; } static bool parseOrientation(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) out->orientation = out->ORIENTATION_ANY; - return true; - } else if (strcmp(name, "port") == 0) { - if (out) out->orientation = out->ORIENTATION_PORT; - return true; - } else if (strcmp(name, "land") == 0) { - if (out) out->orientation = out->ORIENTATION_LAND; - return true; - } else if (strcmp(name, "square") == 0) { - if (out) out->orientation = out->ORIENTATION_SQUARE; - return true; - } + if (strcmp(name, kWildcardName) == 0) { + if (out) out->orientation = out->ORIENTATION_ANY; + return true; + } else if (strcmp(name, "port") == 0) { + if (out) out->orientation = out->ORIENTATION_PORT; + return true; + } else if (strcmp(name, "land") == 0) { + if (out) out->orientation = out->ORIENTATION_LAND; + return true; + } else if (strcmp(name, "square") == 0) { + if (out) out->orientation = out->ORIENTATION_SQUARE; + return true; + } - return false; + return false; } static bool parseUiModeType(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) - | ResTable_config::UI_MODE_TYPE_ANY; - return true; - } else if (strcmp(name, "desk") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) - | ResTable_config::UI_MODE_TYPE_DESK; - return true; - } else if (strcmp(name, "car") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) - | ResTable_config::UI_MODE_TYPE_CAR; - return true; - } else if (strcmp(name, "television") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) - | ResTable_config::UI_MODE_TYPE_TELEVISION; - return true; - } else if (strcmp(name, "appliance") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) - | ResTable_config::UI_MODE_TYPE_APPLIANCE; - return true; - } else if (strcmp(name, "watch") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) - | ResTable_config::UI_MODE_TYPE_WATCH; - return true; - } + if (strcmp(name, kWildcardName) == 0) { + if (out) + out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) | + ResTable_config::UI_MODE_TYPE_ANY; + return true; + } else if (strcmp(name, "desk") == 0) { + if (out) + out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) | + ResTable_config::UI_MODE_TYPE_DESK; + return true; + } else if (strcmp(name, "car") == 0) { + if (out) + out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) | + ResTable_config::UI_MODE_TYPE_CAR; + return true; + } else if (strcmp(name, "television") == 0) { + if (out) + out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) | + ResTable_config::UI_MODE_TYPE_TELEVISION; + return true; + } else if (strcmp(name, "appliance") == 0) { + if (out) + out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) | + ResTable_config::UI_MODE_TYPE_APPLIANCE; + return true; + } else if (strcmp(name, "watch") == 0) { + if (out) + out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) | + ResTable_config::UI_MODE_TYPE_WATCH; + return true; + } - return false; + return false; } static bool parseUiModeNight(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) - | ResTable_config::UI_MODE_NIGHT_ANY; - return true; - } else if (strcmp(name, "night") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) - | ResTable_config::UI_MODE_NIGHT_YES; - return true; - } else if (strcmp(name, "notnight") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) - | ResTable_config::UI_MODE_NIGHT_NO; - return true; - } + if (strcmp(name, kWildcardName) == 0) { + if (out) + out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_NIGHT) | + ResTable_config::UI_MODE_NIGHT_ANY; + return true; + } else if (strcmp(name, "night") == 0) { + if (out) + out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_NIGHT) | + ResTable_config::UI_MODE_NIGHT_YES; + return true; + } else if (strcmp(name, "notnight") == 0) { + if (out) + out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_NIGHT) | + ResTable_config::UI_MODE_NIGHT_NO; + return true; + } - return false; + return false; } static bool parseDensity(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) out->density = ResTable_config::DENSITY_DEFAULT; - return true; - } + if (strcmp(name, kWildcardName) == 0) { + if (out) out->density = ResTable_config::DENSITY_DEFAULT; + return true; + } - if (strcmp(name, "anydpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_ANY; - return true; - } + if (strcmp(name, "anydpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_ANY; + return true; + } - if (strcmp(name, "nodpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_NONE; - return true; - } + if (strcmp(name, "nodpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_NONE; + return true; + } - if (strcmp(name, "ldpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_LOW; - return true; - } + if (strcmp(name, "ldpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_LOW; + return true; + } - if (strcmp(name, "mdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_MEDIUM; - return true; - } + if (strcmp(name, "mdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_MEDIUM; + return true; + } - if (strcmp(name, "tvdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_TV; - return true; - } + if (strcmp(name, "tvdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_TV; + return true; + } - if (strcmp(name, "hdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_HIGH; - return true; - } + if (strcmp(name, "hdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_HIGH; + return true; + } - if (strcmp(name, "xhdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_XHIGH; - return true; - } + if (strcmp(name, "xhdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_XHIGH; + return true; + } - if (strcmp(name, "xxhdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_XXHIGH; - return true; - } + if (strcmp(name, "xxhdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_XXHIGH; + return true; + } - if (strcmp(name, "xxxhdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_XXXHIGH; - return true; - } + if (strcmp(name, "xxxhdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_XXXHIGH; + return true; + } - char* c = (char*)name; - while (*c >= '0' && *c <= '9') { - c++; - } + char* c = (char*)name; + while (*c >= '0' && *c <= '9') { + c++; + } - // check that we have 'dpi' after the last digit. - if (toupper(c[0]) != 'D' || - toupper(c[1]) != 'P' || - toupper(c[2]) != 'I' || - c[3] != 0) { - return false; - } + // check that we have 'dpi' after the last digit. + if (toupper(c[0]) != 'D' || toupper(c[1]) != 'P' || toupper(c[2]) != 'I' || + c[3] != 0) { + return false; + } - // temporarily replace the first letter with \0 to - // use atoi. - char tmp = c[0]; - c[0] = '\0'; + // temporarily replace the first letter with \0 to + // use atoi. + char tmp = c[0]; + c[0] = '\0'; - int d = atoi(name); - c[0] = tmp; + int d = atoi(name); + c[0] = tmp; - if (d != 0) { - if (out) out->density = d; - return true; - } + if (d != 0) { + if (out) out->density = d; + return true; + } - return false; + return false; } static bool parseTouchscreen(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) out->touchscreen = out->TOUCHSCREEN_ANY; - return true; - } else if (strcmp(name, "notouch") == 0) { - if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH; - return true; - } else if (strcmp(name, "stylus") == 0) { - if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS; - return true; - } else if (strcmp(name, "finger") == 0) { - if (out) out->touchscreen = out->TOUCHSCREEN_FINGER; - return true; - } + if (strcmp(name, kWildcardName) == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_ANY; + return true; + } else if (strcmp(name, "notouch") == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH; + return true; + } else if (strcmp(name, "stylus") == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS; + return true; + } else if (strcmp(name, "finger") == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_FINGER; + return true; + } - return false; + return false; } static bool parseKeysHidden(const char* name, ResTable_config* out) { - uint8_t mask = 0; - uint8_t value = 0; - if (strcmp(name, kWildcardName) == 0) { - mask = ResTable_config::MASK_KEYSHIDDEN; - value = ResTable_config::KEYSHIDDEN_ANY; - } else if (strcmp(name, "keysexposed") == 0) { - mask = ResTable_config::MASK_KEYSHIDDEN; - value = ResTable_config::KEYSHIDDEN_NO; - } else if (strcmp(name, "keyshidden") == 0) { - mask = ResTable_config::MASK_KEYSHIDDEN; - value = ResTable_config::KEYSHIDDEN_YES; - } else if (strcmp(name, "keyssoft") == 0) { - mask = ResTable_config::MASK_KEYSHIDDEN; - value = ResTable_config::KEYSHIDDEN_SOFT; - } - - if (mask != 0) { - if (out) out->inputFlags = (out->inputFlags&~mask) | value; - return true; - } + uint8_t mask = 0; + uint8_t value = 0; + if (strcmp(name, kWildcardName) == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_ANY; + } else if (strcmp(name, "keysexposed") == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_NO; + } else if (strcmp(name, "keyshidden") == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_YES; + } else if (strcmp(name, "keyssoft") == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_SOFT; + } + + if (mask != 0) { + if (out) out->inputFlags = (out->inputFlags & ~mask) | value; + return true; + } - return false; + return false; } static bool parseKeyboard(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) out->keyboard = out->KEYBOARD_ANY; - return true; - } else if (strcmp(name, "nokeys") == 0) { - if (out) out->keyboard = out->KEYBOARD_NOKEYS; - return true; - } else if (strcmp(name, "qwerty") == 0) { - if (out) out->keyboard = out->KEYBOARD_QWERTY; - return true; - } else if (strcmp(name, "12key") == 0) { - if (out) out->keyboard = out->KEYBOARD_12KEY; - return true; - } + if (strcmp(name, kWildcardName) == 0) { + if (out) out->keyboard = out->KEYBOARD_ANY; + return true; + } else if (strcmp(name, "nokeys") == 0) { + if (out) out->keyboard = out->KEYBOARD_NOKEYS; + return true; + } else if (strcmp(name, "qwerty") == 0) { + if (out) out->keyboard = out->KEYBOARD_QWERTY; + return true; + } else if (strcmp(name, "12key") == 0) { + if (out) out->keyboard = out->KEYBOARD_12KEY; + return true; + } - return false; + return false; } static bool parseNavHidden(const char* name, ResTable_config* out) { - uint8_t mask = 0; - uint8_t value = 0; - if (strcmp(name, kWildcardName) == 0) { - mask = ResTable_config::MASK_NAVHIDDEN; - value = ResTable_config::NAVHIDDEN_ANY; - } else if (strcmp(name, "navexposed") == 0) { - mask = ResTable_config::MASK_NAVHIDDEN; - value = ResTable_config::NAVHIDDEN_NO; - } else if (strcmp(name, "navhidden") == 0) { - mask = ResTable_config::MASK_NAVHIDDEN; - value = ResTable_config::NAVHIDDEN_YES; - } - - if (mask != 0) { - if (out) out->inputFlags = (out->inputFlags&~mask) | value; - return true; - } + uint8_t mask = 0; + uint8_t value = 0; + if (strcmp(name, kWildcardName) == 0) { + mask = ResTable_config::MASK_NAVHIDDEN; + value = ResTable_config::NAVHIDDEN_ANY; + } else if (strcmp(name, "navexposed") == 0) { + mask = ResTable_config::MASK_NAVHIDDEN; + value = ResTable_config::NAVHIDDEN_NO; + } else if (strcmp(name, "navhidden") == 0) { + mask = ResTable_config::MASK_NAVHIDDEN; + value = ResTable_config::NAVHIDDEN_YES; + } + + if (mask != 0) { + if (out) out->inputFlags = (out->inputFlags & ~mask) | value; + return true; + } - return false; + return false; } static bool parseNavigation(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) out->navigation = out->NAVIGATION_ANY; - return true; - } else if (strcmp(name, "nonav") == 0) { - if (out) out->navigation = out->NAVIGATION_NONAV; - return true; - } else if (strcmp(name, "dpad") == 0) { - if (out) out->navigation = out->NAVIGATION_DPAD; - return true; - } else if (strcmp(name, "trackball") == 0) { - if (out) out->navigation = out->NAVIGATION_TRACKBALL; - return true; - } else if (strcmp(name, "wheel") == 0) { - if (out) out->navigation = out->NAVIGATION_WHEEL; - return true; - } + if (strcmp(name, kWildcardName) == 0) { + if (out) out->navigation = out->NAVIGATION_ANY; + return true; + } else if (strcmp(name, "nonav") == 0) { + if (out) out->navigation = out->NAVIGATION_NONAV; + return true; + } else if (strcmp(name, "dpad") == 0) { + if (out) out->navigation = out->NAVIGATION_DPAD; + return true; + } else if (strcmp(name, "trackball") == 0) { + if (out) out->navigation = out->NAVIGATION_TRACKBALL; + return true; + } else if (strcmp(name, "wheel") == 0) { + if (out) out->navigation = out->NAVIGATION_WHEEL; + return true; + } - return false; + return false; } static bool parseScreenSize(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) { - out->screenWidth = out->SCREENWIDTH_ANY; - out->screenHeight = out->SCREENHEIGHT_ANY; - } - return true; - } - - const char* x = name; - while (*x >= '0' && *x <= '9') x++; - if (x == name || *x != 'x') return false; - std::string xName(name, x-name); - x++; - - const char* y = x; - while (*y >= '0' && *y <= '9') y++; - if (y == name || *y != 0) return false; - std::string yName(x, y-x); - - uint16_t w = (uint16_t)atoi(xName.c_str()); - uint16_t h = (uint16_t)atoi(yName.c_str()); - if (w < h) { - return false; - } - + if (strcmp(name, kWildcardName) == 0) { if (out) { - out->screenWidth = w; - out->screenHeight = h; + out->screenWidth = out->SCREENWIDTH_ANY; + out->screenHeight = out->SCREENHEIGHT_ANY; } - return true; -} + } + + const char* x = name; + while (*x >= '0' && *x <= '9') x++; + if (x == name || *x != 'x') return false; + std::string xName(name, x - name); + x++; + + const char* y = x; + while (*y >= '0' && *y <= '9') y++; + if (y == name || *y != 0) return false; + std::string yName(x, y - x); + + uint16_t w = (uint16_t)atoi(xName.c_str()); + uint16_t h = (uint16_t)atoi(yName.c_str()); + if (w < h) { + return false; + } -static bool parseSmallestScreenWidthDp(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) { - out->smallestScreenWidthDp = out->SCREENWIDTH_ANY; - } - return true; - } + if (out) { + out->screenWidth = w; + out->screenHeight = h; + } - if (*name != 's') return false; - name++; - if (*name != 'w') return false; - name++; - const char* x = name; - while (*x >= '0' && *x <= '9') x++; - if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; - std::string xName(name, x-name); + return true; +} +static bool parseSmallestScreenWidthDp(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { if (out) { - out->smallestScreenWidthDp = (uint16_t)atoi(xName.c_str()); + out->smallestScreenWidthDp = out->SCREENWIDTH_ANY; } - return true; + } + + if (*name != 's') return false; + name++; + if (*name != 'w') return false; + name++; + const char* x = name; + while (*x >= '0' && *x <= '9') x++; + if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; + std::string xName(name, x - name); + + if (out) { + out->smallestScreenWidthDp = (uint16_t)atoi(xName.c_str()); + } + + return true; } static bool parseScreenWidthDp(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) { - out->screenWidthDp = out->SCREENWIDTH_ANY; - } - return true; + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->screenWidthDp = out->SCREENWIDTH_ANY; } + return true; + } - if (*name != 'w') return false; - name++; - const char* x = name; - while (*x >= '0' && *x <= '9') x++; - if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; - std::string xName(name, x-name); + if (*name != 'w') return false; + name++; + const char* x = name; + while (*x >= '0' && *x <= '9') x++; + if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; + std::string xName(name, x - name); - if (out) { - out->screenWidthDp = (uint16_t)atoi(xName.c_str()); - } + if (out) { + out->screenWidthDp = (uint16_t)atoi(xName.c_str()); + } - return true; + return true; } static bool parseScreenHeightDp(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) { - out->screenHeightDp = out->SCREENWIDTH_ANY; - } - return true; + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->screenHeightDp = out->SCREENWIDTH_ANY; } + return true; + } - if (*name != 'h') return false; - name++; - const char* x = name; - while (*x >= '0' && *x <= '9') x++; - if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; - std::string xName(name, x-name); + if (*name != 'h') return false; + name++; + const char* x = name; + while (*x >= '0' && *x <= '9') x++; + if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; + std::string xName(name, x - name); - if (out) { - out->screenHeightDp = (uint16_t)atoi(xName.c_str()); - } + if (out) { + out->screenHeightDp = (uint16_t)atoi(xName.c_str()); + } - return true; + return true; } static bool parseVersion(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) { - out->sdkVersion = out->SDKVERSION_ANY; - out->minorVersion = out->MINORVERSION_ANY; - } - return true; + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->sdkVersion = out->SDKVERSION_ANY; + out->minorVersion = out->MINORVERSION_ANY; } + return true; + } - if (*name != 'v') { - return false; - } + if (*name != 'v') { + return false; + } - name++; - const char* s = name; - while (*s >= '0' && *s <= '9') s++; - if (s == name || *s != 0) return false; - std::string sdkName(name, s-name); + name++; + const char* s = name; + while (*s >= '0' && *s <= '9') s++; + if (s == name || *s != 0) return false; + std::string sdkName(name, s - name); - if (out) { - out->sdkVersion = (uint16_t)atoi(sdkName.c_str()); - out->minorVersion = 0; - } + if (out) { + out->sdkVersion = (uint16_t)atoi(sdkName.c_str()); + out->minorVersion = 0; + } - return true; + return true; } -bool ConfigDescription::parse(const StringPiece& str, ConfigDescription* out) { - std::vector<std::string> parts = util::splitAndLowercase(str, '-'); +bool ConfigDescription::Parse(const StringPiece& str, ConfigDescription* out) { + std::vector<std::string> parts = util::SplitAndLowercase(str, '-'); - ConfigDescription config; - ssize_t partsConsumed = 0; - LocaleValue locale; + ConfigDescription config; + ssize_t parts_consumed = 0; + LocaleValue locale; - const auto partsEnd = parts.end(); - auto partIter = parts.begin(); + const auto parts_end = parts.end(); + auto part_iter = parts.begin(); - if (str.size() == 0) { - goto success; - } + if (str.size() == 0) { + goto success; + } - if (parseMcc(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseMcc(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseMnc(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseMnc(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - // Locale spans a few '-' separators, so we let it - // control the index. - partsConsumed = locale.initFromParts(partIter, partsEnd); - if (partsConsumed < 0) { - return false; - } else { - locale.writeTo(&config); - partIter += partsConsumed; - if (partIter == partsEnd) { - goto success; - } + // Locale spans a few '-' separators, so we let it + // control the index. + parts_consumed = locale.InitFromParts(part_iter, parts_end); + if (parts_consumed < 0) { + return false; + } else { + locale.WriteTo(&config); + part_iter += parts_consumed; + if (part_iter == parts_end) { + goto success; } + } - if (parseLayoutDirection(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseLayoutDirection(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseSmallestScreenWidthDp(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseSmallestScreenWidthDp(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseScreenWidthDp(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseScreenWidthDp(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseScreenHeightDp(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseScreenHeightDp(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseScreenLayoutSize(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseScreenLayoutSize(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseScreenLayoutLong(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseScreenLayoutLong(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseScreenRound(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseScreenRound(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseOrientation(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseOrientation(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseUiModeType(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseUiModeType(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseUiModeNight(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseUiModeNight(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseDensity(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseDensity(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseTouchscreen(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseTouchscreen(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseKeysHidden(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseKeysHidden(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseKeyboard(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseKeyboard(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseNavHidden(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseNavHidden(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseNavigation(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseNavigation(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseScreenSize(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseScreenSize(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - if (parseVersion(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseVersion(part_iter->c_str(), &config)) { + ++part_iter; + if (part_iter == parts_end) { + goto success; } + } - // Unrecognized. - return false; + // Unrecognized. + return false; success: - if (out != NULL) { - applyVersionForCompatibility(&config); - *out = config; - } + if (out != NULL) { + ApplyVersionForCompatibility(&config); + *out = config; + } + return true; +} + +void ConfigDescription::ApplyVersionForCompatibility( + ConfigDescription* config) { + uint16_t min_sdk = 0; + if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) { + min_sdk = SDK_MARSHMALLOW; + } else if (config->density == ResTable_config::DENSITY_ANY) { + min_sdk = SDK_LOLLIPOP; + } else if (config->smallestScreenWidthDp != + ResTable_config::SCREENWIDTH_ANY || + config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY || + config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) { + min_sdk = SDK_HONEYCOMB_MR2; + } else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE) != + ResTable_config::UI_MODE_TYPE_ANY || + (config->uiMode & ResTable_config::MASK_UI_MODE_NIGHT) != + ResTable_config::UI_MODE_NIGHT_ANY) { + min_sdk = SDK_FROYO; + } else if ((config->screenLayout & ResTable_config::MASK_SCREENSIZE) != + ResTable_config::SCREENSIZE_ANY || + (config->screenLayout & ResTable_config::MASK_SCREENLONG) != + ResTable_config::SCREENLONG_ANY || + config->density != ResTable_config::DENSITY_DEFAULT) { + min_sdk = SDK_DONUT; + } + + if (min_sdk > config->sdkVersion) { + config->sdkVersion = min_sdk; + } +} + +ConfigDescription ConfigDescription::CopyWithoutSdkVersion() const { + ConfigDescription copy = *this; + copy.sdkVersion = 0; + return copy; +} + +bool ConfigDescription::Dominates(const ConfigDescription& o) const { + if (*this == DefaultConfig() || *this == o) { return true; + } + return MatchWithDensity(o) && !o.MatchWithDensity(*this) && + !isMoreSpecificThan(o) && !o.HasHigherPrecedenceThan(*this); } -void ConfigDescription::applyVersionForCompatibility(ConfigDescription* config) { - uint16_t minSdk = 0; - if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) { - minSdk = SDK_MARSHMALLOW; - } else if (config->density == ResTable_config::DENSITY_ANY) { - minSdk = SDK_LOLLIPOP; - } else if (config->smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY - || config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY - || config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) { - minSdk = SDK_HONEYCOMB_MR2; - } else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE) - != ResTable_config::UI_MODE_TYPE_ANY - || (config->uiMode & ResTable_config::MASK_UI_MODE_NIGHT) - != ResTable_config::UI_MODE_NIGHT_ANY) { - minSdk = SDK_FROYO; - } else if ((config->screenLayout & ResTable_config::MASK_SCREENSIZE) - != ResTable_config::SCREENSIZE_ANY - || (config->screenLayout & ResTable_config::MASK_SCREENLONG) - != ResTable_config::SCREENLONG_ANY - || config->density != ResTable_config::DENSITY_DEFAULT) { - minSdk = SDK_DONUT; - } +bool ConfigDescription::HasHigherPrecedenceThan( + const ConfigDescription& o) const { + // The order of the following tests defines the importance of one + // configuration parameter over another. Those tests first are more + // important, trumping any values in those following them. + // The ordering should be the same as ResTable_config#isBetterThan. + if (mcc || o.mcc) return (!o.mcc); + if (mnc || o.mnc) return (!o.mnc); + if (language[0] || o.language[0]) return (!o.language[0]); + if (country[0] || o.country[0]) return (!o.country[0]); + // Script and variant require either a language or country, both of which + // have higher precedence. + if ((screenLayout | o.screenLayout) & MASK_LAYOUTDIR) { + return !(o.screenLayout & MASK_LAYOUTDIR); + } + if (smallestScreenWidthDp || o.smallestScreenWidthDp) + return (!o.smallestScreenWidthDp); + if (screenWidthDp || o.screenWidthDp) return (!o.screenWidthDp); + if (screenHeightDp || o.screenHeightDp) return (!o.screenHeightDp); + if ((screenLayout | o.screenLayout) & MASK_SCREENSIZE) { + return !(o.screenLayout & MASK_SCREENSIZE); + } + if ((screenLayout | o.screenLayout) & MASK_SCREENLONG) { + return !(o.screenLayout & MASK_SCREENLONG); + } + if ((screenLayout2 | o.screenLayout2) & MASK_SCREENROUND) { + return !(o.screenLayout2 & MASK_SCREENROUND); + } + if (orientation || o.orientation) return (!o.orientation); + if ((uiMode | o.uiMode) & MASK_UI_MODE_TYPE) { + return !(o.uiMode & MASK_UI_MODE_TYPE); + } + if ((uiMode | o.uiMode) & MASK_UI_MODE_NIGHT) { + return !(o.uiMode & MASK_UI_MODE_NIGHT); + } + if (density || o.density) return (!o.density); + if (touchscreen || o.touchscreen) return (!o.touchscreen); + if ((inputFlags | o.inputFlags) & MASK_KEYSHIDDEN) { + return !(o.inputFlags & MASK_KEYSHIDDEN); + } + if ((inputFlags | o.inputFlags) & MASK_NAVHIDDEN) { + return !(o.inputFlags & MASK_NAVHIDDEN); + } + if (keyboard || o.keyboard) return (!o.keyboard); + if (navigation || o.navigation) return (!o.navigation); + if (screenWidth || o.screenWidth) return (!o.screenWidth); + if (screenHeight || o.screenHeight) return (!o.screenHeight); + if (sdkVersion || o.sdkVersion) return (!o.sdkVersion); + if (minorVersion || o.minorVersion) return (!o.minorVersion); + // Both configurations have nothing defined except some possible future + // value. Returning the comparison of the two configurations is a + // "best effort" at this point to protect against incorrect dominations. + return *this != o; +} - if (minSdk > config->sdkVersion) { - config->sdkVersion = minSdk; - } +bool ConfigDescription::ConflictsWith(const ConfigDescription& o) const { + // This method should be updated as new configuration parameters are + // introduced (e.g. screenConfig2). + auto pred = [](const uint32_t a, const uint32_t b) -> bool { + return a == 0 || b == 0 || a == b; + }; + // The values here can be found in ResTable_config#match. Density and range + // values can't lead to conflicts, and are ignored. + return !pred(mcc, o.mcc) || !pred(mnc, o.mnc) || !pred(locale, o.locale) || + !pred(screenLayout & MASK_LAYOUTDIR, + o.screenLayout & MASK_LAYOUTDIR) || + !pred(screenLayout & MASK_SCREENLONG, + o.screenLayout & MASK_SCREENLONG) || + !pred(screenLayout & MASK_UI_MODE_TYPE, + o.screenLayout & MASK_UI_MODE_TYPE) || + !pred(uiMode & MASK_UI_MODE_TYPE, o.uiMode & MASK_UI_MODE_TYPE) || + !pred(uiMode & MASK_UI_MODE_NIGHT, o.uiMode & MASK_UI_MODE_NIGHT) || + !pred(screenLayout2 & MASK_SCREENROUND, + o.screenLayout2 & MASK_SCREENROUND) || + !pred(orientation, o.orientation) || + !pred(touchscreen, o.touchscreen) || + !pred(inputFlags & MASK_KEYSHIDDEN, o.inputFlags & MASK_KEYSHIDDEN) || + !pred(inputFlags & MASK_NAVHIDDEN, o.inputFlags & MASK_NAVHIDDEN) || + !pred(keyboard, o.keyboard) || !pred(navigation, o.navigation); +} + +bool ConfigDescription::IsCompatibleWith(const ConfigDescription& o) const { + return !ConflictsWith(o) && !Dominates(o) && !o.Dominates(*this); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h index b1397d2e2816..97d0f38a5af1 100644 --- a/tools/aapt2/ConfigDescription.h +++ b/tools/aapt2/ConfigDescription.h @@ -17,11 +17,12 @@ #ifndef AAPT_CONFIG_DESCRIPTION_H #define AAPT_CONFIG_DESCRIPTION_H -#include "util/StringPiece.h" - -#include <androidfw/ResourceTypes.h> #include <ostream> +#include "androidfw/ResourceTypes.h" + +#include "util/StringPiece.h" + namespace aapt { /* @@ -29,106 +30,152 @@ namespace aapt { * initialization and comparison methods. */ struct ConfigDescription : public android::ResTable_config { - /** - * Returns an immutable default config. - */ - static const ConfigDescription& defaultConfig(); - - /* - * Parse a string of the form 'fr-sw600dp-land' and fill in the - * given ResTable_config with resulting configuration parameters. - * - * The resulting configuration has the appropriate sdkVersion defined - * for backwards compatibility. - */ - static bool parse(const StringPiece& str, ConfigDescription* out = nullptr); - - /** - * If the configuration uses an axis that was added after - * the original Android release, make sure the SDK version - * is set accordingly. - */ - static void applyVersionForCompatibility(ConfigDescription* config); - - ConfigDescription(); - ConfigDescription(const android::ResTable_config& o); // NOLINT(implicit) - ConfigDescription(const ConfigDescription& o); - ConfigDescription(ConfigDescription&& o); - - ConfigDescription& operator=(const android::ResTable_config& o); - ConfigDescription& operator=(const ConfigDescription& o); - ConfigDescription& operator=(ConfigDescription&& o); - - bool operator<(const ConfigDescription& o) const; - bool operator<=(const ConfigDescription& o) const; - bool operator==(const ConfigDescription& o) const; - bool operator!=(const ConfigDescription& o) const; - bool operator>=(const ConfigDescription& o) const; - bool operator>(const ConfigDescription& o) const; + /** + * Returns an immutable default config. + */ + static const ConfigDescription& DefaultConfig(); + + /* + * Parse a string of the form 'fr-sw600dp-land' and fill in the + * given ResTable_config with resulting configuration parameters. + * + * The resulting configuration has the appropriate sdkVersion defined + * for backwards compatibility. + */ + static bool Parse(const StringPiece& str, ConfigDescription* out = nullptr); + + /** + * If the configuration uses an axis that was added after + * the original Android release, make sure the SDK version + * is set accordingly. + */ + static void ApplyVersionForCompatibility(ConfigDescription* config); + + ConfigDescription(); + ConfigDescription(const android::ResTable_config& o); // NOLINT(implicit) + ConfigDescription(const ConfigDescription& o); + ConfigDescription(ConfigDescription&& o); + + ConfigDescription& operator=(const android::ResTable_config& o); + ConfigDescription& operator=(const ConfigDescription& o); + ConfigDescription& operator=(ConfigDescription&& o); + + ConfigDescription CopyWithoutSdkVersion() const; + + /** + * A configuration X dominates another configuration Y, if X has at least the + * precedence of Y and X is strictly more general than Y: for any type defined + * by X, the same type is defined by Y with a value equal to or, in the case + * of ranges, more specific than that of X. + * + * For example, the configuration 'en-w800dp' dominates 'en-rGB-w1024dp'. It + * does not dominate 'fr', 'en-w720dp', or 'mcc001-en-w800dp'. + */ + bool Dominates(const ConfigDescription& o) const; + + /** + * Returns true if this configuration defines a more important configuration + * parameter than o. For example, "en" has higher precedence than "v23", + * whereas "en" has the same precedence as "en-v23". + */ + bool HasHigherPrecedenceThan(const ConfigDescription& o) const; + + /** + * A configuration conflicts with another configuration if both + * configurations define an incompatible configuration parameter. An + * incompatible configuration parameter is a non-range, non-density parameter + * that is defined in both configurations as a different, non-default value. + */ + bool ConflictsWith(const ConfigDescription& o) const; + + /** + * A configuration is compatible with another configuration if both + * configurations can match a common concrete device configuration and are + * unrelated by domination. For example, land-v11 conflicts with port-v21 + * but is compatible with v21 (both land-v11 and v21 would match en-land-v23). + */ + bool IsCompatibleWith(const ConfigDescription& o) const; + + bool MatchWithDensity(const ConfigDescription& o) const; + + bool operator<(const ConfigDescription& o) const; + bool operator<=(const ConfigDescription& o) const; + bool operator==(const ConfigDescription& o) const; + bool operator!=(const ConfigDescription& o) const; + bool operator>=(const ConfigDescription& o) const; + bool operator>(const ConfigDescription& o) const; }; inline ConfigDescription::ConfigDescription() { - memset(this, 0, sizeof(*this)); - size = sizeof(android::ResTable_config); + memset(this, 0, sizeof(*this)); + size = sizeof(android::ResTable_config); } inline ConfigDescription::ConfigDescription(const android::ResTable_config& o) { - *static_cast<android::ResTable_config*>(this) = o; - size = sizeof(android::ResTable_config); + *static_cast<android::ResTable_config*>(this) = o; + size = sizeof(android::ResTable_config); } inline ConfigDescription::ConfigDescription(const ConfigDescription& o) { - *static_cast<android::ResTable_config*>(this) = o; + *static_cast<android::ResTable_config*>(this) = o; } inline ConfigDescription::ConfigDescription(ConfigDescription&& o) { - *this = o; + *this = o; } -inline ConfigDescription& ConfigDescription::operator=(const android::ResTable_config& o) { - *static_cast<android::ResTable_config*>(this) = o; - size = sizeof(android::ResTable_config); - return *this; +inline ConfigDescription& ConfigDescription::operator=( + const android::ResTable_config& o) { + *static_cast<android::ResTable_config*>(this) = o; + size = sizeof(android::ResTable_config); + return *this; } -inline ConfigDescription& ConfigDescription::operator=(const ConfigDescription& o) { - *static_cast<android::ResTable_config*>(this) = o; - return *this; +inline ConfigDescription& ConfigDescription::operator=( + const ConfigDescription& o) { + *static_cast<android::ResTable_config*>(this) = o; + return *this; } inline ConfigDescription& ConfigDescription::operator=(ConfigDescription&& o) { - *this = o; - return *this; + *this = o; + return *this; +} + +inline bool ConfigDescription::MatchWithDensity( + const ConfigDescription& o) const { + return match(o) && (density == 0 || density == o.density); } inline bool ConfigDescription::operator<(const ConfigDescription& o) const { - return compare(o) < 0; + return compare(o) < 0; } inline bool ConfigDescription::operator<=(const ConfigDescription& o) const { - return compare(o) <= 0; + return compare(o) <= 0; } inline bool ConfigDescription::operator==(const ConfigDescription& o) const { - return compare(o) == 0; + return compare(o) == 0; } inline bool ConfigDescription::operator!=(const ConfigDescription& o) const { - return compare(o) != 0; + return compare(o) != 0; } inline bool ConfigDescription::operator>=(const ConfigDescription& o) const { - return compare(o) >= 0; + return compare(o) >= 0; } inline bool ConfigDescription::operator>(const ConfigDescription& o) const { - return compare(o) > 0; + return compare(o) > 0; } -inline ::std::ostream& operator<<(::std::ostream& out, const ConfigDescription& o) { - return out << o.toString().string(); +inline ::std::ostream& operator<<(::std::ostream& out, + const ConfigDescription& o) { + return out << o.toString().string(); } -} // namespace aapt +} // namespace aapt -#endif // AAPT_CONFIG_DESCRIPTION_H +#endif // AAPT_CONFIG_DESCRIPTION_H diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp index e68d6be536df..c331dc0f6909 100644 --- a/tools/aapt2/ConfigDescription_test.cpp +++ b/tools/aapt2/ConfigDescription_test.cpp @@ -15,85 +15,88 @@ */ #include "ConfigDescription.h" -#include "SdkConstants.h" - -#include "util/StringPiece.h" -#include <gtest/gtest.h> #include <string> +#include "SdkConstants.h" +#include "test/Test.h" +#include "util/StringPiece.h" + namespace aapt { -static ::testing::AssertionResult TestParse(const StringPiece& input, - ConfigDescription* config = nullptr) { - if (ConfigDescription::parse(input, config)) { - return ::testing::AssertionSuccess() << input << " was successfully parsed"; - } - return ::testing::AssertionFailure() << input << " could not be parsed"; +static ::testing::AssertionResult TestParse( + const StringPiece& input, ConfigDescription* config = nullptr) { + if (ConfigDescription::Parse(input, config)) { + return ::testing::AssertionSuccess() << input << " was successfully parsed"; + } + return ::testing::AssertionFailure() << input << " could not be parsed"; } TEST(ConfigDescriptionTest, ParseFailWhenQualifiersAreOutOfOrder) { - EXPECT_FALSE(TestParse("en-sw600dp-ldrtl")); - EXPECT_FALSE(TestParse("land-en")); - EXPECT_FALSE(TestParse("hdpi-320dpi")); + EXPECT_FALSE(TestParse("en-sw600dp-ldrtl")); + EXPECT_FALSE(TestParse("land-en")); + EXPECT_FALSE(TestParse("hdpi-320dpi")); } TEST(ConfigDescriptionTest, ParseFailWhenQualifiersAreNotMatched) { - EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL")); + EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL")); } TEST(ConfigDescriptionTest, ParseFailWhenQualifiersHaveTrailingDash) { - EXPECT_FALSE(TestParse("en-sw600dp-land-")); + EXPECT_FALSE(TestParse("en-sw600dp-land-")); } TEST(ConfigDescriptionTest, ParseBasicQualifiers) { - ConfigDescription config; - EXPECT_TRUE(TestParse("", &config)); - EXPECT_EQ(std::string(""), config.toString().string()); - - EXPECT_TRUE(TestParse("fr-land", &config)); - EXPECT_EQ(std::string("fr-land"), config.toString().string()); - - EXPECT_TRUE(TestParse("mcc310-pl-sw720dp-normal-long-port-night-" - "xhdpi-keyssoft-qwerty-navexposed-nonav", &config)); - EXPECT_EQ(std::string("mcc310-pl-sw720dp-normal-long-port-night-" - "xhdpi-keyssoft-qwerty-navexposed-nonav-v13"), config.toString().string()); + ConfigDescription config; + EXPECT_TRUE(TestParse("", &config)); + EXPECT_EQ(std::string(""), config.toString().string()); + + EXPECT_TRUE(TestParse("fr-land", &config)); + EXPECT_EQ(std::string("fr-land"), config.toString().string()); + + EXPECT_TRUE( + TestParse("mcc310-pl-sw720dp-normal-long-port-night-" + "xhdpi-keyssoft-qwerty-navexposed-nonav", + &config)); + EXPECT_EQ(std::string("mcc310-pl-sw720dp-normal-long-port-night-" + "xhdpi-keyssoft-qwerty-navexposed-nonav-v13"), + config.toString().string()); } TEST(ConfigDescriptionTest, ParseLocales) { - ConfigDescription config; - EXPECT_TRUE(TestParse("en-rUS", &config)); - EXPECT_EQ(std::string("en-rUS"), config.toString().string()); + ConfigDescription config; + EXPECT_TRUE(TestParse("en-rUS", &config)); + EXPECT_EQ(std::string("en-rUS"), config.toString().string()); } TEST(ConfigDescriptionTest, ParseQualifierAddedInApi13) { - ConfigDescription config; - EXPECT_TRUE(TestParse("sw600dp", &config)); - EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string()); + ConfigDescription config; + EXPECT_TRUE(TestParse("sw600dp", &config)); + EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string()); - EXPECT_TRUE(TestParse("sw600dp-v8", &config)); - EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string()); + EXPECT_TRUE(TestParse("sw600dp-v8", &config)); + EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string()); } TEST(ConfigDescriptionTest, ParseCarAttribute) { - ConfigDescription config; - EXPECT_TRUE(TestParse("car", &config)); - EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_CAR, config.uiMode); + ConfigDescription config; + EXPECT_TRUE(TestParse("car", &config)); + EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_CAR, config.uiMode); } TEST(ConfigDescriptionTest, TestParsingRoundQualifier) { - ConfigDescription config; - EXPECT_TRUE(TestParse("round", &config)); - EXPECT_EQ(android::ResTable_config::SCREENROUND_YES, - config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND); - EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion); - EXPECT_EQ(std::string("round-v23"), config.toString().string()); - - EXPECT_TRUE(TestParse("notround", &config)); - EXPECT_EQ(android::ResTable_config::SCREENROUND_NO, - config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND); - EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion); - EXPECT_EQ(std::string("notround-v23"), config.toString().string()); + ConfigDescription config; + EXPECT_TRUE(TestParse("round", &config)); + EXPECT_EQ(android::ResTable_config::SCREENROUND_YES, + config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND); + EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion); + EXPECT_EQ(std::string("round-v23"), config.toString().string()); + + EXPECT_TRUE(TestParse("notround", &config)); + EXPECT_EQ(android::ResTable_config::SCREENROUND_NO, + config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND); + EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion); + EXPECT_EQ(std::string("notround-v23"), config.toString().string()); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index 19bd5210c840..60b01e372d7b 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -15,10 +15,6 @@ */ #include "Debug.h" -#include "ResourceTable.h" -#include "ResourceValues.h" -#include "util/Util.h" -#include "ValueVisitor.h" #include <algorithm> #include <iostream> @@ -28,224 +24,290 @@ #include <set> #include <vector> +#include "android-base/logging.h" + +#include "ResourceTable.h" +#include "ResourceValues.h" +#include "ValueVisitor.h" +#include "util/Util.h" + namespace aapt { class PrintVisitor : public ValueVisitor { -public: - using ValueVisitor::visit; - - void visit(Attribute* attr) override { - std::cout << "(attr) type="; - attr->printMask(&std::cout); - static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM | - android::ResTable_map::TYPE_FLAGS; - if (attr->typeMask & kMask) { - for (const auto& symbol : attr->symbols) { - std::cout << "\n " << symbol.symbol.name.value().entry; - if (symbol.symbol.id) { - std::cout << " (" << symbol.symbol.id.value() << ")"; - } - std::cout << " = " << symbol.value; - } + public: + using ValueVisitor::Visit; + + void Visit(Attribute* attr) override { + std::cout << "(attr) type="; + attr->PrintMask(&std::cout); + static constexpr uint32_t kMask = + android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS; + if (attr->type_mask & kMask) { + for (const auto& symbol : attr->symbols) { + std::cout << "\n " << symbol.symbol.name.value().entry; + if (symbol.symbol.id) { + std::cout << " (" << symbol.symbol.id.value() << ")"; } + std::cout << " = " << symbol.value; + } } - - void visit(Style* style) override { - std::cout << "(style)"; - if (style->parent) { - const Reference& parentRef = style->parent.value(); - std::cout << " parent="; - if (parentRef.name) { - if (parentRef.privateReference) { - std::cout << "*"; - } - std::cout << parentRef.name.value() << " "; - } - - if (parentRef.id) { - std::cout << parentRef.id.value(); - } + } + + void Visit(Style* style) override { + std::cout << "(style)"; + if (style->parent) { + const Reference& parent_ref = style->parent.value(); + std::cout << " parent="; + if (parent_ref.name) { + if (parent_ref.private_reference) { + std::cout << "*"; } + std::cout << parent_ref.name.value() << " "; + } - for (const auto& entry : style->entries) { - std::cout << "\n "; - if (entry.key.name) { - const ResourceName& name = entry.key.name.value(); - if (!name.package.empty()) { - std::cout << name.package << ":"; - } - std::cout << name.entry; - } - - if (entry.key.id) { - std::cout << "(" << entry.key.id.value() << ")"; - } - - std::cout << "=" << *entry.value; - } + if (parent_ref.id) { + std::cout << parent_ref.id.value(); + } } - void visit(Array* array) override { - array->print(&std::cout); - } + for (const auto& entry : style->entries) { + std::cout << "\n "; + if (entry.key.name) { + const ResourceName& name = entry.key.name.value(); + if (!name.package.empty()) { + std::cout << name.package << ":"; + } + std::cout << name.entry; + } + + if (entry.key.id) { + std::cout << "(" << entry.key.id.value() << ")"; + } - void visit(Plural* plural) override { - plural->print(&std::cout); + std::cout << "=" << *entry.value; } + } - void visit(Styleable* styleable) override { - std::cout << "(styleable)"; - for (const auto& attr : styleable->entries) { - std::cout << "\n "; - if (attr.name) { - const ResourceName& name = attr.name.value(); - if (!name.package.empty()) { - std::cout << name.package << ":"; - } - std::cout << name.entry; - } - - if (attr.id) { - std::cout << "(" << attr.id.value() << ")"; - } + void Visit(Array* array) override { array->Print(&std::cout); } + + void Visit(Plural* plural) override { plural->Print(&std::cout); } + + void Visit(Styleable* styleable) override { + std::cout << "(styleable)"; + for (const auto& attr : styleable->entries) { + std::cout << "\n "; + if (attr.name) { + const ResourceName& name = attr.name.value(); + if (!name.package.empty()) { + std::cout << name.package << ":"; } - } + std::cout << name.entry; + } - void visitItem(Item* item) override { - item->print(&std::cout); + if (attr.id) { + std::cout << "(" << attr.id.value() << ")"; + } } + } + + void VisitItem(Item* item) override { item->Print(&std::cout); } }; -void Debug::printTable(ResourceTable* table, const DebugPrintTableOptions& options) { - PrintVisitor visitor; +void Debug::PrintTable(ResourceTable* table, + const DebugPrintTableOptions& options) { + PrintVisitor visitor; - for (auto& package : table->packages) { - std::cout << "Package name=" << package->name; - if (package->id) { - std::cout << " id=" << std::hex << (int) package->id.value() << std::dec; + for (auto& package : table->packages) { + std::cout << "Package name=" << package->name; + if (package->id) { + std::cout << " id=" << std::hex << (int)package->id.value() << std::dec; + } + std::cout << std::endl; + + for (const auto& type : package->types) { + std::cout << "\n type " << type->type; + if (type->id) { + std::cout << " id=" << std::hex << (int)type->id.value() << std::dec; + } + std::cout << " entryCount=" << type->entries.size() << std::endl; + + std::vector<const ResourceEntry*> sorted_entries; + for (const auto& entry : type->entries) { + auto iter = std::lower_bound( + sorted_entries.begin(), sorted_entries.end(), entry.get(), + [](const ResourceEntry* a, const ResourceEntry* b) -> bool { + if (a->id && b->id) { + return a->id.value() < b->id.value(); + } else if (a->id) { + return true; + } else { + return false; + } + }); + sorted_entries.insert(iter, entry.get()); + } + + for (const ResourceEntry* entry : sorted_entries) { + ResourceId id(package->id ? package->id.value() : uint8_t(0), + type->id ? type->id.value() : uint8_t(0), + entry->id ? entry->id.value() : uint16_t(0)); + ResourceName name(package->name, type->type, entry->name); + + std::cout << " spec resource " << id << " " << name; + switch (entry->symbol_status.state) { + case SymbolState::kPublic: + std::cout << " PUBLIC"; + break; + case SymbolState::kPrivate: + std::cout << " _PRIVATE_"; + break; + default: + break; } + std::cout << std::endl; - for (const auto& type : package->types) { - std::cout << "\n type " << type->type; - if (type->id) { - std::cout << " id=" << std::hex << (int) type->id.value() << std::dec; - } - std::cout << " entryCount=" << type->entries.size() << std::endl; - - std::vector<const ResourceEntry*> sortedEntries; - for (const auto& entry : type->entries) { - auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(), - [](const ResourceEntry* a, const ResourceEntry* b) -> bool { - if (a->id && b->id) { - return a->id.value() < b->id.value(); - } else if (a->id) { - return true; - } else { - return false; - } - }); - sortedEntries.insert(iter, entry.get()); - } - - for (const ResourceEntry* entry : sortedEntries) { - ResourceId id(package->id ? package->id.value() : uint8_t(0), - type->id ? type->id.value() : uint8_t(0), - entry->id ? entry->id.value() : uint16_t(0)); - ResourceName name(package->name, type->type, entry->name); - - std::cout << " spec resource " << id << " " << name; - switch (entry->symbolStatus.state) { - case SymbolState::kPublic: std::cout << " PUBLIC"; break; - case SymbolState::kPrivate: std::cout << " _PRIVATE_"; break; - default: break; - } - - std::cout << std::endl; - - for (const auto& value : entry->values) { - std::cout << " (" << value->config << ") "; - value->value->accept(&visitor); - if (options.showSources && !value->value->getSource().path.empty()) { - std::cout << " src=" << value->value->getSource(); - } - std::cout << std::endl; - } - } + for (const auto& value : entry->values) { + std::cout << " (" << value->config << ") "; + value->value->Accept(&visitor); + if (options.show_sources && !value->value->GetSource().path.empty()) { + std::cout << " src=" << value->value->GetSource(); + } + std::cout << std::endl; } + } } + } } -static size_t getNodeIndex(const std::vector<ResourceName>& names, const ResourceName& name) { - auto iter = std::lower_bound(names.begin(), names.end(), name); - assert(iter != names.end() && *iter == name); - return std::distance(names.begin(), iter); +static size_t GetNodeIndex(const std::vector<ResourceName>& names, + const ResourceName& name) { + auto iter = std::lower_bound(names.begin(), names.end(), name); + CHECK(iter != names.end()); + CHECK(*iter == name); + return std::distance(names.begin(), iter); } -void Debug::printStyleGraph(ResourceTable* table, const ResourceName& targetStyle) { - std::map<ResourceName, std::set<ResourceName>> graph; - - std::queue<ResourceName> stylesToVisit; - stylesToVisit.push(targetStyle); - for (; !stylesToVisit.empty(); stylesToVisit.pop()) { - const ResourceName& styleName = stylesToVisit.front(); - std::set<ResourceName>& parents = graph[styleName]; - if (!parents.empty()) { - // We've already visited this style. - continue; - } - - Maybe<ResourceTable::SearchResult> result = table->findResource(styleName); - if (result) { - ResourceEntry* entry = result.value().entry; - for (const auto& value : entry->values) { - if (Style* style = valueCast<Style>(value->value.get())) { - if (style->parent && style->parent.value().name) { - parents.insert(style->parent.value().name.value()); - stylesToVisit.push(style->parent.value().name.value()); - } - } - } - } +void Debug::PrintStyleGraph(ResourceTable* table, + const ResourceName& target_style) { + std::map<ResourceName, std::set<ResourceName>> graph; + + std::queue<ResourceName> styles_to_visit; + styles_to_visit.push(target_style); + for (; !styles_to_visit.empty(); styles_to_visit.pop()) { + const ResourceName& style_name = styles_to_visit.front(); + std::set<ResourceName>& parents = graph[style_name]; + if (!parents.empty()) { + // We've already visited this style. + continue; } - std::vector<ResourceName> names; - for (const auto& entry : graph) { - names.push_back(entry.first); + Maybe<ResourceTable::SearchResult> result = table->FindResource(style_name); + if (result) { + ResourceEntry* entry = result.value().entry; + for (const auto& value : entry->values) { + if (Style* style = ValueCast<Style>(value->value.get())) { + if (style->parent && style->parent.value().name) { + parents.insert(style->parent.value().name.value()); + styles_to_visit.push(style->parent.value().name.value()); + } + } + } } - - std::cout << "digraph styles {\n"; - for (const auto& name : names) { - std::cout << " node_" << getNodeIndex(names, name) - << " [label=\"" << name << "\"];\n"; + } + + std::vector<ResourceName> names; + for (const auto& entry : graph) { + names.push_back(entry.first); + } + + std::cout << "digraph styles {\n"; + for (const auto& name : names) { + std::cout << " node_" << GetNodeIndex(names, name) << " [label=\"" << name + << "\"];\n"; + } + + for (const auto& entry : graph) { + const ResourceName& style_name = entry.first; + size_t style_node_index = GetNodeIndex(names, style_name); + + for (const auto& parent_name : entry.second) { + std::cout << " node_" << style_node_index << " -> " + << "node_" << GetNodeIndex(names, parent_name) << ";\n"; } + } - for (const auto& entry : graph) { - const ResourceName& styleName = entry.first; - size_t styleNodeIndex = getNodeIndex(names, styleName); + std::cout << "}" << std::endl; +} - for (const auto& parentName : entry.second) { - std::cout << " node_" << styleNodeIndex << " -> " - << "node_" << getNodeIndex(names, parentName) << ";\n"; - } +void Debug::DumpHex(const void* data, size_t len) { + const uint8_t* d = (const uint8_t*)data; + for (size_t i = 0; i < len; i++) { + std::cerr << std::hex << std::setfill('0') << std::setw(2) << (uint32_t)d[i] + << " "; + if (i % 8 == 7) { + std::cerr << "\n"; } + } - std::cout << "}" << std::endl; + if (len - 1 % 8 != 7) { + std::cerr << std::endl; + } } -void Debug::dumpHex(const void* data, size_t len) { - const uint8_t* d = (const uint8_t*) data; - for (size_t i = 0; i < len; i++) { - std::cerr << std::hex << std::setfill('0') << std::setw(2) << (uint32_t) d[i] << " "; - if (i % 8 == 7) { - std::cerr << "\n"; - } - } +namespace { + +class XmlPrinter : public xml::Visitor { + public: + using xml::Visitor::Visit; - if (len - 1 % 8 != 7) { - std::cerr << std::endl; + void Visit(xml::Element* el) override { + std::cerr << prefix_; + std::cerr << "E: "; + if (!el->namespace_uri.empty()) { + std::cerr << el->namespace_uri << ":"; + } + std::cerr << el->name << " (line=" << el->line_number << ")\n"; + + for (const xml::Attribute& attr : el->attributes) { + std::cerr << prefix_ << " A: "; + if (!attr.namespace_uri.empty()) { + std::cerr << attr.namespace_uri << ":"; + } + std::cerr << attr.name << "=" << attr.value << "\n"; } -} + const size_t previous_size = prefix_.size(); + prefix_ += " "; + xml::Visitor::Visit(el); + prefix_.resize(previous_size); + } + + void Visit(xml::Namespace* ns) override { + std::cerr << prefix_; + std::cerr << "N: " << ns->namespace_prefix << "=" << ns->namespace_uri + << " (line=" << ns->line_number << ")\n"; + + const size_t previous_size = prefix_.size(); + prefix_ += " "; + xml::Visitor::Visit(ns); + prefix_.resize(previous_size); + } + + void Visit(xml::Text* text) override { + std::cerr << prefix_; + std::cerr << "T: '" << text->text << "'\n"; + } + + private: + std::string prefix_; +}; + +} // namespace + +void Debug::DumpXml(xml::XmlResource* doc) { + XmlPrinter printer; + doc->root->Accept(&printer); +} -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h index fbe64773d4ed..56e2e95df6cb 100644 --- a/tools/aapt2/Debug.h +++ b/tools/aapt2/Debug.h @@ -17,25 +17,28 @@ #ifndef AAPT_DEBUG_H #define AAPT_DEBUG_H -#include "Resource.h" -#include "ResourceTable.h" - // Include for printf-like debugging. #include <iostream> +#include "Resource.h" +#include "ResourceTable.h" +#include "xml/XmlDom.h" + namespace aapt { struct DebugPrintTableOptions { - bool showSources = false; + bool show_sources = false; }; struct Debug { - static void printTable(ResourceTable* table, const DebugPrintTableOptions& options = {}); - static void printStyleGraph(ResourceTable* table, - const ResourceName& targetStyle); - static void dumpHex(const void* data, size_t len); + static void PrintTable(ResourceTable* table, + const DebugPrintTableOptions& options = {}); + static void PrintStyleGraph(ResourceTable* table, + const ResourceName& target_style); + static void DumpHex(const void* data, size_t len); + static void DumpXml(xml::XmlResource* doc); }; -} // namespace aapt +} // namespace aapt -#endif // AAPT_DEBUG_H +#endif // AAPT_DEBUG_H diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h index e86f2a8830e8..5bc86a9fdbaf 100644 --- a/tools/aapt2/Diagnostics.h +++ b/tools/aapt2/Diagnostics.h @@ -17,131 +17,125 @@ #ifndef AAPT_DIAGNOSTICS_H #define AAPT_DIAGNOSTICS_H -#include "Source.h" -#include "util/StringPiece.h" -#include "util/Util.h" - -#include <android-base/macros.h> #include <iostream> #include <sstream> #include <string> +#include "android-base/macros.h" + +#include "Source.h" +#include "util/StringPiece.h" +#include "util/Util.h" + namespace aapt { struct DiagMessageActual { - Source source; - std::string message; + Source source; + std::string message; }; struct DiagMessage { -private: - Source mSource; - std::stringstream mMessage; + public: + DiagMessage() = default; -public: - DiagMessage() = default; + explicit DiagMessage(const StringPiece& src) : source_(src) {} - DiagMessage(const StringPiece& src) : mSource(src) { - } + explicit DiagMessage(const Source& src) : source_(src) {} - DiagMessage(const Source& src) : mSource(src) { - } + explicit DiagMessage(size_t line) : source_(Source().WithLine(line)) {} - DiagMessage(size_t line) : mSource(Source().withLine(line)) { - } + template <typename T> + DiagMessage& operator<<(const T& value) { + message_ << value; + return *this; + } - template <typename T> - DiagMessage& operator<<(const T& value) { - mMessage << value; - return *this; - } + DiagMessageActual Build() const { + return DiagMessageActual{source_, message_.str()}; + } - DiagMessageActual build() const { - return DiagMessageActual{ mSource, mMessage.str() }; - } + private: + Source source_; + std::stringstream message_; }; struct IDiagnostics { - virtual ~IDiagnostics() = default; + virtual ~IDiagnostics() = default; - enum class Level { - Note, - Warn, - Error - }; + enum class Level { Note, Warn, Error }; - virtual void log(Level level, DiagMessageActual& actualMsg) = 0; + virtual void Log(Level level, DiagMessageActual& actualMsg) = 0; - virtual void error(const DiagMessage& message) { - DiagMessageActual actual = message.build(); - log(Level::Error, actual); - } + virtual void Error(const DiagMessage& message) { + DiagMessageActual actual = message.Build(); + Log(Level::Error, actual); + } - virtual void warn(const DiagMessage& message) { - DiagMessageActual actual = message.build(); - log(Level::Warn, actual); - } + virtual void Warn(const DiagMessage& message) { + DiagMessageActual actual = message.Build(); + Log(Level::Warn, actual); + } - virtual void note(const DiagMessage& message) { - DiagMessageActual actual = message.build(); - log(Level::Note, actual); - } + virtual void Note(const DiagMessage& message) { + DiagMessageActual actual = message.Build(); + Log(Level::Note, actual); + } }; class StdErrDiagnostics : public IDiagnostics { -public: - StdErrDiagnostics() = default; - - void log(Level level, DiagMessageActual& actualMsg) override { - const char* tag; - - switch (level) { - case Level::Error: - mNumErrors++; - if (mNumErrors > 20) { - return; - } - tag = "error"; - break; - - case Level::Warn: - tag = "warn"; - break; - - case Level::Note: - tag = "note"; - break; - } + public: + StdErrDiagnostics() = default; + + void Log(Level level, DiagMessageActual& actual_msg) override { + const char* tag; - if (!actualMsg.source.path.empty()) { - std::cerr << actualMsg.source << ": "; + switch (level) { + case Level::Error: + num_errors_++; + if (num_errors_ > 20) { + return; } - std::cerr << tag << ": " << actualMsg.message << "." << std::endl; + tag = "error"; + break; + + case Level::Warn: + tag = "warn"; + break; + + case Level::Note: + tag = "note"; + break; + } + + if (!actual_msg.source.path.empty()) { + std::cerr << actual_msg.source << ": "; } + std::cerr << tag << ": " << actual_msg.message << "." << std::endl; + } -private: - size_t mNumErrors = 0; + private: + size_t num_errors_ = 0; - DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics); + DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics); }; class SourcePathDiagnostics : public IDiagnostics { -public: - SourcePathDiagnostics(const Source& src, IDiagnostics* diag) : mSource(src), mDiag(diag) { - } + public: + SourcePathDiagnostics(const Source& src, IDiagnostics* diag) + : source_(src), diag_(diag) {} - void log(Level level, DiagMessageActual& actualMsg) override { - actualMsg.source.path = mSource.path; - mDiag->log(level, actualMsg); - } + void Log(Level level, DiagMessageActual& actual_msg) override { + actual_msg.source.path = source_.path; + diag_->Log(level, actual_msg); + } -private: - Source mSource; - IDiagnostics* mDiag; + private: + Source source_; + IDiagnostics* diag_; - DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics); + DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics); }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_DIAGNOSTICS_H */ diff --git a/tools/aapt2/DominatorTree.cpp b/tools/aapt2/DominatorTree.cpp new file mode 100644 index 000000000000..118a385e2253 --- /dev/null +++ b/tools/aapt2/DominatorTree.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "DominatorTree.h" + +#include <algorithm> + +#include "android-base/logging.h" + +#include "ConfigDescription.h" + +namespace aapt { + +DominatorTree::DominatorTree( + const std::vector<std::unique_ptr<ResourceConfigValue>>& configs) { + for (const auto& config : configs) { + product_roots_[config->product].TryAddChild( + util::make_unique<Node>(config.get(), nullptr)); + } +} + +void DominatorTree::Accept(Visitor* visitor) { + for (auto& entry : product_roots_) { + visitor->VisitTree(entry.first, &entry.second); + } +} + +bool DominatorTree::Node::TryAddChild(std::unique_ptr<Node> new_child) { + CHECK(new_child->value_) << "cannot add a root or empty node as a child"; + if (value_ && !Dominates(new_child.get())) { + // This is not the root and the child dominates us. + return false; + } + return AddChild(std::move(new_child)); +} + +bool DominatorTree::Node::AddChild(std::unique_ptr<Node> new_child) { + bool has_dominated_children = false; + // Demote children dominated by the new config. + for (auto& child : children_) { + if (new_child->Dominates(child.get())) { + child->parent_ = new_child.get(); + new_child->children_.push_back(std::move(child)); + child = {}; + has_dominated_children = true; + } + } + // Remove dominated children. + if (has_dominated_children) { + children_.erase( + std::remove_if(children_.begin(), children_.end(), + [](const std::unique_ptr<Node>& child) -> bool { + return child == nullptr; + }), + children_.end()); + } + // Add the new config to a child if a child dominates the new config. + for (auto& child : children_) { + if (child->Dominates(new_child.get())) { + child->AddChild(std::move(new_child)); + return true; + } + } + // The new config is not dominated by a child, so add it here. + new_child->parent_ = this; + children_.push_back(std::move(new_child)); + return true; +} + +bool DominatorTree::Node::Dominates(const Node* other) const { + // Check root node dominations. + if (other->is_root_node()) { + return is_root_node(); + } else if (is_root_node()) { + return true; + } + // Neither node is a root node; compare the configurations. + return value_->config.Dominates(other->value_->config); +} + +} // namespace aapt diff --git a/tools/aapt2/DominatorTree.h b/tools/aapt2/DominatorTree.h new file mode 100644 index 000000000000..7d50935eabfd --- /dev/null +++ b/tools/aapt2/DominatorTree.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef AAPT_DOMINATOR_TREE_H +#define AAPT_DOMINATOR_TREE_H + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "ResourceTable.h" + +namespace aapt { + +/** + * A dominator tree of configurations as defined by resolution rules for Android + * resources. + * + * A node in the tree represents a resource configuration. + * + * The tree has the following property: + * + * Each child of a given configuration defines a strict superset of qualifiers + * and has a value that is at least as specific as that of its ancestors. A + * value is "at least as specific" if it is either identical or it represents a + * stronger requirement. + * For example, v21 is more specific than v11, and w1200dp is more specific than + * w800dp. + * + * The dominator tree relies on the underlying configurations passed to it. If + * the configurations passed to the dominator tree go out of scope, the tree + * will exhibit undefined behavior. + */ +class DominatorTree { + public: + explicit DominatorTree( + const std::vector<std::unique_ptr<ResourceConfigValue>>& configs); + + class Node { + public: + explicit Node(ResourceConfigValue* value = nullptr, Node* parent = nullptr) + : value_(value), parent_(parent) {} + + inline ResourceConfigValue* value() const { return value_; } + + inline Node* parent() const { return parent_; } + + inline bool is_root_node() const { return !value_; } + + inline const std::vector<std::unique_ptr<Node>>& children() const { + return children_; + } + + bool TryAddChild(std::unique_ptr<Node> new_child); + + private: + bool AddChild(std::unique_ptr<Node> new_child); + bool Dominates(const Node* other) const; + + ResourceConfigValue* value_; + Node* parent_; + std::vector<std::unique_ptr<Node>> children_; + + DISALLOW_COPY_AND_ASSIGN(Node); + }; + + struct Visitor { + virtual ~Visitor() = default; + virtual void VisitTree(const std::string& product, Node* root) = 0; + }; + + class BottomUpVisitor : public Visitor { + public: + virtual ~BottomUpVisitor() = default; + + void VisitTree(const std::string& product, Node* root) override { + for (auto& child : root->children()) { + VisitNode(child.get()); + } + } + + virtual void VisitConfig(Node* node) = 0; + + private: + void VisitNode(Node* node) { + for (auto& child : node->children()) { + VisitNode(child.get()); + } + VisitConfig(node); + } + }; + + void Accept(Visitor* visitor); + + inline const std::map<std::string, Node>& product_roots() const { + return product_roots_; + } + + private: + DISALLOW_COPY_AND_ASSIGN(DominatorTree); + + std::map<std::string, Node> product_roots_; +}; + +} // namespace aapt + +#endif // AAPT_DOMINATOR_TREE_H diff --git a/tools/aapt2/DominatorTree_test.cpp b/tools/aapt2/DominatorTree_test.cpp new file mode 100644 index 000000000000..e89c6beb0c57 --- /dev/null +++ b/tools/aapt2/DominatorTree_test.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "DominatorTree.h" + +#include <sstream> +#include <string> +#include <vector> + +#include "test/Test.h" +#include "util/Util.h" + +namespace aapt { + +namespace { + +class PrettyPrinter : public DominatorTree::Visitor { + public: + explicit PrettyPrinter(const int indent = 2) : indent_(indent) {} + + void VisitTree(const std::string& product, + DominatorTree::Node* root) override { + for (auto& child : root->children()) { + VisitNode(child.get(), 0); + } + } + + std::string ToString(DominatorTree* tree) { + buffer_.str(""); + buffer_.clear(); + tree->Accept(this); + return buffer_.str(); + } + + private: + void VisitConfig(const DominatorTree::Node* node, const int indent) { + auto config_string = node->value()->config.toString(); + buffer_ << std::string(indent, ' ') + << (config_string.isEmpty() ? "<default>" : config_string) + << std::endl; + } + + void VisitNode(const DominatorTree::Node* node, const int indent) { + VisitConfig(node, indent); + for (const auto& child : node->children()) { + VisitNode(child.get(), indent + indent_); + } + } + + std::stringstream buffer_; + const int indent_ = 2; +}; + +} // namespace + +TEST(DominatorTreeTest, DefaultDominatesEverything) { + const ConfigDescription default_config = {}; + const ConfigDescription land_config = test::ParseConfigOrDie("land"); + const ConfigDescription sw600dp_land_config = + test::ParseConfigOrDie("sw600dp-land-v13"); + + std::vector<std::unique_ptr<ResourceConfigValue>> configs; + configs.push_back(util::make_unique<ResourceConfigValue>(default_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(land_config, "")); + configs.push_back( + util::make_unique<ResourceConfigValue>(sw600dp_land_config, "")); + + DominatorTree tree(configs); + PrettyPrinter printer; + + std::string expected = + "<default>\n" + " land\n" + " sw600dp-land-v13\n"; + EXPECT_EQ(expected, printer.ToString(&tree)); +} + +TEST(DominatorTreeTest, ProductsAreDominatedSeparately) { + const ConfigDescription default_config = {}; + const ConfigDescription land_config = test::ParseConfigOrDie("land"); + const ConfigDescription sw600dp_land_config = + test::ParseConfigOrDie("sw600dp-land-v13"); + + std::vector<std::unique_ptr<ResourceConfigValue>> configs; + configs.push_back(util::make_unique<ResourceConfigValue>(default_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(land_config, "")); + configs.push_back( + util::make_unique<ResourceConfigValue>(default_config, "phablet")); + configs.push_back( + util::make_unique<ResourceConfigValue>(sw600dp_land_config, "phablet")); + + DominatorTree tree(configs); + PrettyPrinter printer; + + std::string expected = + "<default>\n" + " land\n" + "<default>\n" + " sw600dp-land-v13\n"; + EXPECT_EQ(expected, printer.ToString(&tree)); +} + +TEST(DominatorTreeTest, MoreSpecificConfigurationsAreDominated) { + const ConfigDescription default_config = {}; + const ConfigDescription en_config = test::ParseConfigOrDie("en"); + const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21"); + const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl-v4"); + const ConfigDescription ldrtl_xhdpi_config = + test::ParseConfigOrDie("ldrtl-xhdpi-v4"); + const ConfigDescription sw300dp_config = + test::ParseConfigOrDie("sw300dp-v13"); + const ConfigDescription sw540dp_config = + test::ParseConfigOrDie("sw540dp-v14"); + const ConfigDescription sw600dp_config = + test::ParseConfigOrDie("sw600dp-v14"); + const ConfigDescription sw720dp_config = + test::ParseConfigOrDie("sw720dp-v13"); + const ConfigDescription v20_config = test::ParseConfigOrDie("v20"); + + std::vector<std::unique_ptr<ResourceConfigValue>> configs; + configs.push_back(util::make_unique<ResourceConfigValue>(default_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(en_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(en_v21_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(ldrtl_config, "")); + configs.push_back( + util::make_unique<ResourceConfigValue>(ldrtl_xhdpi_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(sw300dp_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(sw540dp_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(sw600dp_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(sw720dp_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(v20_config, "")); + + DominatorTree tree(configs); + PrettyPrinter printer; + + std::string expected = + "<default>\n" + " en\n" + " en-v21\n" + " ldrtl-v4\n" + " ldrtl-xhdpi-v4\n" + " sw300dp-v13\n" + " sw540dp-v14\n" + " sw600dp-v14\n" + " sw720dp-v13\n" + " v20\n"; + EXPECT_EQ(expected, printer.ToString(&tree)); +} + +} // namespace aapt diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/Flags.cpp index 666e8a8efff1..c98cd374602f 100644 --- a/tools/aapt2/Flags.cpp +++ b/tools/aapt2/Flags.cpp @@ -15,154 +15,178 @@ */ #include "Flags.h" -#include "util/StringPiece.h" -#include "util/Util.h" #include <iomanip> #include <iostream> #include <string> #include <vector> +#include "util/StringPiece.h" +#include "util/Util.h" + namespace aapt { -Flags& Flags::requiredFlag(const StringPiece& name, const StringPiece& description, - std::string* value) { - auto func = [value](const StringPiece& arg) -> bool { - *value = arg.toString(); - return true; - }; +Flags& Flags::RequiredFlag(const StringPiece& name, + const StringPiece& description, std::string* value) { + auto func = [value](const StringPiece& arg) -> bool { + *value = arg.ToString(); + return true; + }; - mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false}); - return *this; + flags_.push_back( + Flag{name.ToString(), description.ToString(), func, true, 1, false}); + return *this; } -Flags& Flags::requiredFlagList(const StringPiece& name, const StringPiece& description, +Flags& Flags::RequiredFlagList(const StringPiece& name, + const StringPiece& description, std::vector<std::string>* value) { - auto func = [value](const StringPiece& arg) -> bool { - value->push_back(arg.toString()); - return true; - }; + auto func = [value](const StringPiece& arg) -> bool { + value->push_back(arg.ToString()); + return true; + }; - mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false }); - return *this; + flags_.push_back( + Flag{name.ToString(), description.ToString(), func, true, 1, false}); + return *this; } -Flags& Flags::optionalFlag(const StringPiece& name, const StringPiece& description, +Flags& Flags::OptionalFlag(const StringPiece& name, + const StringPiece& description, Maybe<std::string>* value) { - auto func = [value](const StringPiece& arg) -> bool { - *value = arg.toString(); - return true; - }; + auto func = [value](const StringPiece& arg) -> bool { + *value = arg.ToString(); + return true; + }; - mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false }); - return *this; + flags_.push_back( + Flag{name.ToString(), description.ToString(), func, false, 1, false}); + return *this; } -Flags& Flags::optionalFlagList(const StringPiece& name, const StringPiece& description, +Flags& Flags::OptionalFlagList(const StringPiece& name, + const StringPiece& description, std::vector<std::string>* value) { - auto func = [value](const StringPiece& arg) -> bool { - value->push_back(arg.toString()); - return true; - }; + auto func = [value](const StringPiece& arg) -> bool { + value->push_back(arg.ToString()); + return true; + }; - mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false }); - return *this; + flags_.push_back( + Flag{name.ToString(), description.ToString(), func, false, 1, false}); + return *this; } -Flags& Flags::optionalSwitch(const StringPiece& name, const StringPiece& description, - bool* value) { - auto func = [value](const StringPiece& arg) -> bool { - *value = true; - return true; - }; +Flags& Flags::OptionalFlagList(const StringPiece& name, + const StringPiece& description, + std::unordered_set<std::string>* value) { + auto func = [value](const StringPiece& arg) -> bool { + value->insert(arg.ToString()); + return true; + }; - mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 0, false }); - return *this; + flags_.push_back( + Flag{name.ToString(), description.ToString(), func, false, 1, false}); + return *this; } -void Flags::usage(const StringPiece& command, std::ostream* out) { - constexpr size_t kWidth = 50; +Flags& Flags::OptionalSwitch(const StringPiece& name, + const StringPiece& description, bool* value) { + auto func = [value](const StringPiece& arg) -> bool { + *value = true; + return true; + }; - *out << command << " [options]"; - for (const Flag& flag : mFlags) { - if (flag.required) { - *out << " " << flag.name << " arg"; - } + flags_.push_back( + Flag{name.ToString(), description.ToString(), func, false, 0, false}); + return *this; +} + +void Flags::Usage(const StringPiece& command, std::ostream* out) { + constexpr size_t kWidth = 50; + + *out << command << " [options]"; + for (const Flag& flag : flags_) { + if (flag.required) { + *out << " " << flag.name << " arg"; } + } - *out << " files...\n\nOptions:\n"; + *out << " files...\n\nOptions:\n"; - for (const Flag& flag : mFlags) { - std::string argLine = flag.name; - if (flag.numArgs > 0) { - argLine += " arg"; - } + for (const Flag& flag : flags_) { + std::string argline = flag.name; + if (flag.num_args > 0) { + argline += " arg"; + } - // Split the description by newlines and write out the argument (which is empty after - // the first line) followed by the description line. This will make sure that multiline - // descriptions are still right justified and aligned. - for (StringPiece line : util::tokenize<char>(flag.description, '\n')) { - *out << " " << std::setw(kWidth) << std::left << argLine << line << "\n"; - argLine = " "; - } + // Split the description by newlines and write out the argument (which is + // empty after + // the first line) followed by the description line. This will make sure + // that multiline + // descriptions are still right justified and aligned. + for (StringPiece line : util::Tokenize(flag.description, '\n')) { + *out << " " << std::setw(kWidth) << std::left << argline << line << "\n"; + argline = " "; } - *out << " " << std::setw(kWidth) << std::left << "-h" << "Displays this help menu\n"; - out->flush(); + } + *out << " " << std::setw(kWidth) << std::left << "-h" + << "Displays this help menu\n"; + out->flush(); } -bool Flags::parse(const StringPiece& command, const std::vector<StringPiece>& args, - std::ostream* outError) { - for (size_t i = 0; i < args.size(); i++) { - StringPiece arg = args[i]; - if (*(arg.data()) != '-') { - mArgs.push_back(arg.toString()); - continue; - } - - if (arg == "-h" || arg == "--help") { - usage(command, outError); - return false; - } +bool Flags::Parse(const StringPiece& command, + const std::vector<StringPiece>& args, + std::ostream* out_error) { + for (size_t i = 0; i < args.size(); i++) { + StringPiece arg = args[i]; + if (*(arg.data()) != '-') { + args_.push_back(arg.ToString()); + continue; + } - bool match = false; - for (Flag& flag : mFlags) { - if (arg == flag.name) { - if (flag.numArgs > 0) { - i++; - if (i >= args.size()) { - *outError << flag.name << " missing argument.\n\n"; - usage(command, outError); - return false; - } - flag.action(args[i]); - } else { - flag.action({}); - } - flag.parsed = true; - match = true; - break; - } - } + if (arg == "-h" || arg == "--help") { + Usage(command, out_error); + return false; + } - if (!match) { - *outError << "unknown option '" << arg << "'.\n\n"; - usage(command, outError); + bool match = false; + for (Flag& flag : flags_) { + if (arg == flag.name) { + if (flag.num_args > 0) { + i++; + if (i >= args.size()) { + *out_error << flag.name << " missing argument.\n\n"; + Usage(command, out_error); return false; + } + flag.action(args[i]); + } else { + flag.action({}); } + flag.parsed = true; + match = true; + break; + } } - for (const Flag& flag : mFlags) { - if (flag.required && !flag.parsed) { - *outError << "missing required flag " << flag.name << "\n\n"; - usage(command, outError); - return false; - } + if (!match) { + *out_error << "unknown option '" << arg << "'.\n\n"; + Usage(command, out_error); + return false; } - return true; -} + } -const std::vector<std::string>& Flags::getArgs() { - return mArgs; + for (const Flag& flag : flags_) { + if (flag.required && !flag.parsed) { + *out_error << "missing required flag " << flag.name << "\n\n"; + Usage(command, out_error); + return false; + } + } + return true; } -} // namespace aapt +const std::vector<std::string>& Flags::GetArgs() { return args_; } + +} // namespace aapt diff --git a/tools/aapt2/Flags.h b/tools/aapt2/Flags.h index ce7a4857eb6e..9feff6b4faca 100644 --- a/tools/aapt2/Flags.h +++ b/tools/aapt2/Flags.h @@ -17,51 +17,57 @@ #ifndef AAPT_FLAGS_H #define AAPT_FLAGS_H -#include "util/Maybe.h" -#include "util/StringPiece.h" - #include <functional> #include <ostream> #include <string> +#include <unordered_set> #include <vector> +#include "util/Maybe.h" +#include "util/StringPiece.h" + namespace aapt { class Flags { -public: - Flags& requiredFlag(const StringPiece& name, const StringPiece& description, - std::string* value); - Flags& requiredFlagList(const StringPiece& name, const StringPiece& description, - std::vector<std::string>* value); - Flags& optionalFlag(const StringPiece& name, const StringPiece& description, - Maybe<std::string>* value); - Flags& optionalFlagList(const StringPiece& name, const StringPiece& description, - std::vector<std::string>* value); - Flags& optionalSwitch(const StringPiece& name, const StringPiece& description, - bool* value); + public: + Flags& RequiredFlag(const StringPiece& name, const StringPiece& description, + std::string* value); + Flags& RequiredFlagList(const StringPiece& name, + const StringPiece& description, + std::vector<std::string>* value); + Flags& OptionalFlag(const StringPiece& name, const StringPiece& description, + Maybe<std::string>* value); + Flags& OptionalFlagList(const StringPiece& name, + const StringPiece& description, + std::vector<std::string>* value); + Flags& OptionalFlagList(const StringPiece& name, + const StringPiece& description, + std::unordered_set<std::string>* value); + Flags& OptionalSwitch(const StringPiece& name, const StringPiece& description, + bool* value); - void usage(const StringPiece& command, std::ostream* out); + void Usage(const StringPiece& command, std::ostream* out); - bool parse(const StringPiece& command, const std::vector<StringPiece>& args, - std::ostream* outError); + bool Parse(const StringPiece& command, const std::vector<StringPiece>& args, + std::ostream* outError); - const std::vector<std::string>& getArgs(); + const std::vector<std::string>& GetArgs(); -private: - struct Flag { - std::string name; - std::string description; - std::function<bool(const StringPiece& value)> action; - bool required; - size_t numArgs; + private: + struct Flag { + std::string name; + std::string description; + std::function<bool(const StringPiece& value)> action; + bool required; + size_t num_args; - bool parsed; - }; + bool parsed; + }; - std::vector<Flag> mFlags; - std::vector<std::string> mArgs; + std::vector<Flag> flags_; + std::vector<std::string> args_; }; -} // namespace aapt +} // namespace aapt -#endif // AAPT_FLAGS_H +#endif // AAPT_FLAGS_H diff --git a/tools/aapt2/Format.proto b/tools/aapt2/Format.proto index d05425c5c64d..0917129ba90d 100644 --- a/tools/aapt2/Format.proto +++ b/tools/aapt2/Format.proto @@ -34,7 +34,7 @@ message CompiledFile { optional string resource_name = 1; optional uint32 line_no = 2; } - + optional string resource_name = 1; optional ConfigDescription config = 2; optional string source_path = 3; diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp index be576613b9b2..78f56c7a5b93 100644 --- a/tools/aapt2/Locale.cpp +++ b/tools/aapt2/Locale.cpp @@ -15,264 +15,240 @@ */ #include "Locale.h" -#include "util/Util.h" -#include <algorithm> #include <ctype.h> + +#include <algorithm> #include <string> #include <vector> +#include "util/Util.h" + namespace aapt { using android::ResTable_config; -void LocaleValue::setLanguage(const char* languageChars) { - size_t i = 0; - while ((*languageChars) != '\0') { - language[i++] = ::tolower(*languageChars); - languageChars++; - } +void LocaleValue::set_language(const char* language_chars) { + size_t i = 0; + while ((*language_chars) != '\0') { + language[i++] = ::tolower(*language_chars); + language_chars++; + } } -void LocaleValue::setRegion(const char* regionChars) { - size_t i = 0; - while ((*regionChars) != '\0') { - region[i++] = ::toupper(*regionChars); - regionChars++; - } +void LocaleValue::set_region(const char* region_chars) { + size_t i = 0; + while ((*region_chars) != '\0') { + region[i++] = ::toupper(*region_chars); + region_chars++; + } } -void LocaleValue::setScript(const char* scriptChars) { - size_t i = 0; - while ((*scriptChars) != '\0') { - if (i == 0) { - script[i++] = ::toupper(*scriptChars); - } else { - script[i++] = ::tolower(*scriptChars); - } - scriptChars++; +void LocaleValue::set_script(const char* script_chars) { + size_t i = 0; + while ((*script_chars) != '\0') { + if (i == 0) { + script[i++] = ::toupper(*script_chars); + } else { + script[i++] = ::tolower(*script_chars); } + script_chars++; + } } -void LocaleValue::setVariant(const char* variantChars) { - size_t i = 0; - while ((*variantChars) != '\0') { - variant[i++] = *variantChars; - variantChars++; - } -} - -static inline bool isAlpha(const std::string& str) { - return std::all_of(std::begin(str), std::end(str), ::isalpha); +void LocaleValue::set_variant(const char* variant_chars) { + size_t i = 0; + while ((*variant_chars) != '\0') { + variant[i++] = *variant_chars; + variant_chars++; + } } -static inline bool isNumber(const std::string& str) { - return std::all_of(std::begin(str), std::end(str), ::isdigit); +static inline bool is_alpha(const std::string& str) { + return std::all_of(std::begin(str), std::end(str), ::isalpha); } -bool LocaleValue::initFromFilterString(const StringPiece& str) { - // A locale (as specified in the filter) is an underscore separated name such - // as "en_US", "en_Latn_US", or "en_US_POSIX". - std::vector<std::string> parts = util::splitAndLowercase(str, '_'); - - const int numTags = parts.size(); - bool valid = false; - if (numTags >= 1) { - const std::string& lang = parts[0]; - if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) { - setLanguage(lang.c_str()); - valid = true; - } - } - - if (!valid || numTags == 1) { - return valid; - } - - // At this point, valid == true && numTags > 1. - const std::string& part2 = parts[1]; - if ((part2.length() == 2 && isAlpha(part2)) || - (part2.length() == 3 && isNumber(part2))) { - setRegion(part2.c_str()); - } else if (part2.length() == 4 && isAlpha(part2)) { - setScript(part2.c_str()); - } else if (part2.length() >= 4 && part2.length() <= 8) { - setVariant(part2.c_str()); - } else { - valid = false; - } - - if (!valid || numTags == 2) { - return valid; - } - - // At this point, valid == true && numTags > 1. - const std::string& part3 = parts[2]; - if (((part3.length() == 2 && isAlpha(part3)) || - (part3.length() == 3 && isNumber(part3))) && script[0]) { - setRegion(part3.c_str()); - } else if (part3.length() >= 4 && part3.length() <= 8) { - setVariant(part3.c_str()); - } else { - valid = false; - } - - if (!valid || numTags == 3) { - return valid; - } - - const std::string& part4 = parts[3]; - if (part4.length() >= 4 && part4.length() <= 8) { - setVariant(part4.c_str()); - } else { - valid = false; - } - - if (!valid || numTags > 4) { - return false; - } - - return true; +static inline bool is_number(const std::string& str) { + return std::all_of(std::begin(str), std::end(str), ::isdigit); } -ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter, - std::vector<std::string>::iterator end) { - const std::vector<std::string>::iterator startIter = iter; - - std::string& part = *iter; - if (part[0] == 'b' && part[1] == '+') { - // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags, - // except that the separator is "+" and not "-". - std::vector<std::string> subtags = util::splitAndLowercase(part, '+'); - subtags.erase(subtags.begin()); - if (subtags.size() == 1) { - setLanguage(subtags[0].c_str()); - } else if (subtags.size() == 2) { - setLanguage(subtags[0].c_str()); - - // The second tag can either be a region, a variant or a script. - switch (subtags[1].size()) { - case 2: - case 3: - setRegion(subtags[1].c_str()); - break; - case 4: - if ('0' <= subtags[1][0] && subtags[1][0] <= '9') { - // This is a variant: fall through - } else { - setScript(subtags[1].c_str()); - break; - } - case 5: - case 6: - case 7: - case 8: - setVariant(subtags[1].c_str()); - break; - default: - return -1; - } - } else if (subtags.size() == 3) { - // The language is always the first subtag. - setLanguage(subtags[0].c_str()); - - // The second subtag can either be a script or a region code. - // If its size is 4, it's a script code, else it's a region code. - if (subtags[1].size() == 4) { - setScript(subtags[1].c_str()); - } else if (subtags[1].size() == 2 || subtags[1].size() == 3) { - setRegion(subtags[1].c_str()); - } else { - return -1; - } - - // The third tag can either be a region code (if the second tag was - // a script), else a variant code. - if (subtags[2].size() >= 4) { - setVariant(subtags[2].c_str()); - } else { - setRegion(subtags[2].c_str()); - } - } else if (subtags.size() == 4) { - setLanguage(subtags[0].c_str()); - setScript(subtags[1].c_str()); - setRegion(subtags[2].c_str()); - setVariant(subtags[3].c_str()); - } else { - return -1; - } - - ++iter; - - } else { - if ((part.length() == 2 || part.length() == 3) - && isAlpha(part) && part != "car") { - setLanguage(part.c_str()); - ++iter; - - if (iter != end) { - const std::string& regionPart = *iter; - if (regionPart.c_str()[0] == 'r' && regionPart.length() == 3) { - setRegion(regionPart.c_str() + 1); - ++iter; - } - } - } +bool LocaleValue::InitFromFilterString(const StringPiece& str) { + // A locale (as specified in the filter) is an underscore separated name such + // as "en_US", "en_Latn_US", or "en_US_POSIX". + std::vector<std::string> parts = util::SplitAndLowercase(str, '_'); + + const int num_tags = parts.size(); + bool valid = false; + if (num_tags >= 1) { + const std::string& lang = parts[0]; + if (is_alpha(lang) && (lang.length() == 2 || lang.length() == 3)) { + set_language(lang.c_str()); + valid = true; } - - return static_cast<ssize_t>(iter - startIter); + } + + if (!valid || num_tags == 1) { + return valid; + } + + // At this point, valid == true && numTags > 1. + const std::string& part2 = parts[1]; + if ((part2.length() == 2 && is_alpha(part2)) || + (part2.length() == 3 && is_number(part2))) { + set_region(part2.c_str()); + } else if (part2.length() == 4 && is_alpha(part2)) { + set_script(part2.c_str()); + } else if (part2.length() >= 4 && part2.length() <= 8) { + set_variant(part2.c_str()); + } else { + valid = false; + } + + if (!valid || num_tags == 2) { + return valid; + } + + // At this point, valid == true && numTags > 1. + const std::string& part3 = parts[2]; + if (((part3.length() == 2 && is_alpha(part3)) || + (part3.length() == 3 && is_number(part3))) && + script[0]) { + set_region(part3.c_str()); + } else if (part3.length() >= 4 && part3.length() <= 8) { + set_variant(part3.c_str()); + } else { + valid = false; + } + + if (!valid || num_tags == 3) { + return valid; + } + + const std::string& part4 = parts[3]; + if (part4.length() >= 4 && part4.length() <= 8) { + set_variant(part4.c_str()); + } else { + valid = false; + } + + if (!valid || num_tags > 4) { + return false; + } + + return true; } - -std::string LocaleValue::toDirName() const { - std::string dirName; - if (language[0]) { - dirName += language; +ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter, + std::vector<std::string>::iterator end) { + const std::vector<std::string>::iterator start_iter = iter; + + std::string& part = *iter; + if (part[0] == 'b' && part[1] == '+') { + // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags, + // except that the separator is "+" and not "-". + std::vector<std::string> subtags = util::SplitAndLowercase(part, '+'); + subtags.erase(subtags.begin()); + if (subtags.size() == 1) { + set_language(subtags[0].c_str()); + } else if (subtags.size() == 2) { + set_language(subtags[0].c_str()); + + // The second tag can either be a region, a variant or a script. + switch (subtags[1].size()) { + case 2: + case 3: + set_region(subtags[1].c_str()); + break; + case 4: + if ('0' <= subtags[1][0] && subtags[1][0] <= '9') { + // This is a variant: fall through + } else { + set_script(subtags[1].c_str()); + break; + } + case 5: + case 6: + case 7: + case 8: + set_variant(subtags[1].c_str()); + break; + default: + return -1; + } + } else if (subtags.size() == 3) { + // The language is always the first subtag. + set_language(subtags[0].c_str()); + + // The second subtag can either be a script or a region code. + // If its size is 4, it's a script code, else it's a region code. + if (subtags[1].size() == 4) { + set_script(subtags[1].c_str()); + } else if (subtags[1].size() == 2 || subtags[1].size() == 3) { + set_region(subtags[1].c_str()); + } else { + return -1; + } + + // The third tag can either be a region code (if the second tag was + // a script), else a variant code. + if (subtags[2].size() >= 4) { + set_variant(subtags[2].c_str()); + } else { + set_region(subtags[2].c_str()); + } + } else if (subtags.size() == 4) { + set_language(subtags[0].c_str()); + set_script(subtags[1].c_str()); + set_region(subtags[2].c_str()); + set_variant(subtags[3].c_str()); } else { - return dirName; + return -1; } - if (script[0]) { - dirName += "-s"; - dirName += script; - } + ++iter; - if (region[0]) { - dirName += "-r"; - dirName += region; - } + } else { + if ((part.length() == 2 || part.length() == 3) && is_alpha(part) && + part != "car") { + set_language(part.c_str()); + ++iter; - if (variant[0]) { - dirName += "-v"; - dirName += variant; + if (iter != end) { + const std::string& region_part = *iter; + if (region_part.c_str()[0] == 'r' && region_part.length() == 3) { + set_region(region_part.c_str() + 1); + ++iter; + } + } } + } - return dirName; + return static_cast<ssize_t>(iter - start_iter); } -void LocaleValue::initFromResTable(const ResTable_config& config) { - config.unpackLanguage(language); - config.unpackRegion(region); - if (config.localeScript[0] && !config.localeScriptWasComputed) { - memcpy(script, config.localeScript, sizeof(config.localeScript)); - } +void LocaleValue::InitFromResTable(const ResTable_config& config) { + config.unpackLanguage(language); + config.unpackRegion(region); + if (config.localeScript[0] && !config.localeScriptWasComputed) { + memcpy(script, config.localeScript, sizeof(config.localeScript)); + } - if (config.localeVariant[0]) { - memcpy(variant, config.localeVariant, sizeof(config.localeVariant)); - } + if (config.localeVariant[0]) { + memcpy(variant, config.localeVariant, sizeof(config.localeVariant)); + } } -void LocaleValue::writeTo(ResTable_config* out) const { - out->packLanguage(language); - out->packRegion(region); +void LocaleValue::WriteTo(ResTable_config* out) const { + out->packLanguage(language); + out->packRegion(region); - if (script[0]) { - memcpy(out->localeScript, script, sizeof(out->localeScript)); - } + if (script[0]) { + memcpy(out->localeScript, script, sizeof(out->localeScript)); + } - if (variant[0]) { - memcpy(out->localeVariant, variant, sizeof(out->localeVariant)); - } + if (variant[0]) { + memcpy(out->localeVariant, variant, sizeof(out->localeVariant)); + } } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/Locale.h b/tools/aapt2/Locale.h index b1c80ab27641..fc6c448ac1dc 100644 --- a/tools/aapt2/Locale.h +++ b/tools/aapt2/Locale.h @@ -17,100 +17,97 @@ #ifndef AAPT_LOCALE_VALUE_H #define AAPT_LOCALE_VALUE_H -#include "util/StringPiece.h" - -#include <androidfw/ResourceTypes.h> #include <string> #include <vector> +#include "androidfw/ResourceTypes.h" + +#include "util/StringPiece.h" + namespace aapt { /** * A convenience class to build and parse locales. */ struct LocaleValue { - char language[4]; - char region[4]; - char script[4]; - char variant[8]; - - inline LocaleValue(); - - /** - * Initialize this LocaleValue from a config string. - */ - bool initFromFilterString(const StringPiece& config); - - /** - * Initialize this LocaleValue from parts of a vector. - */ - ssize_t initFromParts(std::vector<std::string>::iterator iter, - std::vector<std::string>::iterator end); - - /** - * Initialize this LocaleValue from a ResTable_config. - */ - void initFromResTable(const android::ResTable_config& config); - - /** - * Set the locale in a ResTable_config from this LocaleValue. - */ - void writeTo(android::ResTable_config* out) const; - - std::string toDirName() const; - - inline int compare(const LocaleValue& other) const; - - inline bool operator<(const LocaleValue& o) const; - inline bool operator<=(const LocaleValue& o) const; - inline bool operator==(const LocaleValue& o) const; - inline bool operator!=(const LocaleValue& o) const; - inline bool operator>=(const LocaleValue& o) const; - inline bool operator>(const LocaleValue& o) const; - -private: - void setLanguage(const char* language); - void setRegion(const char* language); - void setScript(const char* script); - void setVariant(const char* variant); + char language[4]; + char region[4]; + char script[4]; + char variant[8]; + + inline LocaleValue(); + + /** + * Initialize this LocaleValue from a config string. + */ + bool InitFromFilterString(const StringPiece& config); + + /** + * Initialize this LocaleValue from parts of a vector. + */ + ssize_t InitFromParts(std::vector<std::string>::iterator iter, + std::vector<std::string>::iterator end); + + /** + * Initialize this LocaleValue from a ResTable_config. + */ + void InitFromResTable(const android::ResTable_config& config); + + /** + * Set the locale in a ResTable_config from this LocaleValue. + */ + void WriteTo(android::ResTable_config* out) const; + + inline int compare(const LocaleValue& other) const; + + inline bool operator<(const LocaleValue& o) const; + inline bool operator<=(const LocaleValue& o) const; + inline bool operator==(const LocaleValue& o) const; + inline bool operator!=(const LocaleValue& o) const; + inline bool operator>=(const LocaleValue& o) const; + inline bool operator>(const LocaleValue& o) const; + + private: + void set_language(const char* language); + void set_region(const char* language); + void set_script(const char* script); + void set_variant(const char* variant); }; // // Implementation // -LocaleValue::LocaleValue() { - memset(this, 0, sizeof(LocaleValue)); -} +LocaleValue::LocaleValue() { memset(this, 0, sizeof(LocaleValue)); } int LocaleValue::compare(const LocaleValue& other) const { - return memcmp(this, &other, sizeof(LocaleValue)); + return memcmp(this, &other, sizeof(LocaleValue)); } bool LocaleValue::operator<(const LocaleValue& o) const { - return compare(o) < 0; + return compare(o) < 0; } bool LocaleValue::operator<=(const LocaleValue& o) const { - return compare(o) <= 0; + return compare(o) <= 0; } bool LocaleValue::operator==(const LocaleValue& o) const { - return compare(o) == 0; + return compare(o) == 0; } bool LocaleValue::operator!=(const LocaleValue& o) const { - return compare(o) != 0; + return compare(o) != 0; } bool LocaleValue::operator>=(const LocaleValue& o) const { - return compare(o) >= 0; + return compare(o) >= 0; } bool LocaleValue::operator>(const LocaleValue& o) const { - return compare(o) > 0; + return compare(o) > 0; } -} // namespace aapt +} // namespace aapt -#endif // AAPT_LOCALE_VALUE_H +#endif // AAPT_LOCALE_VALUE_H diff --git a/tools/aapt2/Locale_test.cpp b/tools/aapt2/Locale_test.cpp index 758e1e31c0e7..68b4cae44e15 100644 --- a/tools/aapt2/Locale_test.cpp +++ b/tools/aapt2/Locale_test.cpp @@ -15,68 +15,82 @@ */ #include "Locale.h" -#include "util/Util.h" -#include <gtest/gtest.h> #include <string> +#include "gtest/gtest.h" + +#include "util/Util.h" + namespace aapt { -static ::testing::AssertionResult TestLanguage(const char* input, const char* lang) { - std::vector<std::string> parts = util::splitAndLowercase(std::string(input), '-'); - LocaleValue lv; - ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts)); - if (count < 0) { - return ::testing::AssertionFailure() << " failed to parse '" << input << "'."; - } - - if (count != 1) { - return ::testing::AssertionFailure() << count - << " parts were consumed parsing '" << input << "' but expected 1."; - } - - if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) != 0) { - return ::testing::AssertionFailure() << "expected " << lang << " but got " - << std::string(lv.language, sizeof(lv.language)) << "."; - } - - return ::testing::AssertionSuccess(); +static ::testing::AssertionResult TestLanguage(const char* input, + const char* lang) { + std::vector<std::string> parts = util::SplitAndLowercase(input, '-'); + LocaleValue lv; + ssize_t count = lv.InitFromParts(std::begin(parts), std::end(parts)); + if (count < 0) { + return ::testing::AssertionFailure() << " failed to parse '" << input + << "'."; + } + + if (count != 1) { + return ::testing::AssertionFailure() + << count << " parts were consumed parsing '" << input + << "' but expected 1."; + } + + if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) != + 0) { + return ::testing::AssertionFailure() + << "expected " << lang << " but got " + << std::string(lv.language, sizeof(lv.language)) << "."; + } + + return ::testing::AssertionSuccess(); } -static ::testing::AssertionResult TestLanguageRegion(const char* input, const char* lang, +static ::testing::AssertionResult TestLanguageRegion(const char* input, + const char* lang, const char* region) { - std::vector<std::string> parts = util::splitAndLowercase(std::string(input), '-'); - LocaleValue lv; - ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts)); - if (count < 0) { - return ::testing::AssertionFailure() << " failed to parse '" << input << "'."; - } - - if (count != 2) { - return ::testing::AssertionFailure() << count - << " parts were consumed parsing '" << input << "' but expected 2."; - } - - if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) != 0) { - return ::testing::AssertionFailure() << "expected " << input << " but got " - << std::string(lv.language, sizeof(lv.language)) << "."; - } - - if (memcmp(lv.region, region, std::min(strlen(region), sizeof(lv.region))) != 0) { - return ::testing::AssertionFailure() << "expected " << region << " but got " - << std::string(lv.region, sizeof(lv.region)) << "."; - } - - return ::testing::AssertionSuccess(); + std::vector<std::string> parts = util::SplitAndLowercase(input, '-'); + LocaleValue lv; + ssize_t count = lv.InitFromParts(std::begin(parts), std::end(parts)); + if (count < 0) { + return ::testing::AssertionFailure() << " failed to parse '" << input + << "'."; + } + + if (count != 2) { + return ::testing::AssertionFailure() + << count << " parts were consumed parsing '" << input + << "' but expected 2."; + } + + if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) != + 0) { + return ::testing::AssertionFailure() + << "expected " << input << " but got " + << std::string(lv.language, sizeof(lv.language)) << "."; + } + + if (memcmp(lv.region, region, std::min(strlen(region), sizeof(lv.region))) != + 0) { + return ::testing::AssertionFailure() + << "expected " << region << " but got " + << std::string(lv.region, sizeof(lv.region)) << "."; + } + + return ::testing::AssertionSuccess(); } TEST(ConfigDescriptionTest, ParseLanguage) { - EXPECT_TRUE(TestLanguage("en", "en")); - EXPECT_TRUE(TestLanguage("fr", "fr")); - EXPECT_FALSE(TestLanguage("land", "")); - EXPECT_TRUE(TestLanguage("fr-land", "fr")); + EXPECT_TRUE(TestLanguage("en", "en")); + EXPECT_TRUE(TestLanguage("fr", "fr")); + EXPECT_FALSE(TestLanguage("land", "")); + EXPECT_TRUE(TestLanguage("fr-land", "fr")); - EXPECT_TRUE(TestLanguageRegion("fr-rCA", "fr", "CA")); + EXPECT_TRUE(TestLanguageRegion("fr-rCA", "fr", "CA")); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index 00d8aaeeda55..a3404e5db21c 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -14,45 +14,60 @@ * limitations under the License. */ -#include "util/StringPiece.h" - #include <iostream> #include <vector> +#include "util/StringPiece.h" + namespace aapt { -extern int compile(const std::vector<StringPiece>& args); -extern int link(const std::vector<StringPiece>& args); -extern int dump(const std::vector<StringPiece>& args); -extern int diff(const std::vector<StringPiece>& args); +// DO NOT UPDATE, this is more of a marketing version. +static const char* sMajorVersion = "2"; + +// Update minor version whenever a feature or flag is added. +static const char* sMinorVersion = "3"; -} // namespace aapt +int PrintVersion() { + std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "." + << sMinorVersion << std::endl; + return 0; +} + +extern int Compile(const std::vector<StringPiece>& args); +extern int Link(const std::vector<StringPiece>& args); +extern int Dump(const std::vector<StringPiece>& args); +extern int Diff(const std::vector<StringPiece>& args); + +} // namespace aapt int main(int argc, char** argv) { - if (argc >= 2) { - argv += 1; - argc -= 1; - - std::vector<aapt::StringPiece> args; - for (int i = 1; i < argc; i++) { - args.push_back(argv[i]); - } - - aapt::StringPiece command(argv[0]); - if (command == "compile" || command == "c") { - return aapt::compile(args); - } else if (command == "link" || command == "l") { - return aapt::link(args); - } else if (command == "dump" || command == "d") { - return aapt::dump(args); - } else if (command == "diff") { - return aapt::diff(args); - } - std::cerr << "unknown command '" << command << "'\n"; - } else { - std::cerr << "no command specified\n"; + if (argc >= 2) { + argv += 1; + argc -= 1; + + std::vector<aapt::StringPiece> args; + for (int i = 1; i < argc; i++) { + args.push_back(argv[i]); + } + + aapt::StringPiece command(argv[0]); + if (command == "compile" || command == "c") { + return aapt::Compile(args); + } else if (command == "link" || command == "l") { + return aapt::Link(args); + } else if (command == "dump" || command == "d") { + return aapt::Dump(args); + } else if (command == "diff") { + return aapt::Diff(args); + } else if (command == "version") { + return aapt::PrintVersion(); } + std::cerr << "unknown command '" << command << "'\n"; + } else { + std::cerr << "no command specified\n"; + } - std::cerr << "\nusage: aapt2 [compile|link|dump|diff] ..." << std::endl; - return 1; + std::cerr << "\nusage: aapt2 [compile|link|dump|diff|version] ..." + << std::endl; + return 1; } diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h index 054b9ee116f4..dba2d096dd9d 100644 --- a/tools/aapt2/NameMangler.h +++ b/tools/aapt2/NameMangler.h @@ -17,82 +17,82 @@ #ifndef AAPT_NAME_MANGLER_H #define AAPT_NAME_MANGLER_H -#include "Resource.h" - -#include "util/Maybe.h" - #include <set> #include <string> +#include "Resource.h" +#include "util/Maybe.h" + namespace aapt { struct NameManglerPolicy { - /** - * Represents the package we are trying to build. References pointing - * to this package are not mangled, and mangled references inherit this package name. - */ - std::u16string targetPackageName; - - /** - * We must know which references to mangle, and which to keep (android vs. com.android.support). - */ - std::set<std::u16string> packagesToMangle; + /** + * Represents the package we are trying to build. References pointing + * to this package are not mangled, and mangled references inherit this + * package name. + */ + std::string target_package_name; + + /** + * We must know which references to mangle, and which to keep (android vs. + * com.android.support). + */ + std::set<std::string> packages_to_mangle; }; class NameMangler { -private: - NameManglerPolicy mPolicy; + public: + explicit NameMangler(NameManglerPolicy policy) : policy_(policy) {} -public: - NameMangler(NameManglerPolicy policy) : mPolicy(policy) { + Maybe<ResourceName> MangleName(const ResourceName& name) { + if (policy_.target_package_name == name.package || + policy_.packages_to_mangle.count(name.package) == 0) { + return {}; } - Maybe<ResourceName> mangleName(const ResourceName& name) { - if (mPolicy.targetPackageName == name.package || - mPolicy.packagesToMangle.count(name.package) == 0) { - return {}; - } - - return ResourceName{ - mPolicy.targetPackageName, - name.type, - mangleEntry(name.package, name.entry) - }; - } + std::string mangled_entry_name = MangleEntry(name.package, name.entry); + return ResourceName(policy_.target_package_name, name.type, + mangled_entry_name); + } - bool shouldMangle(const std::u16string& package) const { - if (package.empty() || mPolicy.targetPackageName == package) { - return false; - } - return mPolicy.packagesToMangle.count(package) != 0; + bool ShouldMangle(const std::string& package) const { + if (package.empty() || policy_.target_package_name == package) { + return false; } - - /** - * Returns a mangled name that is a combination of `name` and `package`. - * The mangled name should contain symbols that are illegal to define in XML, - * so that there will never be name mangling collisions. - */ - static std::u16string mangleEntry(const std::u16string& package, const std::u16string& name) { - return package + u"$" + name; + return policy_.packages_to_mangle.count(package) != 0; + } + + /** + * Returns a mangled name that is a combination of `name` and `package`. + * The mangled name should contain symbols that are illegal to define in XML, + * so that there will never be name mangling collisions. + */ + static std::string MangleEntry(const std::string& package, + const std::string& name) { + return package + "$" + name; + } + + /** + * Unmangles the name in `outName`, storing the correct name back in `outName` + * and the package in `outPackage`. Returns true if the name was unmangled or + * false if the name was never mangled to begin with. + */ + static bool Unmangle(std::string* out_name, std::string* out_package) { + size_t pivot = out_name->find('$'); + if (pivot == std::string::npos) { + return false; } - /** - * Unmangles the name in `outName`, storing the correct name back in `outName` - * and the package in `outPackage`. Returns true if the name was unmangled or - * false if the name was never mangled to begin with. - */ - static bool unmangle(std::u16string* outName, std::u16string* outPackage) { - size_t pivot = outName->find(u'$'); - if (pivot == std::string::npos) { - return false; - } - - outPackage->assign(outName->data(), pivot); - outName->assign(outName->data() + pivot + 1, outName->size() - (pivot + 1)); - return true; - } + out_package->assign(out_name->data(), pivot); + out_name->assign(out_name->data() + pivot + 1, + out_name->size() - (pivot + 1)); + return true; + } + + private: + NameManglerPolicy policy_; }; -} // namespace aapt +} // namespace aapt -#endif // AAPT_NAME_MANGLER_H +#endif // AAPT_NAME_MANGLER_H diff --git a/tools/aapt2/NameMangler_test.cpp b/tools/aapt2/NameMangler_test.cpp index 6103655c15e0..bc89b5c3fb43 100644 --- a/tools/aapt2/NameMangler_test.cpp +++ b/tools/aapt2/NameMangler_test.cpp @@ -16,30 +16,32 @@ #include "NameMangler.h" -#include <gtest/gtest.h> #include <string> +#include "test/Test.h" + namespace aapt { TEST(NameManglerTest, MangleName) { - std::u16string package = u"android.appcompat"; - std::u16string name = u"Platform.AppCompat"; + std::string package = "android.appcompat"; + std::string name = "Platform.AppCompat"; - NameMangler::mangle(package, &name); - EXPECT_EQ(name, u"android.appcompat$Platform.AppCompat"); + std::string mangled_name = NameMangler::MangleEntry(package, name); + EXPECT_EQ(mangled_name, "android.appcompat$Platform.AppCompat"); - std::u16string newPackage; - ASSERT_TRUE(NameMangler::unmangle(&name, &newPackage)); - EXPECT_EQ(name, u"Platform.AppCompat"); - EXPECT_EQ(newPackage, u"android.appcompat"); + std::string unmangled_package; + std::string unmangled_name = mangled_name; + ASSERT_TRUE(NameMangler::Unmangle(&unmangled_name, &unmangled_package)); + EXPECT_EQ(unmangled_name, "Platform.AppCompat"); + EXPECT_EQ(unmangled_package, "android.appcompat"); } TEST(NameManglerTest, IgnoreUnmangledName) { - std::u16string package; - std::u16string name = u"foo_bar"; + std::string package; + std::string name = "foo_bar"; - EXPECT_FALSE(NameMangler::unmangle(&name, &package)); - EXPECT_EQ(name, u"foo_bar"); + EXPECT_FALSE(NameMangler::Unmangle(&name, &package)); + EXPECT_EQ(name, "foo_bar"); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp index 9328b697719d..3eef7aa71a92 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -15,82 +15,107 @@ */ #include "Resource.h" -#include "util/StringPiece.h" #include <map> #include <string> namespace aapt { -StringPiece16 toString(ResourceType type) { - switch (type) { - case ResourceType::kAnim: return u"anim"; - case ResourceType::kAnimator: return u"animator"; - case ResourceType::kArray: return u"array"; - case ResourceType::kAttr: return u"attr"; - case ResourceType::kAttrPrivate: return u"^attr-private"; - case ResourceType::kBool: return u"bool"; - case ResourceType::kColor: return u"color"; - case ResourceType::kDimen: return u"dimen"; - case ResourceType::kDrawable: return u"drawable"; - case ResourceType::kFraction: return u"fraction"; - case ResourceType::kId: return u"id"; - case ResourceType::kInteger: return u"integer"; - case ResourceType::kInterpolator: return u"interpolator"; - case ResourceType::kLayout: return u"layout"; - case ResourceType::kMenu: return u"menu"; - case ResourceType::kMipmap: return u"mipmap"; - case ResourceType::kPlurals: return u"plurals"; - case ResourceType::kRaw: return u"raw"; - case ResourceType::kString: return u"string"; - case ResourceType::kStyle: return u"style"; - case ResourceType::kStyleable: return u"styleable"; - case ResourceType::kTransition: return u"transition"; - case ResourceType::kXml: return u"xml"; - } - return {}; +StringPiece ToString(ResourceType type) { + switch (type) { + case ResourceType::kAnim: + return "anim"; + case ResourceType::kAnimator: + return "animator"; + case ResourceType::kArray: + return "array"; + case ResourceType::kAttr: + return "attr"; + case ResourceType::kAttrPrivate: + return "^attr-private"; + case ResourceType::kBool: + return "bool"; + case ResourceType::kColor: + return "color"; + case ResourceType::kDimen: + return "dimen"; + case ResourceType::kDrawable: + return "drawable"; + case ResourceType::kFont: + return "font"; + case ResourceType::kFraction: + return "fraction"; + case ResourceType::kId: + return "id"; + case ResourceType::kInteger: + return "integer"; + case ResourceType::kInterpolator: + return "interpolator"; + case ResourceType::kLayout: + return "layout"; + case ResourceType::kMenu: + return "menu"; + case ResourceType::kMipmap: + return "mipmap"; + case ResourceType::kPlurals: + return "plurals"; + case ResourceType::kRaw: + return "raw"; + case ResourceType::kString: + return "string"; + case ResourceType::kStyle: + return "style"; + case ResourceType::kStyleable: + return "styleable"; + case ResourceType::kTransition: + return "transition"; + case ResourceType::kXml: + return "xml"; + } + return {}; } -static const std::map<StringPiece16, ResourceType> sResourceTypeMap { - { u"anim", ResourceType::kAnim }, - { u"animator", ResourceType::kAnimator }, - { u"array", ResourceType::kArray }, - { u"attr", ResourceType::kAttr }, - { u"^attr-private", ResourceType::kAttrPrivate }, - { u"bool", ResourceType::kBool }, - { u"color", ResourceType::kColor }, - { u"dimen", ResourceType::kDimen }, - { u"drawable", ResourceType::kDrawable }, - { u"fraction", ResourceType::kFraction }, - { u"id", ResourceType::kId }, - { u"integer", ResourceType::kInteger }, - { u"interpolator", ResourceType::kInterpolator }, - { u"layout", ResourceType::kLayout }, - { u"menu", ResourceType::kMenu }, - { u"mipmap", ResourceType::kMipmap }, - { u"plurals", ResourceType::kPlurals }, - { u"raw", ResourceType::kRaw }, - { u"string", ResourceType::kString }, - { u"style", ResourceType::kStyle }, - { u"styleable", ResourceType::kStyleable }, - { u"transition", ResourceType::kTransition }, - { u"xml", ResourceType::kXml }, +static const std::map<StringPiece, ResourceType> sResourceTypeMap{ + {"anim", ResourceType::kAnim}, + {"animator", ResourceType::kAnimator}, + {"array", ResourceType::kArray}, + {"attr", ResourceType::kAttr}, + {"^attr-private", ResourceType::kAttrPrivate}, + {"bool", ResourceType::kBool}, + {"color", ResourceType::kColor}, + {"dimen", ResourceType::kDimen}, + {"drawable", ResourceType::kDrawable}, + {"font", ResourceType::kFont}, + {"fraction", ResourceType::kFraction}, + {"id", ResourceType::kId}, + {"integer", ResourceType::kInteger}, + {"interpolator", ResourceType::kInterpolator}, + {"layout", ResourceType::kLayout}, + {"menu", ResourceType::kMenu}, + {"mipmap", ResourceType::kMipmap}, + {"plurals", ResourceType::kPlurals}, + {"raw", ResourceType::kRaw}, + {"string", ResourceType::kString}, + {"style", ResourceType::kStyle}, + {"styleable", ResourceType::kStyleable}, + {"transition", ResourceType::kTransition}, + {"xml", ResourceType::kXml}, }; -const ResourceType* parseResourceType(const StringPiece16& str) { - auto iter = sResourceTypeMap.find(str); - if (iter == std::end(sResourceTypeMap)) { - return nullptr; - } - return &iter->second; +const ResourceType* ParseResourceType(const StringPiece& str) { + auto iter = sResourceTypeMap.find(str); + if (iter == std::end(sResourceTypeMap)) { + return nullptr; + } + return &iter->second; } bool operator<(const ResourceKey& a, const ResourceKey& b) { - return std::tie(a.name, a.config) < std::tie(b.name, b.config); + return std::tie(a.name, a.config) < std::tie(b.name, b.config); } bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b) { - return std::tie(a.name, a.config) < std::tie(b.name, b.config); + return std::tie(a.name, a.config) < std::tie(b.name, b.config); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 9126b9592faa..13330b548d2c 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -17,17 +17,19 @@ #ifndef AAPT_RESOURCE_H #define AAPT_RESOURCE_H -#include "ConfigDescription.h" -#include "Source.h" - -#include "util/StringPiece.h" - #include <iomanip> #include <limits> +#include <sstream> #include <string> #include <tuple> #include <vector> +#include "utils/JenkinsHash.h" + +#include "ConfigDescription.h" +#include "Source.h" +#include "util/StringPiece.h" + namespace aapt { /** @@ -35,53 +37,56 @@ namespace aapt { * to the 'type' in package:type/entry. */ enum class ResourceType { - kAnim, - kAnimator, - kArray, - kAttr, - kAttrPrivate, - kBool, - kColor, - kDimen, - kDrawable, - kFraction, - kId, - kInteger, - kInterpolator, - kLayout, - kMenu, - kMipmap, - kPlurals, - kRaw, - kString, - kStyle, - kStyleable, - kTransition, - kXml, + kAnim, + kAnimator, + kArray, + kAttr, + kAttrPrivate, + kBool, + kColor, + kDimen, + kDrawable, + kFont, + kFraction, + kId, + kInteger, + kInterpolator, + kLayout, + kMenu, + kMipmap, + kPlurals, + kRaw, + kString, + kStyle, + kStyleable, + kTransition, + kXml, }; -StringPiece16 toString(ResourceType type); +StringPiece ToString(ResourceType type); /** * Returns a pointer to a valid ResourceType, or nullptr if * the string was invalid. */ -const ResourceType* parseResourceType(const StringPiece16& str); +const ResourceType* ParseResourceType(const StringPiece& str); /** * A resource's name. This can uniquely identify * a resource in the ResourceTable. */ struct ResourceName { - std::u16string package; - ResourceType type; - std::u16string entry; + std::string package; + ResourceType type = ResourceType::kRaw; + std::string entry; + + ResourceName() = default; + ResourceName(const StringPiece& p, ResourceType t, const StringPiece& e); - ResourceName() : type(ResourceType::kRaw) {} - ResourceName(const StringPiece16& p, ResourceType t, const StringPiece16& e); + int compare(const ResourceName& other) const; - bool isValid() const; - std::u16string toString() const; + bool is_valid() const; + std::string ToString() const; }; /** @@ -91,21 +96,21 @@ struct ResourceName { * of the original string. */ struct ResourceNameRef { - StringPiece16 package; - ResourceType type; - StringPiece16 entry; - - ResourceNameRef() = default; - ResourceNameRef(const ResourceNameRef&) = default; - ResourceNameRef(ResourceNameRef&&) = default; - ResourceNameRef(const ResourceName& rhs); // NOLINT(implicit) - ResourceNameRef(const StringPiece16& p, ResourceType t, const StringPiece16& e); - ResourceNameRef& operator=(const ResourceNameRef& rhs) = default; - ResourceNameRef& operator=(ResourceNameRef&& rhs) = default; - ResourceNameRef& operator=(const ResourceName& rhs); - - ResourceName toResourceName() const; - bool isValid() const; + StringPiece package; + ResourceType type = ResourceType::kRaw; + StringPiece entry; + + ResourceNameRef() = default; + ResourceNameRef(const ResourceNameRef&) = default; + ResourceNameRef(ResourceNameRef&&) = default; + ResourceNameRef(const ResourceName& rhs); // NOLINT(implicit) + ResourceNameRef(const StringPiece& p, ResourceType t, const StringPiece& e); + ResourceNameRef& operator=(const ResourceNameRef& rhs) = default; + ResourceNameRef& operator=(ResourceNameRef&& rhs) = default; + ResourceNameRef& operator=(const ResourceName& rhs); + + ResourceName ToResourceName() const; + bool is_valid() const; }; /** @@ -120,64 +125,66 @@ struct ResourceNameRef { * EEEE: 16 bit entry identifier. */ struct ResourceId { - uint32_t id; + uint32_t id; - ResourceId(); - ResourceId(const ResourceId& rhs); - ResourceId(uint32_t resId); // NOLINT(implicit) - ResourceId(uint8_t p, uint8_t t, uint16_t e); + ResourceId(); + ResourceId(const ResourceId& rhs); + ResourceId(uint32_t res_id); // NOLINT(implicit) + ResourceId(uint8_t p, uint8_t t, uint16_t e); - bool isValid() const; - uint8_t packageId() const; - uint8_t typeId() const; - uint16_t entryId() const; + bool is_valid() const; + uint8_t package_id() const; + uint8_t type_id() const; + uint16_t entry_id() const; }; struct SourcedResourceName { - ResourceName name; - size_t line; + ResourceName name; + size_t line; }; struct ResourceFile { - // Name - ResourceName name; + // Name + ResourceName name; - // Configuration - ConfigDescription config; + // Configuration + ConfigDescription config; - // Source - Source source; + // Source + Source source; - // Exported symbols - std::vector<SourcedResourceName> exportedSymbols; + // Exported symbols + std::vector<SourcedResourceName> exported_symbols; }; /** - * Useful struct used as a key to represent a unique resource in associative containers. + * Useful struct used as a key to represent a unique resource in associative + * containers. */ struct ResourceKey { - ResourceName name; - ConfigDescription config; + ResourceName name; + ConfigDescription config; }; bool operator<(const ResourceKey& a, const ResourceKey& b); /** - * Useful struct used as a key to represent a unique resource in associative containers. + * Useful struct used as a key to represent a unique resource in associative + * containers. * Holds a reference to the name, so that name better live longer than this key! */ struct ResourceKeyRef { - ResourceNameRef name; - ConfigDescription config; + ResourceNameRef name; + ConfigDescription config; - ResourceKeyRef() = default; - ResourceKeyRef(const ResourceNameRef& n, const ConfigDescription& c) : name(n), config(c) { - } + ResourceKeyRef() = default; + ResourceKeyRef(const ResourceNameRef& n, const ConfigDescription& c) + : name(n), config(c) {} - /** - * Prevent taking a reference to a temporary. This is bad. - */ - ResourceKeyRef(ResourceName&& n, const ConfigDescription& c) = delete; + /** + * Prevent taking a reference to a temporary. This is bad. + */ + ResourceKeyRef(ResourceName&& n, const ConfigDescription& c) = delete; }; bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b); @@ -186,173 +193,194 @@ bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b); // ResourceId implementation. // -inline ResourceId::ResourceId() : id(0) { -} +inline ResourceId::ResourceId() : id(0) {} -inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) { -} +inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {} -inline ResourceId::ResourceId(uint32_t resId) : id(resId) { -} +inline ResourceId::ResourceId(uint32_t res_id) : id(res_id) {} -inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e) : id((p << 24) | (t << 16) | e) { -} +inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e) + : id((p << 24) | (t << 16) | e) {} -inline bool ResourceId::isValid() const { - return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0; +inline bool ResourceId::is_valid() const { + return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0; } -inline uint8_t ResourceId::packageId() const { - return static_cast<uint8_t>(id >> 24); +inline uint8_t ResourceId::package_id() const { + return static_cast<uint8_t>(id >> 24); } -inline uint8_t ResourceId::typeId() const { - return static_cast<uint8_t>(id >> 16); +inline uint8_t ResourceId::type_id() const { + return static_cast<uint8_t>(id >> 16); } -inline uint16_t ResourceId::entryId() const { - return static_cast<uint16_t>(id); +inline uint16_t ResourceId::entry_id() const { + return static_cast<uint16_t>(id); } inline bool operator<(const ResourceId& lhs, const ResourceId& rhs) { - return lhs.id < rhs.id; + return lhs.id < rhs.id; } inline bool operator>(const ResourceId& lhs, const ResourceId& rhs) { - return lhs.id > rhs.id; + return lhs.id > rhs.id; } inline bool operator==(const ResourceId& lhs, const ResourceId& rhs) { - return lhs.id == rhs.id; + return lhs.id == rhs.id; } inline bool operator!=(const ResourceId& lhs, const ResourceId& rhs) { - return lhs.id != rhs.id; + return lhs.id != rhs.id; } -inline ::std::ostream& operator<<(::std::ostream& out, const ResourceId& resId) { - std::ios_base::fmtflags oldFlags = out.flags(); - char oldFill = out.fill(); - out << "0x" << std::internal << std::setfill('0') << std::setw(8) - << std::hex << resId.id; - out.flags(oldFlags); - out.fill(oldFill); - return out; +inline ::std::ostream& operator<<(::std::ostream& out, + const ResourceId& res_id) { + std::ios_base::fmtflags old_flags = out.flags(); + char old_fill = out.fill(); + out << "0x" << std::internal << std::setfill('0') << std::setw(8) << std::hex + << res_id.id; + out.flags(old_flags); + out.fill(old_fill); + return out; } // // ResourceType implementation. // -inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) { - return out << toString(val); +inline ::std::ostream& operator<<(::std::ostream& out, + const ResourceType& val) { + return out << ToString(val); } // // ResourceName implementation. // -inline ResourceName::ResourceName(const StringPiece16& p, ResourceType t, const StringPiece16& e) : - package(p.toString()), type(t), entry(e.toString()) { +inline ResourceName::ResourceName(const StringPiece& p, ResourceType t, + const StringPiece& e) + : package(p.ToString()), type(t), entry(e.ToString()) {} + +inline int ResourceName::compare(const ResourceName& other) const { + int cmp = package.compare(other.package); + if (cmp != 0) return cmp; + cmp = static_cast<int>(type) - static_cast<int>(other.type); + if (cmp != 0) return cmp; + cmp = entry.compare(other.entry); + return cmp; } -inline bool ResourceName::isValid() const { - return !package.empty() && !entry.empty(); +inline bool ResourceName::is_valid() const { + return !package.empty() && !entry.empty(); } inline bool operator<(const ResourceName& lhs, const ResourceName& rhs) { - return std::tie(lhs.package, lhs.type, lhs.entry) - < std::tie(rhs.package, rhs.type, rhs.entry); + return std::tie(lhs.package, lhs.type, lhs.entry) < + std::tie(rhs.package, rhs.type, rhs.entry); } inline bool operator==(const ResourceName& lhs, const ResourceName& rhs) { - return std::tie(lhs.package, lhs.type, lhs.entry) - == std::tie(rhs.package, rhs.type, rhs.entry); + return std::tie(lhs.package, lhs.type, lhs.entry) == + std::tie(rhs.package, rhs.type, rhs.entry); } inline bool operator!=(const ResourceName& lhs, const ResourceName& rhs) { - return std::tie(lhs.package, lhs.type, lhs.entry) - != std::tie(rhs.package, rhs.type, rhs.entry); + return std::tie(lhs.package, lhs.type, lhs.entry) != + std::tie(rhs.package, rhs.type, rhs.entry); } -inline std::u16string ResourceName::toString() const { - std::u16string result; - if (!package.empty()) { - result = package + u":"; - } - return result + aapt::toString(type).toString() + u"/" + entry; +inline ::std::ostream& operator<<(::std::ostream& out, + const ResourceName& name) { + if (!name.package.empty()) { + out << name.package << ":"; + } + return out << name.type << "/" << name.entry; } -inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) { - if (!name.package.empty()) { - out << name.package << ":"; - } - return out << name.type << "/" << name.entry; +inline std::string ResourceName::ToString() const { + std::stringstream stream; + stream << *this; + return stream.str(); } - // // ResourceNameRef implementation. // -inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs) : - package(rhs.package), type(rhs.type), entry(rhs.entry) { -} +inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs) + : package(rhs.package), type(rhs.type), entry(rhs.entry) {} -inline ResourceNameRef::ResourceNameRef(const StringPiece16& p, ResourceType t, - const StringPiece16& e) : - package(p), type(t), entry(e) { -} +inline ResourceNameRef::ResourceNameRef(const StringPiece& p, ResourceType t, + const StringPiece& e) + : package(p), type(t), entry(e) {} inline ResourceNameRef& ResourceNameRef::operator=(const ResourceName& rhs) { - package = rhs.package; - type = rhs.type; - entry = rhs.entry; - return *this; + package = rhs.package; + type = rhs.type; + entry = rhs.entry; + return *this; } -inline ResourceName ResourceNameRef::toResourceName() const { - return { package.toString(), type, entry.toString() }; +inline ResourceName ResourceNameRef::ToResourceName() const { + return ResourceName(package, type, entry); } -inline bool ResourceNameRef::isValid() const { - return !package.empty() && !entry.empty(); +inline bool ResourceNameRef::is_valid() const { + return !package.empty() && !entry.empty(); } inline bool operator<(const ResourceNameRef& lhs, const ResourceNameRef& rhs) { - return std::tie(lhs.package, lhs.type, lhs.entry) - < std::tie(rhs.package, rhs.type, rhs.entry); + return std::tie(lhs.package, lhs.type, lhs.entry) < + std::tie(rhs.package, rhs.type, rhs.entry); } inline bool operator==(const ResourceNameRef& lhs, const ResourceNameRef& rhs) { - return std::tie(lhs.package, lhs.type, lhs.entry) - == std::tie(rhs.package, rhs.type, rhs.entry); + return std::tie(lhs.package, lhs.type, lhs.entry) == + std::tie(rhs.package, rhs.type, rhs.entry); } inline bool operator!=(const ResourceNameRef& lhs, const ResourceNameRef& rhs) { - return std::tie(lhs.package, lhs.type, lhs.entry) - != std::tie(rhs.package, rhs.type, rhs.entry); + return std::tie(lhs.package, lhs.type, lhs.entry) != + std::tie(rhs.package, rhs.type, rhs.entry); } -inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNameRef& name) { - if (!name.package.empty()) { - out << name.package << ":"; - } - return out << name.type << "/" << name.entry; +inline ::std::ostream& operator<<(::std::ostream& out, + const ResourceNameRef& name) { + if (!name.package.empty()) { + out << name.package << ":"; + } + return out << name.type << "/" << name.entry; } inline bool operator<(const ResourceName& lhs, const ResourceNameRef& b) { - return ResourceNameRef(lhs) < b; + return ResourceNameRef(lhs) < b; } inline bool operator!=(const ResourceName& lhs, const ResourceNameRef& rhs) { - return ResourceNameRef(lhs) != rhs; + return ResourceNameRef(lhs) != rhs; } -inline bool operator==(const SourcedResourceName& lhs, const SourcedResourceName& rhs) { - return lhs.name == rhs.name && lhs.line == rhs.line; +inline bool operator==(const SourcedResourceName& lhs, + const SourcedResourceName& rhs) { + return lhs.name == rhs.name && lhs.line == rhs.line; } -} // namespace aapt +} // namespace aapt + +namespace std { + +template <> +struct hash<aapt::ResourceName> { + size_t operator()(const aapt::ResourceName& name) const { + android::hash_t h = 0; + h = android::JenkinsHashMix(h, hash<string>()(name.package)); + h = android::JenkinsHashMix(h, static_cast<uint32_t>(name.type)); + h = android::JenkinsHashMix(h, hash<string>()(name.entry)); + return static_cast<size_t>(h); + } +}; + +} // namespace std -#endif // AAPT_RESOURCE_H +#endif // AAPT_RESOURCE_H diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index a84c306e2733..b16def4025ff 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -15,6 +15,12 @@ */ #include "ResourceParser.h" + +#include <functional> +#include <sstream> + +#include "android-base/logging.h" + #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" @@ -23,450 +29,492 @@ #include "util/Util.h" #include "xml/XmlPullParser.h" -#include <functional> -#include <sstream> - namespace aapt { -constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2"; +constexpr const char* sXliffNamespaceUri = + "urn:oasis:names:tc:xliff:document:1.2"; /** - * Returns true if the element is <skip> or <eat-comment> and can be safely ignored. + * Returns true if the element is <skip> or <eat-comment> and can be safely + * ignored. */ -static bool shouldIgnoreElement(const StringPiece16& ns, const StringPiece16& name) { - return ns.empty() && (name == u"skip" || name == u"eat-comment"); +static bool ShouldIgnoreElement(const StringPiece& ns, + const StringPiece& name) { + return ns.empty() && (name == "skip" || name == "eat-comment"); } -static uint32_t parseFormatType(const StringPiece16& piece) { - if (piece == u"reference") return android::ResTable_map::TYPE_REFERENCE; - else if (piece == u"string") return android::ResTable_map::TYPE_STRING; - else if (piece == u"integer") return android::ResTable_map::TYPE_INTEGER; - else if (piece == u"boolean") return android::ResTable_map::TYPE_BOOLEAN; - else if (piece == u"color") return android::ResTable_map::TYPE_COLOR; - else if (piece == u"float") return android::ResTable_map::TYPE_FLOAT; - else if (piece == u"dimension") return android::ResTable_map::TYPE_DIMENSION; - else if (piece == u"fraction") return android::ResTable_map::TYPE_FRACTION; - else if (piece == u"enum") return android::ResTable_map::TYPE_ENUM; - else if (piece == u"flags") return android::ResTable_map::TYPE_FLAGS; - return 0; +static uint32_t ParseFormatType(const StringPiece& piece) { + if (piece == "reference") + return android::ResTable_map::TYPE_REFERENCE; + else if (piece == "string") + return android::ResTable_map::TYPE_STRING; + else if (piece == "integer") + return android::ResTable_map::TYPE_INTEGER; + else if (piece == "boolean") + return android::ResTable_map::TYPE_BOOLEAN; + else if (piece == "color") + return android::ResTable_map::TYPE_COLOR; + else if (piece == "float") + return android::ResTable_map::TYPE_FLOAT; + else if (piece == "dimension") + return android::ResTable_map::TYPE_DIMENSION; + else if (piece == "fraction") + return android::ResTable_map::TYPE_FRACTION; + else if (piece == "enum") + return android::ResTable_map::TYPE_ENUM; + else if (piece == "flags") + return android::ResTable_map::TYPE_FLAGS; + return 0; } -static uint32_t parseFormatAttribute(const StringPiece16& str) { - uint32_t mask = 0; - for (StringPiece16 part : util::tokenize(str, u'|')) { - StringPiece16 trimmedPart = util::trimWhitespace(part); - uint32_t type = parseFormatType(trimmedPart); - if (type == 0) { - return 0; - } - mask |= type; - } - return mask; +static uint32_t ParseFormatAttribute(const StringPiece& str) { + uint32_t mask = 0; + for (StringPiece part : util::Tokenize(str, '|')) { + StringPiece trimmed_part = util::TrimWhitespace(part); + uint32_t type = ParseFormatType(trimmed_part); + if (type == 0) { + return 0; + } + mask |= type; + } + return mask; } /** * A parsed resource ready to be added to the ResourceTable. */ struct ParsedResource { - ResourceName name; - ConfigDescription config; - std::string product; - Source source; - ResourceId id; - Maybe<SymbolState> symbolState; - std::u16string comment; - std::unique_ptr<Value> value; - std::list<ParsedResource> childResources; + ResourceName name; + ConfigDescription config; + std::string product; + Source source; + ResourceId id; + Maybe<SymbolState> symbol_state; + std::string comment; + std::unique_ptr<Value> value; + std::list<ParsedResource> child_resources; }; // Recursively adds resources to the ResourceTable. -static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) { - StringPiece16 trimmedComment = util::trimWhitespace(res->comment); - if (trimmedComment.size() != res->comment.size()) { - // Only if there was a change do we re-assign. - res->comment = trimmedComment.toString(); - } - - if (res->symbolState) { - Symbol symbol; - symbol.state = res->symbolState.value(); - symbol.source = res->source; - symbol.comment = res->comment; - if (!table->setSymbolState(res->name, res->id, symbol, diag)) { - return false; - } - } - - if (res->value) { - // Attach the comment, source and config to the value. - res->value->setComment(std::move(res->comment)); - res->value->setSource(std::move(res->source)); - - if (!table->addResource(res->name, res->id, res->config, res->product, - std::move(res->value), diag)) { - return false; - } - } - - bool error = false; - for (ParsedResource& child : res->childResources) { - error |= !addResourcesToTable(table, diag, &child); - } - return !error; +static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, + ParsedResource* res) { + StringPiece trimmed_comment = util::TrimWhitespace(res->comment); + if (trimmed_comment.size() != res->comment.size()) { + // Only if there was a change do we re-assign. + res->comment = trimmed_comment.ToString(); + } + + if (res->symbol_state) { + Symbol symbol; + symbol.state = res->symbol_state.value(); + symbol.source = res->source; + symbol.comment = res->comment; + if (!table->SetSymbolState(res->name, res->id, symbol, diag)) { + return false; + } + } + + if (res->value) { + // Attach the comment, source and config to the value. + res->value->SetComment(std::move(res->comment)); + res->value->SetSource(std::move(res->source)); + + if (!table->AddResource(res->name, res->id, res->config, res->product, + std::move(res->value), diag)) { + return false; + } + } + + bool error = false; + for (ParsedResource& child : res->child_resources) { + error |= !AddResourcesToTable(table, diag, &child); + } + return !error; } // Convenient aliases for more readable function calls. -enum { - kAllowRawString = true, - kNoRawString = false -}; +enum { kAllowRawString = true, kNoRawString = false }; -ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, +ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, + const Source& source, const ConfigDescription& config, - const ResourceParserOptions& options) : - mDiag(diag), mTable(table), mSource(source), mConfig(config), mOptions(options) { -} + const ResourceParserOptions& options) + : diag_(diag), + table_(table), + source_(source), + config_(config), + options_(options) {} /** * Build a string from XML that converts nested elements into Span objects. */ -bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString, - StyleString* outStyleString) { - std::vector<Span> spanStack; - - bool error = false; - outRawString->clear(); - outStyleString->spans.clear(); - util::StringBuilder builder; - size_t depth = 1; - while (xml::XmlPullParser::isGoodEvent(parser->next())) { - const xml::XmlPullParser::Event event = parser->getEvent(); - if (event == xml::XmlPullParser::Event::kEndElement) { - if (!parser->getElementNamespace().empty()) { - // We already warned and skipped the start element, so just skip here too - continue; - } - - depth--; - if (depth == 0) { - break; - } - - spanStack.back().lastChar = builder.str().size() - 1; - outStyleString->spans.push_back(spanStack.back()); - spanStack.pop_back(); - - } else if (event == xml::XmlPullParser::Event::kText) { - outRawString->append(parser->getText()); - builder.append(parser->getText()); - - } else if (event == xml::XmlPullParser::Event::kStartElement) { - if (!parser->getElementNamespace().empty()) { - if (parser->getElementNamespace() != sXliffNamespaceUri) { - // Only warn if this isn't an xliff namespace. - mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "skipping element '" - << parser->getElementName() - << "' with unknown namespace '" - << parser->getElementNamespace() - << "'"); - } - continue; - } - depth++; - - // Build a span object out of the nested element. - std::u16string spanName = parser->getElementName(); - const auto endAttrIter = parser->endAttributes(); - for (auto attrIter = parser->beginAttributes(); attrIter != endAttrIter; ++attrIter) { - spanName += u";"; - spanName += attrIter->name; - spanName += u"="; - spanName += attrIter->value; - } - - if (builder.str().size() > std::numeric_limits<uint32_t>::max()) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "style string '" << builder.str() << "' is too long"); - error = true; - } else { - spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) }); - } - - } else if (event == xml::XmlPullParser::Event::kComment) { - // Skip - } else { - assert(false); +bool ResourceParser::FlattenXmlSubtree(xml::XmlPullParser* parser, + std::string* out_raw_string, + StyleString* out_style_string) { + std::vector<Span> span_stack; + + bool error = false; + out_raw_string->clear(); + out_style_string->spans.clear(); + util::StringBuilder builder; + size_t depth = 1; + while (xml::XmlPullParser::IsGoodEvent(parser->Next())) { + const xml::XmlPullParser::Event event = parser->event(); + if (event == xml::XmlPullParser::Event::kEndElement) { + if (!parser->element_namespace().empty()) { + // We already warned and skipped the start element, so just skip here + // too + continue; + } + + depth--; + if (depth == 0) { + break; + } + + span_stack.back().last_char = builder.Utf16Len() - 1; + out_style_string->spans.push_back(span_stack.back()); + span_stack.pop_back(); + + } else if (event == xml::XmlPullParser::Event::kText) { + out_raw_string->append(parser->text()); + builder.Append(parser->text()); + + } else if (event == xml::XmlPullParser::Event::kStartElement) { + if (!parser->element_namespace().empty()) { + if (parser->element_namespace() != sXliffNamespaceUri) { + // Only warn if this isn't an xliff namespace. + diag_->Warn(DiagMessage(source_.WithLine(parser->line_number())) + << "skipping element '" << parser->element_name() + << "' with unknown namespace '" + << parser->element_namespace() << "'"); } + continue; + } + depth++; + + // Build a span object out of the nested element. + std::string span_name = parser->element_name(); + const auto end_attr_iter = parser->end_attributes(); + for (auto attr_iter = parser->begin_attributes(); + attr_iter != end_attr_iter; ++attr_iter) { + span_name += ";"; + span_name += attr_iter->name; + span_name += "="; + span_name += attr_iter->value; + } + + if (builder.Utf16Len() > std::numeric_limits<uint32_t>::max()) { + diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + << "style string '" << builder.ToString() + << "' is too long"); + error = true; + } else { + span_stack.push_back( + Span{span_name, static_cast<uint32_t>(builder.Utf16Len())}); + } + + } else if (event == xml::XmlPullParser::Event::kComment) { + // Skip + } else { + LOG(FATAL) << "unhandled XML event"; } - assert(spanStack.empty() && "spans haven't been fully processed"); + } + CHECK(span_stack.empty()) << "spans haven't been fully processed"; - outStyleString->str = builder.str(); - return !error; + out_style_string->str = builder.ToString(); + return !error; } -bool ResourceParser::parse(xml::XmlPullParser* parser) { - bool error = false; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { - // Skip comments and text. - continue; - } +bool ResourceParser::Parse(xml::XmlPullParser* parser) { + bool error = false; + const size_t depth = parser->depth(); + while (xml::XmlPullParser::NextChildNode(parser, depth)) { + if (parser->event() != xml::XmlPullParser::Event::kStartElement) { + // Skip comments and text. + continue; + } - if (!parser->getElementNamespace().empty() || parser->getElementName() != u"resources") { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "root element must be <resources>"); - return false; - } + if (!parser->element_namespace().empty() || + parser->element_name() != "resources") { + diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + << "root element must be <resources>"); + return false; + } - error |= !parseResources(parser); - break; - }; + error |= !ParseResources(parser); + break; + }; - if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "xml parser error: " << parser->getLastError()); - return false; - } - return !error; + if (parser->event() == xml::XmlPullParser::Event::kBadDocument) { + diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + << "xml parser error: " << parser->error()); + return false; + } + return !error; } -bool ResourceParser::parseResources(xml::XmlPullParser* parser) { - std::set<ResourceName> strippedResources; - - bool error = false; - std::u16string comment; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - const xml::XmlPullParser::Event event = parser->getEvent(); - if (event == xml::XmlPullParser::Event::kComment) { - comment = parser->getComment(); - continue; - } - - if (event == xml::XmlPullParser::Event::kText) { - if (!util::trimWhitespace(parser->getText()).empty()) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "plain text not allowed here"); - error = true; - } - continue; - } +bool ResourceParser::ParseResources(xml::XmlPullParser* parser) { + std::set<ResourceName> stripped_resources; - assert(event == xml::XmlPullParser::Event::kStartElement); + bool error = false; + std::string comment; + const size_t depth = parser->depth(); + while (xml::XmlPullParser::NextChildNode(parser, depth)) { + const xml::XmlPullParser::Event event = parser->event(); + if (event == xml::XmlPullParser::Event::kComment) { + comment = parser->comment(); + continue; + } - if (!parser->getElementNamespace().empty()) { - // Skip unknown namespace. - continue; - } + if (event == xml::XmlPullParser::Event::kText) { + if (!util::TrimWhitespace(parser->text()).empty()) { + diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + << "plain text not allowed here"); + error = true; + } + continue; + } - std::u16string elementName = parser->getElementName(); - if (elementName == u"skip" || elementName == u"eat-comment") { - comment = u""; - continue; - } + CHECK(event == xml::XmlPullParser::Event::kStartElement); - ParsedResource parsedResource; - parsedResource.config = mConfig; - parsedResource.source = mSource.withLine(parser->getLineNumber()); - parsedResource.comment = std::move(comment); + if (!parser->element_namespace().empty()) { + // Skip unknown namespace. + continue; + } - // Extract the product name if it exists. - if (Maybe<StringPiece16> maybeProduct = xml::findNonEmptyAttribute(parser, u"product")) { - parsedResource.product = util::utf16ToUtf8(maybeProduct.value()); - } + std::string element_name = parser->element_name(); + if (element_name == "skip" || element_name == "eat-comment") { + comment = ""; + continue; + } - // Parse the resource regardless of product. - if (!parseResource(parser, &parsedResource)) { - error = true; - continue; - } + ParsedResource parsed_resource; + parsed_resource.config = config_; + parsed_resource.source = source_.WithLine(parser->line_number()); + parsed_resource.comment = std::move(comment); - if (!addResourcesToTable(mTable, mDiag, &parsedResource)) { - error = true; - } + // Extract the product name if it exists. + if (Maybe<StringPiece> maybe_product = + xml::FindNonEmptyAttribute(parser, "product")) { + parsed_resource.product = maybe_product.value().ToString(); } - // Check that we included at least one variant of each stripped resource. - for (const ResourceName& strippedResource : strippedResources) { - if (!mTable->findResource(strippedResource)) { - // Failed to find the resource. - mDiag->error(DiagMessage(mSource) << "resource '" << strippedResource << "' " - "was filtered out but no product variant remains"); - error = true; - } + // Parse the resource regardless of product. + if (!ParseResource(parser, &parsed_resource)) { + error = true; + continue; } - return !error; -} + if (!AddResourcesToTable(table_, diag_, &parsed_resource)) { + error = true; + } + } + // Check that we included at least one variant of each stripped resource. + for (const ResourceName& stripped_resource : stripped_resources) { + if (!table_->FindResource(stripped_resource)) { + // Failed to find the resource. + diag_->Error(DiagMessage(source_) + << "resource '" << stripped_resource + << "' " + "was filtered out but no product variant remains"); + error = true; + } + } -bool ResourceParser::parseResource(xml::XmlPullParser* parser, ParsedResource* outResource) { - struct ItemTypeFormat { - ResourceType type; - uint32_t format; - }; - - using BagParseFunc = std::function<bool(ResourceParser*, xml::XmlPullParser*, ParsedResource*)>; - - static const auto elToItemMap = ImmutableMap<std::u16string, ItemTypeFormat>::createPreSorted({ - { u"bool", { ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN } }, - { u"color", { ResourceType::kColor, android::ResTable_map::TYPE_COLOR } }, - { u"dimen", { ResourceType::kDimen, android::ResTable_map::TYPE_FLOAT - | android::ResTable_map::TYPE_FRACTION - | android::ResTable_map::TYPE_DIMENSION } }, - { u"drawable", { ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR } }, - { u"fraction", { ResourceType::kFraction, android::ResTable_map::TYPE_FLOAT - | android::ResTable_map::TYPE_FRACTION - | android::ResTable_map::TYPE_DIMENSION } }, - { u"integer", { ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER } }, - { u"string", { ResourceType::kString, android::ResTable_map::TYPE_STRING } }, - }); - - static const auto elToBagMap = ImmutableMap<std::u16string, BagParseFunc>::createPreSorted({ - { u"add-resource", std::mem_fn(&ResourceParser::parseAddResource) }, - { u"array", std::mem_fn(&ResourceParser::parseArray) }, - { u"attr", std::mem_fn(&ResourceParser::parseAttr) }, - { u"declare-styleable", std::mem_fn(&ResourceParser::parseDeclareStyleable) }, - { u"integer-array", std::mem_fn(&ResourceParser::parseIntegerArray) }, - { u"java-symbol", std::mem_fn(&ResourceParser::parseSymbol) }, - { u"plurals", std::mem_fn(&ResourceParser::parsePlural) }, - { u"public", std::mem_fn(&ResourceParser::parsePublic) }, - { u"public-group", std::mem_fn(&ResourceParser::parsePublicGroup) }, - { u"string-array", std::mem_fn(&ResourceParser::parseStringArray) }, - { u"style", std::mem_fn(&ResourceParser::parseStyle) }, - { u"symbol", std::mem_fn(&ResourceParser::parseSymbol) }, - }); - - std::u16string resourceType = parser->getElementName(); - - // The value format accepted for this resource. - uint32_t resourceFormat = 0u; - - if (resourceType == u"item") { - // Items have their type encoded in the type attribute. - if (Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type")) { - resourceType = maybeType.value().toString(); - } else { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "<item> must have a 'type' attribute"); - return false; - } + return !error; +} - if (Maybe<StringPiece16> maybeFormat = xml::findNonEmptyAttribute(parser, u"format")) { - // An explicit format for this resource was specified. The resource will retain - // its type in its name, but the accepted value for this type is overridden. - resourceFormat = parseFormatType(maybeFormat.value()); - if (!resourceFormat) { - mDiag->error(DiagMessage(outResource->source) - << "'" << maybeFormat.value() << "' is an invalid format"); - return false; - } - } +bool ResourceParser::ParseResource(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + struct ItemTypeFormat { + ResourceType type; + uint32_t format; + }; + + using BagParseFunc = std::function<bool(ResourceParser*, xml::XmlPullParser*, + ParsedResource*)>; + + static const auto elToItemMap = + ImmutableMap<std::string, ItemTypeFormat>::CreatePreSorted({ + {"bool", {ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN}}, + {"color", {ResourceType::kColor, android::ResTable_map::TYPE_COLOR}}, + {"dimen", + {ResourceType::kDimen, android::ResTable_map::TYPE_FLOAT | + android::ResTable_map::TYPE_FRACTION | + android::ResTable_map::TYPE_DIMENSION}}, + {"drawable", + {ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR}}, + {"fraction", + {ResourceType::kFraction, + android::ResTable_map::TYPE_FLOAT | + android::ResTable_map::TYPE_FRACTION | + android::ResTable_map::TYPE_DIMENSION}}, + {"integer", + {ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER}}, + {"string", + {ResourceType::kString, android::ResTable_map::TYPE_STRING}}, + }); + + static const auto elToBagMap = + ImmutableMap<std::string, BagParseFunc>::CreatePreSorted({ + {"add-resource", std::mem_fn(&ResourceParser::ParseAddResource)}, + {"array", std::mem_fn(&ResourceParser::ParseArray)}, + {"attr", std::mem_fn(&ResourceParser::ParseAttr)}, + {"declare-styleable", + std::mem_fn(&ResourceParser::ParseDeclareStyleable)}, + {"integer-array", std::mem_fn(&ResourceParser::ParseIntegerArray)}, + {"java-symbol", std::mem_fn(&ResourceParser::ParseSymbol)}, + {"plurals", std::mem_fn(&ResourceParser::ParsePlural)}, + {"public", std::mem_fn(&ResourceParser::ParsePublic)}, + {"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)}, + {"string-array", std::mem_fn(&ResourceParser::ParseStringArray)}, + {"style", std::mem_fn(&ResourceParser::ParseStyle)}, + {"symbol", std::mem_fn(&ResourceParser::ParseSymbol)}, + }); + + std::string resource_type = parser->element_name(); + + // The value format accepted for this resource. + uint32_t resource_format = 0u; + + if (resource_type == "item") { + // Items have their type encoded in the type attribute. + if (Maybe<StringPiece> maybe_type = + xml::FindNonEmptyAttribute(parser, "type")) { + resource_type = maybe_type.value().ToString(); + } else { + diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + << "<item> must have a 'type' attribute"); + return false; + } + + if (Maybe<StringPiece> maybe_format = + xml::FindNonEmptyAttribute(parser, "format")) { + // An explicit format for this resource was specified. The resource will + // retain + // its type in its name, but the accepted value for this type is + // overridden. + resource_format = ParseFormatType(maybe_format.value()); + if (!resource_format) { + diag_->Error(DiagMessage(out_resource->source) + << "'" << maybe_format.value() + << "' is an invalid format"); + return false; + } } + } - // Get the name of the resource. This will be checked later, because not all - // XML elements require a name. - Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name"); + // Get the name of the resource. This will be checked later, because not all + // XML elements require a name. + Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); - if (resourceType == u"id") { - if (!maybeName) { - mDiag->error(DiagMessage(outResource->source) - << "<" << parser->getElementName() << "> missing 'name' attribute"); - return false; - } - - outResource->name.type = ResourceType::kId; - outResource->name.entry = maybeName.value().toString(); - outResource->value = util::make_unique<Id>(); - return true; + if (resource_type == "id") { + if (!maybe_name) { + diag_->Error(DiagMessage(out_resource->source) + << "<" << parser->element_name() + << "> missing 'name' attribute"); + return false; } - const auto itemIter = elToItemMap.find(resourceType); - if (itemIter != elToItemMap.end()) { - // This is an item, record its type and format and start parsing. + out_resource->name.type = ResourceType::kId; + out_resource->name.entry = maybe_name.value().ToString(); + out_resource->value = util::make_unique<Id>(); + return true; + } - if (!maybeName) { - mDiag->error(DiagMessage(outResource->source) - << "<" << parser->getElementName() << "> missing 'name' attribute"); - return false; - } + const auto item_iter = elToItemMap.find(resource_type); + if (item_iter != elToItemMap.end()) { + // This is an item, record its type and format and start parsing. - outResource->name.type = itemIter->second.type; - outResource->name.entry = maybeName.value().toString(); + if (!maybe_name) { + diag_->Error(DiagMessage(out_resource->source) + << "<" << parser->element_name() + << "> missing 'name' attribute"); + return false; + } - // Only use the implicit format for this type if it wasn't overridden. - if (!resourceFormat) { - resourceFormat = itemIter->second.format; - } + out_resource->name.type = item_iter->second.type; + out_resource->name.entry = maybe_name.value().ToString(); - if (!parseItem(parser, outResource, resourceFormat)) { - return false; - } - return true; + // Only use the implicit format for this type if it wasn't overridden. + if (!resource_format) { + resource_format = item_iter->second.format; } - // This might be a bag or something. - const auto bagIter = elToBagMap.find(resourceType); - if (bagIter != elToBagMap.end()) { - // Ensure we have a name (unless this is a <public-group>). - if (resourceType != u"public-group") { - if (!maybeName) { - mDiag->error(DiagMessage(outResource->source) - << "<" << parser->getElementName() << "> missing 'name' attribute"); - return false; - } - - outResource->name.entry = maybeName.value().toString(); - } - - // Call the associated parse method. The type will be filled in by the - // parse func. - if (!bagIter->second(this, parser, outResource)) { - return false; - } - return true; + if (!ParseItem(parser, out_resource, resource_format)) { + return false; } + return true; + } + + // This might be a bag or something. + const auto bag_iter = elToBagMap.find(resource_type); + if (bag_iter != elToBagMap.end()) { + // Ensure we have a name (unless this is a <public-group>). + if (resource_type != "public-group") { + if (!maybe_name) { + diag_->Error(DiagMessage(out_resource->source) + << "<" << parser->element_name() + << "> missing 'name' attribute"); + return false; + } - // Try parsing the elementName (or type) as a resource. These shall only be - // resources like 'layout' or 'xml' and they can only be references. - const ResourceType* parsedType = parseResourceType(resourceType); - if (parsedType) { - if (!maybeName) { - mDiag->error(DiagMessage(outResource->source) - << "<" << parser->getElementName() << "> missing 'name' attribute"); - return false; - } + out_resource->name.entry = maybe_name.value().ToString(); + } - outResource->name.type = *parsedType; - outResource->name.entry = maybeName.value().toString(); - outResource->value = parseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString); - if (!outResource->value) { - mDiag->error(DiagMessage(outResource->source) - << "invalid value for type '" << *parsedType << "'. Expected a reference"); - return false; - } - return true; + // Call the associated parse method. The type will be filled in by the + // parse func. + if (!bag_iter->second(this, parser, out_resource)) { + return false; + } + return true; + } + + // Try parsing the elementName (or type) as a resource. These shall only be + // resources like 'layout' or 'xml' and they can only be references. + const ResourceType* parsed_type = ParseResourceType(resource_type); + if (parsed_type) { + if (!maybe_name) { + diag_->Error(DiagMessage(out_resource->source) + << "<" << parser->element_name() + << "> missing 'name' attribute"); + return false; + } + + out_resource->name.type = *parsed_type; + out_resource->name.entry = maybe_name.value().ToString(); + out_resource->value = + ParseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString); + if (!out_resource->value) { + diag_->Error(DiagMessage(out_resource->source) + << "invalid value for type '" << *parsed_type + << "'. Expected a reference"); + return false; } + return true; + } - mDiag->warn(DiagMessage(outResource->source) - << "unknown resource type '" << parser->getElementName() << "'"); - return false; + diag_->Warn(DiagMessage(out_resource->source) + << "unknown resource type '" << parser->element_name() << "'"); + return false; } -bool ResourceParser::parseItem(xml::XmlPullParser* parser, ParsedResource* outResource, +bool ResourceParser::ParseItem(xml::XmlPullParser* parser, + ParsedResource* out_resource, const uint32_t format) { - if (format == android::ResTable_map::TYPE_STRING) { - return parseString(parser, outResource); - } - - outResource->value = parseXml(parser, format, kNoRawString); - if (!outResource->value) { - mDiag->error(DiagMessage(outResource->source) << "invalid " << outResource->name.type); - return false; - } - return true; + if (format == android::ResTable_map::TYPE_STRING) { + return ParseString(parser, out_resource); + } + + out_resource->value = ParseXml(parser, format, kNoRawString); + if (!out_resource->value) { + diag_->Error(DiagMessage(out_resource->source) << "invalid " + << out_resource->name.type); + return false; + } + return true; } /** @@ -476,795 +524,839 @@ bool ResourceParser::parseItem(xml::XmlPullParser* parser, ParsedResource* outRe * an Item. If allowRawValue is false, nullptr is returned in this * case. */ -std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const uint32_t typeMask, - const bool allowRawValue) { - const size_t beginXmlLine = parser->getLineNumber(); - - std::u16string rawValue; - StyleString styleString; - if (!flattenXmlSubtree(parser, &rawValue, &styleString)) { - return {}; - } - - if (!styleString.spans.empty()) { - // This can only be a StyledString. - return util::make_unique<StyledString>( - mTable->stringPool.makeRef(styleString, StringPool::Context{ 1, mConfig })); - } - - auto onCreateReference = [&](const ResourceName& name) { - // name.package can be empty here, as it will assume the package name of the table. - std::unique_ptr<Id> id = util::make_unique<Id>(); - id->setSource(mSource.withLine(beginXmlLine)); - mTable->addResource(name, {}, {}, std::move(id), mDiag); - }; - - // Process the raw value. - std::unique_ptr<Item> processedItem = ResourceUtils::parseItemForAttribute(rawValue, typeMask, - onCreateReference); - if (processedItem) { - // Fix up the reference. - if (Reference* ref = valueCast<Reference>(processedItem.get())) { - transformReferenceFromNamespace(parser, u"", ref); - } - return processedItem; - } - - // Try making a regular string. - if (typeMask & android::ResTable_map::TYPE_STRING) { - // Use the trimmed, escaped string. - return util::make_unique<String>( - mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig })); - } - - if (allowRawValue) { - // We can't parse this so return a RawString if we are allowed. - return util::make_unique<RawString>( - mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig })); - } +std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser, + const uint32_t type_mask, + const bool allow_raw_value) { + const size_t begin_xml_line = parser->line_number(); + + std::string raw_value; + StyleString style_string; + if (!FlattenXmlSubtree(parser, &raw_value, &style_string)) { return {}; + } + + if (!style_string.spans.empty()) { + // This can only be a StyledString. + return util::make_unique<StyledString>(table_->string_pool.MakeRef( + style_string, + StringPool::Context(StringPool::Context::kStylePriority, config_))); + } + + auto on_create_reference = [&](const ResourceName& name) { + // name.package can be empty here, as it will assume the package name of the + // table. + std::unique_ptr<Id> id = util::make_unique<Id>(); + id->SetSource(source_.WithLine(begin_xml_line)); + table_->AddResource(name, {}, {}, std::move(id), diag_); + }; + + // Process the raw value. + std::unique_ptr<Item> processed_item = + ResourceUtils::TryParseItemForAttribute(raw_value, type_mask, + on_create_reference); + if (processed_item) { + // Fix up the reference. + if (Reference* ref = ValueCast<Reference>(processed_item.get())) { + TransformReferenceFromNamespace(parser, "", ref); + } + return processed_item; + } + + // Try making a regular string. + if (type_mask & android::ResTable_map::TYPE_STRING) { + // Use the trimmed, escaped string. + return util::make_unique<String>(table_->string_pool.MakeRef( + style_string.str, StringPool::Context(config_))); + } + + if (allow_raw_value) { + // We can't parse this so return a RawString if we are allowed. + return util::make_unique<RawString>( + table_->string_pool.MakeRef(raw_value, StringPool::Context(config_))); + } + return {}; } -bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) { - bool formatted = true; - if (Maybe<StringPiece16> formattedAttr = xml::findAttribute(parser, u"formatted")) { - if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) { - mDiag->error(DiagMessage(outResource->source) - << "invalid value for 'formatted'. Must be a boolean"); - return false; +bool ResourceParser::ParseString(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + bool formatted = true; + if (Maybe<StringPiece> formatted_attr = + xml::FindAttribute(parser, "formatted")) { + Maybe<bool> maybe_formatted = + ResourceUtils::ParseBool(formatted_attr.value()); + if (!maybe_formatted) { + diag_->Error(DiagMessage(out_resource->source) + << "invalid value for 'formatted'. Must be a boolean"); + return false; + } + formatted = maybe_formatted.value(); + } + + bool translateable = options_.translatable; + if (Maybe<StringPiece> translateable_attr = + xml::FindAttribute(parser, "translatable")) { + Maybe<bool> maybe_translateable = + ResourceUtils::ParseBool(translateable_attr.value()); + if (!maybe_translateable) { + diag_->Error(DiagMessage(out_resource->source) + << "invalid value for 'translatable'. Must be a boolean"); + return false; + } + translateable = maybe_translateable.value(); + } + + out_resource->value = + ParseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString); + if (!out_resource->value) { + diag_->Error(DiagMessage(out_resource->source) << "not a valid string"); + return false; + } + + if (String* string_value = ValueCast<String>(out_resource->value.get())) { + string_value->SetTranslateable(translateable); + + if (formatted && translateable) { + if (!util::VerifyJavaStringFormat(*string_value->value)) { + DiagMessage msg(out_resource->source); + msg << "multiple substitutions specified in non-positional format; " + "did you mean to add the formatted=\"false\" attribute?"; + if (options_.error_on_positional_arguments) { + diag_->Error(msg); + return false; } - } - bool translateable = mOptions.translatable; - if (Maybe<StringPiece16> translateableAttr = xml::findAttribute(parser, u"translatable")) { - if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) { - mDiag->error(DiagMessage(outResource->source) - << "invalid value for 'translatable'. Must be a boolean"); - return false; - } + diag_->Warn(msg); + } } - outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString); - if (!outResource->value) { - mDiag->error(DiagMessage(outResource->source) << "not a valid string"); - return false; - } - - if (String* stringValue = valueCast<String>(outResource->value.get())) { - stringValue->setTranslateable(translateable); - - if (formatted && translateable) { - if (!util::verifyJavaStringFormat(*stringValue->value)) { - DiagMessage msg(outResource->source); - msg << "multiple substitutions specified in non-positional format; " - "did you mean to add the formatted=\"false\" attribute?"; - if (mOptions.errorOnPositionalArguments) { - mDiag->error(msg); - return false; - } - - mDiag->warn(msg); - } - } - - } else if (StyledString* stringValue = valueCast<StyledString>(outResource->value.get())) { - stringValue->setTranslateable(translateable); - } - return true; + } else if (StyledString* string_value = + ValueCast<StyledString>(out_resource->value.get())) { + string_value->SetTranslateable(translateable); + } + return true; } -bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource) { - Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type"); - if (!maybeType) { - mDiag->error(DiagMessage(outResource->source) << "<public> must have a 'type' attribute"); - return false; - } - - const ResourceType* parsedType = parseResourceType(maybeType.value()); - if (!parsedType) { - mDiag->error(DiagMessage(outResource->source) - << "invalid resource type '" << maybeType.value() << "' in <public>"); - return false; - } - - outResource->name.type = *parsedType; - - if (Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"id")) { - android::Res_value val; - bool result = android::ResTable::stringToInt(maybeId.value().data(), - maybeId.value().size(), &val); - ResourceId resourceId(val.data); - if (!result || !resourceId.isValid()) { - mDiag->error(DiagMessage(outResource->source) - << "invalid resource ID '" << maybeId.value() << "' in <public>"); - return false; - } - outResource->id = resourceId; - } - - if (*parsedType == ResourceType::kId) { - // An ID marked as public is also the definition of an ID. - outResource->value = util::make_unique<Id>(); - } +bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); + if (!maybe_type) { + diag_->Error(DiagMessage(out_resource->source) + << "<public> must have a 'type' attribute"); + return false; + } - outResource->symbolState = SymbolState::kPublic; - return true; + const ResourceType* parsed_type = ParseResourceType(maybe_type.value()); + if (!parsed_type) { + diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '" + << maybe_type.value() + << "' in <public>"); + return false; + } + + out_resource->name.type = *parsed_type; + + if (Maybe<StringPiece> maybe_id_str = + xml::FindNonEmptyAttribute(parser, "id")) { + Maybe<ResourceId> maybe_id = + ResourceUtils::ParseResourceId(maybe_id_str.value()); + if (!maybe_id) { + diag_->Error(DiagMessage(out_resource->source) << "invalid resource ID '" + << maybe_id.value() + << "' in <public>"); + return false; + } + out_resource->id = maybe_id.value(); + } + + if (*parsed_type == ResourceType::kId) { + // An ID marked as public is also the definition of an ID. + out_resource->value = util::make_unique<Id>(); + } + + out_resource->symbol_state = SymbolState::kPublic; + return true; } -bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource) { - Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type"); - if (!maybeType) { - mDiag->error(DiagMessage(outResource->source) - << "<public-group> must have a 'type' attribute"); - return false; - } - - const ResourceType* parsedType = parseResourceType(maybeType.value()); - if (!parsedType) { - mDiag->error(DiagMessage(outResource->source) - << "invalid resource type '" << maybeType.value() << "' in <public-group>"); - return false; - } - - Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"first-id"); - if (!maybeId) { - mDiag->error(DiagMessage(outResource->source) - << "<public-group> must have a 'first-id' attribute"); - return false; - } - - android::Res_value val; - bool result = android::ResTable::stringToInt(maybeId.value().data(), - maybeId.value().size(), &val); - ResourceId nextId(val.data); - if (!result || !nextId.isValid()) { - mDiag->error(DiagMessage(outResource->source) - << "invalid resource ID '" << maybeId.value() << "' in <public-group>"); - return false; - } +bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); + if (!maybe_type) { + diag_->Error(DiagMessage(out_resource->source) + << "<public-group> must have a 'type' attribute"); + return false; + } - std::u16string comment; - bool error = false; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { - comment = util::trimWhitespace(parser->getComment()).toString(); - continue; - } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { - // Skip text. - continue; - } + const ResourceType* parsed_type = ParseResourceType(maybe_type.value()); + if (!parsed_type) { + diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '" + << maybe_type.value() + << "' in <public-group>"); + return false; + } - const Source itemSource = mSource.withLine(parser->getLineNumber()); - const std::u16string& elementNamespace = parser->getElementNamespace(); - const std::u16string& elementName = parser->getElementName(); - if (elementNamespace.empty() && elementName == u"public") { - Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name"); - if (!maybeName) { - mDiag->error(DiagMessage(itemSource) << "<public> must have a 'name' attribute"); - error = true; - continue; - } - - if (xml::findNonEmptyAttribute(parser, u"id")) { - mDiag->error(DiagMessage(itemSource) << "'id' is ignored within <public-group>"); - error = true; - continue; - } - - if (xml::findNonEmptyAttribute(parser, u"type")) { - mDiag->error(DiagMessage(itemSource) << "'type' is ignored within <public-group>"); - error = true; - continue; - } - - ParsedResource childResource; - childResource.name.type = *parsedType; - childResource.name.entry = maybeName.value().toString(); - childResource.id = nextId; - childResource.comment = std::move(comment); - childResource.source = itemSource; - childResource.symbolState = SymbolState::kPublic; - outResource->childResources.push_back(std::move(childResource)); - - nextId.id += 1; - - } else if (!shouldIgnoreElement(elementNamespace, elementName)) { - mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">"); - error = true; - } - } - return !error; + Maybe<StringPiece> maybe_id_str = + xml::FindNonEmptyAttribute(parser, "first-id"); + if (!maybe_id_str) { + diag_->Error(DiagMessage(out_resource->source) + << "<public-group> must have a 'first-id' attribute"); + return false; + } + + Maybe<ResourceId> maybe_id = + ResourceUtils::ParseResourceId(maybe_id_str.value()); + if (!maybe_id) { + diag_->Error(DiagMessage(out_resource->source) << "invalid resource ID '" + << maybe_id_str.value() + << "' in <public-group>"); + return false; + } + + ResourceId next_id = maybe_id.value(); + + std::string comment; + bool error = false; + const size_t depth = parser->depth(); + while (xml::XmlPullParser::NextChildNode(parser, depth)) { + if (parser->event() == xml::XmlPullParser::Event::kComment) { + comment = util::TrimWhitespace(parser->comment()).ToString(); + continue; + } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) { + // Skip text. + continue; + } + + const Source item_source = source_.WithLine(parser->line_number()); + const std::string& element_namespace = parser->element_namespace(); + const std::string& element_name = parser->element_name(); + if (element_namespace.empty() && element_name == "public") { + Maybe<StringPiece> maybe_name = + xml::FindNonEmptyAttribute(parser, "name"); + if (!maybe_name) { + diag_->Error(DiagMessage(item_source) + << "<public> must have a 'name' attribute"); + error = true; + continue; + } + + if (xml::FindNonEmptyAttribute(parser, "id")) { + diag_->Error(DiagMessage(item_source) + << "'id' is ignored within <public-group>"); + error = true; + continue; + } + + if (xml::FindNonEmptyAttribute(parser, "type")) { + diag_->Error(DiagMessage(item_source) + << "'type' is ignored within <public-group>"); + error = true; + continue; + } + + ParsedResource child_resource; + child_resource.name.type = *parsed_type; + child_resource.name.entry = maybe_name.value().ToString(); + child_resource.id = next_id; + child_resource.comment = std::move(comment); + child_resource.source = item_source; + child_resource.symbol_state = SymbolState::kPublic; + out_resource->child_resources.push_back(std::move(child_resource)); + + next_id.id += 1; + + } else if (!ShouldIgnoreElement(element_namespace, element_name)) { + diag_->Error(DiagMessage(item_source) << ":" << element_name << ">"); + error = true; + } + } + return !error; } -bool ResourceParser::parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource) { - Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type"); - if (!maybeType) { - mDiag->error(DiagMessage(outResource->source) - << "<" << parser->getElementName() << "> must have a 'type' attribute"); - return false; - } +bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); + if (!maybe_type) { + diag_->Error(DiagMessage(out_resource->source) + << "<" << parser->element_name() + << "> must have a 'type' attribute"); + return false; + } - const ResourceType* parsedType = parseResourceType(maybeType.value()); - if (!parsedType) { - mDiag->error(DiagMessage(outResource->source) - << "invalid resource type '" << maybeType.value() - << "' in <" << parser->getElementName() << ">"); - return false; - } + const ResourceType* parsed_type = ParseResourceType(maybe_type.value()); + if (!parsed_type) { + diag_->Error(DiagMessage(out_resource->source) + << "invalid resource type '" << maybe_type.value() << "' in <" + << parser->element_name() << ">"); + return false; + } - outResource->name.type = *parsedType; - return true; + out_resource->name.type = *parsed_type; + return true; } -bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) { - if (parseSymbolImpl(parser, outResource)) { - outResource->symbolState = SymbolState::kPrivate; - return true; - } - return false; +bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + if (ParseSymbolImpl(parser, out_resource)) { + out_resource->symbol_state = SymbolState::kPrivate; + return true; + } + return false; } -bool ResourceParser::parseAddResource(xml::XmlPullParser* parser, ParsedResource* outResource) { - if (parseSymbolImpl(parser, outResource)) { - outResource->symbolState = SymbolState::kUndefined; - return true; - } - return false; +bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + if (ParseSymbolImpl(parser, out_resource)) { + out_resource->symbol_state = SymbolState::kUndefined; + return true; + } + return false; } - -bool ResourceParser::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) { - return parseAttrImpl(parser, outResource, false); +bool ResourceParser::ParseAttr(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + return ParseAttrImpl(parser, out_resource, false); } -bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, - bool weak) { - outResource->name.type = ResourceType::kAttr; - - // Attributes only end up in default configuration. - if (outResource->config != ConfigDescription::defaultConfig()) { - mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '" - << outResource->config << "' for attribute " << outResource->name); - outResource->config = ConfigDescription::defaultConfig(); - } - - uint32_t typeMask = 0; - - Maybe<StringPiece16> maybeFormat = xml::findAttribute(parser, u"format"); - if (maybeFormat) { - typeMask = parseFormatAttribute(maybeFormat.value()); - if (typeMask == 0) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "invalid attribute format '" << maybeFormat.value() << "'"); - return false; - } - } - - Maybe<int32_t> maybeMin, maybeMax; - - if (Maybe<StringPiece16> maybeMinStr = xml::findAttribute(parser, u"min")) { - StringPiece16 minStr = util::trimWhitespace(maybeMinStr.value()); - if (!minStr.empty()) { - android::Res_value value; - if (android::ResTable::stringToInt(minStr.data(), minStr.size(), &value)) { - maybeMin = static_cast<int32_t>(value.data); - } - } - - if (!maybeMin) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "invalid 'min' value '" << minStr << "'"); - return false; +bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, + ParsedResource* out_resource, bool weak) { + out_resource->name.type = ResourceType::kAttr; + + // Attributes only end up in default configuration. + if (out_resource->config != ConfigDescription::DefaultConfig()) { + diag_->Warn(DiagMessage(out_resource->source) + << "ignoring configuration '" << out_resource->config + << "' for attribute " << out_resource->name); + out_resource->config = ConfigDescription::DefaultConfig(); + } + + uint32_t type_mask = 0; + + Maybe<StringPiece> maybe_format = xml::FindAttribute(parser, "format"); + if (maybe_format) { + type_mask = ParseFormatAttribute(maybe_format.value()); + if (type_mask == 0) { + diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + << "invalid attribute format '" << maybe_format.value() + << "'"); + return false; + } + } + + Maybe<int32_t> maybe_min, maybe_max; + + if (Maybe<StringPiece> maybe_min_str = xml::FindAttribute(parser, "min")) { + StringPiece min_str = util::TrimWhitespace(maybe_min_str.value()); + if (!min_str.empty()) { + std::u16string min_str16 = util::Utf8ToUtf16(min_str); + android::Res_value value; + if (android::ResTable::stringToInt(min_str16.data(), min_str16.size(), + &value)) { + maybe_min = static_cast<int32_t>(value.data); + } + } + + if (!maybe_min) { + diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + << "invalid 'min' value '" << min_str << "'"); + return false; + } + } + + if (Maybe<StringPiece> maybe_max_str = xml::FindAttribute(parser, "max")) { + StringPiece max_str = util::TrimWhitespace(maybe_max_str.value()); + if (!max_str.empty()) { + std::u16string max_str16 = util::Utf8ToUtf16(max_str); + android::Res_value value; + if (android::ResTable::stringToInt(max_str16.data(), max_str16.size(), + &value)) { + maybe_max = static_cast<int32_t>(value.data); + } + } + + if (!maybe_max) { + diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + << "invalid 'max' value '" << max_str << "'"); + return false; + } + } + + if ((maybe_min || maybe_max) && + (type_mask & android::ResTable_map::TYPE_INTEGER) == 0) { + diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + << "'min' and 'max' can only be used when format='integer'"); + return false; + } + + struct SymbolComparator { + bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) { + return a.symbol.name.value() < b.symbol.name.value(); + } + }; + + std::set<Attribute::Symbol, SymbolComparator> items; + + std::string comment; + bool error = false; + const size_t depth = parser->depth(); + while (xml::XmlPullParser::NextChildNode(parser, depth)) { + if (parser->event() == xml::XmlPullParser::Event::kComment) { + comment = util::TrimWhitespace(parser->comment()).ToString(); + continue; + } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) { + // Skip text. + continue; + } + + const Source item_source = source_.WithLine(parser->line_number()); + const std::string& element_namespace = parser->element_namespace(); + const std::string& element_name = parser->element_name(); + if (element_namespace.empty() && + (element_name == "flag" || element_name == "enum")) { + if (element_name == "enum") { + if (type_mask & android::ResTable_map::TYPE_FLAGS) { + diag_->Error(DiagMessage(item_source) + << "can not define an <enum>; already defined a <flag>"); + error = true; + continue; } - } - - if (Maybe<StringPiece16> maybeMaxStr = xml::findAttribute(parser, u"max")) { - StringPiece16 maxStr = util::trimWhitespace(maybeMaxStr.value()); - if (!maxStr.empty()) { - android::Res_value value; - if (android::ResTable::stringToInt(maxStr.data(), maxStr.size(), &value)) { - maybeMax = static_cast<int32_t>(value.data); - } + type_mask |= android::ResTable_map::TYPE_ENUM; + + } else if (element_name == "flag") { + if (type_mask & android::ResTable_map::TYPE_ENUM) { + diag_->Error(DiagMessage(item_source) + << "can not define a <flag>; already defined an <enum>"); + error = true; + continue; } - - if (!maybeMax) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "invalid 'max' value '" << maxStr << "'"); - return false; + type_mask |= android::ResTable_map::TYPE_FLAGS; + } + + if (Maybe<Attribute::Symbol> s = + ParseEnumOrFlagItem(parser, element_name)) { + Attribute::Symbol& symbol = s.value(); + ParsedResource child_resource; + child_resource.name = symbol.symbol.name.value(); + child_resource.source = item_source; + child_resource.value = util::make_unique<Id>(); + out_resource->child_resources.push_back(std::move(child_resource)); + + symbol.symbol.SetComment(std::move(comment)); + symbol.symbol.SetSource(item_source); + + auto insert_result = items.insert(std::move(symbol)); + if (!insert_result.second) { + const Attribute::Symbol& existing_symbol = *insert_result.first; + diag_->Error(DiagMessage(item_source) + << "duplicate symbol '" + << existing_symbol.symbol.name.value().entry << "'"); + + diag_->Note(DiagMessage(existing_symbol.symbol.GetSource()) + << "first defined here"); + error = true; } + } else { + error = true; + } + } else if (!ShouldIgnoreElement(element_namespace, element_name)) { + diag_->Error(DiagMessage(item_source) << ":" << element_name << ">"); + error = true; } - if ((maybeMin || maybeMax) && (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "'min' and 'max' can only be used when format='integer'"); - return false; - } + comment = {}; + } - struct SymbolComparator { - bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) { - return a.symbol.name.value() < b.symbol.name.value(); - } - }; - - std::set<Attribute::Symbol, SymbolComparator> items; - - std::u16string comment; - bool error = false; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { - comment = util::trimWhitespace(parser->getComment()).toString(); - continue; - } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { - // Skip text. - continue; - } + if (error) { + return false; + } + + std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak); + attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end()); + attr->type_mask = + type_mask ? type_mask : uint32_t(android::ResTable_map::TYPE_ANY); + if (maybe_min) { + attr->min_int = maybe_min.value(); + } + + if (maybe_max) { + attr->max_int = maybe_max.value(); + } + out_resource->value = std::move(attr); + return true; +} - const Source itemSource = mSource.withLine(parser->getLineNumber()); - const std::u16string& elementNamespace = parser->getElementNamespace(); - const std::u16string& elementName = parser->getElementName(); - if (elementNamespace.empty() && (elementName == u"flag" || elementName == u"enum")) { - if (elementName == u"enum") { - if (typeMask & android::ResTable_map::TYPE_FLAGS) { - mDiag->error(DiagMessage(itemSource) - << "can not define an <enum>; already defined a <flag>"); - error = true; - continue; - } - typeMask |= android::ResTable_map::TYPE_ENUM; - - } else if (elementName == u"flag") { - if (typeMask & android::ResTable_map::TYPE_ENUM) { - mDiag->error(DiagMessage(itemSource) - << "can not define a <flag>; already defined an <enum>"); - error = true; - continue; - } - typeMask |= android::ResTable_map::TYPE_FLAGS; - } - - if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) { - Attribute::Symbol& symbol = s.value(); - ParsedResource childResource; - childResource.name = symbol.symbol.name.value(); - childResource.source = itemSource; - childResource.value = util::make_unique<Id>(); - outResource->childResources.push_back(std::move(childResource)); - - symbol.symbol.setComment(std::move(comment)); - symbol.symbol.setSource(itemSource); - - auto insertResult = items.insert(std::move(symbol)); - if (!insertResult.second) { - const Attribute::Symbol& existingSymbol = *insertResult.first; - mDiag->error(DiagMessage(itemSource) - << "duplicate symbol '" << existingSymbol.symbol.name.value().entry - << "'"); - - mDiag->note(DiagMessage(existingSymbol.symbol.getSource()) - << "first defined here"); - error = true; - } - } else { - error = true; - } - } else if (!shouldIgnoreElement(elementNamespace, elementName)) { - mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">"); - error = true; - } +Maybe<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem( + xml::XmlPullParser* parser, const StringPiece& tag) { + const Source source = source_.WithLine(parser->line_number()); - comment = {}; - } - - if (error) { - return false; - } + Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); + if (!maybe_name) { + diag_->Error(DiagMessage(source) << "no attribute 'name' found for tag <" + << tag << ">"); + return {}; + } - std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak); - attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end()); - attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY); - if (maybeMin) { - attr->minInt = maybeMin.value(); - } + Maybe<StringPiece> maybe_value = xml::FindNonEmptyAttribute(parser, "value"); + if (!maybe_value) { + diag_->Error(DiagMessage(source) << "no attribute 'value' found for tag <" + << tag << ">"); + return {}; + } + + std::u16string value16 = util::Utf8ToUtf16(maybe_value.value()); + android::Res_value val; + if (!android::ResTable::stringToInt(value16.data(), value16.size(), &val)) { + diag_->Error(DiagMessage(source) << "invalid value '" << maybe_value.value() + << "' for <" << tag + << ">; must be an integer"); + return {}; + } - if (maybeMax) { - attr->maxInt = maybeMax.value(); - } - outResource->value = std::move(attr); - return true; + return Attribute::Symbol{ + Reference(ResourceNameRef({}, ResourceType::kId, maybe_name.value())), + val.data}; } -Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(xml::XmlPullParser* parser, - const StringPiece16& tag) { - const Source source = mSource.withLine(parser->getLineNumber()); - - Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name"); - if (!maybeName) { - mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">"); - return {}; - } +bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) { + const Source source = source_.WithLine(parser->line_number()); - Maybe<StringPiece16> maybeValue = xml::findNonEmptyAttribute(parser, u"value"); - if (!maybeValue) { - mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">"); - return {}; - } - - android::Res_value val; - if (!android::ResTable::stringToInt(maybeValue.value().data(), - maybeValue.value().size(), &val)) { - mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value() - << "' for <" << tag << ">; must be an integer"); - return {}; - } + Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); + if (!maybe_name) { + diag_->Error(DiagMessage(source) << "<item> must have a 'name' attribute"); + return false; + } - return Attribute::Symbol{ - Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())), val.data }; -} + Maybe<Reference> maybe_key = + ResourceUtils::ParseXmlAttributeName(maybe_name.value()); + if (!maybe_key) { + diag_->Error(DiagMessage(source) << "invalid attribute name '" + << maybe_name.value() << "'"); + return false; + } -static Maybe<Reference> parseXmlAttributeName(StringPiece16 str) { - str = util::trimWhitespace(str); - const char16_t* start = str.data(); - const char16_t* const end = start + str.size(); - const char16_t* p = start; - - Reference ref; - if (p != end && *p == u'*') { - ref.privateReference = true; - start++; - p++; - } + TransformReferenceFromNamespace(parser, "", &maybe_key.value()); + maybe_key.value().SetSource(source); - StringPiece16 package; - StringPiece16 name; - while (p != end) { - if (*p == u':') { - package = StringPiece16(start, p - start); - name = StringPiece16(p + 1, end - (p + 1)); - break; - } - p++; - } + std::unique_ptr<Item> value = ParseXml(parser, 0, kAllowRawString); + if (!value) { + diag_->Error(DiagMessage(source) << "could not parse style item"); + return false; + } - ref.name = ResourceName(package.toString(), ResourceType::kAttr, - name.empty() ? str.toString() : name.toString()); - return Maybe<Reference>(std::move(ref)); + style->entries.push_back( + Style::Entry{std::move(maybe_key.value()), std::move(value)}); + return true; } -bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) { - const Source source = mSource.withLine(parser->getLineNumber()); - - Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name"); - if (!maybeName) { - mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute"); +bool ResourceParser::ParseStyle(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + out_resource->name.type = ResourceType::kStyle; + + std::unique_ptr<Style> style = util::make_unique<Style>(); + + Maybe<StringPiece> maybe_parent = xml::FindAttribute(parser, "parent"); + if (maybe_parent) { + // If the parent is empty, we don't have a parent, but we also don't infer + // either. + if (!maybe_parent.value().empty()) { + std::string err_str; + style->parent = ResourceUtils::ParseStyleParentReference( + maybe_parent.value(), &err_str); + if (!style->parent) { + diag_->Error(DiagMessage(out_resource->source) << err_str); return false; - } + } - Maybe<Reference> maybeKey = parseXmlAttributeName(maybeName.value()); - if (!maybeKey) { - mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'"); - return false; + // Transform the namespace prefix to the actual package name, and mark the + // reference as + // private if appropriate. + TransformReferenceFromNamespace(parser, "", &style->parent.value()); } - transformReferenceFromNamespace(parser, u"", &maybeKey.value()); - maybeKey.value().setSource(source); - - std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString); - if (!value) { - mDiag->error(DiagMessage(source) << "could not parse style item"); - return false; + } else { + // No parent was specified, so try inferring it from the style name. + std::string style_name = out_resource->name.entry; + size_t pos = style_name.find_last_of(u'.'); + if (pos != std::string::npos) { + style->parent_inferred = true; + style->parent = Reference( + ResourceName({}, ResourceType::kStyle, style_name.substr(0, pos))); } + } - style->entries.push_back(Style::Entry{ std::move(maybeKey.value()), std::move(value) }); - return true; -} - -bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) { - outResource->name.type = ResourceType::kStyle; - - std::unique_ptr<Style> style = util::make_unique<Style>(); - - Maybe<StringPiece16> maybeParent = xml::findAttribute(parser, u"parent"); - if (maybeParent) { - // If the parent is empty, we don't have a parent, but we also don't infer either. - if (!maybeParent.value().empty()) { - std::string errStr; - style->parent = ResourceUtils::parseStyleParentReference(maybeParent.value(), &errStr); - if (!style->parent) { - mDiag->error(DiagMessage(outResource->source) << errStr); - return false; - } - - // Transform the namespace prefix to the actual package name, and mark the reference as - // private if appropriate. - transformReferenceFromNamespace(parser, u"", &style->parent.value()); - } - - } else { - // No parent was specified, so try inferring it from the style name. - std::u16string styleName = outResource->name.entry; - size_t pos = styleName.find_last_of(u'.'); - if (pos != std::string::npos) { - style->parentInferred = true; - style->parent = Reference(ResourceName({}, ResourceType::kStyle, - styleName.substr(0, pos))); - } + bool error = false; + const size_t depth = parser->depth(); + while (xml::XmlPullParser::NextChildNode(parser, depth)) { + if (parser->event() != xml::XmlPullParser::Event::kStartElement) { + // Skip text and comments. + continue; } - bool error = false; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { - // Skip text and comments. - continue; - } - - const std::u16string& elementNamespace = parser->getElementNamespace(); - const std::u16string& elementName = parser->getElementName(); - if (elementNamespace == u"" && elementName == u"item") { - error |= !parseStyleItem(parser, style.get()); + const std::string& element_namespace = parser->element_namespace(); + const std::string& element_name = parser->element_name(); + if (element_namespace == "" && element_name == "item") { + error |= !ParseStyleItem(parser, style.get()); - } else if (!shouldIgnoreElement(elementNamespace, elementName)) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << ":" << elementName << ">"); - error = true; - } + } else if (!ShouldIgnoreElement(element_namespace, element_name)) { + diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + << ":" << element_name << ">"); + error = true; } + } - if (error) { - return false; - } + if (error) { + return false; + } - outResource->value = std::move(style); - return true; + out_resource->value = std::move(style); + return true; } -bool ResourceParser::parseArray(xml::XmlPullParser* parser, ParsedResource* outResource) { - return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_ANY); +bool ResourceParser::ParseArray(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_ANY); } -bool ResourceParser::parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource) { - return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_INTEGER); +bool ResourceParser::ParseIntegerArray(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + return ParseArrayImpl(parser, out_resource, + android::ResTable_map::TYPE_INTEGER); } -bool ResourceParser::parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource) { - return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_STRING); +bool ResourceParser::ParseStringArray(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + return ParseArrayImpl(parser, out_resource, + android::ResTable_map::TYPE_STRING); } -bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource, +bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser, + ParsedResource* out_resource, const uint32_t typeMask) { - outResource->name.type = ResourceType::kArray; - - std::unique_ptr<Array> array = util::make_unique<Array>(); - - bool translateable = mOptions.translatable; - if (Maybe<StringPiece16> translateableAttr = xml::findAttribute(parser, u"translatable")) { - if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) { - mDiag->error(DiagMessage(outResource->source) - << "invalid value for 'translatable'. Must be a boolean"); - return false; - } - } - array->setTranslateable(translateable); - - bool error = false; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { - // Skip text and comments. - continue; - } - - const Source itemSource = mSource.withLine(parser->getLineNumber()); - const std::u16string& elementNamespace = parser->getElementNamespace(); - const std::u16string& elementName = parser->getElementName(); - if (elementNamespace.empty() && elementName == u"item") { - std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString); - if (!item) { - mDiag->error(DiagMessage(itemSource) << "could not parse array item"); - error = true; - continue; - } - item->setSource(itemSource); - array->items.emplace_back(std::move(item)); - - } else if (!shouldIgnoreElement(elementNamespace, elementName)) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "unknown tag <" << elementNamespace << ":" << elementName << ">"); - error = true; - } - } - - if (error) { - return false; - } + out_resource->name.type = ResourceType::kArray; + + std::unique_ptr<Array> array = util::make_unique<Array>(); + + bool translateable = options_.translatable; + if (Maybe<StringPiece> translateable_attr = + xml::FindAttribute(parser, "translatable")) { + Maybe<bool> maybe_translateable = + ResourceUtils::ParseBool(translateable_attr.value()); + if (!maybe_translateable) { + diag_->Error(DiagMessage(out_resource->source) + << "invalid value for 'translatable'. Must be a boolean"); + return false; + } + translateable = maybe_translateable.value(); + } + array->SetTranslateable(translateable); + + bool error = false; + const size_t depth = parser->depth(); + while (xml::XmlPullParser::NextChildNode(parser, depth)) { + if (parser->event() != xml::XmlPullParser::Event::kStartElement) { + // Skip text and comments. + continue; + } + + const Source item_source = source_.WithLine(parser->line_number()); + const std::string& element_namespace = parser->element_namespace(); + const std::string& element_name = parser->element_name(); + if (element_namespace.empty() && element_name == "item") { + std::unique_ptr<Item> item = ParseXml(parser, typeMask, kNoRawString); + if (!item) { + diag_->Error(DiagMessage(item_source) << "could not parse array item"); + error = true; + continue; + } + item->SetSource(item_source); + array->items.emplace_back(std::move(item)); + + } else if (!ShouldIgnoreElement(element_namespace, element_name)) { + diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + << "unknown tag <" << element_namespace << ":" + << element_name << ">"); + error = true; + } + } + + if (error) { + return false; + } - outResource->value = std::move(array); - return true; + out_resource->value = std::move(array); + return true; } -bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource) { - outResource->name.type = ResourceType::kPlurals; - - std::unique_ptr<Plural> plural = util::make_unique<Plural>(); - - bool error = false; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { - // Skip text and comments. - continue; - } - - const Source itemSource = mSource.withLine(parser->getLineNumber()); - const std::u16string& elementNamespace = parser->getElementNamespace(); - const std::u16string& elementName = parser->getElementName(); - if (elementNamespace.empty() && elementName == u"item") { - Maybe<StringPiece16> maybeQuantity = xml::findNonEmptyAttribute(parser, u"quantity"); - if (!maybeQuantity) { - mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute " - << "'quantity'"); - error = true; - continue; - } - - StringPiece16 trimmedQuantity = util::trimWhitespace(maybeQuantity.value()); - size_t index = 0; - if (trimmedQuantity == u"zero") { - index = Plural::Zero; - } else if (trimmedQuantity == u"one") { - index = Plural::One; - } else if (trimmedQuantity == u"two") { - index = Plural::Two; - } else if (trimmedQuantity == u"few") { - index = Plural::Few; - } else if (trimmedQuantity == u"many") { - index = Plural::Many; - } else if (trimmedQuantity == u"other") { - index = Plural::Other; - } else { - mDiag->error(DiagMessage(itemSource) - << "<item> in <plural> has invalid value '" << trimmedQuantity - << "' for attribute 'quantity'"); - error = true; - continue; - } - - if (plural->values[index]) { - mDiag->error(DiagMessage(itemSource) - << "duplicate quantity '" << trimmedQuantity << "'"); - error = true; - continue; - } - - if (!(plural->values[index] = parseXml(parser, android::ResTable_map::TYPE_STRING, - kNoRawString))) { - error = true; - } - plural->values[index]->setSource(itemSource); - - } else if (!shouldIgnoreElement(elementNamespace, elementName)) { - mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":" - << elementName << ">"); - error = true; - } - } - - if (error) { - return false; - } +bool ResourceParser::ParsePlural(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + out_resource->name.type = ResourceType::kPlurals; + + std::unique_ptr<Plural> plural = util::make_unique<Plural>(); + + bool error = false; + const size_t depth = parser->depth(); + while (xml::XmlPullParser::NextChildNode(parser, depth)) { + if (parser->event() != xml::XmlPullParser::Event::kStartElement) { + // Skip text and comments. + continue; + } + + const Source item_source = source_.WithLine(parser->line_number()); + const std::string& element_namespace = parser->element_namespace(); + const std::string& element_name = parser->element_name(); + if (element_namespace.empty() && element_name == "item") { + Maybe<StringPiece> maybe_quantity = + xml::FindNonEmptyAttribute(parser, "quantity"); + if (!maybe_quantity) { + diag_->Error(DiagMessage(item_source) + << "<item> in <plurals> requires attribute " + << "'quantity'"); + error = true; + continue; + } + + StringPiece trimmed_quantity = + util::TrimWhitespace(maybe_quantity.value()); + size_t index = 0; + if (trimmed_quantity == "zero") { + index = Plural::Zero; + } else if (trimmed_quantity == "one") { + index = Plural::One; + } else if (trimmed_quantity == "two") { + index = Plural::Two; + } else if (trimmed_quantity == "few") { + index = Plural::Few; + } else if (trimmed_quantity == "many") { + index = Plural::Many; + } else if (trimmed_quantity == "other") { + index = Plural::Other; + } else { + diag_->Error(DiagMessage(item_source) + << "<item> in <plural> has invalid value '" + << trimmed_quantity << "' for attribute 'quantity'"); + error = true; + continue; + } + + if (plural->values[index]) { + diag_->Error(DiagMessage(item_source) << "duplicate quantity '" + << trimmed_quantity << "'"); + error = true; + continue; + } + + if (!(plural->values[index] = ParseXml( + parser, android::ResTable_map::TYPE_STRING, kNoRawString))) { + error = true; + } + plural->values[index]->SetSource(item_source); + + } else if (!ShouldIgnoreElement(element_namespace, element_name)) { + diag_->Error(DiagMessage(item_source) << "unknown tag <" + << element_namespace << ":" + << element_name << ">"); + error = true; + } + } + + if (error) { + return false; + } - outResource->value = std::move(plural); - return true; + out_resource->value = std::move(plural); + return true; } -bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser, - ParsedResource* outResource) { - outResource->name.type = ResourceType::kStyleable; - - // Declare-styleable is kPrivate by default, because it technically only exists in R.java. - outResource->symbolState = SymbolState::kPublic; - - // Declare-styleable only ends up in default config; - if (outResource->config != ConfigDescription::defaultConfig()) { - mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '" - << outResource->config << "' for styleable " - << outResource->name.entry); - outResource->config = ConfigDescription::defaultConfig(); - } - - std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); - - std::u16string comment; - bool error = false; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { - comment = util::trimWhitespace(parser->getComment()).toString(); - continue; - } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { - // Ignore text. - continue; - } - - const Source itemSource = mSource.withLine(parser->getLineNumber()); - const std::u16string& elementNamespace = parser->getElementNamespace(); - const std::u16string& elementName = parser->getElementName(); - if (elementNamespace.empty() && elementName == u"attr") { - Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name"); - if (!maybeName) { - mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute"); - error = true; - continue; - } - - // If this is a declaration, the package name may be in the name. Separate these out. - // Eg. <attr name="android:text" /> - Maybe<Reference> maybeRef = parseXmlAttributeName(maybeName.value()); - if (!maybeRef) { - mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '" - << maybeName.value() << "'"); - error = true; - continue; - } - - Reference& childRef = maybeRef.value(); - xml::transformReferenceFromNamespace(parser, u"", &childRef); - - // Create the ParsedResource that will add the attribute to the table. - ParsedResource childResource; - childResource.name = childRef.name.value(); - childResource.source = itemSource; - childResource.comment = std::move(comment); - - if (!parseAttrImpl(parser, &childResource, true)) { - error = true; - continue; - } - - // Create the reference to this attribute. - childRef.setComment(childResource.comment); - childRef.setSource(itemSource); - styleable->entries.push_back(std::move(childRef)); - - outResource->childResources.push_back(std::move(childResource)); - - } else if (!shouldIgnoreElement(elementNamespace, elementName)) { - mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":" - << elementName << ">"); - error = true; - } - - comment = {}; - } - - if (error) { - return false; - } +bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + out_resource->name.type = ResourceType::kStyleable; + + // Declare-styleable is kPrivate by default, because it technically only + // exists in R.java. + out_resource->symbol_state = SymbolState::kPublic; + + // Declare-styleable only ends up in default config; + if (out_resource->config != ConfigDescription::DefaultConfig()) { + diag_->Warn(DiagMessage(out_resource->source) + << "ignoring configuration '" << out_resource->config + << "' for styleable " << out_resource->name.entry); + out_resource->config = ConfigDescription::DefaultConfig(); + } + + std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); + + std::string comment; + bool error = false; + const size_t depth = parser->depth(); + while (xml::XmlPullParser::NextChildNode(parser, depth)) { + if (parser->event() == xml::XmlPullParser::Event::kComment) { + comment = util::TrimWhitespace(parser->comment()).ToString(); + continue; + } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) { + // Ignore text. + continue; + } + + const Source item_source = source_.WithLine(parser->line_number()); + const std::string& element_namespace = parser->element_namespace(); + const std::string& element_name = parser->element_name(); + if (element_namespace.empty() && element_name == "attr") { + Maybe<StringPiece> maybe_name = + xml::FindNonEmptyAttribute(parser, "name"); + if (!maybe_name) { + diag_->Error(DiagMessage(item_source) + << "<attr> tag must have a 'name' attribute"); + error = true; + continue; + } + + // If this is a declaration, the package name may be in the name. Separate + // these out. + // Eg. <attr name="android:text" /> + Maybe<Reference> maybe_ref = + ResourceUtils::ParseXmlAttributeName(maybe_name.value()); + if (!maybe_ref) { + diag_->Error(DiagMessage(item_source) << "<attr> tag has invalid name '" + << maybe_name.value() << "'"); + error = true; + continue; + } + + Reference& child_ref = maybe_ref.value(); + xml::TransformReferenceFromNamespace(parser, "", &child_ref); + + // Create the ParsedResource that will add the attribute to the table. + ParsedResource child_resource; + child_resource.name = child_ref.name.value(); + child_resource.source = item_source; + child_resource.comment = std::move(comment); + + if (!ParseAttrImpl(parser, &child_resource, true)) { + error = true; + continue; + } + + // Create the reference to this attribute. + child_ref.SetComment(child_resource.comment); + child_ref.SetSource(item_source); + styleable->entries.push_back(std::move(child_ref)); + + out_resource->child_resources.push_back(std::move(child_resource)); + + } else if (!ShouldIgnoreElement(element_namespace, element_name)) { + diag_->Error(DiagMessage(item_source) << "unknown tag <" + << element_namespace << ":" + << element_name << ">"); + error = true; + } + + comment = {}; + } + + if (error) { + return false; + } - outResource->value = std::move(styleable); - return true; + out_resource->value = std::move(styleable); + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index ee5b33788312..11b1e5b787ca 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -17,6 +17,10 @@ #ifndef AAPT_RESOURCE_PARSER_H #define AAPT_RESOURCE_PARSER_H +#include <memory> + +#include "android-base/macros.h" + #include "ConfigDescription.h" #include "Diagnostics.h" #include "ResourceTable.h" @@ -26,86 +30,98 @@ #include "util/StringPiece.h" #include "xml/XmlPullParser.h" -#include <memory> - namespace aapt { struct ParsedResource; struct ResourceParserOptions { - /** - * Whether the default setting for this parser is to allow translation. - */ - bool translatable = true; - - /** - * Whether positional arguments in formatted strings are treated as errors or warnings. - */ - bool errorOnPositionalArguments = true; + /** + * Whether the default setting for this parser is to allow translation. + */ + bool translatable = true; + + /** + * Whether positional arguments in formatted strings are treated as errors or + * warnings. + */ + bool error_on_positional_arguments = true; }; /* * Parses an XML file for resources and adds them to a ResourceTable. */ class ResourceParser { -public: - ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, - const ConfigDescription& config, const ResourceParserOptions& options = {}); - - ResourceParser(const ResourceParser&) = delete; // No copy. - - bool parse(xml::XmlPullParser* parser); - -private: - /* - * Parses the XML subtree as a StyleString (flattened XML representation for strings - * with formatting). If successful, `outStyleString` - * contains the escaped and whitespace trimmed text, while `outRawString` - * contains the unescaped text. Returns true on success. - */ - bool flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString, - StyleString* outStyleString); - - /* - * Parses the XML subtree and returns an Item. - * The type of Item that can be parsed is denoted by the `typeMask`. - * If `allowRawValue` is true and the subtree can not be parsed as a regular Item, then a - * RawString is returned. Otherwise this returns false; - */ - std::unique_ptr<Item> parseXml(xml::XmlPullParser* parser, const uint32_t typeMask, - const bool allowRawValue); - - bool parseResources(xml::XmlPullParser* parser); - bool parseResource(xml::XmlPullParser* parser, ParsedResource* outResource); - - bool parseItem(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t format); - bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource); - - bool parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseAddResource(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, bool weak); - Maybe<Attribute::Symbol> parseEnumOrFlagItem(xml::XmlPullParser* parser, - const StringPiece16& tag); - bool parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseStyleItem(xml::XmlPullParser* parser, Style* style); - bool parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseArray(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask); - bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource); - - IDiagnostics* mDiag; - ResourceTable* mTable; - Source mSource; - ConfigDescription mConfig; - ResourceParserOptions mOptions; + public: + ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, + const ConfigDescription& config, + const ResourceParserOptions& options = {}); + bool Parse(xml::XmlPullParser* parser); + + private: + DISALLOW_COPY_AND_ASSIGN(ResourceParser); + + /* + * Parses the XML subtree as a StyleString (flattened XML representation for + * strings + * with formatting). If successful, `out_style_string` + * contains the escaped and whitespace trimmed text, while `out_raw_string` + * contains the unescaped text. Returns true on success. + */ + bool FlattenXmlSubtree(xml::XmlPullParser* parser, + std::string* out_raw_string, + StyleString* out_style_string); + + /* + * Parses the XML subtree and returns an Item. + * The type of Item that can be parsed is denoted by the `type_mask`. + * If `allow_raw_value` is true and the subtree can not be parsed as a regular + * Item, then a + * RawString is returned. Otherwise this returns false; + */ + std::unique_ptr<Item> ParseXml(xml::XmlPullParser* parser, + const uint32_t type_mask, + const bool allow_raw_value); + + bool ParseResources(xml::XmlPullParser* parser); + bool ParseResource(xml::XmlPullParser* parser, ParsedResource* out_resource); + + bool ParseItem(xml::XmlPullParser* parser, ParsedResource* out_resource, + uint32_t format); + bool ParseString(xml::XmlPullParser* parser, ParsedResource* out_resource); + + bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource); + bool ParsePublicGroup(xml::XmlPullParser* parser, + ParsedResource* out_resource); + bool ParseSymbolImpl(xml::XmlPullParser* parser, + ParsedResource* out_resource); + bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource); + bool ParseAddResource(xml::XmlPullParser* parser, + ParsedResource* out_resource); + bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource); + bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, + bool weak); + Maybe<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser, + const StringPiece& tag); + bool ParseStyle(xml::XmlPullParser* parser, ParsedResource* out_resource); + bool ParseStyleItem(xml::XmlPullParser* parser, Style* style); + bool ParseDeclareStyleable(xml::XmlPullParser* parser, + ParsedResource* out_resource); + bool ParseArray(xml::XmlPullParser* parser, ParsedResource* out_resource); + bool ParseIntegerArray(xml::XmlPullParser* parser, + ParsedResource* out_resource); + bool ParseStringArray(xml::XmlPullParser* parser, + ParsedResource* out_resource); + bool ParseArrayImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, + uint32_t typeMask); + bool ParsePlural(xml::XmlPullParser* parser, ParsedResource* out_resource); + + IDiagnostics* diag_; + ResourceTable* table_; + Source source_; + ConfigDescription config_; + ResourceParserOptions options_; }; -} // namespace aapt +} // namespace aapt -#endif // AAPT_RESOURCE_PARSER_H +#endif // AAPT_RESOURCE_PARSER_H diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 3450de9078bb..2463911445e7 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -15,490 +15,573 @@ */ #include "ResourceParser.h" + +#include <sstream> +#include <string> + #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" -#include "test/Context.h" +#include "test/Test.h" #include "xml/XmlPullParser.h" -#include <gtest/gtest.h> -#include <sstream> -#include <string> - namespace aapt { -constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; +constexpr const char* kXmlPreamble = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::stringstream input(kXmlPreamble); - input << "<attr name=\"foo\"/>" << std::endl; - ResourceTable table; - ResourceParser parser(context->getDiagnostics(), &table, Source{ "test" }, {}); - xml::XmlPullParser xmlParser(input); - ASSERT_FALSE(parser.parse(&xmlParser)); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::stringstream input(kXmlPreamble); + input << "<attr name=\"foo\"/>" << std::endl; + ResourceTable table; + ResourceParser parser(context->GetDiagnostics(), &table, Source{"test"}, {}); + xml::XmlPullParser xml_parser(input); + ASSERT_FALSE(parser.Parse(&xml_parser)); } -struct ResourceParserTest : public ::testing::Test { - ResourceTable mTable; - std::unique_ptr<IAaptContext> mContext; +class ResourceParserTest : public ::testing::Test { + public: + void SetUp() override { context_ = test::ContextBuilder().Build(); } - void SetUp() override { - mContext = test::ContextBuilder().build(); - } + ::testing::AssertionResult TestParse(const StringPiece& str) { + return TestParse(str, ConfigDescription{}); + } - ::testing::AssertionResult testParse(const StringPiece& str) { - return testParse(str, ConfigDescription{}); + ::testing::AssertionResult TestParse(const StringPiece& str, + const ConfigDescription& config) { + std::stringstream input(kXmlPreamble); + input << "<resources>\n" << str << "\n</resources>" << std::endl; + ResourceParserOptions parserOptions; + ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"}, + config, parserOptions); + xml::XmlPullParser xmlParser(input); + if (parser.Parse(&xmlParser)) { + return ::testing::AssertionSuccess(); } + return ::testing::AssertionFailure(); + } - ::testing::AssertionResult testParse(const StringPiece& str, const ConfigDescription& config) { - std::stringstream input(kXmlPreamble); - input << "<resources>\n" << str << "\n</resources>" << std::endl; - ResourceParserOptions parserOptions; - ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, config, - parserOptions); - xml::XmlPullParser xmlParser(input); - if (parser.parse(&xmlParser)) { - return ::testing::AssertionSuccess(); - } - return ::testing::AssertionFailure(); - } + protected: + ResourceTable table_; + std::unique_ptr<IAaptContext> context_; }; TEST_F(ResourceParserTest, ParseQuotedString) { - std::string input = "<string name=\"foo\"> \" hey there \" </string>"; - ASSERT_TRUE(testParse(input)); + std::string input = "<string name=\"foo\"> \" hey there \" </string>"; + ASSERT_TRUE(TestParse(input)); - String* str = test::getValue<String>(&mTable, u"@string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::u16string(u" hey there "), *str->value); + String* str = test::GetValue<String>(&table_, "string/foo"); + ASSERT_NE(nullptr, str); + EXPECT_EQ(std::string(" hey there "), *str->value); } TEST_F(ResourceParserTest, ParseEscapedString) { - std::string input = "<string name=\"foo\">\\?123</string>"; - ASSERT_TRUE(testParse(input)); + std::string input = "<string name=\"foo\">\\?123</string>"; + ASSERT_TRUE(TestParse(input)); - String* str = test::getValue<String>(&mTable, u"@string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::u16string(u"?123"), *str->value); + String* str = test::GetValue<String>(&table_, "string/foo"); + ASSERT_NE(nullptr, str); + EXPECT_EQ(std::string("?123"), *str->value); } TEST_F(ResourceParserTest, ParseFormattedString) { - std::string input = "<string name=\"foo\">%d %s</string>"; - ASSERT_FALSE(testParse(input)); + std::string input = "<string name=\"foo\">%d %s</string>"; + ASSERT_FALSE(TestParse(input)); - input = "<string name=\"foo\">%1$d %2$s</string>"; - ASSERT_TRUE(testParse(input)); + input = "<string name=\"foo\">%1$d %2$s</string>"; + ASSERT_TRUE(TestParse(input)); } -TEST_F(ResourceParserTest, IgnoreXliffTags) { - std::string input = "<string name=\"foo\" \n" - " xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n" - " There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>"; - ASSERT_TRUE(testParse(input)); +TEST_F(ResourceParserTest, ParseStyledString) { + // Use a surrogate pair unicode point so that we can verify that the span + // indices + // use UTF-16 length and not UTF-18 length. + std::string input = + "<string name=\"foo\">This is my aunt\u2019s <b>string</b></string>"; + ASSERT_TRUE(TestParse(input)); + + StyledString* str = test::GetValue<StyledString>(&table_, "string/foo"); + ASSERT_NE(nullptr, str); - String* str = test::getValue<String>(&mTable, u"@string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(StringPiece16(u"There are %1$d apples"), StringPiece16(*str->value)); + const std::string expected_str = "This is my aunt\u2019s string"; + EXPECT_EQ(expected_str, *str->value->str); + EXPECT_EQ(1u, str->value->spans.size()); + + EXPECT_EQ(std::string("b"), *str->value->spans[0].name); + EXPECT_EQ(17u, str->value->spans[0].first_char); + EXPECT_EQ(23u, str->value->spans[0].last_char); } -TEST_F(ResourceParserTest, ParseNull) { - std::string input = "<integer name=\"foo\">@null</integer>"; - ASSERT_TRUE(testParse(input)); +TEST_F(ResourceParserTest, ParseStringWithWhitespace) { + std::string input = "<string name=\"foo\"> This is what I think </string>"; + ASSERT_TRUE(TestParse(input)); + + String* str = test::GetValue<String>(&table_, "string/foo"); + ASSERT_NE(nullptr, str); + EXPECT_EQ(std::string("This is what I think"), *str->value); - // The Android runtime treats a value of android::Res_value::TYPE_NULL as - // a non-existing value, and this causes problems in styles when trying to resolve - // an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE - // with a data value of 0. - BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo"); - ASSERT_NE(nullptr, integer); - EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType); - EXPECT_EQ(0u, integer->value.data); + input = "<string name=\"foo2\">\" This is what I think \"</string>"; + ASSERT_TRUE(TestParse(input)); + + str = test::GetValue<String>(&table_, "string/foo2"); + ASSERT_NE(nullptr, str); + EXPECT_EQ(std::string(" This is what I think "), *str->value); } -TEST_F(ResourceParserTest, ParseEmpty) { - std::string input = "<integer name=\"foo\">@empty</integer>"; - ASSERT_TRUE(testParse(input)); +TEST_F(ResourceParserTest, IgnoreXliffTags) { + std::string input = + "<string name=\"foo\" \n" + " xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n" + " There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>"; + ASSERT_TRUE(TestParse(input)); - BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo"); - ASSERT_NE(nullptr, integer); - EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType); - EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data); + String* str = test::GetValue<String>(&table_, "string/foo"); + ASSERT_NE(nullptr, str); + EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value)); } -TEST_F(ResourceParserTest, ParseAttr) { - std::string input = "<attr name=\"foo\" format=\"string\"/>\n" - "<attr name=\"bar\"/>"; - ASSERT_TRUE(testParse(input)); +TEST_F(ResourceParserTest, ParseNull) { + std::string input = "<integer name=\"foo\">@null</integer>"; + ASSERT_TRUE(TestParse(input)); + + // The Android runtime treats a value of android::Res_value::TYPE_NULL as + // a non-existing value, and this causes problems in styles when trying to + // resolve + // an attribute. Null values must be encoded as + // android::Res_value::TYPE_REFERENCE + // with a data value of 0. + BinaryPrimitive* integer = + test::GetValue<BinaryPrimitive>(&table_, "integer/foo"); + ASSERT_NE(nullptr, integer); + EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), + integer->value.dataType); + EXPECT_EQ(0u, integer->value.data); +} - Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); +TEST_F(ResourceParserTest, ParseEmpty) { + std::string input = "<integer name=\"foo\">@empty</integer>"; + ASSERT_TRUE(TestParse(input)); - attr = test::getValue<Attribute>(&mTable, u"@attr/bar"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask); + BinaryPrimitive* integer = + test::GetValue<BinaryPrimitive>(&table_, "integer/foo"); + ASSERT_NE(nullptr, integer); + EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType); + EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data); } -// Old AAPT allowed attributes to be defined under different configurations, but ultimately -// stored them with the default configuration. Check that we have the same behavior. -TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) { - const ConfigDescription watchConfig = test::parseConfigOrDie("watch"); - std::string input = R"EOF( +TEST_F(ResourceParserTest, ParseAttr) { + std::string input = + "<attr name=\"foo\" format=\"string\"/>\n" + "<attr name=\"bar\"/>"; + ASSERT_TRUE(TestParse(input)); + + Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->type_mask); + + attr = test::GetValue<Attribute>(&table_, "attr/bar"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->type_mask); +} + +// Old AAPT allowed attributes to be defined under different configurations, but +// ultimately +// stored them with the default configuration. Check that we have the same +// behavior. +TEST_F(ResourceParserTest, + ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) { + const ConfigDescription watch_config = test::ParseConfigOrDie("watch"); + std::string input = R"EOF( <attr name="foo" /> <declare-styleable name="bar"> <attr name="baz" /> </declare-styleable>)EOF"; - ASSERT_TRUE(testParse(input, watchConfig)); + ASSERT_TRUE(TestParse(input, watch_config)); - EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, u"@attr/foo", watchConfig)); - EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, u"@attr/baz", watchConfig)); - EXPECT_EQ(nullptr, test::getValueForConfig<Styleable>(&mTable, u"@styleable/bar", watchConfig)); + EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/foo", + watch_config)); + EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/baz", + watch_config)); + EXPECT_EQ(nullptr, test::GetValueForConfig<Styleable>( + &table_, "styleable/bar", watch_config)); - EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, u"@attr/foo")); - EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, u"@attr/baz")); - EXPECT_NE(nullptr, test::getValue<Styleable>(&mTable, u"@styleable/bar")); + EXPECT_NE(nullptr, test::GetValue<Attribute>(&table_, "attr/foo")); + EXPECT_NE(nullptr, test::GetValue<Attribute>(&table_, "attr/baz")); + EXPECT_NE(nullptr, test::GetValue<Styleable>(&table_, "styleable/bar")); } TEST_F(ResourceParserTest, ParseAttrWithMinMax) { - std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>"; + ASSERT_TRUE(TestParse(input)); - Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->typeMask); - EXPECT_EQ(10, attr->minInt); - EXPECT_EQ(23, attr->maxInt); + Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->type_mask); + EXPECT_EQ(10, attr->min_int); + EXPECT_EQ(23, attr->max_int); } TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) { - std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>"; - ASSERT_FALSE(testParse(input)); + std::string input = + "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>"; + ASSERT_FALSE(TestParse(input)); } TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { - std::string input = "<declare-styleable name=\"Styleable\">\n" - " <attr name=\"foo\" />\n" - "</declare-styleable>\n" - "<attr name=\"foo\" format=\"string\"/>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<declare-styleable name=\"Styleable\">\n" + " <attr name=\"foo\" />\n" + "</declare-styleable>\n" + "<attr name=\"foo\" format=\"string\"/>"; + ASSERT_TRUE(TestParse(input)); - Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); + Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->type_mask); } TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { - std::string input = "<declare-styleable name=\"Theme\">" - " <attr name=\"foo\" />\n" - "</declare-styleable>\n" - "<declare-styleable name=\"Window\">\n" - " <attr name=\"foo\" format=\"boolean\"/>\n" - "</declare-styleable>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<declare-styleable name=\"Theme\">" + " <attr name=\"foo\" />\n" + "</declare-styleable>\n" + "<declare-styleable name=\"Window\">\n" + " <attr name=\"foo\" format=\"boolean\"/>\n" + "</declare-styleable>"; + ASSERT_TRUE(TestParse(input)); - Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask); + Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->type_mask); } TEST_F(ResourceParserTest, ParseEnumAttr) { - std::string input = "<attr name=\"foo\">\n" - " <enum name=\"bar\" value=\"0\"/>\n" - " <enum name=\"bat\" value=\"1\"/>\n" - " <enum name=\"baz\" value=\"2\"/>\n" - "</attr>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<attr name=\"foo\">\n" + " <enum name=\"bar\" value=\"0\"/>\n" + " <enum name=\"bat\" value=\"1\"/>\n" + " <enum name=\"baz\" value=\"2\"/>\n" + "</attr>"; + ASSERT_TRUE(TestParse(input)); - Attribute* enumAttr = test::getValue<Attribute>(&mTable, u"@attr/foo"); - ASSERT_NE(enumAttr, nullptr); - EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM); - ASSERT_EQ(enumAttr->symbols.size(), 3u); + Attribute* enum_attr = test::GetValue<Attribute>(&table_, "attr/foo"); + ASSERT_NE(enum_attr, nullptr); + EXPECT_EQ(enum_attr->type_mask, android::ResTable_map::TYPE_ENUM); + ASSERT_EQ(enum_attr->symbols.size(), 3u); - AAPT_ASSERT_TRUE(enumAttr->symbols[0].symbol.name); - EXPECT_EQ(enumAttr->symbols[0].symbol.name.value().entry, u"bar"); - EXPECT_EQ(enumAttr->symbols[0].value, 0u); + AAPT_ASSERT_TRUE(enum_attr->symbols[0].symbol.name); + EXPECT_EQ(enum_attr->symbols[0].symbol.name.value().entry, "bar"); + EXPECT_EQ(enum_attr->symbols[0].value, 0u); - AAPT_ASSERT_TRUE(enumAttr->symbols[1].symbol.name); - EXPECT_EQ(enumAttr->symbols[1].symbol.name.value().entry, u"bat"); - EXPECT_EQ(enumAttr->symbols[1].value, 1u); + AAPT_ASSERT_TRUE(enum_attr->symbols[1].symbol.name); + EXPECT_EQ(enum_attr->symbols[1].symbol.name.value().entry, "bat"); + EXPECT_EQ(enum_attr->symbols[1].value, 1u); - AAPT_ASSERT_TRUE(enumAttr->symbols[2].symbol.name); - EXPECT_EQ(enumAttr->symbols[2].symbol.name.value().entry, u"baz"); - EXPECT_EQ(enumAttr->symbols[2].value, 2u); + AAPT_ASSERT_TRUE(enum_attr->symbols[2].symbol.name); + EXPECT_EQ(enum_attr->symbols[2].symbol.name.value().entry, "baz"); + EXPECT_EQ(enum_attr->symbols[2].value, 2u); } TEST_F(ResourceParserTest, ParseFlagAttr) { - std::string input = "<attr name=\"foo\">\n" - " <flag name=\"bar\" value=\"0\"/>\n" - " <flag name=\"bat\" value=\"1\"/>\n" - " <flag name=\"baz\" value=\"2\"/>\n" - "</attr>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<attr name=\"foo\">\n" + " <flag name=\"bar\" value=\"0\"/>\n" + " <flag name=\"bat\" value=\"1\"/>\n" + " <flag name=\"baz\" value=\"2\"/>\n" + "</attr>"; + ASSERT_TRUE(TestParse(input)); - Attribute* flagAttr = test::getValue<Attribute>(&mTable, u"@attr/foo"); - ASSERT_NE(nullptr, flagAttr); - EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS); - ASSERT_EQ(flagAttr->symbols.size(), 3u); + Attribute* flag_attr = test::GetValue<Attribute>(&table_, "attr/foo"); + ASSERT_NE(nullptr, flag_attr); + EXPECT_EQ(flag_attr->type_mask, android::ResTable_map::TYPE_FLAGS); + ASSERT_EQ(flag_attr->symbols.size(), 3u); - AAPT_ASSERT_TRUE(flagAttr->symbols[0].symbol.name); - EXPECT_EQ(flagAttr->symbols[0].symbol.name.value().entry, u"bar"); - EXPECT_EQ(flagAttr->symbols[0].value, 0u); + AAPT_ASSERT_TRUE(flag_attr->symbols[0].symbol.name); + EXPECT_EQ(flag_attr->symbols[0].symbol.name.value().entry, "bar"); + EXPECT_EQ(flag_attr->symbols[0].value, 0u); - AAPT_ASSERT_TRUE(flagAttr->symbols[1].symbol.name); - EXPECT_EQ(flagAttr->symbols[1].symbol.name.value().entry, u"bat"); - EXPECT_EQ(flagAttr->symbols[1].value, 1u); + AAPT_ASSERT_TRUE(flag_attr->symbols[1].symbol.name); + EXPECT_EQ(flag_attr->symbols[1].symbol.name.value().entry, "bat"); + EXPECT_EQ(flag_attr->symbols[1].value, 1u); - AAPT_ASSERT_TRUE(flagAttr->symbols[2].symbol.name); - EXPECT_EQ(flagAttr->symbols[2].symbol.name.value().entry, u"baz"); - EXPECT_EQ(flagAttr->symbols[2].value, 2u); + AAPT_ASSERT_TRUE(flag_attr->symbols[2].symbol.name); + EXPECT_EQ(flag_attr->symbols[2].symbol.name.value().entry, "baz"); + EXPECT_EQ(flag_attr->symbols[2].value, 2u); - std::unique_ptr<BinaryPrimitive> flagValue = ResourceUtils::tryParseFlagSymbol(flagAttr, - u"baz|bat"); - ASSERT_NE(nullptr, flagValue); - EXPECT_EQ(flagValue->value.data, 1u | 2u); + std::unique_ptr<BinaryPrimitive> flag_value = + ResourceUtils::TryParseFlagSymbol(flag_attr, "baz|bat"); + ASSERT_NE(nullptr, flag_value); + EXPECT_EQ(flag_value->value.data, 1u | 2u); } TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { - std::string input = "<attr name=\"foo\">\n" - " <enum name=\"bar\" value=\"0\"/>\n" - " <enum name=\"bat\" value=\"1\"/>\n" - " <enum name=\"bat\" value=\"2\"/>\n" - "</attr>"; - ASSERT_FALSE(testParse(input)); + std::string input = + "<attr name=\"foo\">\n" + " <enum name=\"bar\" value=\"0\"/>\n" + " <enum name=\"bat\" value=\"1\"/>\n" + " <enum name=\"bat\" value=\"2\"/>\n" + "</attr>"; + ASSERT_FALSE(TestParse(input)); } TEST_F(ResourceParserTest, ParseStyle) { - std::string input = "<style name=\"foo\" parent=\"@style/fu\">\n" - " <item name=\"bar\">#ffffffff</item>\n" - " <item name=\"bat\">@string/hey</item>\n" - " <item name=\"baz\"><b>hey</b></item>\n" - "</style>"; - ASSERT_TRUE(testParse(input)); - - Style* style = test::getValue<Style>(&mTable, u"@style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(test::parseNameOrDie(u"@style/fu"), style->parent.value().name.value()); - ASSERT_EQ(3u, style->entries.size()); - - AAPT_ASSERT_TRUE(style->entries[0].key.name); - EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), style->entries[0].key.name.value()); - - AAPT_ASSERT_TRUE(style->entries[1].key.name); - EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), style->entries[1].key.name.value()); - - AAPT_ASSERT_TRUE(style->entries[2].key.name); - EXPECT_EQ(test::parseNameOrDie(u"@attr/baz"), style->entries[2].key.name.value()); + std::string input = + "<style name=\"foo\" parent=\"@style/fu\">\n" + " <item name=\"bar\">#ffffffff</item>\n" + " <item name=\"bat\">@string/hey</item>\n" + " <item name=\"baz\"><b>hey</b></item>\n" + "</style>"; + ASSERT_TRUE(TestParse(input)); + + Style* style = test::GetValue<Style>(&table_, "style/foo"); + ASSERT_NE(nullptr, style); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().name); + EXPECT_EQ(test::ParseNameOrDie("style/fu"), + style->parent.value().name.value()); + ASSERT_EQ(3u, style->entries.size()); + + AAPT_ASSERT_TRUE(style->entries[0].key.name); + EXPECT_EQ(test::ParseNameOrDie("attr/bar"), + style->entries[0].key.name.value()); + + AAPT_ASSERT_TRUE(style->entries[1].key.name); + EXPECT_EQ(test::ParseNameOrDie("attr/bat"), + style->entries[1].key.name.value()); + + AAPT_ASSERT_TRUE(style->entries[2].key.name); + EXPECT_EQ(test::ParseNameOrDie("attr/baz"), + style->entries[2].key.name.value()); } TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { - std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>"; - ASSERT_TRUE(testParse(input)); + std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>"; + ASSERT_TRUE(TestParse(input)); - Style* style = test::getValue<Style>(&mTable, u"@style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(test::parseNameOrDie(u"@com.app:style/Theme"), style->parent.value().name.value()); + Style* style = test::GetValue<Style>(&table_, "style/foo"); + ASSERT_NE(nullptr, style); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().name); + EXPECT_EQ(test::ParseNameOrDie("com.app:style/Theme"), + style->parent.value().name.value()); } TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { - std::string input = "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" - " name=\"foo\" parent=\"app:Theme\"/>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" + " name=\"foo\" parent=\"app:Theme\"/>"; + ASSERT_TRUE(TestParse(input)); - Style* style = test::getValue<Style>(&mTable, u"@style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(test::parseNameOrDie(u"@android:style/Theme"), style->parent.value().name.value()); + Style* style = test::GetValue<Style>(&table_, "style/foo"); + ASSERT_NE(nullptr, style); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().name); + EXPECT_EQ(test::ParseNameOrDie("android:style/Theme"), + style->parent.value().name.value()); } TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { - std::string input = - "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" name=\"foo\">\n" - " <item name=\"app:bar\">0</item>\n" - "</style>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" " + "name=\"foo\">\n" + " <item name=\"app:bar\">0</item>\n" + "</style>"; + ASSERT_TRUE(TestParse(input)); - Style* style = test::getValue<Style>(&mTable, u"@style/foo"); - ASSERT_NE(nullptr, style); - ASSERT_EQ(1u, style->entries.size()); - EXPECT_EQ(test::parseNameOrDie(u"@android:attr/bar"), style->entries[0].key.name.value()); + Style* style = test::GetValue<Style>(&table_, "style/foo"); + ASSERT_NE(nullptr, style); + ASSERT_EQ(1u, style->entries.size()); + EXPECT_EQ(test::ParseNameOrDie("android:attr/bar"), + style->entries[0].key.name.value()); } TEST_F(ResourceParserTest, ParseStyleWithInferredParent) { - std::string input = "<style name=\"foo.bar\"/>"; - ASSERT_TRUE(testParse(input)); + std::string input = "<style name=\"foo.bar\"/>"; + ASSERT_TRUE(TestParse(input)); - Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(style->parent.value().name.value(), test::parseNameOrDie(u"@style/foo")); - EXPECT_TRUE(style->parentInferred); + Style* style = test::GetValue<Style>(&table_, "style/foo.bar"); + ASSERT_NE(nullptr, style); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().name); + EXPECT_EQ(style->parent.value().name.value(), + test::ParseNameOrDie("style/foo")); + EXPECT_TRUE(style->parent_inferred); } -TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAttribute) { - std::string input = "<style name=\"foo.bar\" parent=\"\"/>"; - ASSERT_TRUE(testParse(input)); +TEST_F(ResourceParserTest, + ParseStyleWithInferredParentOverridenByEmptyParentAttribute) { + std::string input = "<style name=\"foo.bar\" parent=\"\"/>"; + ASSERT_TRUE(TestParse(input)); - Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar"); - ASSERT_NE(nullptr, style); - AAPT_EXPECT_FALSE(style->parent); - EXPECT_FALSE(style->parentInferred); + Style* style = test::GetValue<Style>(&table_, "style/foo.bar"); + ASSERT_NE(nullptr, style); + AAPT_EXPECT_FALSE(style->parent); + EXPECT_FALSE(style->parent_inferred); } TEST_F(ResourceParserTest, ParseStyleWithPrivateParentReference) { - std::string input = R"EOF(<style name="foo" parent="*android:style/bar" />)EOF"; - ASSERT_TRUE(testParse(input)); + std::string input = + R"EOF(<style name="foo" parent="*android:style/bar" />)EOF"; + ASSERT_TRUE(TestParse(input)); - Style* style = test::getValue<Style>(&mTable, u"@style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - EXPECT_TRUE(style->parent.value().privateReference); + Style* style = test::GetValue<Style>(&table_, "style/foo"); + ASSERT_NE(nullptr, style); + AAPT_ASSERT_TRUE(style->parent); + EXPECT_TRUE(style->parent.value().private_reference); } TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { - std::string input = "<string name=\"foo\">@+id/bar</string>"; - ASSERT_TRUE(testParse(input)); + std::string input = "<string name=\"foo\">@+id/bar</string>"; + ASSERT_TRUE(TestParse(input)); - Id* id = test::getValue<Id>(&mTable, u"@id/bar"); - ASSERT_NE(id, nullptr); + Id* id = test::GetValue<Id>(&table_, "id/bar"); + ASSERT_NE(id, nullptr); } TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { - std::string input = "<declare-styleable name=\"foo\">\n" - " <attr name=\"bar\" />\n" - " <attr name=\"bat\" format=\"string|reference\"/>\n" - " <attr name=\"baz\">\n" - " <enum name=\"foo\" value=\"1\"/>\n" - " </attr>\n" - "</declare-styleable>"; - ASSERT_TRUE(testParse(input)); - - Maybe<ResourceTable::SearchResult> result = - mTable.findResource(test::parseNameOrDie(u"@styleable/foo")); - AAPT_ASSERT_TRUE(result); - EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state); - - Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/bar"); - ASSERT_NE(attr, nullptr); - EXPECT_TRUE(attr->isWeak()); - - attr = test::getValue<Attribute>(&mTable, u"@attr/bat"); - ASSERT_NE(attr, nullptr); - EXPECT_TRUE(attr->isWeak()); - - attr = test::getValue<Attribute>(&mTable, u"@attr/baz"); - ASSERT_NE(attr, nullptr); - EXPECT_TRUE(attr->isWeak()); - EXPECT_EQ(1u, attr->symbols.size()); - - EXPECT_NE(nullptr, test::getValue<Id>(&mTable, u"@id/foo")); - - Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo"); - ASSERT_NE(styleable, nullptr); - ASSERT_EQ(3u, styleable->entries.size()); - - EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), styleable->entries[0].name.value()); - EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), styleable->entries[1].name.value()); + std::string input = + "<declare-styleable name=\"foo\">\n" + " <attr name=\"bar\" />\n" + " <attr name=\"bat\" format=\"string|reference\"/>\n" + " <attr name=\"baz\">\n" + " <enum name=\"foo\" value=\"1\"/>\n" + " </attr>\n" + "</declare-styleable>"; + ASSERT_TRUE(TestParse(input)); + + Maybe<ResourceTable::SearchResult> result = + table_.FindResource(test::ParseNameOrDie("styleable/foo")); + AAPT_ASSERT_TRUE(result); + EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state); + + Attribute* attr = test::GetValue<Attribute>(&table_, "attr/bar"); + ASSERT_NE(attr, nullptr); + EXPECT_TRUE(attr->IsWeak()); + + attr = test::GetValue<Attribute>(&table_, "attr/bat"); + ASSERT_NE(attr, nullptr); + EXPECT_TRUE(attr->IsWeak()); + + attr = test::GetValue<Attribute>(&table_, "attr/baz"); + ASSERT_NE(attr, nullptr); + EXPECT_TRUE(attr->IsWeak()); + EXPECT_EQ(1u, attr->symbols.size()); + + EXPECT_NE(nullptr, test::GetValue<Id>(&table_, "id/foo")); + + Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo"); + ASSERT_NE(styleable, nullptr); + ASSERT_EQ(3u, styleable->entries.size()); + + EXPECT_EQ(test::ParseNameOrDie("attr/bar"), + styleable->entries[0].name.value()); + EXPECT_EQ(test::ParseNameOrDie("attr/bat"), + styleable->entries[1].name.value()); } TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) { - std::string input = "<declare-styleable name=\"foo\" xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n" - " <attr name=\"*android:bar\" />\n" - " <attr name=\"privAndroid:bat\" />\n" - "</declare-styleable>"; - ASSERT_TRUE(testParse(input)); - Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo"); - ASSERT_NE(nullptr, styleable); - ASSERT_EQ(2u, styleable->entries.size()); - - EXPECT_TRUE(styleable->entries[0].privateReference); - AAPT_ASSERT_TRUE(styleable->entries[0].name); - EXPECT_EQ(std::u16string(u"android"), styleable->entries[0].name.value().package); - - EXPECT_TRUE(styleable->entries[1].privateReference); - AAPT_ASSERT_TRUE(styleable->entries[1].name); - EXPECT_EQ(std::u16string(u"android"), styleable->entries[1].name.value().package); + std::string input = + "<declare-styleable name=\"foo\" " + "xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n" + " <attr name=\"*android:bar\" />\n" + " <attr name=\"privAndroid:bat\" />\n" + "</declare-styleable>"; + ASSERT_TRUE(TestParse(input)); + Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo"); + ASSERT_NE(nullptr, styleable); + ASSERT_EQ(2u, styleable->entries.size()); + + EXPECT_TRUE(styleable->entries[0].private_reference); + AAPT_ASSERT_TRUE(styleable->entries[0].name); + EXPECT_EQ(std::string("android"), styleable->entries[0].name.value().package); + + EXPECT_TRUE(styleable->entries[1].private_reference); + AAPT_ASSERT_TRUE(styleable->entries[1].name); + EXPECT_EQ(std::string("android"), styleable->entries[1].name.value().package); } TEST_F(ResourceParserTest, ParseArray) { - std::string input = "<array name=\"foo\">\n" - " <item>@string/ref</item>\n" - " <item>hey</item>\n" - " <item>23</item>\n" - "</array>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<array name=\"foo\">\n" + " <item>@string/ref</item>\n" + " <item>hey</item>\n" + " <item>23</item>\n" + "</array>"; + ASSERT_TRUE(TestParse(input)); - Array* array = test::getValue<Array>(&mTable, u"@array/foo"); - ASSERT_NE(array, nullptr); - ASSERT_EQ(3u, array->items.size()); + Array* array = test::GetValue<Array>(&table_, "array/foo"); + ASSERT_NE(array, nullptr); + ASSERT_EQ(3u, array->items.size()); - EXPECT_NE(nullptr, valueCast<Reference>(array->items[0].get())); - EXPECT_NE(nullptr, valueCast<String>(array->items[1].get())); - EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get())); + EXPECT_NE(nullptr, ValueCast<Reference>(array->items[0].get())); + EXPECT_NE(nullptr, ValueCast<String>(array->items[1].get())); + EXPECT_NE(nullptr, ValueCast<BinaryPrimitive>(array->items[2].get())); } TEST_F(ResourceParserTest, ParseStringArray) { - std::string input = "<string-array name=\"foo\">\n" - " <item>\"Werk\"</item>\n" - "</string-array>\n"; - ASSERT_TRUE(testParse(input)); - EXPECT_NE(nullptr, test::getValue<Array>(&mTable, u"@array/foo")); + std::string input = + "<string-array name=\"foo\">\n" + " <item>\"Werk\"</item>\n" + "</string-array>\n"; + ASSERT_TRUE(TestParse(input)); + EXPECT_NE(nullptr, test::GetValue<Array>(&table_, "array/foo")); } TEST_F(ResourceParserTest, ParsePlural) { - std::string input = "<plurals name=\"foo\">\n" - " <item quantity=\"other\">apples</item>\n" - " <item quantity=\"one\">apple</item>\n" - "</plurals>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<plurals name=\"foo\">\n" + " <item quantity=\"other\">apples</item>\n" + " <item quantity=\"one\">apple</item>\n" + "</plurals>"; + ASSERT_TRUE(TestParse(input)); } TEST_F(ResourceParserTest, ParseCommentsWithResource) { - std::string input = "<!--This is a comment-->\n" - "<string name=\"foo\">Hi</string>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<!--This is a comment-->\n" + "<string name=\"foo\">Hi</string>"; + ASSERT_TRUE(TestParse(input)); - String* value = test::getValue<String>(&mTable, u"@string/foo"); - ASSERT_NE(nullptr, value); - EXPECT_EQ(value->getComment(), u"This is a comment"); + String* value = test::GetValue<String>(&table_, "string/foo"); + ASSERT_NE(nullptr, value); + EXPECT_EQ(value->GetComment(), "This is a comment"); } TEST_F(ResourceParserTest, DoNotCombineMultipleComments) { - std::string input = "<!--One-->\n" - "<!--Two-->\n" - "<string name=\"foo\">Hi</string>"; + std::string input = + "<!--One-->\n" + "<!--Two-->\n" + "<string name=\"foo\">Hi</string>"; - ASSERT_TRUE(testParse(input)); + ASSERT_TRUE(TestParse(input)); - String* value = test::getValue<String>(&mTable, u"@string/foo"); - ASSERT_NE(nullptr, value); - EXPECT_EQ(value->getComment(), u"Two"); + String* value = test::GetValue<String>(&table_, "string/foo"); + ASSERT_NE(nullptr, value); + EXPECT_EQ(value->GetComment(), "Two"); } TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) { - std::string input = "<!--One-->\n" - "<string name=\"foo\">\n" - " Hi\n" - "<!--Two-->\n" - "</string>"; + std::string input = + "<!--One-->\n" + "<string name=\"foo\">\n" + " Hi\n" + "<!--Two-->\n" + "</string>"; - ASSERT_TRUE(testParse(input)); + ASSERT_TRUE(TestParse(input)); - String* value = test::getValue<String>(&mTable, u"@string/foo"); - ASSERT_NE(nullptr, value); - EXPECT_EQ(value->getComment(), u"One"); + String* value = test::GetValue<String>(&table_, "string/foo"); + ASSERT_NE(nullptr, value); + EXPECT_EQ(value->GetComment(), "One"); } TEST_F(ResourceParserTest, ParseNestedComments) { - // We only care about declare-styleable and enum/flag attributes because comments - // from those end up in R.java - std::string input = R"EOF( + // We only care about declare-styleable and enum/flag attributes because + // comments + // from those end up in R.java + std::string input = R"EOF( <declare-styleable name="foo"> <!-- The name of the bar --> <attr name="barName" format="string|reference" /> @@ -508,19 +591,21 @@ TEST_F(ResourceParserTest, ParseNestedComments) { <!-- The very first --> <enum name="one" value="1" /> </attr>)EOF"; - ASSERT_TRUE(testParse(input)); + ASSERT_TRUE(TestParse(input)); - Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo"); - ASSERT_NE(nullptr, styleable); - ASSERT_EQ(1u, styleable->entries.size()); + Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo"); + ASSERT_NE(nullptr, styleable); + ASSERT_EQ(1u, styleable->entries.size()); - EXPECT_EQ(StringPiece16(u"The name of the bar"), styleable->entries.front().getComment()); + EXPECT_EQ(StringPiece("The name of the bar"), + styleable->entries.front().GetComment()); - Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); - ASSERT_NE(nullptr, attr); - ASSERT_EQ(1u, attr->symbols.size()); + Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); + ASSERT_NE(nullptr, attr); + ASSERT_EQ(1u, attr->symbols.size()); - EXPECT_EQ(StringPiece16(u"The very first"), attr->symbols.front().symbol.getComment()); + EXPECT_EQ(StringPiece("The very first"), + attr->symbols.front().symbol.GetComment()); } /* @@ -528,15 +613,15 @@ TEST_F(ResourceParserTest, ParseNestedComments) { * (as an ID has no value). */ TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { - std::string input = "<public type=\"id\" name=\"foo\"/>"; - ASSERT_TRUE(testParse(input)); + std::string input = "<public type=\"id\" name=\"foo\"/>"; + ASSERT_TRUE(TestParse(input)); - Id* id = test::getValue<Id>(&mTable, u"@id/foo"); - ASSERT_NE(nullptr, id); + Id* id = test::GetValue<Id>(&table_, "id/foo"); + ASSERT_NE(nullptr, id); } TEST_F(ResourceParserTest, KeepAllProducts) { - std::string input = R"EOF( + std::string input = R"EOF( <string name="foo" product="phone">hi</string> <string name="foo" product="no-sdcard">ho</string> <string name="bar" product="">wee</string> @@ -544,88 +629,92 @@ TEST_F(ResourceParserTest, KeepAllProducts) { <string name="bit" product="phablet">hoot</string> <string name="bot" product="default">yes</string> )EOF"; - ASSERT_TRUE(testParse(input)); - - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/foo", - ConfigDescription::defaultConfig(), - "phone")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/foo", - ConfigDescription::defaultConfig(), - "no-sdcard")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/bar", - ConfigDescription::defaultConfig(), - "")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/baz", - ConfigDescription::defaultConfig(), - "")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/bit", - ConfigDescription::defaultConfig(), - "phablet")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/bot", - ConfigDescription::defaultConfig(), - "default")); + ASSERT_TRUE(TestParse(input)); + + EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>( + &table_, "string/foo", + ConfigDescription::DefaultConfig(), "phone")); + EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>( + &table_, "string/foo", + ConfigDescription::DefaultConfig(), "no-sdcard")); + EXPECT_NE(nullptr, + test::GetValueForConfigAndProduct<String>( + &table_, "string/bar", ConfigDescription::DefaultConfig(), "")); + EXPECT_NE(nullptr, + test::GetValueForConfigAndProduct<String>( + &table_, "string/baz", ConfigDescription::DefaultConfig(), "")); + EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>( + &table_, "string/bit", + ConfigDescription::DefaultConfig(), "phablet")); + EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>( + &table_, "string/bot", + ConfigDescription::DefaultConfig(), "default")); } TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) { - std::string input = R"EOF( + std::string input = R"EOF( <public-group type="attr" first-id="0x01010040"> <public name="foo" /> <public name="bar" /> </public-group>)EOF"; - ASSERT_TRUE(testParse(input)); + ASSERT_TRUE(TestParse(input)); - Maybe<ResourceTable::SearchResult> result = mTable.findResource( - test::parseNameOrDie(u"@attr/foo")); - AAPT_ASSERT_TRUE(result); + Maybe<ResourceTable::SearchResult> result = + table_.FindResource(test::ParseNameOrDie("attr/foo")); + AAPT_ASSERT_TRUE(result); - AAPT_ASSERT_TRUE(result.value().package->id); - AAPT_ASSERT_TRUE(result.value().type->id); - AAPT_ASSERT_TRUE(result.value().entry->id); - ResourceId actualId(result.value().package->id.value(), - result.value().type->id.value(), - result.value().entry->id.value()); - EXPECT_EQ(ResourceId(0x01010040), actualId); + AAPT_ASSERT_TRUE(result.value().package->id); + AAPT_ASSERT_TRUE(result.value().type->id); + AAPT_ASSERT_TRUE(result.value().entry->id); + ResourceId actual_id(result.value().package->id.value(), + result.value().type->id.value(), + result.value().entry->id.value()); + EXPECT_EQ(ResourceId(0x01010040), actual_id); - result = mTable.findResource(test::parseNameOrDie(u"@attr/bar")); - AAPT_ASSERT_TRUE(result); + result = table_.FindResource(test::ParseNameOrDie("attr/bar")); + AAPT_ASSERT_TRUE(result); - AAPT_ASSERT_TRUE(result.value().package->id); - AAPT_ASSERT_TRUE(result.value().type->id); - AAPT_ASSERT_TRUE(result.value().entry->id); - actualId = ResourceId(result.value().package->id.value(), - result.value().type->id.value(), - result.value().entry->id.value()); - EXPECT_EQ(ResourceId(0x01010041), actualId); + AAPT_ASSERT_TRUE(result.value().package->id); + AAPT_ASSERT_TRUE(result.value().type->id); + AAPT_ASSERT_TRUE(result.value().entry->id); + actual_id = ResourceId(result.value().package->id.value(), + result.value().type->id.value(), + result.value().entry->id.value()); + EXPECT_EQ(ResourceId(0x01010041), actual_id); } TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) { - std::string input = R"EOF(<item type="layout" name="foo">@layout/bar</item>)EOF"; - ASSERT_TRUE(testParse(input)); + std::string input = + R"EOF(<item type="layout" name="foo">@layout/bar</item>)EOF"; + ASSERT_TRUE(TestParse(input)); - input = R"EOF(<item type="layout" name="bar">"this is a string"</item>)EOF"; - ASSERT_FALSE(testParse(input)); + input = R"EOF(<item type="layout" name="bar">"this is a string"</item>)EOF"; + ASSERT_FALSE(TestParse(input)); } -TEST_F(ResourceParserTest, AddResourcesElementShouldAddEntryWithUndefinedSymbol) { - std::string input = R"EOF(<add-resource name="bar" type="string" />)EOF"; - ASSERT_TRUE(testParse(input)); +TEST_F(ResourceParserTest, + AddResourcesElementShouldAddEntryWithUndefinedSymbol) { + std::string input = R"EOF(<add-resource name="bar" type="string" />)EOF"; + ASSERT_TRUE(TestParse(input)); - Maybe<ResourceTable::SearchResult> result = mTable.findResource( - test::parseNameOrDie(u"@string/bar")); - AAPT_ASSERT_TRUE(result); - const ResourceEntry* entry = result.value().entry; - ASSERT_NE(nullptr, entry); - EXPECT_EQ(SymbolState::kUndefined, entry->symbolStatus.state); + Maybe<ResourceTable::SearchResult> result = + table_.FindResource(test::ParseNameOrDie("string/bar")); + AAPT_ASSERT_TRUE(result); + const ResourceEntry* entry = result.value().entry; + ASSERT_NE(nullptr, entry); + EXPECT_EQ(SymbolState::kUndefined, entry->symbol_status.state); } TEST_F(ResourceParserTest, ParseItemElementWithFormat) { - std::string input = R"EOF(<item name="foo" type="integer" format="float">0.3</item>)EOF"; - ASSERT_TRUE(testParse(input)); + std::string input = + R"EOF(<item name="foo" type="integer" format="float">0.3</item>)EOF"; + ASSERT_TRUE(TestParse(input)); - BinaryPrimitive* val = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo"); - ASSERT_NE(nullptr, val); + BinaryPrimitive* val = + test::GetValue<BinaryPrimitive>(&table_, "integer/foo"); + ASSERT_NE(nullptr, val); - EXPECT_EQ(uint32_t(android::Res_value::TYPE_FLOAT), val->value.dataType); + EXPECT_EQ(uint32_t(android::Res_value::TYPE_FLOAT), val->value.dataType); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index f9707e41bb3d..4e6a50ae149b 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -14,531 +14,543 @@ * limitations under the License. */ +#include "ResourceTable.h" #include "ConfigDescription.h" #include "NameMangler.h" -#include "ResourceTable.h" #include "ResourceValues.h" #include "ValueVisitor.h" #include "util/Util.h" -#include <algorithm> +#include <android-base/logging.h> #include <androidfw/ResourceTypes.h> +#include <algorithm> #include <memory> #include <string> #include <tuple> namespace aapt { -static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) { - return lhs->type < rhs; +static bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs, + ResourceType rhs) { + return lhs->type < rhs; } template <typename T> -static bool lessThanStructWithName(const std::unique_ptr<T>& lhs, - const StringPiece16& rhs) { - return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0; +static bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, + const StringPiece& rhs) { + return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0; } -ResourceTablePackage* ResourceTable::findPackage(const StringPiece16& name) { - const auto last = packages.end(); - auto iter = std::lower_bound(packages.begin(), last, name, - lessThanStructWithName<ResourceTablePackage>); - if (iter != last && name == (*iter)->name) { - return iter->get(); - } - return nullptr; +ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) { + const auto last = packages.end(); + auto iter = + std::lower_bound(packages.begin(), last, name, + less_than_struct_with_name<ResourceTablePackage>); + if (iter != last && name == (*iter)->name) { + return iter->get(); + } + return nullptr; } -ResourceTablePackage* ResourceTable::findPackageById(uint8_t id) { - for (auto& package : packages) { - if (package->id && package->id.value() == id) { - return package.get(); - } +ResourceTablePackage* ResourceTable::FindPackageById(uint8_t id) { + for (auto& package : packages) { + if (package->id && package->id.value() == id) { + return package.get(); } - return nullptr; + } + return nullptr; } -ResourceTablePackage* ResourceTable::createPackage(const StringPiece16& name, Maybe<uint8_t> id) { - ResourceTablePackage* package = findOrCreatePackage(name); - if (id && !package->id) { - package->id = id; - return package; - } - - if (id && package->id && package->id.value() != id.value()) { - return nullptr; - } +ResourceTablePackage* ResourceTable::CreatePackage(const StringPiece& name, + Maybe<uint8_t> id) { + ResourceTablePackage* package = FindOrCreatePackage(name); + if (id && !package->id) { + package->id = id; return package; -} + } -ResourceTablePackage* ResourceTable::findOrCreatePackage(const StringPiece16& name) { - const auto last = packages.end(); - auto iter = std::lower_bound(packages.begin(), last, name, - lessThanStructWithName<ResourceTablePackage>); - if (iter != last && name == (*iter)->name) { - return iter->get(); - } + if (id && package->id && package->id.value() != id.value()) { + return nullptr; + } + return package; +} - std::unique_ptr<ResourceTablePackage> newPackage = util::make_unique<ResourceTablePackage>(); - newPackage->name = name.toString(); - return packages.emplace(iter, std::move(newPackage))->get(); +ResourceTablePackage* ResourceTable::FindOrCreatePackage( + const StringPiece& name) { + const auto last = packages.end(); + auto iter = + std::lower_bound(packages.begin(), last, name, + less_than_struct_with_name<ResourceTablePackage>); + if (iter != last && name == (*iter)->name) { + return iter->get(); + } + + std::unique_ptr<ResourceTablePackage> new_package = + util::make_unique<ResourceTablePackage>(); + new_package->name = name.ToString(); + return packages.emplace(iter, std::move(new_package))->get(); } -ResourceTableType* ResourceTablePackage::findType(ResourceType type) { - const auto last = types.end(); - auto iter = std::lower_bound(types.begin(), last, type, lessThanType); - if (iter != last && (*iter)->type == type) { - return iter->get(); - } - return nullptr; +ResourceTableType* ResourceTablePackage::FindType(ResourceType type) { + const auto last = types.end(); + auto iter = std::lower_bound(types.begin(), last, type, less_than_type); + if (iter != last && (*iter)->type == type) { + return iter->get(); + } + return nullptr; } -ResourceTableType* ResourceTablePackage::findOrCreateType(ResourceType type) { - const auto last = types.end(); - auto iter = std::lower_bound(types.begin(), last, type, lessThanType); - if (iter != last && (*iter)->type == type) { - return iter->get(); - } - return types.emplace(iter, new ResourceTableType(type))->get(); +ResourceTableType* ResourceTablePackage::FindOrCreateType(ResourceType type) { + const auto last = types.end(); + auto iter = std::lower_bound(types.begin(), last, type, less_than_type); + if (iter != last && (*iter)->type == type) { + return iter->get(); + } + return types.emplace(iter, new ResourceTableType(type))->get(); } -ResourceEntry* ResourceTableType::findEntry(const StringPiece16& name) { - const auto last = entries.end(); - auto iter = std::lower_bound(entries.begin(), last, name, - lessThanStructWithName<ResourceEntry>); - if (iter != last && name == (*iter)->name) { - return iter->get(); - } - return nullptr; +ResourceEntry* ResourceTableType::FindEntry(const StringPiece& name) { + const auto last = entries.end(); + auto iter = std::lower_bound(entries.begin(), last, name, + less_than_struct_with_name<ResourceEntry>); + if (iter != last && name == (*iter)->name) { + return iter->get(); + } + return nullptr; } -ResourceEntry* ResourceTableType::findOrCreateEntry(const StringPiece16& name) { - auto last = entries.end(); - auto iter = std::lower_bound(entries.begin(), last, name, - lessThanStructWithName<ResourceEntry>); - if (iter != last && name == (*iter)->name) { - return iter->get(); - } - return entries.emplace(iter, new ResourceEntry(name))->get(); +ResourceEntry* ResourceTableType::FindOrCreateEntry(const StringPiece& name) { + auto last = entries.end(); + auto iter = std::lower_bound(entries.begin(), last, name, + less_than_struct_with_name<ResourceEntry>); + if (iter != last && name == (*iter)->name) { + return iter->get(); + } + return entries.emplace(iter, new ResourceEntry(name))->get(); } -ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config) { - return findValue(config, StringPiece()); +ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config) { + return FindValue(config, StringPiece()); } struct ConfigKey { - const ConfigDescription* config; - const StringPiece& product; + const ConfigDescription* config; + const StringPiece& product; }; -bool ltConfigKeyRef(const std::unique_ptr<ResourceConfigValue>& lhs, const ConfigKey& rhs) { - int cmp = lhs->config.compare(*rhs.config); - if (cmp == 0) { - cmp = StringPiece(lhs->product).compare(rhs.product); - } - return cmp < 0; +bool ltConfigKeyRef(const std::unique_ptr<ResourceConfigValue>& lhs, + const ConfigKey& rhs) { + int cmp = lhs->config.compare(*rhs.config); + if (cmp == 0) { + cmp = StringPiece(lhs->product).compare(rhs.product); + } + return cmp < 0; } -ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config, +ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config, const StringPiece& product) { - auto iter = std::lower_bound(values.begin(), values.end(), - ConfigKey{ &config, product }, ltConfigKeyRef); - if (iter != values.end()) { - ResourceConfigValue* value = iter->get(); - if (value->config == config && StringPiece(value->product) == product) { - return value; - } - } - return nullptr; + auto iter = std::lower_bound(values.begin(), values.end(), + ConfigKey{&config, product}, ltConfigKeyRef); + if (iter != values.end()) { + ResourceConfigValue* value = iter->get(); + if (value->config == config && StringPiece(value->product) == product) { + return value; + } + } + return nullptr; } -ResourceConfigValue* ResourceEntry::findOrCreateValue(const ConfigDescription& config, - const StringPiece& product) { - auto iter = std::lower_bound(values.begin(), values.end(), - ConfigKey{ &config, product }, ltConfigKeyRef); - if (iter != values.end()) { - ResourceConfigValue* value = iter->get(); - if (value->config == config && StringPiece(value->product) == product) { - return value; - } - } - ResourceConfigValue* newValue = values.insert( - iter, util::make_unique<ResourceConfigValue>(config, product))->get(); - return newValue; +ResourceConfigValue* ResourceEntry::FindOrCreateValue( + const ConfigDescription& config, const StringPiece& product) { + auto iter = std::lower_bound(values.begin(), values.end(), + ConfigKey{&config, product}, ltConfigKeyRef); + if (iter != values.end()) { + ResourceConfigValue* value = iter->get(); + if (value->config == config && StringPiece(value->product) == product) { + return value; + } + } + ResourceConfigValue* newValue = + values + .insert(iter, util::make_unique<ResourceConfigValue>(config, product)) + ->get(); + return newValue; } -std::vector<ResourceConfigValue*> ResourceEntry::findAllValues(const ConfigDescription& config) { - std::vector<ResourceConfigValue*> results; - - auto iter = values.begin(); - for (; iter != values.end(); ++iter) { - ResourceConfigValue* value = iter->get(); - if (value->config == config) { - results.push_back(value); - ++iter; - break; - } +std::vector<ResourceConfigValue*> ResourceEntry::findAllValues( + const ConfigDescription& config) { + std::vector<ResourceConfigValue*> results; + + auto iter = values.begin(); + for (; iter != values.end(); ++iter) { + ResourceConfigValue* value = iter->get(); + if (value->config == config) { + results.push_back(value); + ++iter; + break; } + } - for (; iter != values.end(); ++iter) { - ResourceConfigValue* value = iter->get(); - if (value->config == config) { - results.push_back(value); - } + for (; iter != values.end(); ++iter) { + ResourceConfigValue* value = iter->get(); + if (value->config == config) { + results.push_back(value); } - return results; + } + return results; } -std::vector<ResourceConfigValue*> ResourceEntry::findValuesIf( - const std::function<bool(ResourceConfigValue*)>& f) { - std::vector<ResourceConfigValue*> results; - for (auto& configValue : values) { - if (f(configValue.get())) { - results.push_back(configValue.get()); - } +std::vector<ResourceConfigValue*> ResourceEntry::FindValuesIf( + const std::function<bool(ResourceConfigValue*)>& f) { + std::vector<ResourceConfigValue*> results; + for (auto& configValue : values) { + if (f(configValue.get())) { + results.push_back(configValue.get()); } - return results; + } + return results; } /** - * The default handler for collisions. A return value of -1 means keep the - * existing value, 0 means fail, and +1 means take the incoming value. + * The default handler for collisions. + * + * Typically, a weak value will be overridden by a strong value. An existing + * weak + * value will not be overridden by an incoming weak value. + * + * There are some exceptions: + * + * Attributes: There are two types of Attribute values: USE and DECL. + * + * USE is anywhere an Attribute is declared without a format, and in a place + * that would + * be legal to declare if the Attribute already existed. This is typically in a + * <declare-styleable> tag. Attributes defined in a <declare-styleable> are also + * weak. + * + * DECL is an absolute declaration of an Attribute and specifies an explicit + * format. + * + * A DECL will override a USE without error. Two DECLs must match in their + * format for there to be + * no error. */ -int ResourceTable::resolveValueCollision(Value* existing, Value* incoming) { - Attribute* existingAttr = valueCast<Attribute>(existing); - Attribute* incomingAttr = valueCast<Attribute>(incoming); - - if (!incomingAttr) { - if (incoming->isWeak()) { - // We're trying to add a weak resource but a resource - // already exists. Keep the existing. - return -1; - } else if (existing->isWeak()) { - // Override the weak resource with the new strong resource. - return 1; - } - // The existing and incoming values are strong, this is an error - // if the values are not both attributes. - return 0; - } - - if (!existingAttr) { - if (existing->isWeak()) { - // The existing value is not an attribute and it is weak, - // so take the incoming attribute value. - return 1; - } - // The existing value is not an attribute and it is strong, - // so the incoming attribute value is an error. - return 0; - } - - assert(incomingAttr && existingAttr); - - // - // Attribute specific handling. At this point we know both - // values are attributes. Since we can declare and define - // attributes all-over, we do special handling to see - // which definition sticks. - // - if (existingAttr->typeMask == incomingAttr->typeMask) { - // The two attributes are both DECLs, but they are plain attributes - // with the same formats. - // Keep the strongest one. - return existingAttr->isWeak() ? 1 : -1; - } - - if (existingAttr->isWeak() && existingAttr->typeMask == android::ResTable_map::TYPE_ANY) { - // Any incoming attribute is better than this. - return 1; - } - - if (incomingAttr->isWeak() && incomingAttr->typeMask == android::ResTable_map::TYPE_ANY) { - // The incoming attribute may be a USE instead of a DECL. - // Keep the existing attribute. - return -1; - } - return 0; +ResourceTable::CollisionResult ResourceTable::ResolveValueCollision( + Value* existing, Value* incoming) { + Attribute* existing_attr = ValueCast<Attribute>(existing); + Attribute* incoming_attr = ValueCast<Attribute>(incoming); + if (!incoming_attr) { + if (incoming->IsWeak()) { + // We're trying to add a weak resource but a resource + // already exists. Keep the existing. + return CollisionResult::kKeepOriginal; + } else if (existing->IsWeak()) { + // Override the weak resource with the new strong resource. + return CollisionResult::kTakeNew; + } + // The existing and incoming values are strong, this is an error + // if the values are not both attributes. + return CollisionResult::kConflict; + } + + if (!existing_attr) { + if (existing->IsWeak()) { + // The existing value is not an attribute and it is weak, + // so take the incoming attribute value. + return CollisionResult::kTakeNew; + } + // The existing value is not an attribute and it is strong, + // so the incoming attribute value is an error. + return CollisionResult::kConflict; + } + + CHECK(incoming_attr != nullptr && existing_attr != nullptr); + + // + // Attribute specific handling. At this point we know both + // values are attributes. Since we can declare and define + // attributes all-over, we do special handling to see + // which definition sticks. + // + if (existing_attr->type_mask == incoming_attr->type_mask) { + // The two attributes are both DECLs, but they are plain attributes + // with the same formats. + // Keep the strongest one. + return existing_attr->IsWeak() ? CollisionResult::kTakeNew + : CollisionResult::kKeepOriginal; + } + + if (existing_attr->IsWeak() && + existing_attr->type_mask == android::ResTable_map::TYPE_ANY) { + // Any incoming attribute is better than this. + return CollisionResult::kTakeNew; + } + + if (incoming_attr->IsWeak() && + incoming_attr->type_mask == android::ResTable_map::TYPE_ANY) { + // The incoming attribute may be a USE instead of a DECL. + // Keep the existing attribute. + return CollisionResult::kKeepOriginal; + } + return CollisionResult::kConflict; } -static constexpr const char16_t* kValidNameChars = u"._-"; -static constexpr const char16_t* kValidNameMangledChars = u"._-$"; +static constexpr const char* kValidNameChars = "._-"; +static constexpr const char* kValidNameMangledChars = "._-$"; -bool ResourceTable::addResource(const ResourceNameRef& name, +bool ResourceTable::AddResource(const ResourceNameRef& name, const ConfigDescription& config, const StringPiece& product, std::unique_ptr<Value> value, IDiagnostics* diag) { - return addResourceImpl(name, {}, config, product, std::move(value), kValidNameChars, - resolveValueCollision, diag); + return AddResourceImpl(name, {}, config, product, std::move(value), + kValidNameChars, ResolveValueCollision, diag); } -bool ResourceTable::addResource(const ResourceNameRef& name, - const ResourceId& resId, +bool ResourceTable::AddResource(const ResourceNameRef& name, + const ResourceId& res_id, const ConfigDescription& config, const StringPiece& product, std::unique_ptr<Value> value, IDiagnostics* diag) { - return addResourceImpl(name, resId, config, product, std::move(value), kValidNameChars, - resolveValueCollision, diag); + return AddResourceImpl(name, res_id, config, product, std::move(value), + kValidNameChars, ResolveValueCollision, diag); } -bool ResourceTable::addFileReference(const ResourceNameRef& name, +bool ResourceTable::AddFileReference(const ResourceNameRef& name, const ConfigDescription& config, const Source& source, - const StringPiece16& path, + const StringPiece& path, IDiagnostics* diag) { - return addFileReferenceImpl(name, config, source, path, nullptr, kValidNameChars, diag); + return AddFileReferenceImpl(name, config, source, path, nullptr, + kValidNameChars, diag); } -bool ResourceTable::addFileReferenceAllowMangled(const ResourceNameRef& name, - const ConfigDescription& config, - const Source& source, - const StringPiece16& path, - io::IFile* file, - IDiagnostics* diag) { - return addFileReferenceImpl(name, config, source, path, file, kValidNameMangledChars, diag); +bool ResourceTable::AddFileReferenceAllowMangled( + const ResourceNameRef& name, const ConfigDescription& config, + const Source& source, const StringPiece& path, io::IFile* file, + IDiagnostics* diag) { + return AddFileReferenceImpl(name, config, source, path, file, + kValidNameMangledChars, diag); } -bool ResourceTable::addFileReferenceImpl(const ResourceNameRef& name, - const ConfigDescription& config, - const Source& source, - const StringPiece16& path, - io::IFile* file, - const char16_t* validChars, - IDiagnostics* diag) { - std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>( - stringPool.makeRef(path)); - fileRef->setSource(source); - fileRef->file = file; - return addResourceImpl(name, ResourceId{}, config, StringPiece{}, std::move(fileRef), - kValidNameChars, resolveValueCollision, diag); +bool ResourceTable::AddFileReferenceImpl( + const ResourceNameRef& name, const ConfigDescription& config, + const Source& source, const StringPiece& path, io::IFile* file, + const char* valid_chars, IDiagnostics* diag) { + std::unique_ptr<FileReference> fileRef = + util::make_unique<FileReference>(string_pool.MakeRef(path)); + fileRef->SetSource(source); + fileRef->file = file; + return AddResourceImpl(name, ResourceId{}, config, StringPiece{}, + std::move(fileRef), valid_chars, ResolveValueCollision, + diag); } -bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name, +bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config, const StringPiece& product, std::unique_ptr<Value> value, IDiagnostics* diag) { - return addResourceImpl(name, ResourceId{}, config, product, std::move(value), - kValidNameMangledChars, resolveValueCollision, diag); + return AddResourceImpl(name, ResourceId{}, config, product, std::move(value), + kValidNameMangledChars, ResolveValueCollision, diag); } -bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name, +bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name, const ResourceId& id, const ConfigDescription& config, const StringPiece& product, std::unique_ptr<Value> value, IDiagnostics* diag) { - return addResourceImpl(name, id, config, product, std::move(value), kValidNameMangledChars, - resolveValueCollision, diag); + return AddResourceImpl(name, id, config, product, std::move(value), + kValidNameMangledChars, ResolveValueCollision, diag); } -bool ResourceTable::addResourceImpl(const ResourceNameRef& name, - const ResourceId& resId, - const ConfigDescription& config, - const StringPiece& product, - std::unique_ptr<Value> value, - const char16_t* validChars, - std::function<int(Value*,Value*)> conflictResolver, - IDiagnostics* diag) { - assert(value && "value can't be nullptr"); - assert(diag && "diagnostics can't be nullptr"); - - auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars); - if (badCharIter != name.entry.end()) { - diag->error(DiagMessage(value->getSource()) - << "resource '" - << name - << "' has invalid entry name '" - << name.entry - << "'. Invalid character '" - << StringPiece16(badCharIter, 1) - << "'"); - return false; - } - - ResourceTablePackage* package = findOrCreatePackage(name.package); - if (resId.isValid() && package->id && package->id.value() != resId.packageId()) { - diag->error(DiagMessage(value->getSource()) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but package '" - << package->name - << "' already has ID " - << std::hex << (int) package->id.value() << std::dec); - return false; - } - - ResourceTableType* type = package->findOrCreateType(name.type); - if (resId.isValid() && type->id && type->id.value() != resId.typeId()) { - diag->error(DiagMessage(value->getSource()) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but type '" - << type->type - << "' already has ID " - << std::hex << (int) type->id.value() << std::dec); - return false; - } - - ResourceEntry* entry = type->findOrCreateEntry(name.entry); - if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) { - diag->error(DiagMessage(value->getSource()) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but resource already has ID " - << ResourceId(package->id.value(), type->id.value(), entry->id.value())); +bool ResourceTable::AddResourceImpl( + const ResourceNameRef& name, const ResourceId& res_id, + const ConfigDescription& config, const StringPiece& product, + std::unique_ptr<Value> value, const char* valid_chars, + const CollisionResolverFunc& conflictResolver, IDiagnostics* diag) { + CHECK(value != nullptr); + CHECK(diag != nullptr); + + auto bad_char_iter = + util::FindNonAlphaNumericAndNotInSet(name.entry, valid_chars); + if (bad_char_iter != name.entry.end()) { + diag->Error(DiagMessage(value->GetSource()) + << "resource '" << name << "' has invalid entry name '" + << name.entry << "'. Invalid character '" + << StringPiece(bad_char_iter, 1) << "'"); + return false; + } + + ResourceTablePackage* package = FindOrCreatePackage(name.package); + if (res_id.is_valid() && package->id && + package->id.value() != res_id.package_id()) { + diag->Error(DiagMessage(value->GetSource()) + << "trying to add resource '" << name << "' with ID " << res_id + << " but package '" << package->name << "' already has ID " + << std::hex << (int)package->id.value() << std::dec); + return false; + } + + ResourceTableType* type = package->FindOrCreateType(name.type); + if (res_id.is_valid() && type->id && type->id.value() != res_id.type_id()) { + diag->Error(DiagMessage(value->GetSource()) + << "trying to add resource '" << name << "' with ID " << res_id + << " but type '" << type->type << "' already has ID " + << std::hex << (int)type->id.value() << std::dec); + return false; + } + + ResourceEntry* entry = type->FindOrCreateEntry(name.entry); + if (res_id.is_valid() && entry->id && + entry->id.value() != res_id.entry_id()) { + diag->Error(DiagMessage(value->GetSource()) + << "trying to add resource '" << name << "' with ID " << res_id + << " but resource already has ID " + << ResourceId(package->id.value(), type->id.value(), + entry->id.value())); + return false; + } + + ResourceConfigValue* config_value = entry->FindOrCreateValue(config, product); + if (!config_value->value) { + // Resource does not exist, add it now. + config_value->value = std::move(value); + + } else { + switch (conflictResolver(config_value->value.get(), value.get())) { + case CollisionResult::kTakeNew: + // Take the incoming value. + config_value->value = std::move(value); + break; + + case CollisionResult::kConflict: + diag->Error(DiagMessage(value->GetSource()) + << "duplicate value for resource '" << name << "' " + << "with config '" << config << "'"); + diag->Error(DiagMessage(config_value->value->GetSource()) + << "resource previously defined here"); return false; - } - ResourceConfigValue* configValue = entry->findOrCreateValue(config, product); - if (!configValue->value) { - // Resource does not exist, add it now. - configValue->value = std::move(value); - - } else { - int collisionResult = conflictResolver(configValue->value.get(), value.get()); - if (collisionResult > 0) { - // Take the incoming value. - configValue->value = std::move(value); - } else if (collisionResult == 0) { - diag->error(DiagMessage(value->getSource()) - << "duplicate value for resource '" << name << "' " - << "with config '" << config << "'"); - diag->error(DiagMessage(configValue->value->getSource()) - << "resource previously defined here"); - return false; - } + case CollisionResult::kKeepOriginal: + break; } + } - if (resId.isValid()) { - package->id = resId.packageId(); - type->id = resId.typeId(); - entry->id = resId.entryId(); - } - return true; + if (res_id.is_valid()) { + package->id = res_id.package_id(); + type->id = res_id.type_id(); + entry->id = res_id.entry_id(); + } + return true; } -bool ResourceTable::setSymbolState(const ResourceNameRef& name, const ResourceId& resId, +bool ResourceTable::SetSymbolState(const ResourceNameRef& name, + const ResourceId& res_id, const Symbol& symbol, IDiagnostics* diag) { - return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag); + return SetSymbolStateImpl(name, res_id, symbol, kValidNameChars, diag); } -bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name, - const ResourceId& resId, - const Symbol& symbol, IDiagnostics* diag) { - return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag); +bool ResourceTable::SetSymbolStateAllowMangled(const ResourceNameRef& name, + const ResourceId& res_id, + const Symbol& symbol, + IDiagnostics* diag) { + return SetSymbolStateImpl(name, res_id, symbol, kValidNameMangledChars, diag); } -bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const ResourceId& resId, - const Symbol& symbol, const char16_t* validChars, +bool ResourceTable::SetSymbolStateImpl(const ResourceNameRef& name, + const ResourceId& res_id, + const Symbol& symbol, + const char* valid_chars, IDiagnostics* diag) { - assert(diag && "diagnostics can't be nullptr"); - - auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars); - if (badCharIter != name.entry.end()) { - diag->error(DiagMessage(symbol.source) - << "resource '" - << name - << "' has invalid entry name '" - << name.entry - << "'. Invalid character '" - << StringPiece16(badCharIter, 1) - << "'"); - return false; - } - - ResourceTablePackage* package = findOrCreatePackage(name.package); - if (resId.isValid() && package->id && package->id.value() != resId.packageId()) { - diag->error(DiagMessage(symbol.source) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but package '" - << package->name - << "' already has ID " - << std::hex << (int) package->id.value() << std::dec); - return false; - } - - ResourceTableType* type = package->findOrCreateType(name.type); - if (resId.isValid() && type->id && type->id.value() != resId.typeId()) { - diag->error(DiagMessage(symbol.source) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but type '" - << type->type - << "' already has ID " - << std::hex << (int) type->id.value() << std::dec); - return false; - } - - ResourceEntry* entry = type->findOrCreateEntry(name.entry); - if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) { - diag->error(DiagMessage(symbol.source) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but resource already has ID " - << ResourceId(package->id.value(), type->id.value(), entry->id.value())); - return false; - } - - if (resId.isValid()) { - package->id = resId.packageId(); - type->id = resId.typeId(); - entry->id = resId.entryId(); - } - - // Only mark the type state as public, it doesn't care about being private. - if (symbol.state == SymbolState::kPublic) { - type->symbolStatus.state = SymbolState::kPublic; - } - - if (symbol.state == SymbolState::kUndefined && - entry->symbolStatus.state != SymbolState::kUndefined) { - // We can't undefine a symbol (remove its visibility). Ignore. - return true; - } - - if (symbol.state == SymbolState::kPrivate && - entry->symbolStatus.state == SymbolState::kPublic) { - // We can't downgrade public to private. Ignore. - return true; - } - - entry->symbolStatus = std::move(symbol); + CHECK(diag != nullptr); + + auto bad_char_iter = + util::FindNonAlphaNumericAndNotInSet(name.entry, valid_chars); + if (bad_char_iter != name.entry.end()) { + diag->Error(DiagMessage(symbol.source) + << "resource '" << name << "' has invalid entry name '" + << name.entry << "'. Invalid character '" + << StringPiece(bad_char_iter, 1) << "'"); + return false; + } + + ResourceTablePackage* package = FindOrCreatePackage(name.package); + if (res_id.is_valid() && package->id && + package->id.value() != res_id.package_id()) { + diag->Error(DiagMessage(symbol.source) + << "trying to add resource '" << name << "' with ID " << res_id + << " but package '" << package->name << "' already has ID " + << std::hex << (int)package->id.value() << std::dec); + return false; + } + + ResourceTableType* type = package->FindOrCreateType(name.type); + if (res_id.is_valid() && type->id && type->id.value() != res_id.type_id()) { + diag->Error(DiagMessage(symbol.source) + << "trying to add resource '" << name << "' with ID " << res_id + << " but type '" << type->type << "' already has ID " + << std::hex << (int)type->id.value() << std::dec); + return false; + } + + ResourceEntry* entry = type->FindOrCreateEntry(name.entry); + if (res_id.is_valid() && entry->id && + entry->id.value() != res_id.entry_id()) { + diag->Error(DiagMessage(symbol.source) + << "trying to add resource '" << name << "' with ID " << res_id + << " but resource already has ID " + << ResourceId(package->id.value(), type->id.value(), + entry->id.value())); + return false; + } + + if (res_id.is_valid()) { + package->id = res_id.package_id(); + type->id = res_id.type_id(); + entry->id = res_id.entry_id(); + } + + // Only mark the type state as public, it doesn't care about being private. + if (symbol.state == SymbolState::kPublic) { + type->symbol_status.state = SymbolState::kPublic; + } + + if (symbol.state == SymbolState::kUndefined && + entry->symbol_status.state != SymbolState::kUndefined) { + // We can't undefine a symbol (remove its visibility). Ignore. return true; -} + } -Maybe<ResourceTable::SearchResult> -ResourceTable::findResource(const ResourceNameRef& name) { - ResourceTablePackage* package = findPackage(name.package); - if (!package) { - return {}; - } + if (symbol.state == SymbolState::kPrivate && + entry->symbol_status.state == SymbolState::kPublic) { + // We can't downgrade public to private. Ignore. + return true; + } - ResourceTableType* type = package->findType(name.type); - if (!type) { - return {}; - } + entry->symbol_status = std::move(symbol); + return true; +} - ResourceEntry* entry = type->findEntry(name.entry); - if (!entry) { - return {}; - } - return SearchResult{ package, type, entry }; +Maybe<ResourceTable::SearchResult> ResourceTable::FindResource( + const ResourceNameRef& name) { + ResourceTablePackage* package = FindPackage(name.package); + if (!package) { + return {}; + } + + ResourceTableType* type = package->FindType(name.type); + if (!type) { + return {}; + } + + ResourceEntry* entry = type->FindEntry(name.entry); + if (!entry) { + return {}; + } + return SearchResult{package, type, entry}; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index cf9e7b8bfc00..a0c3217df610 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -37,42 +37,43 @@ namespace aapt { enum class SymbolState { - kUndefined, - kPublic, - kPrivate + kUndefined, + kPrivate, + kPublic, }; /** * The Public status of a resource. */ struct Symbol { - SymbolState state = SymbolState::kUndefined; - Source source; - std::u16string comment; + SymbolState state = SymbolState::kUndefined; + Source source; + std::string comment; }; class ResourceConfigValue { -public: - /** - * The configuration for which this value is defined. - */ - const ConfigDescription config; - - /** - * The product for which this value is defined. - */ - const std::string product; - - /** - * The actual Value. - */ - std::unique_ptr<Value> value; - - ResourceConfigValue(const ConfigDescription& config, const StringPiece& product) : - config(config), product(product.toString()) { } - -private: - DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue); + public: + /** + * The configuration for which this value is defined. + */ + const ConfigDescription config; + + /** + * The product for which this value is defined. + */ + const std::string product; + + /** + * The actual Value. + */ + std::unique_ptr<Value> value; + + ResourceConfigValue(const ConfigDescription& config, + const StringPiece& product) + : config(config), product(product.ToString()) {} + + private: + DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue); }; /** @@ -80,42 +81,44 @@ private: * varying values for each defined configuration. */ class ResourceEntry { -public: - /** - * The name of the resource. Immutable, as - * this determines the order of this resource - * when doing lookups. - */ - const std::u16string name; - - /** - * The entry ID for this resource. - */ - Maybe<uint16_t> id; - - /** - * Whether this resource is public (and must maintain the same entry ID across builds). - */ - Symbol symbolStatus; - - /** - * The resource's values for each configuration. - */ - std::vector<std::unique_ptr<ResourceConfigValue>> values; - - explicit ResourceEntry(const StringPiece16& name) : name(name.toString()) { } - - ResourceConfigValue* findValue(const ConfigDescription& config); - ResourceConfigValue* findValue(const ConfigDescription& config, const StringPiece& product); - ResourceConfigValue* findOrCreateValue(const ConfigDescription& config, - const StringPiece& product); - std::vector<ResourceConfigValue*> findAllValues(const ConfigDescription& config); - std::vector<ResourceConfigValue*> findValuesIf( - const std::function<bool(ResourceConfigValue*)>& f); - - -private: - DISALLOW_COPY_AND_ASSIGN(ResourceEntry); + public: + /** + * The name of the resource. Immutable, as + * this determines the order of this resource + * when doing lookups. + */ + const std::string name; + + /** + * The entry ID for this resource. + */ + Maybe<uint16_t> id; + + /** + * Whether this resource is public (and must maintain the same entry ID across + * builds). + */ + Symbol symbol_status; + + /** + * The resource's values for each configuration. + */ + std::vector<std::unique_ptr<ResourceConfigValue>> values; + + explicit ResourceEntry(const StringPiece& name) : name(name.ToString()) {} + + ResourceConfigValue* FindValue(const ConfigDescription& config); + ResourceConfigValue* FindValue(const ConfigDescription& config, + const StringPiece& product); + ResourceConfigValue* FindOrCreateValue(const ConfigDescription& config, + const StringPiece& product); + std::vector<ResourceConfigValue*> findAllValues( + const ConfigDescription& config); + std::vector<ResourceConfigValue*> FindValuesIf( + const std::function<bool(ResourceConfigValue*)>& f); + + private: + DISALLOW_COPY_AND_ASSIGN(ResourceEntry); }; /** @@ -123,58 +126,53 @@ private: * for this type. */ class ResourceTableType { -public: - /** - * The logical type of resource (string, drawable, layout, etc.). - */ - const ResourceType type; - - /** - * The type ID for this resource. - */ - Maybe<uint8_t> id; - - /** - * Whether this type is public (and must maintain the same - * type ID across builds). - */ - Symbol symbolStatus; - - /** - * List of resources for this type. - */ - std::vector<std::unique_ptr<ResourceEntry>> entries; - - explicit ResourceTableType(const ResourceType type) : type(type) { } - - ResourceEntry* findEntry(const StringPiece16& name); - ResourceEntry* findOrCreateEntry(const StringPiece16& name); - -private: - DISALLOW_COPY_AND_ASSIGN(ResourceTableType); + public: + /** + * The logical type of resource (string, drawable, layout, etc.). + */ + const ResourceType type; + + /** + * The type ID for this resource. + */ + Maybe<uint8_t> id; + + /** + * Whether this type is public (and must maintain the same + * type ID across builds). + */ + Symbol symbol_status; + + /** + * List of resources for this type. + */ + std::vector<std::unique_ptr<ResourceEntry>> entries; + + explicit ResourceTableType(const ResourceType type) : type(type) {} + + ResourceEntry* FindEntry(const StringPiece& name); + ResourceEntry* FindOrCreateEntry(const StringPiece& name); + + private: + DISALLOW_COPY_AND_ASSIGN(ResourceTableType); }; -enum class PackageType { - System, - Vendor, - App, - Dynamic -}; +enum class PackageType { System, Vendor, App, Dynamic }; class ResourceTablePackage { -public: - PackageType type = PackageType::App; - Maybe<uint8_t> id; - std::u16string name; + public: + PackageType type = PackageType::App; + Maybe<uint8_t> id; + std::string name; - std::vector<std::unique_ptr<ResourceTableType>> types; + std::vector<std::unique_ptr<ResourceTableType>> types; - ResourceTablePackage() = default; - ResourceTableType* findType(ResourceType type); - ResourceTableType* findOrCreateType(const ResourceType type); + ResourceTablePackage() = default; + ResourceTableType* FindType(ResourceType type); + ResourceTableType* FindOrCreateType(const ResourceType type); -private: - DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage); + private: + DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage); }; /** @@ -182,135 +180,129 @@ private: * flattened into a binary resource table (resources.arsc). */ class ResourceTable { -public: - ResourceTable() = default; - - /** - * When a collision of resources occurs, this method decides which value to keep. - * Returns -1 if the existing value should be chosen. - * Returns 0 if the collision can not be resolved (error). - * Returns 1 if the incoming value should be chosen. - */ - static int resolveValueCollision(Value* existing, Value* incoming); - - bool addResource(const ResourceNameRef& name, - const ConfigDescription& config, - const StringPiece& product, - std::unique_ptr<Value> value, - IDiagnostics* diag); - - bool addResource(const ResourceNameRef& name, - const ResourceId& resId, - const ConfigDescription& config, - const StringPiece& product, - std::unique_ptr<Value> value, - IDiagnostics* diag); - - bool addFileReference(const ResourceNameRef& name, - const ConfigDescription& config, - const Source& source, - const StringPiece16& path, - IDiagnostics* diag); - - bool addFileReferenceAllowMangled(const ResourceNameRef& name, - const ConfigDescription& config, - const Source& source, - const StringPiece16& path, - io::IFile* file, - IDiagnostics* diag); - - /** - * Same as addResource, but doesn't verify the validity of the name. This is used - * when loading resources from an existing binary resource table that may have mangled - * names. - */ - bool addResourceAllowMangled(const ResourceNameRef& name, - const ConfigDescription& config, - const StringPiece& product, - std::unique_ptr<Value> value, - IDiagnostics* diag); - - bool addResourceAllowMangled(const ResourceNameRef& name, - const ResourceId& id, - const ConfigDescription& config, - const StringPiece& product, - std::unique_ptr<Value> value, - IDiagnostics* diag); - - bool setSymbolState(const ResourceNameRef& name, - const ResourceId& resId, - const Symbol& symbol, - IDiagnostics* diag); - - bool setSymbolStateAllowMangled(const ResourceNameRef& name, - const ResourceId& resId, - const Symbol& symbol, + public: + ResourceTable() = default; + + enum class CollisionResult { kKeepOriginal, kConflict, kTakeNew }; + + using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>; + + /** + * When a collision of resources occurs, this method decides which value to + * keep. + */ + static CollisionResult ResolveValueCollision(Value* existing, + Value* incoming); + + bool AddResource(const ResourceNameRef& name, const ConfigDescription& config, + const StringPiece& product, std::unique_ptr<Value> value, + IDiagnostics* diag); + + bool AddResource(const ResourceNameRef& name, const ResourceId& res_id, + const ConfigDescription& config, const StringPiece& product, + std::unique_ptr<Value> value, IDiagnostics* diag); + + bool AddFileReference(const ResourceNameRef& name, + const ConfigDescription& config, const Source& source, + const StringPiece& path, IDiagnostics* diag); + + bool AddFileReferenceAllowMangled(const ResourceNameRef& name, + const ConfigDescription& config, + const Source& source, + const StringPiece& path, io::IFile* file, IDiagnostics* diag); - struct SearchResult { - ResourceTablePackage* package; - ResourceTableType* type; - ResourceEntry* entry; - }; - - Maybe<SearchResult> findResource(const ResourceNameRef& name); - - /** - * The string pool used by this resource table. Values that reference strings must use - * this pool to create their strings. - * - * NOTE: `stringPool` must come before `packages` so that it is destroyed after. - * When `string pool` references are destroyed (as they will be when `packages` - * is destroyed), they decrement a refCount, which would cause invalid - * memory access if the pool was already destroyed. - */ - StringPool stringPool; - - /** - * The list of packages in this table, sorted alphabetically by package name. - */ - std::vector<std::unique_ptr<ResourceTablePackage>> packages; - - /** - * Returns the package struct with the given name, or nullptr if such a package does not - * exist. The empty string is a valid package and typically is used to represent the - * 'current' package before it is known to the ResourceTable. - */ - ResourceTablePackage* findPackage(const StringPiece16& name); - - ResourceTablePackage* findPackageById(uint8_t id); - - ResourceTablePackage* createPackage(const StringPiece16& name, Maybe<uint8_t> id = {}); - -private: - ResourceTablePackage* findOrCreatePackage(const StringPiece16& name); - - bool addFileReferenceImpl(const ResourceNameRef& name, - const ConfigDescription& config, - const Source& source, - const StringPiece16& path, - io::IFile* file, - const char16_t* validChars, - IDiagnostics* diag); - - bool addResourceImpl(const ResourceNameRef& name, - const ResourceId& resId, - const ConfigDescription& config, - const StringPiece& product, - std::unique_ptr<Value> value, - const char16_t* validChars, - std::function<int(Value*,Value*)> conflictResolver, - IDiagnostics* diag); - - bool setSymbolStateImpl(const ResourceNameRef& name, - const ResourceId& resId, - const Symbol& symbol, - const char16_t* validChars, + /** + * Same as AddResource, but doesn't verify the validity of the name. This is + * used + * when loading resources from an existing binary resource table that may have + * mangled + * names. + */ + bool AddResourceAllowMangled(const ResourceNameRef& name, + const ConfigDescription& config, + const StringPiece& product, + std::unique_ptr<Value> value, + IDiagnostics* diag); + + bool AddResourceAllowMangled(const ResourceNameRef& name, + const ResourceId& id, + const ConfigDescription& config, + const StringPiece& product, + std::unique_ptr<Value> value, + IDiagnostics* diag); + + bool SetSymbolState(const ResourceNameRef& name, const ResourceId& res_id, + const Symbol& symbol, IDiagnostics* diag); + + bool SetSymbolStateAllowMangled(const ResourceNameRef& name, + const ResourceId& res_id, + const Symbol& symbol, IDiagnostics* diag); + + struct SearchResult { + ResourceTablePackage* package; + ResourceTableType* type; + ResourceEntry* entry; + }; + + Maybe<SearchResult> FindResource(const ResourceNameRef& name); + + /** + * The string pool used by this resource table. Values that reference strings + * must use + * this pool to create their strings. + * + * NOTE: `string_pool` must come before `packages` so that it is destroyed + * after. + * When `string_pool` references are destroyed (as they will be when + * `packages` + * is destroyed), they decrement a refCount, which would cause invalid + * memory access if the pool was already destroyed. + */ + StringPool string_pool; + + /** + * The list of packages in this table, sorted alphabetically by package name. + */ + std::vector<std::unique_ptr<ResourceTablePackage>> packages; + + /** + * Returns the package struct with the given name, or nullptr if such a + * package does not + * exist. The empty string is a valid package and typically is used to + * represent the + * 'current' package before it is known to the ResourceTable. + */ + ResourceTablePackage* FindPackage(const StringPiece& name); + + ResourceTablePackage* FindPackageById(uint8_t id); + + ResourceTablePackage* CreatePackage(const StringPiece& name, + Maybe<uint8_t> id = {}); + + private: + ResourceTablePackage* FindOrCreatePackage(const StringPiece& name); + + bool AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id, + const ConfigDescription& config, + const StringPiece& product, std::unique_ptr<Value> value, + const char* valid_chars, + const CollisionResolverFunc& conflict_resolver, + IDiagnostics* diag); + + bool AddFileReferenceImpl(const ResourceNameRef& name, + const ConfigDescription& config, + const Source& source, const StringPiece& path, + io::IFile* file, const char* valid_chars, IDiagnostics* diag); - DISALLOW_COPY_AND_ASSIGN(ResourceTable); + bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id, + const Symbol& symbol, const char* valid_chars, + IDiagnostics* diag); + + DISALLOW_COPY_AND_ASSIGN(ResourceTable); }; -} // namespace aapt +} // namespace aapt -#endif // AAPT_RESOURCE_TABLE_H +#endif // AAPT_RESOURCE_TABLE_H diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index d6c52ab83d93..cb3699a00f75 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -14,140 +14,126 @@ * limitations under the License. */ -#include "Diagnostics.h" #include "ResourceTable.h" +#include "Diagnostics.h" #include "ResourceValues.h" +#include "test/Test.h" #include "util/Util.h" -#include "test/Builders.h" - #include <algorithm> -#include <gtest/gtest.h> #include <ostream> #include <string> namespace aapt { TEST(ResourceTableTest, FailToAddResourceWithBadName) { - ResourceTable table; - - EXPECT_FALSE(table.addResource( - ResourceNameRef(u"android", ResourceType::kId, u"hey,there"), - ConfigDescription{}, "", - test::ValueBuilder<Id>().setSource("test.xml", 21u).build(), - test::getDiagnostics())); - - EXPECT_FALSE(table.addResource( - ResourceNameRef(u"android", ResourceType::kId, u"hey:there"), - ConfigDescription{}, "", - test::ValueBuilder<Id>().setSource("test.xml", 21u).build(), - test::getDiagnostics())); + ResourceTable table; + + EXPECT_FALSE(table.AddResource( + test::ParseNameOrDie("android:id/hey,there"), ConfigDescription{}, "", + test::ValueBuilder<Id>().SetSource("test.xml", 21u).Build(), + test::GetDiagnostics())); + + EXPECT_FALSE(table.AddResource( + test::ParseNameOrDie("android:id/hey:there"), ConfigDescription{}, "", + test::ValueBuilder<Id>().SetSource("test.xml", 21u).Build(), + test::GetDiagnostics())); } TEST(ResourceTableTest, AddOneResource) { - ResourceTable table; + ResourceTable table; - EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"), - ConfigDescription{}, - "", - test::ValueBuilder<Id>() - .setSource("test/path/file.xml", 23u).build(), - test::getDiagnostics())); + EXPECT_TRUE(table.AddResource( + test::ParseNameOrDie("android:attr/id"), ConfigDescription{}, "", + test::ValueBuilder<Id>().SetSource("test/path/file.xml", 23u).Build(), + test::GetDiagnostics())); - ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id")); + ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:attr/id")); } TEST(ResourceTableTest, AddMultipleResources) { - ResourceTable table; - - ConfigDescription config; - ConfigDescription languageConfig; - memcpy(languageConfig.language, "pl", sizeof(languageConfig.language)); - - EXPECT_TRUE(table.addResource( - test::parseNameOrDie(u"@android:attr/layout_width"), - config, - "", - test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(), - test::getDiagnostics())); - - EXPECT_TRUE(table.addResource( - test::parseNameOrDie(u"@android:attr/id"), - config, - "", - test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(), - test::getDiagnostics())); - - EXPECT_TRUE(table.addResource( - test::parseNameOrDie(u"@android:string/ok"), - config, - "", - test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(), - test::getDiagnostics())); - - EXPECT_TRUE(table.addResource( - test::parseNameOrDie(u"@android:string/ok"), - languageConfig, - "", - test::ValueBuilder<BinaryPrimitive>(android::Res_value{}) - .setSource("test/path/file.xml", 20u) - .build(), - test::getDiagnostics())); - - ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/layout_width")); - ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id")); - ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:string/ok")); - ASSERT_NE(nullptr, test::getValueForConfig<BinaryPrimitive>(&table, u"@android:string/ok", - languageConfig)); + ResourceTable table; + + ConfigDescription config; + ConfigDescription language_config; + memcpy(language_config.language, "pl", sizeof(language_config.language)); + + EXPECT_TRUE(table.AddResource( + test::ParseNameOrDie("android:attr/layout_width"), config, "", + test::ValueBuilder<Id>().SetSource("test/path/file.xml", 10u).Build(), + test::GetDiagnostics())); + + EXPECT_TRUE(table.AddResource( + test::ParseNameOrDie("android:attr/id"), config, "", + test::ValueBuilder<Id>().SetSource("test/path/file.xml", 12u).Build(), + test::GetDiagnostics())); + + EXPECT_TRUE(table.AddResource( + test::ParseNameOrDie("android:string/ok"), config, "", + test::ValueBuilder<Id>().SetSource("test/path/file.xml", 14u).Build(), + test::GetDiagnostics())); + + EXPECT_TRUE(table.AddResource( + test::ParseNameOrDie("android:string/ok"), language_config, "", + test::ValueBuilder<BinaryPrimitive>(android::Res_value{}) + .SetSource("test/path/file.xml", 20u) + .Build(), + test::GetDiagnostics())); + + ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:attr/layout_width")); + ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:attr/id")); + ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:string/ok")); + ASSERT_NE(nullptr, test::GetValueForConfig<BinaryPrimitive>( + &table, "android:string/ok", language_config)); } TEST(ResourceTableTest, OverrideWeakResourceValue) { - ResourceTable table; + ResourceTable table; - ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{}, - "", util::make_unique<Attribute>(true), test::getDiagnostics())); + ASSERT_TRUE(table.AddResource( + test::ParseNameOrDie("android:attr/foo"), ConfigDescription{}, "", + util::make_unique<Attribute>(true), test::GetDiagnostics())); - Attribute* attr = test::getValue<Attribute>(&table, u"@android:attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_TRUE(attr->isWeak()); + Attribute* attr = test::GetValue<Attribute>(&table, "android:attr/foo"); + ASSERT_NE(nullptr, attr); + EXPECT_TRUE(attr->IsWeak()); - ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{}, - "", util::make_unique<Attribute>(false), test::getDiagnostics())); + ASSERT_TRUE(table.AddResource( + test::ParseNameOrDie("android:attr/foo"), ConfigDescription{}, "", + util::make_unique<Attribute>(false), test::GetDiagnostics())); - attr = test::getValue<Attribute>(&table, u"@android:attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_FALSE(attr->isWeak()); + attr = test::GetValue<Attribute>(&table, "android:attr/foo"); + ASSERT_NE(nullptr, attr); + EXPECT_FALSE(attr->IsWeak()); } TEST(ResourceTableTest, ProductVaryingValues) { - ResourceTable table; - - EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"), - test::parseConfigOrDie("land"), - "tablet", - util::make_unique<Id>(), - test::getDiagnostics())); - EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"), - test::parseConfigOrDie("land"), - "phone", - util::make_unique<Id>(), - test::getDiagnostics())); - - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/foo", - test::parseConfigOrDie("land"), - "tablet")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/foo", - test::parseConfigOrDie("land"), - "phone")); - - Maybe<ResourceTable::SearchResult> sr = table.findResource( - test::parseNameOrDie(u"@android:string/foo")); - AAPT_ASSERT_TRUE(sr); - std::vector<ResourceConfigValue*> values = sr.value().entry->findAllValues( - test::parseConfigOrDie("land")); - ASSERT_EQ(2u, values.size()); - EXPECT_EQ(std::string("phone"), values[0]->product); - EXPECT_EQ(std::string("tablet"), values[1]->product); + ResourceTable table; + + EXPECT_TRUE(table.AddResource(test::ParseNameOrDie("android:string/foo"), + test::ParseConfigOrDie("land"), "tablet", + util::make_unique<Id>(), + test::GetDiagnostics())); + EXPECT_TRUE(table.AddResource(test::ParseNameOrDie("android:string/foo"), + test::ParseConfigOrDie("land"), "phone", + util::make_unique<Id>(), + test::GetDiagnostics())); + + EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>( + &table, "android:string/foo", + test::ParseConfigOrDie("land"), "tablet")); + EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>( + &table, "android:string/foo", + test::ParseConfigOrDie("land"), "phone")); + + Maybe<ResourceTable::SearchResult> sr = + table.FindResource(test::ParseNameOrDie("android:string/foo")); + AAPT_ASSERT_TRUE(sr); + std::vector<ResourceConfigValue*> values = + sr.value().entry->findAllValues(test::ParseConfigOrDie("land")); + ASSERT_EQ(2u, values.size()); + EXPECT_EQ(std::string("phone"), values[0]->product); + EXPECT_EQ(std::string("tablet"), values[1]->product); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index a0a7efc46476..fce9b338d726 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -14,167 +14,211 @@ * limitations under the License. */ -#include "NameMangler.h" #include "ResourceUtils.h" + +#include <sstream> + +#include "androidfw/ResourceTypes.h" + +#include "NameMangler.h" +#include "SdkConstants.h" #include "flatten/ResourceTypeExtensions.h" #include "util/Files.h" #include "util/Util.h" -#include <androidfw/ResourceTypes.h> -#include <sstream> - namespace aapt { namespace ResourceUtils { -bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage, - StringPiece16* outType, StringPiece16* outEntry) { - bool hasPackageSeparator = false; - bool hasTypeSeparator = false; - const char16_t* start = str.data(); - const char16_t* end = start + str.size(); - const char16_t* current = start; - while (current != end) { - if (outType->size() == 0 && *current == u'/') { - hasTypeSeparator = true; - outType->assign(start, current - start); - start = current + 1; - } else if (outPackage->size() == 0 && *current == u':') { - hasPackageSeparator = true; - outPackage->assign(start, current - start); - start = current + 1; - } - current++; - } - outEntry->assign(start, end - start); +Maybe<ResourceName> ToResourceName( + const android::ResTable::resource_name& name_in) { + ResourceName name_out; + if (!name_in.package) { + return {}; + } + + name_out.package = + util::Utf16ToUtf8(StringPiece16(name_in.package, name_in.packageLen)); + + const ResourceType* type; + if (name_in.type) { + type = ParseResourceType( + util::Utf16ToUtf8(StringPiece16(name_in.type, name_in.typeLen))); + } else if (name_in.type8) { + type = ParseResourceType(StringPiece(name_in.type8, name_in.typeLen)); + } else { + return {}; + } + + if (!type) { + return {}; + } - return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty()); + name_out.type = *type; + + if (name_in.name) { + name_out.entry = + util::Utf16ToUtf8(StringPiece16(name_in.name, name_in.nameLen)); + } else if (name_in.name8) { + name_out.entry = StringPiece(name_in.name8, name_in.nameLen).ToString(); + } else { + return {}; + } + return name_out; +} + +bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, + StringPiece* out_type, StringPiece* out_entry) { + bool has_package_separator = false; + bool has_type_separator = false; + const char* start = str.data(); + const char* end = start + str.size(); + const char* current = start; + while (current != end) { + if (out_type->size() == 0 && *current == '/') { + has_type_separator = true; + out_type->assign(start, current - start); + start = current + 1; + } else if (out_package->size() == 0 && *current == ':') { + has_package_separator = true; + out_package->assign(start, current - start); + start = current + 1; + } + current++; + } + out_entry->assign(start, end - start); + + return !(has_package_separator && out_package->empty()) && + !(has_type_separator && out_type->empty()); } -bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) { - if (str.empty()) { - return false; +bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_ref, + bool* out_private) { + if (str.empty()) { + return false; + } + + size_t offset = 0; + bool priv = false; + if (str.data()[0] == '*') { + priv = true; + offset = 1; + } + + StringPiece package; + StringPiece type; + StringPiece entry; + if (!ExtractResourceName(str.substr(offset, str.size() - offset), &package, + &type, &entry)) { + return false; + } + + const ResourceType* parsed_type = ParseResourceType(type); + if (!parsed_type) { + return false; + } + + if (entry.empty()) { + return false; + } + + if (out_ref) { + out_ref->package = package; + out_ref->type = *parsed_type; + out_ref->entry = entry; + } + + if (out_private) { + *out_private = priv; + } + return true; +} + +bool ParseReference(const StringPiece& str, ResourceNameRef* out_ref, + bool* out_create, bool* out_private) { + StringPiece trimmed_str(util::TrimWhitespace(str)); + if (trimmed_str.empty()) { + return false; + } + + bool create = false; + bool priv = false; + if (trimmed_str.data()[0] == '@') { + size_t offset = 1; + if (trimmed_str.data()[1] == '+') { + create = true; + offset += 1; } - size_t offset = 0; - bool priv = false; - if (str.data()[0] == u'*') { - priv = true; - offset = 1; + ResourceNameRef name; + if (!ParseResourceName( + trimmed_str.substr(offset, trimmed_str.size() - offset), &name, + &priv)) { + return false; } - StringPiece16 package; - StringPiece16 type; - StringPiece16 entry; - if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) { - return false; + if (create && priv) { + return false; } - const ResourceType* parsedType = parseResourceType(type); - if (!parsedType) { - return false; + if (create && name.type != ResourceType::kId) { + return false; } - if (entry.empty()) { - return false; + if (out_ref) { + *out_ref = name; } - if (outRef) { - outRef->package = package; - outRef->type = *parsedType; - outRef->entry = entry; + if (out_create) { + *out_create = create; } - if (outPrivate) { - *outPrivate = priv; + if (out_private) { + *out_private = priv; } return true; + } + return false; } -bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate, - bool* outPrivate) { - StringPiece16 trimmedStr(util::trimWhitespace(str)); - if (trimmedStr.empty()) { - return false; - } +bool IsReference(const StringPiece& str) { + return ParseReference(str, nullptr, nullptr, nullptr); +} - bool create = false; - bool priv = false; - if (trimmedStr.data()[0] == u'@') { - size_t offset = 1; - if (trimmedStr.data()[1] == u'+') { - create = true; - offset += 1; - } - - ResourceNameRef name; - if (!parseResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), - &name, &priv)) { - return false; - } - - if (create && priv) { - return false; - } - - if (create && name.type != ResourceType::kId) { - return false; - } - - if (outRef) { - *outRef = name; - } - - if (outCreate) { - *outCreate = create; - } - - if (outPrivate) { - *outPrivate = priv; - } - return true; - } +bool ParseAttributeReference(const StringPiece& str, ResourceNameRef* out_ref) { + StringPiece trimmed_str(util::TrimWhitespace(str)); + if (trimmed_str.empty()) { return false; -} + } -bool isReference(const StringPiece16& str) { - return tryParseReference(str, nullptr, nullptr, nullptr); -} + if (*trimmed_str.data() == '?') { + StringPiece package; + StringPiece type; + StringPiece entry; + if (!ExtractResourceName(trimmed_str.substr(1, trimmed_str.size() - 1), + &package, &type, &entry)) { + return false; + } -bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) { - StringPiece16 trimmedStr(util::trimWhitespace(str)); - if (trimmedStr.empty()) { - return false; + if (!type.empty() && type != "attr") { + return false; } - if (*trimmedStr.data() == u'?') { - StringPiece16 package; - StringPiece16 type; - StringPiece16 entry; - if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), - &package, &type, &entry)) { - return false; - } - - if (!type.empty() && type != u"attr") { - return false; - } - - if (entry.empty()) { - return false; - } - - if (outRef) { - outRef->package = package; - outRef->type = ResourceType::kAttr; - outRef->entry = entry; - } - return true; + if (entry.empty()) { + return false; } - return false; + + if (out_ref) { + out_ref->package = package; + out_ref->type = ResourceType::kAttr; + out_ref->entry = entry; + } + return true; + } + return false; } -bool isAttributeReference(const StringPiece16& str) { - return tryParseAttributeReference(str, nullptr); +bool IsAttributeReference(const StringPiece& str) { + return ParseAttributeReference(str, nullptr); } /* @@ -185,396 +229,473 @@ bool isAttributeReference(const StringPiece16& str) { * <[*]package>:[style/]<entry> * [[*]package:style/]<entry> */ -Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) { - if (str.empty()) { - return {}; - } +Maybe<Reference> ParseStyleParentReference(const StringPiece& str, + std::string* out_error) { + if (str.empty()) { + return {}; + } + + StringPiece name = str; + + bool has_leading_identifiers = false; + bool private_ref = false; + + // Skip over these identifiers. A style's parent is a normal reference. + if (name.data()[0] == '@' || name.data()[0] == '?') { + has_leading_identifiers = true; + name = name.substr(1, name.size() - 1); + } + + if (name.data()[0] == '*') { + private_ref = true; + name = name.substr(1, name.size() - 1); + } + + ResourceNameRef ref; + ref.type = ResourceType::kStyle; + + StringPiece type_str; + ExtractResourceName(name, &ref.package, &type_str, &ref.entry); + if (!type_str.empty()) { + // If we have a type, make sure it is a Style. + const ResourceType* parsed_type = ParseResourceType(type_str); + if (!parsed_type || *parsed_type != ResourceType::kStyle) { + std::stringstream err; + err << "invalid resource type '" << type_str << "' for parent of style"; + *out_error = err.str(); + return {}; + } + } + + if (!has_leading_identifiers && ref.package.empty() && !type_str.empty()) { + std::stringstream err; + err << "invalid parent reference '" << str << "'"; + *out_error = err.str(); + return {}; + } - StringPiece16 name = str; + Reference result(ref); + result.private_reference = private_ref; + return result; +} - bool hasLeadingIdentifiers = false; - bool privateRef = false; +Maybe<Reference> ParseXmlAttributeName(const StringPiece& str) { + StringPiece trimmed_str = util::TrimWhitespace(str); + const char* start = trimmed_str.data(); + const char* const end = start + trimmed_str.size(); + const char* p = start; + + Reference ref; + if (p != end && *p == '*') { + ref.private_reference = true; + start++; + p++; + } + + StringPiece package; + StringPiece name; + while (p != end) { + if (*p == ':') { + package = StringPiece(start, p - start); + name = StringPiece(p + 1, end - (p + 1)); + break; + } + p++; + } + + ref.name = + ResourceName(package.ToString(), ResourceType::kAttr, + name.empty() ? trimmed_str.ToString() : name.ToString()); + return Maybe<Reference>(std::move(ref)); +} - // Skip over these identifiers. A style's parent is a normal reference. - if (name.data()[0] == u'@' || name.data()[0] == u'?') { - hasLeadingIdentifiers = true; - name = name.substr(1, name.size() - 1); - } +std::unique_ptr<Reference> TryParseReference(const StringPiece& str, + bool* out_create) { + ResourceNameRef ref; + bool private_ref = false; + if (ParseReference(str, &ref, out_create, &private_ref)) { + std::unique_ptr<Reference> value = util::make_unique<Reference>(ref); + value->private_reference = private_ref; + return value; + } + + if (ParseAttributeReference(str, &ref)) { + if (out_create) { + *out_create = false; + } + return util::make_unique<Reference>(ref, Reference::Type::kAttribute); + } + return {}; +} - if (name.data()[0] == u'*') { - privateRef = true; - name = name.substr(1, name.size() - 1); - } +std::unique_ptr<BinaryPrimitive> TryParseNullOrEmpty(const StringPiece& str) { + StringPiece trimmed_str(util::TrimWhitespace(str)); + android::Res_value value = {}; + if (trimmed_str == "@null") { + // TYPE_NULL with data set to 0 is interpreted by the runtime as an error. + // Instead we set the data type to TYPE_REFERENCE with a value of 0. + value.dataType = android::Res_value::TYPE_REFERENCE; + } else if (trimmed_str == "@empty") { + // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime. + value.dataType = android::Res_value::TYPE_NULL; + value.data = android::Res_value::DATA_NULL_EMPTY; + } else { + return {}; + } + return util::make_unique<BinaryPrimitive>(value); +} - ResourceNameRef ref; - ref.type = ResourceType::kStyle; - - StringPiece16 typeStr; - extractResourceName(name, &ref.package, &typeStr, &ref.entry); - if (!typeStr.empty()) { - // If we have a type, make sure it is a Style. - const ResourceType* parsedType = parseResourceType(typeStr); - if (!parsedType || *parsedType != ResourceType::kStyle) { - std::stringstream err; - err << "invalid resource type '" << typeStr << "' for parent of style"; - *outError = err.str(); - return {}; - } - } +std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, + const StringPiece& str) { + StringPiece trimmed_str(util::TrimWhitespace(str)); + for (const Attribute::Symbol& symbol : enum_attr->symbols) { + // Enum symbols are stored as @package:id/symbol resources, + // so we need to match against the 'entry' part of the identifier. + const ResourceName& enum_symbol_resource_name = symbol.symbol.name.value(); + if (trimmed_str == enum_symbol_resource_name.entry) { + android::Res_value value = {}; + value.dataType = android::Res_value::TYPE_INT_DEC; + value.data = symbol.value; + return util::make_unique<BinaryPrimitive>(value); + } + } + return {}; +} - if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) { - std::stringstream err; - err << "invalid parent reference '" << str << "'"; - *outError = err.str(); - return {}; - } +std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr, + const StringPiece& str) { + android::Res_value flags = {}; + flags.dataType = android::Res_value::TYPE_INT_HEX; + flags.data = 0u; - Reference result(ref); - result.privateReference = privateRef; - return result; -} + if (util::TrimWhitespace(str).empty()) { + // Empty string is a valid flag (0). + return util::make_unique<BinaryPrimitive>(flags); + } -std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate) { - ResourceNameRef ref; - bool privateRef = false; - if (tryParseReference(str, &ref, outCreate, &privateRef)) { - std::unique_ptr<Reference> value = util::make_unique<Reference>(ref); - value->privateReference = privateRef; - return value; + for (StringPiece part : util::Tokenize(str, '|')) { + StringPiece trimmed_part = util::TrimWhitespace(part); + + bool flag_set = false; + for (const Attribute::Symbol& symbol : flag_attr->symbols) { + // Flag symbols are stored as @package:id/symbol resources, + // so we need to match against the 'entry' part of the identifier. + const ResourceName& flag_symbol_resource_name = + symbol.symbol.name.value(); + if (trimmed_part == flag_symbol_resource_name.entry) { + flags.data |= symbol.value; + flag_set = true; + break; + } } - if (tryParseAttributeReference(str, &ref)) { - if (outCreate) { - *outCreate = false; - } - return util::make_unique<Reference>(ref, Reference::Type::kAttribute); + if (!flag_set) { + return {}; } - return {}; + } + return util::make_unique<BinaryPrimitive>(flags); } -std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str) { - StringPiece16 trimmedStr(util::trimWhitespace(str)); - android::Res_value value = { }; - if (trimmedStr == u"@null") { - // TYPE_NULL with data set to 0 is interpreted by the runtime as an error. - // Instead we set the data type to TYPE_REFERENCE with a value of 0. - value.dataType = android::Res_value::TYPE_REFERENCE; - } else if (trimmedStr == u"@empty") { - // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime. - value.dataType = android::Res_value::TYPE_NULL; - value.data = android::Res_value::DATA_NULL_EMPTY; - } else { - return {}; - } - return util::make_unique<BinaryPrimitive>(value); +static uint32_t ParseHex(char c, bool* out_error) { + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'a' && c <= 'f') { + return c - 'a' + 0xa; + } else if (c >= 'A' && c <= 'F') { + return c - 'A' + 0xa; + } else { + *out_error = true; + return 0xffffffffu; + } } -std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr, - const StringPiece16& str) { - StringPiece16 trimmedStr(util::trimWhitespace(str)); - for (const Attribute::Symbol& symbol : enumAttr->symbols) { - // Enum symbols are stored as @package:id/symbol resources, - // so we need to match against the 'entry' part of the identifier. - const ResourceName& enumSymbolResourceName = symbol.symbol.name.value(); - if (trimmedStr == enumSymbolResourceName.entry) { - android::Res_value value = { }; - value.dataType = android::Res_value::TYPE_INT_DEC; - value.data = symbol.value; - return util::make_unique<BinaryPrimitive>(value); - } - } +std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str) { + StringPiece color_str(util::TrimWhitespace(str)); + const char* start = color_str.data(); + const size_t len = color_str.size(); + if (len == 0 || start[0] != '#') { + return {}; + } + + android::Res_value value = {}; + bool error = false; + if (len == 4) { + value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4; + value.data = 0xff000000u; + value.data |= ParseHex(start[1], &error) << 20; + value.data |= ParseHex(start[1], &error) << 16; + value.data |= ParseHex(start[2], &error) << 12; + value.data |= ParseHex(start[2], &error) << 8; + value.data |= ParseHex(start[3], &error) << 4; + value.data |= ParseHex(start[3], &error); + } else if (len == 5) { + value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4; + value.data |= ParseHex(start[1], &error) << 28; + value.data |= ParseHex(start[1], &error) << 24; + value.data |= ParseHex(start[2], &error) << 20; + value.data |= ParseHex(start[2], &error) << 16; + value.data |= ParseHex(start[3], &error) << 12; + value.data |= ParseHex(start[3], &error) << 8; + value.data |= ParseHex(start[4], &error) << 4; + value.data |= ParseHex(start[4], &error); + } else if (len == 7) { + value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8; + value.data = 0xff000000u; + value.data |= ParseHex(start[1], &error) << 20; + value.data |= ParseHex(start[2], &error) << 16; + value.data |= ParseHex(start[3], &error) << 12; + value.data |= ParseHex(start[4], &error) << 8; + value.data |= ParseHex(start[5], &error) << 4; + value.data |= ParseHex(start[6], &error); + } else if (len == 9) { + value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8; + value.data |= ParseHex(start[1], &error) << 28; + value.data |= ParseHex(start[2], &error) << 24; + value.data |= ParseHex(start[3], &error) << 20; + value.data |= ParseHex(start[4], &error) << 16; + value.data |= ParseHex(start[5], &error) << 12; + value.data |= ParseHex(start[6], &error) << 8; + value.data |= ParseHex(start[7], &error) << 4; + value.data |= ParseHex(start[8], &error); + } else { return {}; + } + return error ? std::unique_ptr<BinaryPrimitive>() + : util::make_unique<BinaryPrimitive>(value); } -std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr, - const StringPiece16& str) { - android::Res_value flags = { }; - flags.dataType = android::Res_value::TYPE_INT_HEX; - flags.data = 0u; - - if (util::trimWhitespace(str).empty()) { - // Empty string is a valid flag (0). - return util::make_unique<BinaryPrimitive>(flags); - } - - for (StringPiece16 part : util::tokenize(str, u'|')) { - StringPiece16 trimmedPart = util::trimWhitespace(part); - - bool flagSet = false; - for (const Attribute::Symbol& symbol : flagAttr->symbols) { - // Flag symbols are stored as @package:id/symbol resources, - // so we need to match against the 'entry' part of the identifier. - const ResourceName& flagSymbolResourceName = symbol.symbol.name.value(); - if (trimmedPart == flagSymbolResourceName.entry) { - flags.data |= symbol.value; - flagSet = true; - break; - } - } - - if (!flagSet) { - return {}; - } - } - return util::make_unique<BinaryPrimitive>(flags); +Maybe<bool> ParseBool(const StringPiece& str) { + StringPiece trimmed_str(util::TrimWhitespace(str)); + if (trimmed_str == "true" || trimmed_str == "TRUE" || trimmed_str == "True") { + return Maybe<bool>(true); + } else if (trimmed_str == "false" || trimmed_str == "FALSE" || + trimmed_str == "False") { + return Maybe<bool>(false); + } + return {}; } -static uint32_t parseHex(char16_t c, bool* outError) { - if (c >= u'0' && c <= u'9') { - return c - u'0'; - } else if (c >= u'a' && c <= u'f') { - return c - u'a' + 0xa; - } else if (c >= u'A' && c <= u'F') { - return c - u'A' + 0xa; - } else { - *outError = true; - return 0xffffffffu; - } +Maybe<uint32_t> ParseInt(const StringPiece& str) { + std::u16string str16 = util::Utf8ToUtf16(str); + android::Res_value value; + if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { + return value.data; + } + return {}; } -std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str) { - StringPiece16 colorStr(util::trimWhitespace(str)); - const char16_t* start = colorStr.data(); - const size_t len = colorStr.size(); - if (len == 0 || start[0] != u'#') { - return {}; - } +Maybe<ResourceId> ParseResourceId(const StringPiece& str) { + StringPiece trimmed_str(util::TrimWhitespace(str)); - android::Res_value value = { }; - bool error = false; - if (len == 4) { - value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4; - value.data = 0xff000000u; - value.data |= parseHex(start[1], &error) << 20; - value.data |= parseHex(start[1], &error) << 16; - value.data |= parseHex(start[2], &error) << 12; - value.data |= parseHex(start[2], &error) << 8; - value.data |= parseHex(start[3], &error) << 4; - value.data |= parseHex(start[3], &error); - } else if (len == 5) { - value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4; - value.data |= parseHex(start[1], &error) << 28; - value.data |= parseHex(start[1], &error) << 24; - value.data |= parseHex(start[2], &error) << 20; - value.data |= parseHex(start[2], &error) << 16; - value.data |= parseHex(start[3], &error) << 12; - value.data |= parseHex(start[3], &error) << 8; - value.data |= parseHex(start[4], &error) << 4; - value.data |= parseHex(start[4], &error); - } else if (len == 7) { - value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8; - value.data = 0xff000000u; - value.data |= parseHex(start[1], &error) << 20; - value.data |= parseHex(start[2], &error) << 16; - value.data |= parseHex(start[3], &error) << 12; - value.data |= parseHex(start[4], &error) << 8; - value.data |= parseHex(start[5], &error) << 4; - value.data |= parseHex(start[6], &error); - } else if (len == 9) { - value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8; - value.data |= parseHex(start[1], &error) << 28; - value.data |= parseHex(start[2], &error) << 24; - value.data |= parseHex(start[3], &error) << 20; - value.data |= parseHex(start[4], &error) << 16; - value.data |= parseHex(start[5], &error) << 12; - value.data |= parseHex(start[6], &error) << 8; - value.data |= parseHex(start[7], &error) << 4; - value.data |= parseHex(start[8], &error); - } else { - return {}; + std::u16string str16 = util::Utf8ToUtf16(trimmed_str); + android::Res_value value; + if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { + if (value.dataType == android::Res_value::TYPE_INT_HEX) { + ResourceId id(value.data); + if (id.is_valid()) { + return id; + } } - return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value); + } + return {}; } -bool tryParseBool(const StringPiece16& str, bool* outValue) { - StringPiece16 trimmedStr(util::trimWhitespace(str)); - if (trimmedStr == u"true" || trimmedStr == u"TRUE" || trimmedStr == u"True") { - if (outValue) { - *outValue = true; - } - return true; - } else if (trimmedStr == u"false" || trimmedStr == u"FALSE" || trimmedStr == u"False") { - if (outValue) { - *outValue = false; - } - return true; - } - return false; +Maybe<int> ParseSdkVersion(const StringPiece& str) { + StringPiece trimmed_str(util::TrimWhitespace(str)); + + std::u16string str16 = util::Utf8ToUtf16(trimmed_str); + android::Res_value value; + if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { + return static_cast<int>(value.data); + } + + // Try parsing the code name. + std::pair<StringPiece, int> entry = GetDevelopmentSdkCodeNameAndVersion(); + if (entry.first == trimmed_str) { + return entry.second; + } + return {}; } -std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) { - bool result = false; - if (tryParseBool(str, &result)) { - android::Res_value value = {}; - value.dataType = android::Res_value::TYPE_INT_BOOLEAN; - - if (result) { - value.data = 0xffffffffu; - } else { - value.data = 0; - } - return util::make_unique<BinaryPrimitive>(value); - } - return {}; -} +std::unique_ptr<BinaryPrimitive> TryParseBool(const StringPiece& str) { + if (Maybe<bool> maybe_result = ParseBool(str)) { + android::Res_value value = {}; + value.dataType = android::Res_value::TYPE_INT_BOOLEAN; -std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str) { - android::Res_value value; - if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) { - return {}; + if (maybe_result.value()) { + value.data = 0xffffffffu; + } else { + value.data = 0; } return util::make_unique<BinaryPrimitive>(value); + } + return {}; } -std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str) { - android::Res_value value; - if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) { - return {}; - } - return util::make_unique<BinaryPrimitive>(value); +std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str) { + std::u16string str16 = util::Utf8ToUtf16(str); + android::Res_value value; + if (!android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { + return {}; + } + return util::make_unique<BinaryPrimitive>(value); +} + +std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str) { + std::u16string str16 = util::Utf8ToUtf16(str); + android::Res_value value; + if (!android::ResTable::stringToFloat(str16.data(), str16.size(), &value)) { + return {}; + } + return util::make_unique<BinaryPrimitive>(value); } -uint32_t androidTypeToAttributeTypeMask(uint16_t type) { - switch (type) { +uint32_t AndroidTypeToAttributeTypeMask(uint16_t type) { + switch (type) { case android::Res_value::TYPE_NULL: case android::Res_value::TYPE_REFERENCE: case android::Res_value::TYPE_ATTRIBUTE: case android::Res_value::TYPE_DYNAMIC_REFERENCE: - return android::ResTable_map::TYPE_REFERENCE; + return android::ResTable_map::TYPE_REFERENCE; case android::Res_value::TYPE_STRING: - return android::ResTable_map::TYPE_STRING; + return android::ResTable_map::TYPE_STRING; case android::Res_value::TYPE_FLOAT: - return android::ResTable_map::TYPE_FLOAT; + return android::ResTable_map::TYPE_FLOAT; case android::Res_value::TYPE_DIMENSION: - return android::ResTable_map::TYPE_DIMENSION; + return android::ResTable_map::TYPE_DIMENSION; case android::Res_value::TYPE_FRACTION: - return android::ResTable_map::TYPE_FRACTION; + return android::ResTable_map::TYPE_FRACTION; case android::Res_value::TYPE_INT_DEC: case android::Res_value::TYPE_INT_HEX: - return android::ResTable_map::TYPE_INTEGER | android::ResTable_map::TYPE_ENUM - | android::ResTable_map::TYPE_FLAGS; + return android::ResTable_map::TYPE_INTEGER | + android::ResTable_map::TYPE_ENUM | + android::ResTable_map::TYPE_FLAGS; case android::Res_value::TYPE_INT_BOOLEAN: - return android::ResTable_map::TYPE_BOOLEAN; + return android::ResTable_map::TYPE_BOOLEAN; case android::Res_value::TYPE_INT_COLOR_ARGB8: case android::Res_value::TYPE_INT_COLOR_RGB8: case android::Res_value::TYPE_INT_COLOR_ARGB4: case android::Res_value::TYPE_INT_COLOR_RGB4: - return android::ResTable_map::TYPE_COLOR; + return android::ResTable_map::TYPE_COLOR; default: - return 0; - }; + return 0; + }; } -std::unique_ptr<Item> parseItemForAttribute( - const StringPiece16& value, uint32_t typeMask, - std::function<void(const ResourceName&)> onCreateReference) { - std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value); - if (nullOrEmpty) { - return std::move(nullOrEmpty); - } - - bool create = false; - std::unique_ptr<Reference> reference = tryParseReference(value, &create); - if (reference) { - if (create && onCreateReference) { - onCreateReference(reference->name.value()); - } - return std::move(reference); - } - - if (typeMask & android::ResTable_map::TYPE_COLOR) { - // Try parsing this as a color. - std::unique_ptr<BinaryPrimitive> color = tryParseColor(value); - if (color) { - return std::move(color); - } - } - - if (typeMask & android::ResTable_map::TYPE_BOOLEAN) { - // Try parsing this as a boolean. - std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value); - if (boolean) { - return std::move(boolean); - } - } - - if (typeMask & android::ResTable_map::TYPE_INTEGER) { - // Try parsing this as an integer. - std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value); - if (integer) { - return std::move(integer); - } - } - - const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT - | android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FRACTION; - if (typeMask & floatMask) { - // Try parsing this as a float. - std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value); - if (floatingPoint) { - if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) { - return std::move(floatingPoint); - } - } - } - return {}; +std::unique_ptr<Item> TryParseItemForAttribute( + const StringPiece& value, uint32_t type_mask, + const std::function<void(const ResourceName&)>& on_create_reference) { + std::unique_ptr<BinaryPrimitive> null_or_empty = TryParseNullOrEmpty(value); + if (null_or_empty) { + return std::move(null_or_empty); + } + + bool create = false; + std::unique_ptr<Reference> reference = TryParseReference(value, &create); + if (reference) { + if (create && on_create_reference) { + on_create_reference(reference->name.value()); + } + return std::move(reference); + } + + if (type_mask & android::ResTable_map::TYPE_COLOR) { + // Try parsing this as a color. + std::unique_ptr<BinaryPrimitive> color = TryParseColor(value); + if (color) { + return std::move(color); + } + } + + if (type_mask & android::ResTable_map::TYPE_BOOLEAN) { + // Try parsing this as a boolean. + std::unique_ptr<BinaryPrimitive> boolean = TryParseBool(value); + if (boolean) { + return std::move(boolean); + } + } + + if (type_mask & android::ResTable_map::TYPE_INTEGER) { + // Try parsing this as an integer. + std::unique_ptr<BinaryPrimitive> integer = TryParseInt(value); + if (integer) { + return std::move(integer); + } + } + + const uint32_t float_mask = android::ResTable_map::TYPE_FLOAT | + android::ResTable_map::TYPE_DIMENSION | + android::ResTable_map::TYPE_FRACTION; + if (type_mask & float_mask) { + // Try parsing this as a float. + std::unique_ptr<BinaryPrimitive> floating_point = TryParseFloat(value); + if (floating_point) { + if (type_mask & + AndroidTypeToAttributeTypeMask(floating_point->value.dataType)) { + return std::move(floating_point); + } + } + } + return {}; } /** * We successively try to parse the string as a resource type that the Attribute * allows. */ -std::unique_ptr<Item> parseItemForAttribute( - const StringPiece16& str, const Attribute* attr, - std::function<void(const ResourceName&)> onCreateReference) { - const uint32_t typeMask = attr->typeMask; - std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference); - if (value) { - return value; - } - - if (typeMask & android::ResTable_map::TYPE_ENUM) { - // Try parsing this as an enum. - std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str); - if (enumValue) { - return std::move(enumValue); - } - } - - if (typeMask & android::ResTable_map::TYPE_FLAGS) { - // Try parsing this as a flag. - std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str); - if (flagValue) { - return std::move(flagValue); - } - } - return {}; +std::unique_ptr<Item> TryParseItemForAttribute( + const StringPiece& str, const Attribute* attr, + const std::function<void(const ResourceName&)>& on_create_reference) { + const uint32_t type_mask = attr->type_mask; + std::unique_ptr<Item> value = + TryParseItemForAttribute(str, type_mask, on_create_reference); + if (value) { + return value; + } + + if (type_mask & android::ResTable_map::TYPE_ENUM) { + // Try parsing this as an enum. + std::unique_ptr<BinaryPrimitive> enum_value = TryParseEnumSymbol(attr, str); + if (enum_value) { + return std::move(enum_value); + } + } + + if (type_mask & android::ResTable_map::TYPE_FLAGS) { + // Try parsing this as a flag. + std::unique_ptr<BinaryPrimitive> flag_value = TryParseFlagSymbol(attr, str); + if (flag_value) { + return std::move(flag_value); + } + } + return {}; } -std::string buildResourceFileName(const ResourceFile& resFile, const NameMangler* mangler) { - std::stringstream out; - out << "res/" << resFile.name.type; - if (resFile.config != ConfigDescription{}) { - out << "-" << resFile.config; - } - out << "/"; - - if (mangler && mangler->shouldMangle(resFile.name.package)) { - out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry); - } else { - out << resFile.name.entry; - } - out << file::getExtension(resFile.source.path); - return out.str(); +std::string BuildResourceFileName(const ResourceFile& res_file, + const NameMangler* mangler) { + std::stringstream out; + out << "res/" << res_file.name.type; + if (res_file.config != ConfigDescription{}) { + out << "-" << res_file.config; + } + out << "/"; + + if (mangler && mangler->ShouldMangle(res_file.name.package)) { + out << NameMangler::MangleEntry(res_file.name.package, res_file.name.entry); + } else { + out << res_file.name.entry; + } + out << file::GetExtension(res_file.source.path); + return out.str(); } -} // namespace ResourceUtils -} // namespace aapt +} // namespace ResourceUtils +} // namespace aapt diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h index a0fbcc6e700b..9766f6a7b2fa 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -17,14 +17,14 @@ #ifndef AAPT_RESOURCEUTILS_H #define AAPT_RESOURCEUTILS_H +#include <functional> +#include <memory> + #include "NameMangler.h" #include "Resource.h" #include "ResourceValues.h" #include "util/StringPiece.h" -#include <functional> -#include <memory> - namespace aapt { namespace ResourceUtils { @@ -37,136 +37,183 @@ namespace ResourceUtils { * individual extracted piece to verify that the pieces are valid. * Returns false if there was no package but a ':' was present. */ -bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage, - StringPiece16* outType, StringPiece16* outEntry); +bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, + StringPiece* out_type, StringPiece* out_entry); /** - * Returns true if the string was parsed as a resource name ([*][package:]type/name), with - * `outResource` set to the parsed resource name and `outPrivate` set to true if a '*' prefix - * was present. + * Returns true if the string was parsed as a resource name + * ([*][package:]type/name), with + * `out_resource` set to the parsed resource name and `out_private` set to true + * if a '*' prefix was present. */ -bool parseResourceName(const StringPiece16& str, ResourceNameRef* outResource, - bool* outPrivate = nullptr); +bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_resource, + bool* out_private = nullptr); /* - * Returns true if the string was parsed as a reference (@[+][package:]type/name), with - * `outReference` set to the parsed reference. + * Returns true if the string was parsed as a reference + * (@[+][package:]type/name), with + * `out_reference` set to the parsed reference. * - * If '+' was present in the reference, `outCreate` is set to true. - * If '*' was present in the reference, `outPrivate` is set to true. + * If '+' was present in the reference, `out_create` is set to true. + * If '*' was present in the reference, `out_private` is set to true. */ -bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference, - bool* outCreate = nullptr, bool* outPrivate = nullptr); +bool ParseReference(const StringPiece& str, ResourceNameRef* out_reference, + bool* out_create = nullptr, bool* out_private = nullptr); /* - * Returns true if the string is in the form of a resource reference (@[+][package:]type/name). + * Returns true if the string is in the form of a resource reference + * (@[+][package:]type/name). */ -bool isReference(const StringPiece16& str); +bool IsReference(const StringPiece& str); /* - * Returns true if the string was parsed as an attribute reference (?[package:][type/]name), - * with `outReference` set to the parsed reference. + * Returns true if the string was parsed as an attribute reference + * (?[package:][type/]name), + * with `out_reference` set to the parsed reference. + */ +bool ParseAttributeReference(const StringPiece& str, + ResourceNameRef* out_reference); + +/** + * Returns true if the string is in the form of an attribute + * reference(?[package:][type/]name). */ -bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outReference); +bool IsAttributeReference(const StringPiece& str); /** - * Returns true if the string is in the form of an attribute reference(?[package:][type/]name). + * Convert an android::ResTable::resource_name to an aapt::ResourceName struct. */ -bool isAttributeReference(const StringPiece16& str); +Maybe<ResourceName> ToResourceName( + const android::ResTable::resource_name& name); /** - * Returns true if the value is a boolean, putting the result in `outValue`. + * Returns a boolean value if the string is equal to TRUE, true, True, FALSE, + * false, or False. */ -bool tryParseBool(const StringPiece16& str, bool* outValue); +Maybe<bool> ParseBool(const StringPiece& str); + +/** + * Returns a uint32_t if the string is an integer. + */ +Maybe<uint32_t> ParseInt(const StringPiece& str); + +/** + * Returns an ID if it the string represented a valid ID. + */ +Maybe<ResourceId> ParseResourceId(const StringPiece& str); + +/** + * Parses an SDK version, which can be an integer, or a letter from A-Z. + */ +Maybe<int> ParseSdkVersion(const StringPiece& str); /* - * Returns a Reference, or None Maybe instance if the string `str` was parsed as a + * Returns a Reference, or None Maybe instance if the string `str` was parsed as + * a * valid reference to a style. - * The format for a style parent is slightly more flexible than a normal reference: + * The format for a style parent is slightly more flexible than a normal + * reference: * * @[package:]style/<entry> or * ?[package:]style/<entry> or * <package>:[style/]<entry> */ -Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError); +Maybe<Reference> ParseStyleParentReference(const StringPiece& str, + std::string* out_error); /* - * Returns a Reference object if the string was parsed as a resource or attribute reference, - * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if + * Returns a Reference if the string `str` was parsed as a valid XML attribute + * name. + * The valid format for an XML attribute name is: + * + * package:entry + */ +Maybe<Reference> ParseXmlAttributeName(const StringPiece& str); + +/* + * Returns a Reference object if the string was parsed as a resource or + * attribute reference, + * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true + * if * the '+' was present in the string. */ -std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate = nullptr); +std::unique_ptr<Reference> TryParseReference(const StringPiece& str, + bool* out_create = nullptr); /* - * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed - * as one. + * Returns a BinaryPrimitve object representing @null or @empty if the string + * was parsed as one. */ -std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str); +std::unique_ptr<BinaryPrimitive> TryParseNullOrEmpty(const StringPiece& str); /* * Returns a BinaryPrimitve object representing a color if the string was parsed * as one. */ -std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str); +std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str); /* - * Returns a BinaryPrimitve object representing a boolean if the string was parsed - * as one. + * Returns a BinaryPrimitve object representing a boolean if the string was + * parsed as one. */ -std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str); +std::unique_ptr<BinaryPrimitive> TryParseBool(const StringPiece& str); /* - * Returns a BinaryPrimitve object representing an integer if the string was parsed - * as one. + * Returns a BinaryPrimitve object representing an integer if the string was + * parsed as one. */ -std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str); +std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str); /* * Returns a BinaryPrimitve object representing a floating point number * (float, dimension, etc) if the string was parsed as one. */ -std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str); +std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str); /* - * Returns a BinaryPrimitve object representing an enum symbol if the string was parsed - * as one. + * Returns a BinaryPrimitve object representing an enum symbol if the string was + * parsed as one. */ -std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr, - const StringPiece16& str); +std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, + const StringPiece& str); /* - * Returns a BinaryPrimitve object representing a flag symbol if the string was parsed - * as one. + * Returns a BinaryPrimitve object representing a flag symbol if the string was + * parsed as one. */ -std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* enumAttr, - const StringPiece16& str); +std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr, + const StringPiece& str); /* - * Try to convert a string to an Item for the given attribute. The attribute will + * Try to convert a string to an Item for the given attribute. The attribute + * will * restrict what values the string can be converted to. - * The callback function onCreateReference is called when the parsed item is a + * The callback function on_create_reference is called when the parsed item is a * reference to an ID that must be created (@+id/foo). */ -std::unique_ptr<Item> parseItemForAttribute( - const StringPiece16& value, const Attribute* attr, - std::function<void(const ResourceName&)> onCreateReference = {}); +std::unique_ptr<Item> TryParseItemForAttribute( + const StringPiece& value, const Attribute* attr, + const std::function<void(const ResourceName&)>& on_create_reference = {}); -std::unique_ptr<Item> parseItemForAttribute( - const StringPiece16& value, uint32_t typeMask, - std::function<void(const ResourceName&)> onCreateReference = {}); +std::unique_ptr<Item> TryParseItemForAttribute( + const StringPiece& value, uint32_t type_mask, + const std::function<void(const ResourceName&)>& on_create_reference = {}); -uint32_t androidTypeToAttributeTypeMask(uint16_t type); +uint32_t AndroidTypeToAttributeTypeMask(uint16_t type); /** - * Returns a string path suitable for use within an APK. The path will look like: + * Returns a string path suitable for use within an APK. The path will look + * like: * * res/type[-config]/<name>.<ext> * - * Then name may be mangled if a NameMangler is supplied (can be nullptr) and the package + * Then name may be mangled if a NameMangler is supplied (can be nullptr) and + * the package * requires mangling. */ -std::string buildResourceFileName(const ResourceFile& resFile, const NameMangler* mangler); +std::string BuildResourceFileName(const ResourceFile& res_file, + const NameMangler* mangler = nullptr); -} // namespace ResourceUtils -} // namespace aapt +} // namespace ResourceUtils +} // namespace aapt #endif /* AAPT_RESOURCEUTILS_H */ diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index 7425f97ef8de..f9c500b42c13 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -14,194 +14,191 @@ * limitations under the License. */ -#include "Resource.h" #include "ResourceUtils.h" -#include "test/Builders.h" -#include "test/Common.h" -#include <gtest/gtest.h> +#include "Resource.h" +#include "test/Test.h" namespace aapt { TEST(ResourceUtilsTest, ParseBool) { - bool val = false; - EXPECT_TRUE(ResourceUtils::tryParseBool(u"true", &val)); - EXPECT_TRUE(val); - - EXPECT_TRUE(ResourceUtils::tryParseBool(u"TRUE", &val)); - EXPECT_TRUE(val); - - EXPECT_TRUE(ResourceUtils::tryParseBool(u"True", &val)); - EXPECT_TRUE(val); - - EXPECT_TRUE(ResourceUtils::tryParseBool(u"false", &val)); - EXPECT_FALSE(val); - - EXPECT_TRUE(ResourceUtils::tryParseBool(u"FALSE", &val)); - EXPECT_FALSE(val); - - EXPECT_TRUE(ResourceUtils::tryParseBool(u"False", &val)); - EXPECT_FALSE(val); + EXPECT_EQ(Maybe<bool>(true), ResourceUtils::ParseBool("true")); + EXPECT_EQ(Maybe<bool>(true), ResourceUtils::ParseBool("TRUE")); + EXPECT_EQ(Maybe<bool>(true), ResourceUtils::ParseBool("True")); + EXPECT_EQ(Maybe<bool>(false), ResourceUtils::ParseBool("false")); + EXPECT_EQ(Maybe<bool>(false), ResourceUtils::ParseBool("FALSE")); + EXPECT_EQ(Maybe<bool>(false), ResourceUtils::ParseBool("False")); } TEST(ResourceUtilsTest, ParseResourceName) { - ResourceNameRef actual; - bool actualPriv = false; - EXPECT_TRUE(ResourceUtils::parseResourceName(u"android:color/foo", &actual, &actualPriv)); - EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual); - EXPECT_FALSE(actualPriv); - - EXPECT_TRUE(ResourceUtils::parseResourceName(u"color/foo", &actual, &actualPriv)); - EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, u"foo"), actual); - EXPECT_FALSE(actualPriv); - - EXPECT_TRUE(ResourceUtils::parseResourceName(u"*android:color/foo", &actual, &actualPriv)); - EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual); - EXPECT_TRUE(actualPriv); - - EXPECT_FALSE(ResourceUtils::parseResourceName(StringPiece16(), &actual, &actualPriv)); + ResourceNameRef actual; + bool actual_priv = false; + EXPECT_TRUE(ResourceUtils::ParseResourceName("android:color/foo", &actual, + &actual_priv)); + EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual); + EXPECT_FALSE(actual_priv); + + EXPECT_TRUE( + ResourceUtils::ParseResourceName("color/foo", &actual, &actual_priv)); + EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, "foo"), actual); + EXPECT_FALSE(actual_priv); + + EXPECT_TRUE(ResourceUtils::ParseResourceName("*android:color/foo", &actual, + &actual_priv)); + EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual); + EXPECT_TRUE(actual_priv); + + EXPECT_FALSE( + ResourceUtils::ParseResourceName(StringPiece(), &actual, &actual_priv)); } TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) { - ResourceNameRef expected({}, ResourceType::kColor, u"foo"); - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceUtils::tryParseReference(u"@color/foo", &actual, &create, &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_FALSE(privateRef); + ResourceNameRef expected({}, ResourceType::kColor, "foo"); + ResourceNameRef actual; + bool create = false; + bool private_ref = false; + EXPECT_TRUE(ResourceUtils::ParseReference("@color/foo", &actual, &create, + &private_ref)); + EXPECT_EQ(expected, actual); + EXPECT_FALSE(create); + EXPECT_FALSE(private_ref); } TEST(ResourceUtilsTest, ParseReferenceWithPackage) { - ResourceNameRef expected(u"android", ResourceType::kColor, u"foo"); - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceUtils::tryParseReference(u"@android:color/foo", &actual, &create, - &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_FALSE(privateRef); + ResourceNameRef expected("android", ResourceType::kColor, "foo"); + ResourceNameRef actual; + bool create = false; + bool private_ref = false; + EXPECT_TRUE(ResourceUtils::ParseReference("@android:color/foo", &actual, + &create, &private_ref)); + EXPECT_EQ(expected, actual); + EXPECT_FALSE(create); + EXPECT_FALSE(private_ref); } TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) { - ResourceNameRef expected(u"android", ResourceType::kColor, u"foo"); - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceUtils::tryParseReference(u"\t @android:color/foo\n \n\t", &actual, - &create, &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_FALSE(privateRef); + ResourceNameRef expected("android", ResourceType::kColor, "foo"); + ResourceNameRef actual; + bool create = false; + bool private_ref = false; + EXPECT_TRUE(ResourceUtils::ParseReference("\t @android:color/foo\n \n\t", + &actual, &create, &private_ref)); + EXPECT_EQ(expected, actual); + EXPECT_FALSE(create); + EXPECT_FALSE(private_ref); } TEST(ResourceUtilsTest, ParseAutoCreateIdReference) { - ResourceNameRef expected(u"android", ResourceType::kId, u"foo"); - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceUtils::tryParseReference(u"@+android:id/foo", &actual, &create, - &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_TRUE(create); - EXPECT_FALSE(privateRef); + ResourceNameRef expected("android", ResourceType::kId, "foo"); + ResourceNameRef actual; + bool create = false; + bool private_ref = false; + EXPECT_TRUE(ResourceUtils::ParseReference("@+android:id/foo", &actual, + &create, &private_ref)); + EXPECT_EQ(expected, actual); + EXPECT_TRUE(create); + EXPECT_FALSE(private_ref); } TEST(ResourceUtilsTest, ParsePrivateReference) { - ResourceNameRef expected(u"android", ResourceType::kId, u"foo"); - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceUtils::tryParseReference(u"@*android:id/foo", &actual, &create, - &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_TRUE(privateRef); + ResourceNameRef expected("android", ResourceType::kId, "foo"); + ResourceNameRef actual; + bool create = false; + bool private_ref = false; + EXPECT_TRUE(ResourceUtils::ParseReference("@*android:id/foo", &actual, + &create, &private_ref)); + EXPECT_EQ(expected, actual); + EXPECT_FALSE(create); + EXPECT_TRUE(private_ref); } TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) { - bool create = false; - bool privateRef = false; - ResourceNameRef actual; - EXPECT_FALSE(ResourceUtils::tryParseReference(u"@+android:color/foo", &actual, &create, - &privateRef)); + bool create = false; + bool private_ref = false; + ResourceNameRef actual; + EXPECT_FALSE(ResourceUtils::ParseReference("@+android:color/foo", &actual, + &create, &private_ref)); } TEST(ResourceUtilsTest, ParseAttributeReferences) { - EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android")); - EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:foo")); - EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?attr/foo")); - EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:attr/foo")); + EXPECT_TRUE(ResourceUtils::IsAttributeReference("?android")); + EXPECT_TRUE(ResourceUtils::IsAttributeReference("?android:foo")); + EXPECT_TRUE(ResourceUtils::IsAttributeReference("?attr/foo")); + EXPECT_TRUE(ResourceUtils::IsAttributeReference("?android:attr/foo")); } TEST(ResourceUtilsTest, FailParseIncompleteReference) { - EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?style/foo")); - EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:style/foo")); - EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:")); - EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:attr/")); - EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/")); - EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/foo")); - EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/")); - EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/foo")); - EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?attr/")); - EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?/foo")); + EXPECT_FALSE(ResourceUtils::IsAttributeReference("?style/foo")); + EXPECT_FALSE(ResourceUtils::IsAttributeReference("?android:style/foo")); + EXPECT_FALSE(ResourceUtils::IsAttributeReference("?android:")); + EXPECT_FALSE(ResourceUtils::IsAttributeReference("?android:attr/")); + EXPECT_FALSE(ResourceUtils::IsAttributeReference("?:attr/")); + EXPECT_FALSE(ResourceUtils::IsAttributeReference("?:attr/foo")); + EXPECT_FALSE(ResourceUtils::IsAttributeReference("?:/")); + EXPECT_FALSE(ResourceUtils::IsAttributeReference("?:/foo")); + EXPECT_FALSE(ResourceUtils::IsAttributeReference("?attr/")); + EXPECT_FALSE(ResourceUtils::IsAttributeReference("?/foo")); } TEST(ResourceUtilsTest, ParseStyleParentReference) { - const ResourceName kAndroidStyleFooName(u"android", ResourceType::kStyle, u"foo"); - const ResourceName kStyleFooName({}, ResourceType::kStyle, u"foo"); - - std::string errStr; - Maybe<Reference> ref = ResourceUtils::parseStyleParentReference(u"@android:style/foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); - - ref = ResourceUtils::parseStyleParentReference(u"@style/foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kStyleFooName); - - ref = ResourceUtils::parseStyleParentReference(u"?android:style/foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); - - ref = ResourceUtils::parseStyleParentReference(u"?style/foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kStyleFooName); - - ref = ResourceUtils::parseStyleParentReference(u"android:style/foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); - - ref = ResourceUtils::parseStyleParentReference(u"android:foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); - - ref = ResourceUtils::parseStyleParentReference(u"@android:foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); - - ref = ResourceUtils::parseStyleParentReference(u"foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kStyleFooName); - - ref = ResourceUtils::parseStyleParentReference(u"*android:style/foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); - EXPECT_TRUE(ref.value().privateReference); + const ResourceName kAndroidStyleFooName("android", ResourceType::kStyle, + "foo"); + const ResourceName kStyleFooName({}, ResourceType::kStyle, "foo"); + + std::string err_str; + Maybe<Reference> ref = + ResourceUtils::ParseStyleParentReference("@android:style/foo", &err_str); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + + ref = ResourceUtils::ParseStyleParentReference("@style/foo", &err_str); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kStyleFooName); + + ref = + ResourceUtils::ParseStyleParentReference("?android:style/foo", &err_str); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + + ref = ResourceUtils::ParseStyleParentReference("?style/foo", &err_str); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kStyleFooName); + + ref = ResourceUtils::ParseStyleParentReference("android:style/foo", &err_str); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + + ref = ResourceUtils::ParseStyleParentReference("android:foo", &err_str); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + + ref = ResourceUtils::ParseStyleParentReference("@android:foo", &err_str); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + + ref = ResourceUtils::ParseStyleParentReference("foo", &err_str); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kStyleFooName); + + ref = + ResourceUtils::ParseStyleParentReference("*android:style/foo", &err_str); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + EXPECT_TRUE(ref.value().private_reference); } TEST(ResourceUtilsTest, ParseEmptyFlag) { - std::unique_ptr<Attribute> attr = test::AttributeBuilder(false) - .setTypeMask(android::ResTable_map::TYPE_FLAGS) - .addItem(u"one", 0x01) - .addItem(u"two", 0x02) - .build(); - - std::unique_ptr<BinaryPrimitive> result = ResourceUtils::tryParseFlagSymbol(attr.get(), u""); - ASSERT_NE(nullptr, result); - EXPECT_EQ(0u, result->value.data); + std::unique_ptr<Attribute> attr = + test::AttributeBuilder(false) + .SetTypeMask(android::ResTable_map::TYPE_FLAGS) + .AddItem("one", 0x01) + .AddItem("two", 0x02) + .Build(); + + std::unique_ptr<BinaryPrimitive> result = + ResourceUtils::TryParseFlagSymbol(attr.get(), ""); + ASSERT_NE(nullptr, result); + EXPECT_EQ(0u, result->value.data); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index c10b134cb36e..7956ad826acd 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -14,665 +14,760 @@ * limitations under the License. */ +#include "ResourceValues.h" + +#include <algorithm> +#include <limits> +#include <set> + +#include "androidfw/ResourceTypes.h" + #include "Resource.h" #include "ResourceUtils.h" -#include "ResourceValues.h" #include "ValueVisitor.h" -#include "io/File.h" #include "util/Util.h" -#include <androidfw/ResourceTypes.h> -#include <limits> - namespace aapt { template <typename Derived> -void BaseValue<Derived>::accept(RawValueVisitor* visitor) { - visitor->visit(static_cast<Derived*>(this)); +void BaseValue<Derived>::Accept(RawValueVisitor* visitor) { + visitor->Visit(static_cast<Derived*>(this)); } template <typename Derived> -void BaseItem<Derived>::accept(RawValueVisitor* visitor) { - visitor->visit(static_cast<Derived*>(this)); +void BaseItem<Derived>::Accept(RawValueVisitor* visitor) { + visitor->Visit(static_cast<Derived*>(this)); } -RawString::RawString(const StringPool::Ref& ref) : value(ref) { -} +RawString::RawString(const StringPool::Ref& ref) : value(ref) {} -bool RawString::equals(const Value* value) const { - const RawString* other = valueCast<RawString>(value); - if (!other) { - return false; - } - return *this->value == *other->value; +bool RawString::Equals(const Value* value) const { + const RawString* other = ValueCast<RawString>(value); + if (!other) { + return false; + } + return *this->value == *other->value; } -RawString* RawString::clone(StringPool* newPool) const { - RawString* rs = new RawString(newPool->makeRef(*value)); - rs->mComment = mComment; - rs->mSource = mSource; - return rs; +RawString* RawString::Clone(StringPool* new_pool) const { + RawString* rs = new RawString(new_pool->MakeRef(*value)); + rs->comment_ = comment_; + rs->source_ = source_; + return rs; } -bool RawString::flatten(android::Res_value* outValue) const { - outValue->dataType = android::Res_value::TYPE_STRING; - outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex())); - return true; +bool RawString::Flatten(android::Res_value* out_value) const { + out_value->dataType = android::Res_value::TYPE_STRING; + out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index())); + return true; } -void RawString::print(std::ostream* out) const { - *out << "(raw string) " << *value; +void RawString::Print(std::ostream* out) const { + *out << "(raw string) " << *value; } -Reference::Reference() : referenceType(Reference::Type::kResource) { -} +Reference::Reference() : reference_type(Type::kResource) {} -Reference::Reference(const ResourceNameRef& n, Type t) : - name(n.toResourceName()), referenceType(t) { -} +Reference::Reference(const ResourceNameRef& n, Type t) + : name(n.ToResourceName()), reference_type(t) {} -Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) { -} +Reference::Reference(const ResourceId& i, Type type) + : id(i), reference_type(type) {} -bool Reference::equals(const Value* value) const { - const Reference* other = valueCast<Reference>(value); - if (!other) { - return false; - } - return referenceType == other->referenceType && privateReference == other->privateReference && - id == other->id && name == other->name; +Reference::Reference(const ResourceNameRef& n, const ResourceId& i) + : name(n.ToResourceName()), id(i), reference_type(Type::kResource) {} + +bool Reference::Equals(const Value* value) const { + const Reference* other = ValueCast<Reference>(value); + if (!other) { + return false; + } + return reference_type == other->reference_type && + private_reference == other->private_reference && id == other->id && + name == other->name; } -bool Reference::flatten(android::Res_value* outValue) const { - outValue->dataType = (referenceType == Reference::Type::kResource) ? - android::Res_value::TYPE_REFERENCE : android::Res_value::TYPE_ATTRIBUTE; - outValue->data = util::hostToDevice32(id ? id.value().id : 0); - return true; +bool Reference::Flatten(android::Res_value* out_value) const { + out_value->dataType = (reference_type == Reference::Type::kResource) + ? android::Res_value::TYPE_REFERENCE + : android::Res_value::TYPE_ATTRIBUTE; + out_value->data = util::HostToDevice32(id ? id.value().id : 0); + return true; } -Reference* Reference::clone(StringPool* /*newPool*/) const { - return new Reference(*this); +Reference* Reference::Clone(StringPool* /*new_pool*/) const { + return new Reference(*this); } -void Reference::print(std::ostream* out) const { - *out << "(reference) "; - if (referenceType == Reference::Type::kResource) { - *out << "@"; - if (privateReference) { - *out << "*"; - } - } else { - *out << "?"; +void Reference::Print(std::ostream* out) const { + *out << "(reference) "; + if (reference_type == Reference::Type::kResource) { + *out << "@"; + if (private_reference) { + *out << "*"; } + } else { + *out << "?"; + } - if (name) { - *out << name.value(); - } + if (name) { + *out << name.value(); + } - if (id && !Res_INTERNALID(id.value().id)) { - *out << " " << id.value(); - } + if (id && !Res_INTERNALID(id.value().id)) { + *out << " " << id.value(); + } } -bool Id::equals(const Value* value) const { - return valueCast<Id>(value) != nullptr; +bool Id::Equals(const Value* value) const { + return ValueCast<Id>(value) != nullptr; } -bool Id::flatten(android::Res_value* out) const { - out->dataType = android::Res_value::TYPE_INT_BOOLEAN; - out->data = util::hostToDevice32(0); - return true; +bool Id::Flatten(android::Res_value* out) const { + out->dataType = android::Res_value::TYPE_INT_BOOLEAN; + out->data = util::HostToDevice32(0); + return true; } -Id* Id::clone(StringPool* /*newPool*/) const { - return new Id(*this); -} +Id* Id::Clone(StringPool* /*new_pool*/) const { return new Id(*this); } -void Id::print(std::ostream* out) const { - *out << "(id)"; -} +void Id::Print(std::ostream* out) const { *out << "(id)"; } -String::String(const StringPool::Ref& ref) : value(ref) { -} +String::String(const StringPool::Ref& ref) : value(ref) {} -bool String::equals(const Value* value) const { - const String* other = valueCast<String>(value); - if (!other) { - return false; - } - return *this->value == *other->value; +bool String::Equals(const Value* value) const { + const String* other = ValueCast<String>(value); + if (!other) { + return false; + } + return *this->value == *other->value; } -bool String::flatten(android::Res_value* outValue) const { - // Verify that our StringPool index is within encode-able limits. - if (value.getIndex() > std::numeric_limits<uint32_t>::max()) { - return false; - } - - outValue->dataType = android::Res_value::TYPE_STRING; - outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex())); - return true; -} +bool String::Flatten(android::Res_value* out_value) const { + // Verify that our StringPool index is within encode-able limits. + if (value.index() > std::numeric_limits<uint32_t>::max()) { + return false; + } -String* String::clone(StringPool* newPool) const { - String* str = new String(newPool->makeRef(*value)); - str->mComment = mComment; - str->mSource = mSource; - return str; + out_value->dataType = android::Res_value::TYPE_STRING; + out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index())); + return true; } -void String::print(std::ostream* out) const { - *out << "(string) \"" << *value << "\""; +String* String::Clone(StringPool* new_pool) const { + String* str = new String(new_pool->MakeRef(*value)); + str->comment_ = comment_; + str->source_ = source_; + return str; } -StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) { +void String::Print(std::ostream* out) const { + *out << "(string) \"" << *value << "\""; } -bool StyledString::equals(const Value* value) const { - const StyledString* other = valueCast<StyledString>(value); - if (!other) { - return false; - } +StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {} - if (*this->value->str == *other->value->str) { - const std::vector<StringPool::Span>& spansA = this->value->spans; - const std::vector<StringPool::Span>& spansB = other->value->spans; - return std::equal(spansA.begin(), spansA.end(), spansB.begin(), - [](const StringPool::Span& a, const StringPool::Span& b) -> bool { - return *a.name == *b.name && a.firstChar == b.firstChar && a.lastChar == b.lastChar; - }); - } +bool StyledString::Equals(const Value* value) const { + const StyledString* other = ValueCast<StyledString>(value); + if (!other) { return false; + } + + if (*this->value->str == *other->value->str) { + const std::vector<StringPool::Span>& spans_a = this->value->spans; + const std::vector<StringPool::Span>& spans_b = other->value->spans; + return std::equal( + spans_a.begin(), spans_a.end(), spans_b.begin(), + [](const StringPool::Span& a, const StringPool::Span& b) -> bool { + return *a.name == *b.name && a.first_char == b.first_char && + a.last_char == b.last_char; + }); + } + return false; } -bool StyledString::flatten(android::Res_value* outValue) const { - if (value.getIndex() > std::numeric_limits<uint32_t>::max()) { - return false; - } +bool StyledString::Flatten(android::Res_value* out_value) const { + if (value.index() > std::numeric_limits<uint32_t>::max()) { + return false; + } - outValue->dataType = android::Res_value::TYPE_STRING; - outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex())); - return true; + out_value->dataType = android::Res_value::TYPE_STRING; + out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index())); + return true; } -StyledString* StyledString::clone(StringPool* newPool) const { - StyledString* str = new StyledString(newPool->makeRef(value)); - str->mComment = mComment; - str->mSource = mSource; - return str; +StyledString* StyledString::Clone(StringPool* new_pool) const { + StyledString* str = new StyledString(new_pool->MakeRef(value)); + str->comment_ = comment_; + str->source_ = source_; + return str; } -void StyledString::print(std::ostream* out) const { - *out << "(styled string) \"" << *value->str << "\""; - for (const StringPool::Span& span : value->spans) { - *out << " "<< *span.name << ":" << span.firstChar << "," << span.lastChar; - } +void StyledString::Print(std::ostream* out) const { + *out << "(styled string) \"" << *value->str << "\""; + for (const StringPool::Span& span : value->spans) { + *out << " " << *span.name << ":" << span.first_char << "," + << span.last_char; + } } -FileReference::FileReference(const StringPool::Ref& _path) : path(_path) { -} +FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {} -bool FileReference::equals(const Value* value) const { - const FileReference* other = valueCast<FileReference>(value); - if (!other) { - return false; - } - return *path == *other->path; +bool FileReference::Equals(const Value* value) const { + const FileReference* other = ValueCast<FileReference>(value); + if (!other) { + return false; + } + return *path == *other->path; } -bool FileReference::flatten(android::Res_value* outValue) const { - if (path.getIndex() > std::numeric_limits<uint32_t>::max()) { - return false; - } +bool FileReference::Flatten(android::Res_value* out_value) const { + if (path.index() > std::numeric_limits<uint32_t>::max()) { + return false; + } - outValue->dataType = android::Res_value::TYPE_STRING; - outValue->data = util::hostToDevice32(static_cast<uint32_t>(path.getIndex())); - return true; + out_value->dataType = android::Res_value::TYPE_STRING; + out_value->data = util::HostToDevice32(static_cast<uint32_t>(path.index())); + return true; } -FileReference* FileReference::clone(StringPool* newPool) const { - FileReference* fr = new FileReference(newPool->makeRef(*path)); - fr->file = file; - fr->mComment = mComment; - fr->mSource = mSource; - return fr; +FileReference* FileReference::Clone(StringPool* new_pool) const { + FileReference* fr = new FileReference(new_pool->MakeRef(*path)); + fr->file = file; + fr->comment_ = comment_; + fr->source_ = source_; + return fr; } -void FileReference::print(std::ostream* out) const { - *out << "(file) " << *path; +void FileReference::Print(std::ostream* out) const { + *out << "(file) " << *path; } -BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) { -} +BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {} BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) { - value.dataType = dataType; - value.data = data; -} - -bool BinaryPrimitive::equals(const Value* value) const { - const BinaryPrimitive* other = valueCast<BinaryPrimitive>(value); - if (!other) { - return false; - } - return this->value.dataType == other->value.dataType && this->value.data == other->value.data; -} - -bool BinaryPrimitive::flatten(android::Res_value* outValue) const { - outValue->dataType = value.dataType; - outValue->data = util::hostToDevice32(value.data); - return true; -} - -BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const { - return new BinaryPrimitive(*this); -} - -void BinaryPrimitive::print(std::ostream* out) const { - switch (value.dataType) { - case android::Res_value::TYPE_NULL: - *out << "(null)"; - break; - case android::Res_value::TYPE_INT_DEC: - *out << "(integer) " << static_cast<int32_t>(value.data); - break; - case android::Res_value::TYPE_INT_HEX: - *out << "(integer) 0x" << std::hex << value.data << std::dec; - break; - case android::Res_value::TYPE_INT_BOOLEAN: - *out << "(boolean) " << (value.data != 0 ? "true" : "false"); - break; - case android::Res_value::TYPE_INT_COLOR_ARGB8: - case android::Res_value::TYPE_INT_COLOR_RGB8: - case android::Res_value::TYPE_INT_COLOR_ARGB4: - case android::Res_value::TYPE_INT_COLOR_RGB4: - *out << "(color) #" << std::hex << value.data << std::dec; - break; - default: - *out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x" - << std::hex << value.data << std::dec; - break; - } + value.dataType = dataType; + value.data = data; } -Attribute::Attribute(bool w, uint32_t t) : - typeMask(t), - minInt(std::numeric_limits<int32_t>::min()), - maxInt(std::numeric_limits<int32_t>::max()) { - mWeak = w; -} - -bool Attribute::equals(const Value* value) const { - const Attribute* other = valueCast<Attribute>(value); - if (!other) { - return false; - } - - return this->typeMask == other->typeMask && this->minInt == other->minInt && - this->maxInt == other->maxInt && - std::equal(this->symbols.begin(), this->symbols.end(), - other->symbols.begin(), - [](const Symbol& a, const Symbol& b) -> bool { - return a.symbol.equals(&b.symbol) && a.value == b.value; - }); -} - -Attribute* Attribute::clone(StringPool* /*newPool*/) const { - return new Attribute(*this); -} +bool BinaryPrimitive::Equals(const Value* value) const { + const BinaryPrimitive* other = ValueCast<BinaryPrimitive>(value); + if (!other) { + return false; + } + return this->value.dataType == other->value.dataType && + this->value.data == other->value.data; +} + +bool BinaryPrimitive::Flatten(android::Res_value* out_value) const { + out_value->dataType = value.dataType; + out_value->data = util::HostToDevice32(value.data); + return true; +} + +BinaryPrimitive* BinaryPrimitive::Clone(StringPool* /*new_pool*/) const { + return new BinaryPrimitive(*this); +} + +void BinaryPrimitive::Print(std::ostream* out) const { + switch (value.dataType) { + case android::Res_value::TYPE_NULL: + *out << "(null)"; + break; + case android::Res_value::TYPE_INT_DEC: + *out << "(integer) " << static_cast<int32_t>(value.data); + break; + case android::Res_value::TYPE_INT_HEX: + *out << "(integer) 0x" << std::hex << value.data << std::dec; + break; + case android::Res_value::TYPE_INT_BOOLEAN: + *out << "(boolean) " << (value.data != 0 ? "true" : "false"); + break; + case android::Res_value::TYPE_INT_COLOR_ARGB8: + case android::Res_value::TYPE_INT_COLOR_RGB8: + case android::Res_value::TYPE_INT_COLOR_ARGB4: + case android::Res_value::TYPE_INT_COLOR_RGB4: + *out << "(color) #" << std::hex << value.data << std::dec; + break; + default: + *out << "(unknown 0x" << std::hex << (int)value.dataType << ") 0x" + << std::hex << value.data << std::dec; + break; + } +} + +Attribute::Attribute(bool w, uint32_t t) + : type_mask(t), + min_int(std::numeric_limits<int32_t>::min()), + max_int(std::numeric_limits<int32_t>::max()) { + weak_ = w; +} + +template <typename T> +T* addPointer(T& val) { + return &val; +} + +bool Attribute::Equals(const Value* value) const { + const Attribute* other = ValueCast<Attribute>(value); + if (!other) { + return false; + } -void Attribute::printMask(std::ostream* out) const { - if (typeMask == android::ResTable_map::TYPE_ANY) { - *out << "any"; - return; - } + if (symbols.size() != other->symbols.size()) { + return false; + } - bool set = false; - if ((typeMask & android::ResTable_map::TYPE_REFERENCE) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "reference"; + if (type_mask != other->type_mask || min_int != other->min_int || + max_int != other->max_int) { + return false; + } + + std::vector<const Symbol*> sorted_a; + std::transform(symbols.begin(), symbols.end(), std::back_inserter(sorted_a), + addPointer<const Symbol>); + std::sort(sorted_a.begin(), sorted_a.end(), + [](const Symbol* a, const Symbol* b) -> bool { + return a->symbol.name < b->symbol.name; + }); + + std::vector<const Symbol*> sorted_b; + std::transform(other->symbols.begin(), other->symbols.end(), + std::back_inserter(sorted_b), addPointer<const Symbol>); + std::sort(sorted_b.begin(), sorted_b.end(), + [](const Symbol* a, const Symbol* b) -> bool { + return a->symbol.name < b->symbol.name; + }); + + return std::equal(sorted_a.begin(), sorted_a.end(), sorted_b.begin(), + [](const Symbol* a, const Symbol* b) -> bool { + return a->symbol.Equals(&b->symbol) && + a->value == b->value; + }); +} + +Attribute* Attribute::Clone(StringPool* /*new_pool*/) const { + return new Attribute(*this); +} + +void Attribute::PrintMask(std::ostream* out) const { + if (type_mask == android::ResTable_map::TYPE_ANY) { + *out << "any"; + return; + } + + bool set = false; + if ((type_mask & android::ResTable_map::TYPE_REFERENCE) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "reference"; + } - if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "string"; + if ((type_mask & android::ResTable_map::TYPE_STRING) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "string"; + } - if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "integer"; + if ((type_mask & android::ResTable_map::TYPE_INTEGER) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "integer"; + } - if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "boolean"; + if ((type_mask & android::ResTable_map::TYPE_BOOLEAN) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "boolean"; + } - if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "color"; + if ((type_mask & android::ResTable_map::TYPE_COLOR) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "color"; + } - if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "float"; + if ((type_mask & android::ResTable_map::TYPE_FLOAT) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "float"; + } - if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "dimension"; + if ((type_mask & android::ResTable_map::TYPE_DIMENSION) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "dimension"; + } - if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "fraction"; + if ((type_mask & android::ResTable_map::TYPE_FRACTION) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "fraction"; + } - if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "enum"; + if ((type_mask & android::ResTable_map::TYPE_ENUM) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "enum"; + } - if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "flags"; + if ((type_mask & android::ResTable_map::TYPE_FLAGS) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "flags"; + } } -void Attribute::print(std::ostream* out) const { - *out << "(attr) "; - printMask(out); +void Attribute::Print(std::ostream* out) const { + *out << "(attr) "; + PrintMask(out); - if (!symbols.empty()) { - *out << " [" - << util::joiner(symbols.begin(), symbols.end(), ", ") - << "]"; - } + if (!symbols.empty()) { + *out << " [" << util::Joiner(symbols, ", ") << "]"; + } - if (minInt != std::numeric_limits<int32_t>::min()) { - *out << " min=" << minInt; - } + if (min_int != std::numeric_limits<int32_t>::min()) { + *out << " min=" << min_int; + } - if (maxInt != std::numeric_limits<int32_t>::max()) { - *out << " max=" << maxInt; - } + if (max_int != std::numeric_limits<int32_t>::max()) { + *out << " max=" << max_int; + } - if (isWeak()) { - *out << " [weak]"; - } + if (IsWeak()) { + *out << " [weak]"; + } } -static void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr, +static void BuildAttributeMismatchMessage(DiagMessage* msg, + const Attribute* attr, const Item* value) { - *msg << "expected"; - if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) { - *msg << " boolean"; - } + *msg << "expected"; + if (attr->type_mask & android::ResTable_map::TYPE_BOOLEAN) { + *msg << " boolean"; + } - if (attr->typeMask & android::ResTable_map::TYPE_COLOR) { - *msg << " color"; - } + if (attr->type_mask & android::ResTable_map::TYPE_COLOR) { + *msg << " color"; + } - if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) { - *msg << " dimension"; - } + if (attr->type_mask & android::ResTable_map::TYPE_DIMENSION) { + *msg << " dimension"; + } - if (attr->typeMask & android::ResTable_map::TYPE_ENUM) { - *msg << " enum"; - } + if (attr->type_mask & android::ResTable_map::TYPE_ENUM) { + *msg << " enum"; + } - if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) { - *msg << " flags"; - } + if (attr->type_mask & android::ResTable_map::TYPE_FLAGS) { + *msg << " flags"; + } - if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) { - *msg << " float"; - } + if (attr->type_mask & android::ResTable_map::TYPE_FLOAT) { + *msg << " float"; + } - if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) { - *msg << " fraction"; - } + if (attr->type_mask & android::ResTable_map::TYPE_FRACTION) { + *msg << " fraction"; + } - if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) { - *msg << " integer"; - } + if (attr->type_mask & android::ResTable_map::TYPE_INTEGER) { + *msg << " integer"; + } - if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) { - *msg << " reference"; - } + if (attr->type_mask & android::ResTable_map::TYPE_REFERENCE) { + *msg << " reference"; + } - if (attr->typeMask & android::ResTable_map::TYPE_STRING) { - *msg << " string"; - } + if (attr->type_mask & android::ResTable_map::TYPE_STRING) { + *msg << " string"; + } - *msg << " but got " << *value; -} - -bool Attribute::matches(const Item* item, DiagMessage* outMsg) const { - android::Res_value val = {}; - item->flatten(&val); - - // Always allow references. - const uint32_t mask = typeMask | android::ResTable_map::TYPE_REFERENCE; - if (!(mask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) { - if (outMsg) { - buildAttributeMismatchMessage(outMsg, this, item); - } - return false; - - } else if (ResourceUtils::androidTypeToAttributeTypeMask(val.dataType) & - android::ResTable_map::TYPE_INTEGER) { - if (static_cast<int32_t>(util::deviceToHost32(val.data)) < minInt) { - if (outMsg) { - *outMsg << *item << " is less than minimum integer " << minInt; - } - return false; - } else if (static_cast<int32_t>(util::deviceToHost32(val.data)) > maxInt) { - if (outMsg) { - *outMsg << *item << " is greater than maximum integer " << maxInt; - } - return false; - } - } - return true; + *msg << " but got " << *value; } -bool Style::equals(const Value* value) const { - const Style* other = valueCast<Style>(value); - if (!other) { - return false; - } - if (bool(parent) != bool(other->parent) || - (parent && other->parent && !parent.value().equals(&other->parent.value()))) { - return false; - } - return std::equal(entries.begin(), entries.end(), other->entries.begin(), - [](const Entry& a, const Entry& b) -> bool { - return a.key.equals(&b.key) && a.value->equals(b.value.get()); - }); -} - -Style* Style::clone(StringPool* newPool) const { - Style* style = new Style(); - style->parent = parent; - style->parentInferred = parentInferred; - style->mComment = mComment; - style->mSource = mSource; - for (auto& entry : entries) { - style->entries.push_back(Entry{ - entry.key, - std::unique_ptr<Item>(entry.value->clone(newPool)) - }); +bool Attribute::Matches(const Item* item, DiagMessage* out_msg) const { + android::Res_value val = {}; + item->Flatten(&val); + + // Always allow references. + const uint32_t mask = type_mask | android::ResTable_map::TYPE_REFERENCE; + if (!(mask & ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType))) { + if (out_msg) { + BuildAttributeMismatchMessage(out_msg, this, item); } - return style; + return false; + + } else if (ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType) & + android::ResTable_map::TYPE_INTEGER) { + if (static_cast<int32_t>(util::DeviceToHost32(val.data)) < min_int) { + if (out_msg) { + *out_msg << *item << " is less than minimum integer " << min_int; + } + return false; + } else if (static_cast<int32_t>(util::DeviceToHost32(val.data)) > max_int) { + if (out_msg) { + *out_msg << *item << " is greater than maximum integer " << max_int; + } + return false; + } + } + return true; +} + +bool Style::Equals(const Value* value) const { + const Style* other = ValueCast<Style>(value); + if (!other) { + return false; + } + if (bool(parent) != bool(other->parent) || + (parent && other->parent && + !parent.value().Equals(&other->parent.value()))) { + return false; + } + + if (entries.size() != other->entries.size()) { + return false; + } + + std::vector<const Entry*> sorted_a; + std::transform(entries.begin(), entries.end(), std::back_inserter(sorted_a), + addPointer<const Entry>); + std::sort(sorted_a.begin(), sorted_a.end(), + [](const Entry* a, const Entry* b) -> bool { + return a->key.name < b->key.name; + }); + + std::vector<const Entry*> sorted_b; + std::transform(other->entries.begin(), other->entries.end(), + std::back_inserter(sorted_b), addPointer<const Entry>); + std::sort(sorted_b.begin(), sorted_b.end(), + [](const Entry* a, const Entry* b) -> bool { + return a->key.name < b->key.name; + }); + + return std::equal(sorted_a.begin(), sorted_a.end(), sorted_b.begin(), + [](const Entry* a, const Entry* b) -> bool { + return a->key.Equals(&b->key) && + a->value->Equals(b->value.get()); + }); +} + +Style* Style::Clone(StringPool* new_pool) const { + Style* style = new Style(); + style->parent = parent; + style->parent_inferred = parent_inferred; + style->comment_ = comment_; + style->source_ = source_; + for (auto& entry : entries) { + style->entries.push_back( + Entry{entry.key, std::unique_ptr<Item>(entry.value->Clone(new_pool))}); + } + return style; +} + +void Style::Print(std::ostream* out) const { + *out << "(style) "; + if (parent && parent.value().name) { + if (parent.value().private_reference) { + *out << "*"; + } + *out << parent.value().name.value(); + } + *out << " [" << util::Joiner(entries, ", ") << "]"; +} + +static ::std::ostream& operator<<(::std::ostream& out, + const Style::Entry& value) { + if (value.key.name) { + out << value.key.name.value(); + } else if (value.key.id) { + out << value.key.id.value(); + } else { + out << "???"; + } + out << " = "; + value.value->Print(&out); + return out; +} + +bool Array::Equals(const Value* value) const { + const Array* other = ValueCast<Array>(value); + if (!other) { + return false; + } + + if (items.size() != other->items.size()) { + return false; + } + + return std::equal(items.begin(), items.end(), other->items.begin(), + [](const std::unique_ptr<Item>& a, + const std::unique_ptr<Item>& b) -> bool { + return a->Equals(b.get()); + }); } -void Style::print(std::ostream* out) const { - *out << "(style) "; - if (parent && parent.value().name) { - if (parent.value().privateReference) { - *out << "*"; - } - *out << parent.value().name.value(); - } - *out << " [" - << util::joiner(entries.begin(), entries.end(), ", ") - << "]"; +Array* Array::Clone(StringPool* new_pool) const { + Array* array = new Array(); + array->comment_ = comment_; + array->source_ = source_; + for (auto& item : items) { + array->items.emplace_back(std::unique_ptr<Item>(item->Clone(new_pool))); + } + return array; } -static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value) { - if (value.key.name) { - out << value.key.name.value(); - } else { - out << "???"; - } - out << " = "; - value.value->print(&out); - return out; +void Array::Print(std::ostream* out) const { + *out << "(array) [" << util::Joiner(items, ", ") << "]"; } -bool Array::equals(const Value* value) const { - const Array* other = valueCast<Array>(value); - if (!other) { - return false; - } +bool Plural::Equals(const Value* value) const { + const Plural* other = ValueCast<Plural>(value); + if (!other) { + return false; + } + + if (values.size() != other->values.size()) { + return false; + } - return std::equal(items.begin(), items.end(), other->items.begin(), - [](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool { - return a->equals(b.get()); - }); + return std::equal(values.begin(), values.end(), other->values.begin(), + [](const std::unique_ptr<Item>& a, + const std::unique_ptr<Item>& b) -> bool { + if (bool(a) != bool(b)) { + return false; + } + return bool(a) == bool(b) || a->Equals(b.get()); + }); } -Array* Array::clone(StringPool* newPool) const { - Array* array = new Array(); - array->mComment = mComment; - array->mSource = mSource; - for (auto& item : items) { - array->items.emplace_back(std::unique_ptr<Item>(item->clone(newPool))); +Plural* Plural::Clone(StringPool* new_pool) const { + Plural* p = new Plural(); + p->comment_ = comment_; + p->source_ = source_; + const size_t count = values.size(); + for (size_t i = 0; i < count; i++) { + if (values[i]) { + p->values[i] = std::unique_ptr<Item>(values[i]->Clone(new_pool)); } - return array; + } + return p; } -void Array::print(std::ostream* out) const { - *out << "(array) [" - << util::joiner(items.begin(), items.end(), ", ") - << "]"; -} +void Plural::Print(std::ostream* out) const { + *out << "(plural)"; + if (values[Zero]) { + *out << " zero=" << *values[Zero]; + } -bool Plural::equals(const Value* value) const { - const Plural* other = valueCast<Plural>(value); - if (!other) { - return false; - } + if (values[One]) { + *out << " one=" << *values[One]; + } - return std::equal(values.begin(), values.end(), other->values.begin(), - [](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool { - if (bool(a) != bool(b)) { - return false; - } - return bool(a) == bool(b) || a->equals(b.get()); - }); -} - -Plural* Plural::clone(StringPool* newPool) const { - Plural* p = new Plural(); - p->mComment = mComment; - p->mSource = mSource; - const size_t count = values.size(); - for (size_t i = 0; i < count; i++) { - if (values[i]) { - p->values[i] = std::unique_ptr<Item>(values[i]->clone(newPool)); - } - } - return p; + if (values[Two]) { + *out << " two=" << *values[Two]; + } + + if (values[Few]) { + *out << " few=" << *values[Few]; + } + + if (values[Many]) { + *out << " many=" << *values[Many]; + } } -void Plural::print(std::ostream* out) const { - *out << "(plural)"; - if (values[Zero]) { - *out << " zero=" << *values[Zero]; - } +static ::std::ostream& operator<<(::std::ostream& out, + const std::unique_ptr<Item>& item) { + return out << *item; +} - if (values[One]) { - *out << " one=" << *values[One]; - } +bool Styleable::Equals(const Value* value) const { + const Styleable* other = ValueCast<Styleable>(value); + if (!other) { + return false; + } - if (values[Two]) { - *out << " two=" << *values[Two]; - } + if (entries.size() != other->entries.size()) { + return false; + } - if (values[Few]) { - *out << " few=" << *values[Few]; - } + return std::equal(entries.begin(), entries.end(), other->entries.begin(), + [](const Reference& a, const Reference& b) -> bool { + return a.Equals(&b); + }); +} - if (values[Many]) { - *out << " many=" << *values[Many]; - } +Styleable* Styleable::Clone(StringPool* /*new_pool*/) const { + return new Styleable(*this); } -static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) { - return out << *item; +void Styleable::Print(std::ostream* out) const { + *out << "(styleable) " + << " [" << util::Joiner(entries, ", ") << "]"; } -bool Styleable::equals(const Value* value) const { - const Styleable* other = valueCast<Styleable>(value); - if (!other) { - return false; - } - return std::equal(entries.begin(), entries.end(), other->entries.begin(), - [](const Reference& a, const Reference& b) -> bool { - return a.equals(&b); - }); +bool operator<(const Reference& a, const Reference& b) { + int cmp = a.name.value_or_default({}).compare(b.name.value_or_default({})); + if (cmp != 0) return cmp < 0; + return a.id < b.id; } -Styleable* Styleable::clone(StringPool* /*newPool*/) const { - return new Styleable(*this); +bool operator==(const Reference& a, const Reference& b) { + return a.name == b.name && a.id == b.id; } -void Styleable::print(std::ostream* out) const { - *out << "(styleable) " << " [" - << util::joiner(entries.begin(), entries.end(), ", ") - << "]"; +bool operator!=(const Reference& a, const Reference& b) { + return a.name != b.name || a.id != b.id; +} + +struct NameOnlyComparator { + bool operator()(const Reference& a, const Reference& b) const { + return a.name < b.name; + } +}; + +void Styleable::MergeWith(Styleable* other) { + // Compare only names, because some References may already have their IDs + // assigned + // (framework IDs that don't change). + std::set<Reference, NameOnlyComparator> references; + references.insert(entries.begin(), entries.end()); + references.insert(other->entries.begin(), other->entries.end()); + entries.clear(); + entries.reserve(references.size()); + entries.insert(entries.end(), references.begin(), references.end()); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index 9a6f1a1dff96..ea73615e372a 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -17,17 +17,18 @@ #ifndef AAPT_RESOURCE_VALUES_H #define AAPT_RESOURCE_VALUES_H +#include <array> +#include <ostream> +#include <vector> + +#include "androidfw/ResourceTypes.h" + #include "Diagnostics.h" #include "Resource.h" #include "StringPool.h" #include "io/File.h" #include "util/Maybe.h" -#include <array> -#include <androidfw/ResourceTypes.h> -#include <ostream> -#include <vector> - namespace aapt { struct RawValueVisitor; @@ -40,84 +41,65 @@ struct RawValueVisitor; * but it is the simplest strategy. */ struct Value { - virtual ~Value() = default; - - /** - * Whether this value is weak and can be overridden without - * warning or error. Default is false. - */ - bool isWeak() const { - return mWeak; - } - - void setWeak(bool val) { - mWeak = val; - } - - // Whether the value is marked as translateable. - // This does not persist when flattened. - // It is only used during compilation phase. - void setTranslateable(bool val) { - mTranslateable = val; - } - - // Default true. - bool isTranslateable() const { - return mTranslateable; - } - - /** - * Returns the source where this value was defined. - */ - const Source& getSource() const { - return mSource; - } - - void setSource(const Source& source) { - mSource = source; - } - - void setSource(Source&& source) { - mSource = std::move(source); - } - - /** - * Returns the comment that was associated with this resource. - */ - StringPiece16 getComment() const { - return mComment; - } - - void setComment(const StringPiece16& str) { - mComment = str.toString(); - } - - void setComment(std::u16string&& str) { - mComment = std::move(str); - } - - virtual bool equals(const Value* value) const = 0; - - /** - * Calls the appropriate overload of ValueVisitor. - */ - virtual void accept(RawValueVisitor* visitor) = 0; - - /** - * Clone the value. - */ - virtual Value* clone(StringPool* newPool) const = 0; - - /** - * Human readable printout of this value. - */ - virtual void print(std::ostream* out) const = 0; - -protected: - Source mSource; - std::u16string mComment; - bool mWeak = false; - bool mTranslateable = true; + virtual ~Value() = default; + + /** + * Whether this value is weak and can be overridden without + * warning or error. Default is false. + */ + bool IsWeak() const { return weak_; } + + void SetWeak(bool val) { weak_ = val; } + + // Whether the value is marked as translateable. + // This does not persist when flattened. + // It is only used during compilation phase. + void SetTranslateable(bool val) { translateable_ = val; } + + // Default true. + bool IsTranslateable() const { return translateable_; } + + /** + * Returns the source where this value was defined. + */ + const Source& GetSource() const { return source_; } + + void SetSource(const Source& source) { source_ = source; } + + void SetSource(Source&& source) { source_ = std::move(source); } + + /** + * Returns the comment that was associated with this resource. + */ + const std::string& GetComment() const { return comment_; } + + void SetComment(const StringPiece& str) { comment_ = str.ToString(); } + + void SetComment(std::string&& str) { comment_ = std::move(str); } + + virtual bool Equals(const Value* value) const = 0; + + /** + * Calls the appropriate overload of ValueVisitor. + */ + virtual void Accept(RawValueVisitor* visitor) = 0; + + /** + * Clone the value. new_pool is the new StringPool that + * any resources with strings should use when copying their string. + */ + virtual Value* Clone(StringPool* new_pool) const = 0; + + /** + * Human readable printout of this value. + */ + virtual void Print(std::ostream* out) const = 0; + + protected: + Source source_; + std::string comment_; + bool weak_ = false; + bool translateable_ = true; }; /** @@ -125,23 +107,24 @@ protected: */ template <typename Derived> struct BaseValue : public Value { - void accept(RawValueVisitor* visitor) override; + void Accept(RawValueVisitor* visitor) override; }; /** * A resource item with a single value. This maps to android::ResTable_entry. */ struct Item : public Value { - /** - * Clone the Item. - */ - virtual Item* clone(StringPool* newPool) const override = 0; - - /** - * Fills in an android::Res_value structure with this Item's binary representation. - * Returns false if an error occurred. - */ - virtual bool flatten(android::Res_value* outValue) const = 0; + /** + * Clone the Item. + */ + virtual Item* Clone(StringPool* new_pool) const override = 0; + + /** + * Fills in an android::Res_value structure with this Item's binary + * representation. + * Returns false if an error occurred. + */ + virtual bool Flatten(android::Res_value* out_value) const = 0; }; /** @@ -149,45 +132,51 @@ struct Item : public Value { */ template <typename Derived> struct BaseItem : public Item { - void accept(RawValueVisitor* visitor) override; + void Accept(RawValueVisitor* visitor) override; }; /** - * A reference to another resource. This maps to android::Res_value::TYPE_REFERENCE. + * A reference to another resource. This maps to + * android::Res_value::TYPE_REFERENCE. * - * A reference can be symbolic (with the name set to a valid resource name) or be + * A reference can be symbolic (with the name set to a valid resource name) or + * be * numeric (the id is set to a valid resource ID). */ struct Reference : public BaseItem<Reference> { - enum class Type { - kResource, - kAttribute, - }; - - Maybe<ResourceName> name; - Maybe<ResourceId> id; - Reference::Type referenceType; - bool privateReference = false; - - Reference(); - explicit Reference(const ResourceNameRef& n, Type type = Type::kResource); - explicit Reference(const ResourceId& i, Type type = Type::kResource); - - bool equals(const Value* value) const override; - bool flatten(android::Res_value* outValue) const override; - Reference* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + enum class Type { + kResource, + kAttribute, + }; + + Maybe<ResourceName> name; + Maybe<ResourceId> id; + Reference::Type reference_type; + bool private_reference = false; + + Reference(); + explicit Reference(const ResourceNameRef& n, Type type = Type::kResource); + explicit Reference(const ResourceId& i, Type type = Type::kResource); + Reference(const ResourceNameRef& n, const ResourceId& i); + + bool Equals(const Value* value) const override; + bool Flatten(android::Res_value* out_value) const override; + Reference* Clone(StringPool* new_pool) const override; + void Print(std::ostream* out) const override; }; +bool operator<(const Reference&, const Reference&); +bool operator==(const Reference&, const Reference&); + /** * An ID resource. Has no real value, just a place holder. */ struct Id : public BaseItem<Id> { - Id() { mWeak = true; } - bool equals(const Value* value) const override; - bool flatten(android::Res_value* out) const override; - Id* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + Id() { weak_ = true; } + bool Equals(const Value* value) const override; + bool Flatten(android::Res_value* out) const override; + Id* Clone(StringPool* new_pool) const override; + void Print(std::ostream* out) const override; }; /** @@ -196,163 +185,157 @@ struct Id : public BaseItem<Id> { * end up in the final resource table. */ struct RawString : public BaseItem<RawString> { - StringPool::Ref value; + StringPool::Ref value; - explicit RawString(const StringPool::Ref& ref); + explicit RawString(const StringPool::Ref& ref); - bool equals(const Value* value) const override; - bool flatten(android::Res_value* outValue) const override; - RawString* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + bool Equals(const Value* value) const override; + bool Flatten(android::Res_value* out_value) const override; + RawString* Clone(StringPool* new_pool) const override; + void Print(std::ostream* out) const override; }; struct String : public BaseItem<String> { - StringPool::Ref value; + StringPool::Ref value; - explicit String(const StringPool::Ref& ref); + explicit String(const StringPool::Ref& ref); - bool equals(const Value* value) const override; - bool flatten(android::Res_value* outValue) const override; - String* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + bool Equals(const Value* value) const override; + bool Flatten(android::Res_value* out_value) const override; + String* Clone(StringPool* new_pool) const override; + void Print(std::ostream* out) const override; }; struct StyledString : public BaseItem<StyledString> { - StringPool::StyleRef value; + StringPool::StyleRef value; - explicit StyledString(const StringPool::StyleRef& ref); + explicit StyledString(const StringPool::StyleRef& ref); - bool equals(const Value* value) const override; - bool flatten(android::Res_value* outValue) const override; - StyledString* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + bool Equals(const Value* value) const override; + bool Flatten(android::Res_value* out_value) const override; + StyledString* Clone(StringPool* new_pool) const override; + void Print(std::ostream* out) const override; }; struct FileReference : public BaseItem<FileReference> { - StringPool::Ref path; + StringPool::Ref path; - /** - * A handle to the file object from which this file can be read. - */ - io::IFile* file = nullptr; + /** + * A handle to the file object from which this file can be read. + */ + io::IFile* file = nullptr; - FileReference() = default; - explicit FileReference(const StringPool::Ref& path); + FileReference() = default; + explicit FileReference(const StringPool::Ref& path); - bool equals(const Value* value) const override; - bool flatten(android::Res_value* outValue) const override; - FileReference* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + bool Equals(const Value* value) const override; + bool Flatten(android::Res_value* out_value) const override; + FileReference* Clone(StringPool* new_pool) const override; + void Print(std::ostream* out) const override; }; /** * Represents any other android::Res_value. */ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> { - android::Res_value value; + android::Res_value value; - BinaryPrimitive() = default; - explicit BinaryPrimitive(const android::Res_value& val); - BinaryPrimitive(uint8_t dataType, uint32_t data); + BinaryPrimitive() = default; + explicit BinaryPrimitive(const android::Res_value& val); + BinaryPrimitive(uint8_t dataType, uint32_t data); - bool equals(const Value* value) const override; - bool flatten(android::Res_value* outValue) const override; - BinaryPrimitive* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + bool Equals(const Value* value) const override; + bool Flatten(android::Res_value* out_value) const override; + BinaryPrimitive* Clone(StringPool* new_pool) const override; + void Print(std::ostream* out) const override; }; struct Attribute : public BaseValue<Attribute> { - struct Symbol { - Reference symbol; - uint32_t value; - }; - - uint32_t typeMask; - int32_t minInt; - int32_t maxInt; - std::vector<Symbol> symbols; - - explicit Attribute(bool w, uint32_t t = 0u); - - bool equals(const Value* value) const override; - Attribute* clone(StringPool* newPool) const override; - void printMask(std::ostream* out) const; - void print(std::ostream* out) const override; - bool matches(const Item* item, DiagMessage* outMsg) const; + struct Symbol { + Reference symbol; + uint32_t value; + }; + + uint32_t type_mask; + int32_t min_int; + int32_t max_int; + std::vector<Symbol> symbols; + + explicit Attribute(bool w, uint32_t t = 0u); + + bool Equals(const Value* value) const override; + Attribute* Clone(StringPool* new_pool) const override; + void PrintMask(std::ostream* out) const; + void Print(std::ostream* out) const override; + bool Matches(const Item* item, DiagMessage* out_msg) const; }; struct Style : public BaseValue<Style> { - struct Entry { - Reference key; - std::unique_ptr<Item> value; - }; + struct Entry { + Reference key; + std::unique_ptr<Item> value; + }; - Maybe<Reference> parent; + Maybe<Reference> parent; - /** - * If set to true, the parent was auto inferred from the - * style's name. - */ - bool parentInferred = false; + /** + * If set to true, the parent was auto inferred from the + * style's name. + */ + bool parent_inferred = false; - std::vector<Entry> entries; + std::vector<Entry> entries; - bool equals(const Value* value) const override; - Style* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + bool Equals(const Value* value) const override; + Style* Clone(StringPool* new_pool) const override; + void Print(std::ostream* out) const override; }; struct Array : public BaseValue<Array> { - std::vector<std::unique_ptr<Item>> items; + std::vector<std::unique_ptr<Item>> items; - bool equals(const Value* value) const override; - Array* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + bool Equals(const Value* value) const override; + Array* Clone(StringPool* new_pool) const override; + void Print(std::ostream* out) const override; }; struct Plural : public BaseValue<Plural> { - enum { - Zero = 0, - One, - Two, - Few, - Many, - Other, - Count - }; - - std::array<std::unique_ptr<Item>, Count> values; - - bool equals(const Value* value) const override; - Plural* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + enum { Zero = 0, One, Two, Few, Many, Other, Count }; + + std::array<std::unique_ptr<Item>, Count> values; + + bool Equals(const Value* value) const override; + Plural* Clone(StringPool* new_pool) const override; + void Print(std::ostream* out) const override; }; struct Styleable : public BaseValue<Styleable> { - std::vector<Reference> entries; + std::vector<Reference> entries; - bool equals(const Value* value) const override; - Styleable* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + bool Equals(const Value* value) const override; + Styleable* Clone(StringPool* newPool) const override; + void Print(std::ostream* out) const override; + void MergeWith(Styleable* styleable); }; /** * Stream operator for printing Value objects. */ inline ::std::ostream& operator<<(::std::ostream& out, const Value& value) { - value.print(&out); - return out; + value.Print(&out); + return out; } -inline ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) { - if (s.symbol.name) { - out << s.symbol.name.value().entry; - } else { - out << "???"; - } - return out << "=" << s.value; +inline ::std::ostream& operator<<(::std::ostream& out, + const Attribute::Symbol& s) { + if (s.symbol.name) { + out << s.symbol.name.value().entry; + } else { + out << "???"; + } + return out << "=" << s.value; } -} // namespace aapt +} // namespace aapt -#endif // AAPT_RESOURCE_VALUES_H +#endif // AAPT_RESOURCE_VALUES_H diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp index 48dc521d843c..6acb4d3eb850 100644 --- a/tools/aapt2/Resource_test.cpp +++ b/tools/aapt2/Resource_test.cpp @@ -14,103 +14,107 @@ * limitations under the License. */ -#include <gtest/gtest.h> - #include "Resource.h" +#include "test/Test.h" + namespace aapt { TEST(ResourceTypeTest, ParseResourceTypes) { - const ResourceType* type = parseResourceType(u"anim"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kAnim); + const ResourceType* type = ParseResourceType("anim"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kAnim); + + type = ParseResourceType("animator"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kAnimator); - type = parseResourceType(u"animator"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kAnimator); + type = ParseResourceType("array"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kArray); - type = parseResourceType(u"array"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kArray); + type = ParseResourceType("attr"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kAttr); - type = parseResourceType(u"attr"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kAttr); + type = ParseResourceType("^attr-private"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kAttrPrivate); - type = parseResourceType(u"^attr-private"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kAttrPrivate); + type = ParseResourceType("bool"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kBool); - type = parseResourceType(u"bool"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kBool); + type = ParseResourceType("color"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kColor); - type = parseResourceType(u"color"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kColor); + type = ParseResourceType("dimen"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kDimen); - type = parseResourceType(u"dimen"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kDimen); + type = ParseResourceType("drawable"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kDrawable); - type = parseResourceType(u"drawable"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kDrawable); + type = ParseResourceType("font"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kFont); - type = parseResourceType(u"fraction"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kFraction); + type = ParseResourceType("fraction"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kFraction); - type = parseResourceType(u"id"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kId); + type = ParseResourceType("id"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kId); - type = parseResourceType(u"integer"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kInteger); + type = ParseResourceType("integer"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kInteger); - type = parseResourceType(u"interpolator"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kInterpolator); + type = ParseResourceType("interpolator"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kInterpolator); - type = parseResourceType(u"layout"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kLayout); + type = ParseResourceType("layout"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kLayout); - type = parseResourceType(u"menu"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kMenu); + type = ParseResourceType("menu"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kMenu); - type = parseResourceType(u"mipmap"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kMipmap); + type = ParseResourceType("mipmap"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kMipmap); - type = parseResourceType(u"plurals"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kPlurals); + type = ParseResourceType("plurals"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kPlurals); - type = parseResourceType(u"raw"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kRaw); + type = ParseResourceType("raw"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kRaw); - type = parseResourceType(u"string"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kString); + type = ParseResourceType("string"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kString); - type = parseResourceType(u"style"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kStyle); + type = ParseResourceType("style"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kStyle); - type = parseResourceType(u"transition"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kTransition); + type = ParseResourceType("transition"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kTransition); - type = parseResourceType(u"xml"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kXml); + type = ParseResourceType("xml"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kXml); - type = parseResourceType(u"blahaha"); - EXPECT_EQ(type, nullptr); + type = ParseResourceType("blahaha"); + EXPECT_EQ(type, nullptr); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp index 00bac05fb3b4..c7f920ac6c58 100644 --- a/tools/aapt2/SdkConstants.cpp +++ b/tools/aapt2/SdkConstants.cpp @@ -23,716 +23,725 @@ namespace aapt { +static const char* sDevelopmentSdkCodeName = "O"; +static int sDevelopmentSdkLevel = 26; + static const std::vector<std::pair<uint16_t, size_t>> sAttrIdMap = { - { 0x021c, 1 }, - { 0x021d, 2 }, - { 0x0269, SDK_CUPCAKE }, - { 0x028d, SDK_DONUT }, - { 0x02ad, SDK_ECLAIR }, - { 0x02b3, SDK_ECLAIR_0_1 }, - { 0x02b5, SDK_ECLAIR_MR1 }, - { 0x02bd, SDK_FROYO }, - { 0x02cb, SDK_GINGERBREAD }, - { 0x0361, SDK_HONEYCOMB }, - { 0x0363, SDK_HONEYCOMB_MR1 }, - { 0x0366, SDK_HONEYCOMB_MR2 }, - { 0x03a6, SDK_ICE_CREAM_SANDWICH }, - { 0x03ae, SDK_JELLY_BEAN }, - { 0x03cc, SDK_JELLY_BEAN_MR1 }, - { 0x03da, SDK_JELLY_BEAN_MR2 }, - { 0x03f1, SDK_KITKAT }, - { 0x03f6, SDK_KITKAT_WATCH }, - { 0x04ce, SDK_LOLLIPOP }, + {0x021c, 1}, + {0x021d, 2}, + {0x0269, SDK_CUPCAKE}, + {0x028d, SDK_DONUT}, + {0x02ad, SDK_ECLAIR}, + {0x02b3, SDK_ECLAIR_0_1}, + {0x02b5, SDK_ECLAIR_MR1}, + {0x02bd, SDK_FROYO}, + {0x02cb, SDK_GINGERBREAD}, + {0x0361, SDK_HONEYCOMB}, + {0x0363, SDK_HONEYCOMB_MR1}, + {0x0366, SDK_HONEYCOMB_MR2}, + {0x03a6, SDK_ICE_CREAM_SANDWICH}, + {0x03ae, SDK_JELLY_BEAN}, + {0x03cc, SDK_JELLY_BEAN_MR1}, + {0x03da, SDK_JELLY_BEAN_MR2}, + {0x03f1, SDK_KITKAT}, + {0x03f6, SDK_KITKAT_WATCH}, + {0x04ce, SDK_LOLLIPOP}, }; -static bool lessEntryId(const std::pair<uint16_t, size_t>& p, uint16_t entryId) { - return p.first < entryId; +static bool less_entry_id(const std::pair<uint16_t, size_t>& p, + uint16_t entryId) { + return p.first < entryId; } -size_t findAttributeSdkLevel(const ResourceId& id) { - if (id.packageId() != 0x01 && id.typeId() != 0x01) { - return 0; - } - auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(), id.entryId(), lessEntryId); - if (iter == sAttrIdMap.end()) { - return SDK_LOLLIPOP_MR1; - } - return iter->second; +size_t 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()) { + return SDK_LOLLIPOP_MR1; + } + return iter->second; } -static const std::unordered_map<std::u16string, size_t> sAttrMap = { - { u"marqueeRepeatLimit", 2 }, - { u"windowNoDisplay", 3 }, - { u"backgroundDimEnabled", 3 }, - { u"inputType", 3 }, - { u"isDefault", 3 }, - { u"windowDisablePreview", 3 }, - { u"privateImeOptions", 3 }, - { u"editorExtras", 3 }, - { u"settingsActivity", 3 }, - { u"fastScrollEnabled", 3 }, - { u"reqTouchScreen", 3 }, - { u"reqKeyboardType", 3 }, - { u"reqHardKeyboard", 3 }, - { u"reqNavigation", 3 }, - { u"windowSoftInputMode", 3 }, - { u"imeFullscreenBackground", 3 }, - { u"noHistory", 3 }, - { u"headerDividersEnabled", 3 }, - { u"footerDividersEnabled", 3 }, - { u"candidatesTextStyleSpans", 3 }, - { u"smoothScrollbar", 3 }, - { u"reqFiveWayNav", 3 }, - { u"keyBackground", 3 }, - { u"keyTextSize", 3 }, - { u"labelTextSize", 3 }, - { u"keyTextColor", 3 }, - { u"keyPreviewLayout", 3 }, - { u"keyPreviewOffset", 3 }, - { u"keyPreviewHeight", 3 }, - { u"verticalCorrection", 3 }, - { u"popupLayout", 3 }, - { u"state_long_pressable", 3 }, - { u"keyWidth", 3 }, - { u"keyHeight", 3 }, - { u"horizontalGap", 3 }, - { u"verticalGap", 3 }, - { u"rowEdgeFlags", 3 }, - { u"codes", 3 }, - { u"popupKeyboard", 3 }, - { u"popupCharacters", 3 }, - { u"keyEdgeFlags", 3 }, - { u"isModifier", 3 }, - { u"isSticky", 3 }, - { u"isRepeatable", 3 }, - { u"iconPreview", 3 }, - { u"keyOutputText", 3 }, - { u"keyLabel", 3 }, - { u"keyIcon", 3 }, - { u"keyboardMode", 3 }, - { u"isScrollContainer", 3 }, - { u"fillEnabled", 3 }, - { u"updatePeriodMillis", 3 }, - { u"initialLayout", 3 }, - { u"voiceSearchMode", 3 }, - { u"voiceLanguageModel", 3 }, - { u"voicePromptText", 3 }, - { u"voiceLanguage", 3 }, - { u"voiceMaxResults", 3 }, - { u"bottomOffset", 3 }, - { u"topOffset", 3 }, - { u"allowSingleTap", 3 }, - { u"handle", 3 }, - { u"content", 3 }, - { u"animateOnClick", 3 }, - { u"configure", 3 }, - { u"hapticFeedbackEnabled", 3 }, - { u"innerRadius", 3 }, - { u"thickness", 3 }, - { u"sharedUserLabel", 3 }, - { u"dropDownWidth", 3 }, - { u"dropDownAnchor", 3 }, - { u"imeOptions", 3 }, - { u"imeActionLabel", 3 }, - { u"imeActionId", 3 }, - { u"imeExtractEnterAnimation", 3 }, - { u"imeExtractExitAnimation", 3 }, - { u"tension", 4 }, - { u"extraTension", 4 }, - { u"anyDensity", 4 }, - { u"searchSuggestThreshold", 4 }, - { u"includeInGlobalSearch", 4 }, - { u"onClick", 4 }, - { u"targetSdkVersion", 4 }, - { u"maxSdkVersion", 4 }, - { u"testOnly", 4 }, - { u"contentDescription", 4 }, - { u"gestureStrokeWidth", 4 }, - { u"gestureColor", 4 }, - { u"uncertainGestureColor", 4 }, - { u"fadeOffset", 4 }, - { u"fadeDuration", 4 }, - { u"gestureStrokeType", 4 }, - { u"gestureStrokeLengthThreshold", 4 }, - { u"gestureStrokeSquarenessThreshold", 4 }, - { u"gestureStrokeAngleThreshold", 4 }, - { u"eventsInterceptionEnabled", 4 }, - { u"fadeEnabled", 4 }, - { u"backupAgent", 4 }, - { u"allowBackup", 4 }, - { u"glEsVersion", 4 }, - { u"queryAfterZeroResults", 4 }, - { u"dropDownHeight", 4 }, - { u"smallScreens", 4 }, - { u"normalScreens", 4 }, - { u"largeScreens", 4 }, - { u"progressBarStyleInverse", 4 }, - { u"progressBarStyleSmallInverse", 4 }, - { u"progressBarStyleLargeInverse", 4 }, - { u"searchSettingsDescription", 4 }, - { u"textColorPrimaryInverseDisableOnly", 4 }, - { u"autoUrlDetect", 4 }, - { u"resizeable", 4 }, - { u"required", 5 }, - { u"accountType", 5 }, - { u"contentAuthority", 5 }, - { u"userVisible", 5 }, - { u"windowShowWallpaper", 5 }, - { u"wallpaperOpenEnterAnimation", 5 }, - { u"wallpaperOpenExitAnimation", 5 }, - { u"wallpaperCloseEnterAnimation", 5 }, - { u"wallpaperCloseExitAnimation", 5 }, - { u"wallpaperIntraOpenEnterAnimation", 5 }, - { u"wallpaperIntraOpenExitAnimation", 5 }, - { u"wallpaperIntraCloseEnterAnimation", 5 }, - { u"wallpaperIntraCloseExitAnimation", 5 }, - { u"supportsUploading", 5 }, - { u"killAfterRestore", 5 }, - { u"restoreNeedsApplication", 5 }, - { u"smallIcon", 5 }, - { u"accountPreferences", 5 }, - { u"textAppearanceSearchResultSubtitle", 5 }, - { u"textAppearanceSearchResultTitle", 5 }, - { u"summaryColumn", 5 }, - { u"detailColumn", 5 }, - { u"detailSocialSummary", 5 }, - { u"thumbnail", 5 }, - { u"detachWallpaper", 5 }, - { u"finishOnCloseSystemDialogs", 5 }, - { u"scrollbarFadeDuration", 5 }, - { u"scrollbarDefaultDelayBeforeFade", 5 }, - { u"fadeScrollbars", 5 }, - { u"colorBackgroundCacheHint", 5 }, - { u"dropDownHorizontalOffset", 5 }, - { u"dropDownVerticalOffset", 5 }, - { u"quickContactBadgeStyleWindowSmall", 6 }, - { u"quickContactBadgeStyleWindowMedium", 6 }, - { u"quickContactBadgeStyleWindowLarge", 6 }, - { u"quickContactBadgeStyleSmallWindowSmall", 6 }, - { u"quickContactBadgeStyleSmallWindowMedium", 6 }, - { u"quickContactBadgeStyleSmallWindowLarge", 6 }, - { u"author", 7 }, - { u"autoStart", 7 }, - { u"expandableListViewWhiteStyle", 8 }, - { u"installLocation", 8 }, - { u"vmSafeMode", 8 }, - { u"webTextViewStyle", 8 }, - { u"restoreAnyVersion", 8 }, - { u"tabStripLeft", 8 }, - { u"tabStripRight", 8 }, - { u"tabStripEnabled", 8 }, - { u"logo", 9 }, - { u"xlargeScreens", 9 }, - { u"immersive", 9 }, - { u"overScrollMode", 9 }, - { u"overScrollHeader", 9 }, - { u"overScrollFooter", 9 }, - { u"filterTouchesWhenObscured", 9 }, - { u"textSelectHandleLeft", 9 }, - { u"textSelectHandleRight", 9 }, - { u"textSelectHandle", 9 }, - { u"textSelectHandleWindowStyle", 9 }, - { u"popupAnimationStyle", 9 }, - { u"screenSize", 9 }, - { u"screenDensity", 9 }, - { u"allContactsName", 11 }, - { u"windowActionBar", 11 }, - { u"actionBarStyle", 11 }, - { u"navigationMode", 11 }, - { u"displayOptions", 11 }, - { u"subtitle", 11 }, - { u"customNavigationLayout", 11 }, - { u"hardwareAccelerated", 11 }, - { u"measureWithLargestChild", 11 }, - { u"animateFirstView", 11 }, - { u"dropDownSpinnerStyle", 11 }, - { u"actionDropDownStyle", 11 }, - { u"actionButtonStyle", 11 }, - { u"showAsAction", 11 }, - { u"previewImage", 11 }, - { u"actionModeBackground", 11 }, - { u"actionModeCloseDrawable", 11 }, - { u"windowActionModeOverlay", 11 }, - { u"valueFrom", 11 }, - { u"valueTo", 11 }, - { u"valueType", 11 }, - { u"propertyName", 11 }, - { u"ordering", 11 }, - { u"fragment", 11 }, - { u"windowActionBarOverlay", 11 }, - { u"fragmentOpenEnterAnimation", 11 }, - { u"fragmentOpenExitAnimation", 11 }, - { u"fragmentCloseEnterAnimation", 11 }, - { u"fragmentCloseExitAnimation", 11 }, - { u"fragmentFadeEnterAnimation", 11 }, - { u"fragmentFadeExitAnimation", 11 }, - { u"actionBarSize", 11 }, - { u"imeSubtypeLocale", 11 }, - { u"imeSubtypeMode", 11 }, - { u"imeSubtypeExtraValue", 11 }, - { u"splitMotionEvents", 11 }, - { u"listChoiceBackgroundIndicator", 11 }, - { u"spinnerMode", 11 }, - { u"animateLayoutChanges", 11 }, - { u"actionBarTabStyle", 11 }, - { u"actionBarTabBarStyle", 11 }, - { u"actionBarTabTextStyle", 11 }, - { u"actionOverflowButtonStyle", 11 }, - { u"actionModeCloseButtonStyle", 11 }, - { u"titleTextStyle", 11 }, - { u"subtitleTextStyle", 11 }, - { u"iconifiedByDefault", 11 }, - { u"actionLayout", 11 }, - { u"actionViewClass", 11 }, - { u"activatedBackgroundIndicator", 11 }, - { u"state_activated", 11 }, - { u"listPopupWindowStyle", 11 }, - { u"popupMenuStyle", 11 }, - { u"textAppearanceLargePopupMenu", 11 }, - { u"textAppearanceSmallPopupMenu", 11 }, - { u"breadCrumbTitle", 11 }, - { u"breadCrumbShortTitle", 11 }, - { u"listDividerAlertDialog", 11 }, - { u"textColorAlertDialogListItem", 11 }, - { u"loopViews", 11 }, - { u"dialogTheme", 11 }, - { u"alertDialogTheme", 11 }, - { u"dividerVertical", 11 }, - { u"homeAsUpIndicator", 11 }, - { u"enterFadeDuration", 11 }, - { u"exitFadeDuration", 11 }, - { u"selectableItemBackground", 11 }, - { u"autoAdvanceViewId", 11 }, - { u"useIntrinsicSizeAsMinimum", 11 }, - { u"actionModeCutDrawable", 11 }, - { u"actionModeCopyDrawable", 11 }, - { u"actionModePasteDrawable", 11 }, - { u"textEditPasteWindowLayout", 11 }, - { u"textEditNoPasteWindowLayout", 11 }, - { u"textIsSelectable", 11 }, - { u"windowEnableSplitTouch", 11 }, - { u"indeterminateProgressStyle", 11 }, - { u"progressBarPadding", 11 }, - { u"animationResolution", 11 }, - { u"state_accelerated", 11 }, - { u"baseline", 11 }, - { u"homeLayout", 11 }, - { u"opacity", 11 }, - { u"alpha", 11 }, - { u"transformPivotX", 11 }, - { u"transformPivotY", 11 }, - { u"translationX", 11 }, - { u"translationY", 11 }, - { u"scaleX", 11 }, - { u"scaleY", 11 }, - { u"rotation", 11 }, - { u"rotationX", 11 }, - { u"rotationY", 11 }, - { u"showDividers", 11 }, - { u"dividerPadding", 11 }, - { u"borderlessButtonStyle", 11 }, - { u"dividerHorizontal", 11 }, - { u"itemPadding", 11 }, - { u"buttonBarStyle", 11 }, - { u"buttonBarButtonStyle", 11 }, - { u"segmentedButtonStyle", 11 }, - { u"staticWallpaperPreview", 11 }, - { u"allowParallelSyncs", 11 }, - { u"isAlwaysSyncable", 11 }, - { u"verticalScrollbarPosition", 11 }, - { u"fastScrollAlwaysVisible", 11 }, - { u"fastScrollThumbDrawable", 11 }, - { u"fastScrollPreviewBackgroundLeft", 11 }, - { u"fastScrollPreviewBackgroundRight", 11 }, - { u"fastScrollTrackDrawable", 11 }, - { u"fastScrollOverlayPosition", 11 }, - { u"customTokens", 11 }, - { u"nextFocusForward", 11 }, - { u"firstDayOfWeek", 11 }, - { u"showWeekNumber", 11 }, - { u"minDate", 11 }, - { u"maxDate", 11 }, - { u"shownWeekCount", 11 }, - { u"selectedWeekBackgroundColor", 11 }, - { u"focusedMonthDateColor", 11 }, - { u"unfocusedMonthDateColor", 11 }, - { u"weekNumberColor", 11 }, - { u"weekSeparatorLineColor", 11 }, - { u"selectedDateVerticalBar", 11 }, - { u"weekDayTextAppearance", 11 }, - { u"dateTextAppearance", 11 }, - { u"solidColor", 11 }, - { u"spinnersShown", 11 }, - { u"calendarViewShown", 11 }, - { u"state_multiline", 11 }, - { u"detailsElementBackground", 11 }, - { u"textColorHighlightInverse", 11 }, - { u"textColorLinkInverse", 11 }, - { u"editTextColor", 11 }, - { u"editTextBackground", 11 }, - { u"horizontalScrollViewStyle", 11 }, - { u"layerType", 11 }, - { u"alertDialogIcon", 11 }, - { u"windowMinWidthMajor", 11 }, - { u"windowMinWidthMinor", 11 }, - { u"queryHint", 11 }, - { u"fastScrollTextColor", 11 }, - { u"largeHeap", 11 }, - { u"windowCloseOnTouchOutside", 11 }, - { u"datePickerStyle", 11 }, - { u"calendarViewStyle", 11 }, - { u"textEditSidePasteWindowLayout", 11 }, - { u"textEditSideNoPasteWindowLayout", 11 }, - { u"actionMenuTextAppearance", 11 }, - { u"actionMenuTextColor", 11 }, - { u"textCursorDrawable", 12 }, - { u"resizeMode", 12 }, - { u"requiresSmallestWidthDp", 12 }, - { u"compatibleWidthLimitDp", 12 }, - { u"largestWidthLimitDp", 12 }, - { u"state_hovered", 13 }, - { u"state_drag_can_accept", 13 }, - { u"state_drag_hovered", 13 }, - { u"stopWithTask", 13 }, - { u"switchTextOn", 13 }, - { u"switchTextOff", 13 }, - { u"switchPreferenceStyle", 13 }, - { u"switchTextAppearance", 13 }, - { u"track", 13 }, - { u"switchMinWidth", 13 }, - { u"switchPadding", 13 }, - { u"thumbTextPadding", 13 }, - { u"textSuggestionsWindowStyle", 13 }, - { u"textEditSuggestionItemLayout", 13 }, - { u"rowCount", 13 }, - { u"rowOrderPreserved", 13 }, - { u"columnCount", 13 }, - { u"columnOrderPreserved", 13 }, - { u"useDefaultMargins", 13 }, - { u"alignmentMode", 13 }, - { u"layout_row", 13 }, - { u"layout_rowSpan", 13 }, - { u"layout_columnSpan", 13 }, - { u"actionModeSelectAllDrawable", 13 }, - { u"isAuxiliary", 13 }, - { u"accessibilityEventTypes", 13 }, - { u"packageNames", 13 }, - { u"accessibilityFeedbackType", 13 }, - { u"notificationTimeout", 13 }, - { u"accessibilityFlags", 13 }, - { u"canRetrieveWindowContent", 13 }, - { u"listPreferredItemHeightLarge", 13 }, - { u"listPreferredItemHeightSmall", 13 }, - { u"actionBarSplitStyle", 13 }, - { u"actionProviderClass", 13 }, - { u"backgroundStacked", 13 }, - { u"backgroundSplit", 13 }, - { u"textAllCaps", 13 }, - { u"colorPressedHighlight", 13 }, - { u"colorLongPressedHighlight", 13 }, - { u"colorFocusedHighlight", 13 }, - { u"colorActivatedHighlight", 13 }, - { u"colorMultiSelectHighlight", 13 }, - { u"drawableStart", 13 }, - { u"drawableEnd", 13 }, - { u"actionModeStyle", 13 }, - { u"minResizeWidth", 13 }, - { u"minResizeHeight", 13 }, - { u"actionBarWidgetTheme", 13 }, - { u"uiOptions", 13 }, - { u"subtypeLocale", 13 }, - { u"subtypeExtraValue", 13 }, - { u"actionBarDivider", 13 }, - { u"actionBarItemBackground", 13 }, - { u"actionModeSplitBackground", 13 }, - { u"textAppearanceListItem", 13 }, - { u"textAppearanceListItemSmall", 13 }, - { u"targetDescriptions", 13 }, - { u"directionDescriptions", 13 }, - { u"overridesImplicitlyEnabledSubtype", 13 }, - { u"listPreferredItemPaddingLeft", 13 }, - { u"listPreferredItemPaddingRight", 13 }, - { u"requiresFadingEdge", 13 }, - { u"publicKey", 13 }, - { u"parentActivityName", 16 }, - { u"isolatedProcess", 16 }, - { u"importantForAccessibility", 16 }, - { u"keyboardLayout", 16 }, - { u"fontFamily", 16 }, - { u"mediaRouteButtonStyle", 16 }, - { u"mediaRouteTypes", 16 }, - { u"supportsRtl", 17 }, - { u"textDirection", 17 }, - { u"textAlignment", 17 }, - { u"layoutDirection", 17 }, - { u"paddingStart", 17 }, - { u"paddingEnd", 17 }, - { u"layout_marginStart", 17 }, - { u"layout_marginEnd", 17 }, - { u"layout_toStartOf", 17 }, - { u"layout_toEndOf", 17 }, - { u"layout_alignStart", 17 }, - { u"layout_alignEnd", 17 }, - { u"layout_alignParentStart", 17 }, - { u"layout_alignParentEnd", 17 }, - { u"listPreferredItemPaddingStart", 17 }, - { u"listPreferredItemPaddingEnd", 17 }, - { u"singleUser", 17 }, - { u"presentationTheme", 17 }, - { u"subtypeId", 17 }, - { u"initialKeyguardLayout", 17 }, - { u"widgetCategory", 17 }, - { u"permissionGroupFlags", 17 }, - { u"labelFor", 17 }, - { u"permissionFlags", 17 }, - { u"checkedTextViewStyle", 17 }, - { u"showOnLockScreen", 17 }, - { u"format12Hour", 17 }, - { u"format24Hour", 17 }, - { u"timeZone", 17 }, - { u"mipMap", 18 }, - { u"mirrorForRtl", 18 }, - { u"windowOverscan", 18 }, - { u"requiredForAllUsers", 18 }, - { u"indicatorStart", 18 }, - { u"indicatorEnd", 18 }, - { u"childIndicatorStart", 18 }, - { u"childIndicatorEnd", 18 }, - { u"restrictedAccountType", 18 }, - { u"requiredAccountType", 18 }, - { u"canRequestTouchExplorationMode", 18 }, - { u"canRequestEnhancedWebAccessibility", 18 }, - { u"canRequestFilterKeyEvents", 18 }, - { u"layoutMode", 18 }, - { u"keySet", 19 }, - { u"targetId", 19 }, - { u"fromScene", 19 }, - { u"toScene", 19 }, - { u"transition", 19 }, - { u"transitionOrdering", 19 }, - { u"fadingMode", 19 }, - { u"startDelay", 19 }, - { u"ssp", 19 }, - { u"sspPrefix", 19 }, - { u"sspPattern", 19 }, - { u"addPrintersActivity", 19 }, - { u"vendor", 19 }, - { u"category", 19 }, - { u"isAsciiCapable", 19 }, - { u"autoMirrored", 19 }, - { u"supportsSwitchingToNextInputMethod", 19 }, - { u"requireDeviceUnlock", 19 }, - { u"apduServiceBanner", 19 }, - { u"accessibilityLiveRegion", 19 }, - { u"windowTranslucentStatus", 19 }, - { u"windowTranslucentNavigation", 19 }, - { u"advancedPrintOptionsActivity", 19 }, - { u"banner", 20 }, - { u"windowSwipeToDismiss", 20 }, - { u"isGame", 20 }, - { u"allowEmbedded", 20 }, - { u"setupActivity", 20 }, - { u"fastScrollStyle", 21 }, - { u"windowContentTransitions", 21 }, - { u"windowContentTransitionManager", 21 }, - { u"translationZ", 21 }, - { u"tintMode", 21 }, - { u"controlX1", 21 }, - { u"controlY1", 21 }, - { u"controlX2", 21 }, - { u"controlY2", 21 }, - { u"transitionName", 21 }, - { u"transitionGroup", 21 }, - { u"viewportWidth", 21 }, - { u"viewportHeight", 21 }, - { u"fillColor", 21 }, - { u"pathData", 21 }, - { u"strokeColor", 21 }, - { u"strokeWidth", 21 }, - { u"trimPathStart", 21 }, - { u"trimPathEnd", 21 }, - { u"trimPathOffset", 21 }, - { u"strokeLineCap", 21 }, - { u"strokeLineJoin", 21 }, - { u"strokeMiterLimit", 21 }, - { u"colorControlNormal", 21 }, - { u"colorControlActivated", 21 }, - { u"colorButtonNormal", 21 }, - { u"colorControlHighlight", 21 }, - { u"persistableMode", 21 }, - { u"titleTextAppearance", 21 }, - { u"subtitleTextAppearance", 21 }, - { u"slideEdge", 21 }, - { u"actionBarTheme", 21 }, - { u"textAppearanceListItemSecondary", 21 }, - { u"colorPrimary", 21 }, - { u"colorPrimaryDark", 21 }, - { u"colorAccent", 21 }, - { u"nestedScrollingEnabled", 21 }, - { u"windowEnterTransition", 21 }, - { u"windowExitTransition", 21 }, - { u"windowSharedElementEnterTransition", 21 }, - { u"windowSharedElementExitTransition", 21 }, - { u"windowAllowReturnTransitionOverlap", 21 }, - { u"windowAllowEnterTransitionOverlap", 21 }, - { u"sessionService", 21 }, - { u"stackViewStyle", 21 }, - { u"switchStyle", 21 }, - { u"elevation", 21 }, - { u"excludeId", 21 }, - { u"excludeClass", 21 }, - { u"hideOnContentScroll", 21 }, - { u"actionOverflowMenuStyle", 21 }, - { u"documentLaunchMode", 21 }, - { u"maxRecents", 21 }, - { u"autoRemoveFromRecents", 21 }, - { u"stateListAnimator", 21 }, - { u"toId", 21 }, - { u"fromId", 21 }, - { u"reversible", 21 }, - { u"splitTrack", 21 }, - { u"targetName", 21 }, - { u"excludeName", 21 }, - { u"matchOrder", 21 }, - { u"windowDrawsSystemBarBackgrounds", 21 }, - { u"statusBarColor", 21 }, - { u"navigationBarColor", 21 }, - { u"contentInsetStart", 21 }, - { u"contentInsetEnd", 21 }, - { u"contentInsetLeft", 21 }, - { u"contentInsetRight", 21 }, - { u"paddingMode", 21 }, - { u"layout_rowWeight", 21 }, - { u"layout_columnWeight", 21 }, - { u"translateX", 21 }, - { u"translateY", 21 }, - { u"selectableItemBackgroundBorderless", 21 }, - { u"elegantTextHeight", 21 }, - { u"searchKeyphraseId", 21 }, - { u"searchKeyphrase", 21 }, - { u"searchKeyphraseSupportedLocales", 21 }, - { u"windowTransitionBackgroundFadeDuration", 21 }, - { u"overlapAnchor", 21 }, - { u"progressTint", 21 }, - { u"progressTintMode", 21 }, - { u"progressBackgroundTint", 21 }, - { u"progressBackgroundTintMode", 21 }, - { u"secondaryProgressTint", 21 }, - { u"secondaryProgressTintMode", 21 }, - { u"indeterminateTint", 21 }, - { u"indeterminateTintMode", 21 }, - { u"backgroundTint", 21 }, - { u"backgroundTintMode", 21 }, - { u"foregroundTint", 21 }, - { u"foregroundTintMode", 21 }, - { u"buttonTint", 21 }, - { u"buttonTintMode", 21 }, - { u"thumbTint", 21 }, - { u"thumbTintMode", 21 }, - { u"fullBackupOnly", 21 }, - { u"propertyXName", 21 }, - { u"propertyYName", 21 }, - { u"relinquishTaskIdentity", 21 }, - { u"tileModeX", 21 }, - { u"tileModeY", 21 }, - { u"actionModeShareDrawable", 21 }, - { u"actionModeFindDrawable", 21 }, - { u"actionModeWebSearchDrawable", 21 }, - { u"transitionVisibilityMode", 21 }, - { u"minimumHorizontalAngle", 21 }, - { u"minimumVerticalAngle", 21 }, - { u"maximumAngle", 21 }, - { u"searchViewStyle", 21 }, - { u"closeIcon", 21 }, - { u"goIcon", 21 }, - { u"searchIcon", 21 }, - { u"voiceIcon", 21 }, - { u"commitIcon", 21 }, - { u"suggestionRowLayout", 21 }, - { u"queryBackground", 21 }, - { u"submitBackground", 21 }, - { u"buttonBarPositiveButtonStyle", 21 }, - { u"buttonBarNeutralButtonStyle", 21 }, - { u"buttonBarNegativeButtonStyle", 21 }, - { u"popupElevation", 21 }, - { u"actionBarPopupTheme", 21 }, - { u"multiArch", 21 }, - { u"touchscreenBlocksFocus", 21 }, - { u"windowElevation", 21 }, - { u"launchTaskBehindTargetAnimation", 21 }, - { u"launchTaskBehindSourceAnimation", 21 }, - { u"restrictionType", 21 }, - { u"dayOfWeekBackground", 21 }, - { u"dayOfWeekTextAppearance", 21 }, - { u"headerMonthTextAppearance", 21 }, - { u"headerDayOfMonthTextAppearance", 21 }, - { u"headerYearTextAppearance", 21 }, - { u"yearListItemTextAppearance", 21 }, - { u"yearListSelectorColor", 21 }, - { u"calendarTextColor", 21 }, - { u"recognitionService", 21 }, - { u"timePickerStyle", 21 }, - { u"timePickerDialogTheme", 21 }, - { u"headerTimeTextAppearance", 21 }, - { u"headerAmPmTextAppearance", 21 }, - { u"numbersTextColor", 21 }, - { u"numbersBackgroundColor", 21 }, - { u"numbersSelectorColor", 21 }, - { u"amPmTextColor", 21 }, - { u"amPmBackgroundColor", 21 }, - { u"searchKeyphraseRecognitionFlags", 21 }, - { u"checkMarkTint", 21 }, - { u"checkMarkTintMode", 21 }, - { u"popupTheme", 21 }, - { u"toolbarStyle", 21 }, - { u"windowClipToOutline", 21 }, - { u"datePickerDialogTheme", 21 }, - { u"showText", 21 }, - { u"windowReturnTransition", 21 }, - { u"windowReenterTransition", 21 }, - { u"windowSharedElementReturnTransition", 21 }, - { u"windowSharedElementReenterTransition", 21 }, - { u"resumeWhilePausing", 21 }, - { u"datePickerMode", 21 }, - { u"timePickerMode", 21 }, - { u"inset", 21 }, - { u"letterSpacing", 21 }, - { u"fontFeatureSettings", 21 }, - { u"outlineProvider", 21 }, - { u"contentAgeHint", 21 }, - { u"country", 21 }, - { u"windowSharedElementsUseOverlay", 21 }, - { u"reparent", 21 }, - { u"reparentWithOverlay", 21 }, - { u"ambientShadowAlpha", 21 }, - { u"spotShadowAlpha", 21 }, - { u"navigationIcon", 21 }, - { u"navigationContentDescription", 21 }, - { u"fragmentExitTransition", 21 }, - { u"fragmentEnterTransition", 21 }, - { u"fragmentSharedElementEnterTransition", 21 }, - { u"fragmentReturnTransition", 21 }, - { u"fragmentSharedElementReturnTransition", 21 }, - { u"fragmentReenterTransition", 21 }, - { u"fragmentAllowEnterTransitionOverlap", 21 }, - { u"fragmentAllowReturnTransitionOverlap", 21 }, - { u"patternPathData", 21 }, - { u"strokeAlpha", 21 }, - { u"fillAlpha", 21 }, - { u"windowActivityTransitions", 21 }, - { u"colorEdgeEffect", 21 } -}; +static const std::unordered_map<std::string, size_t> sAttrMap = { + {"marqueeRepeatLimit", 2}, + {"windowNoDisplay", 3}, + {"backgroundDimEnabled", 3}, + {"inputType", 3}, + {"isDefault", 3}, + {"windowDisablePreview", 3}, + {"privateImeOptions", 3}, + {"editorExtras", 3}, + {"settingsActivity", 3}, + {"fastScrollEnabled", 3}, + {"reqTouchScreen", 3}, + {"reqKeyboardType", 3}, + {"reqHardKeyboard", 3}, + {"reqNavigation", 3}, + {"windowSoftInputMode", 3}, + {"imeFullscreenBackground", 3}, + {"noHistory", 3}, + {"headerDividersEnabled", 3}, + {"footerDividersEnabled", 3}, + {"candidatesTextStyleSpans", 3}, + {"smoothScrollbar", 3}, + {"reqFiveWayNav", 3}, + {"keyBackground", 3}, + {"keyTextSize", 3}, + {"labelTextSize", 3}, + {"keyTextColor", 3}, + {"keyPreviewLayout", 3}, + {"keyPreviewOffset", 3}, + {"keyPreviewHeight", 3}, + {"verticalCorrection", 3}, + {"popupLayout", 3}, + {"state_long_pressable", 3}, + {"keyWidth", 3}, + {"keyHeight", 3}, + {"horizontalGap", 3}, + {"verticalGap", 3}, + {"rowEdgeFlags", 3}, + {"codes", 3}, + {"popupKeyboard", 3}, + {"popupCharacters", 3}, + {"keyEdgeFlags", 3}, + {"isModifier", 3}, + {"isSticky", 3}, + {"isRepeatable", 3}, + {"iconPreview", 3}, + {"keyOutputText", 3}, + {"keyLabel", 3}, + {"keyIcon", 3}, + {"keyboardMode", 3}, + {"isScrollContainer", 3}, + {"fillEnabled", 3}, + {"updatePeriodMillis", 3}, + {"initialLayout", 3}, + {"voiceSearchMode", 3}, + {"voiceLanguageModel", 3}, + {"voicePromptText", 3}, + {"voiceLanguage", 3}, + {"voiceMaxResults", 3}, + {"bottomOffset", 3}, + {"topOffset", 3}, + {"allowSingleTap", 3}, + {"handle", 3}, + {"content", 3}, + {"animateOnClick", 3}, + {"configure", 3}, + {"hapticFeedbackEnabled", 3}, + {"innerRadius", 3}, + {"thickness", 3}, + {"sharedUserLabel", 3}, + {"dropDownWidth", 3}, + {"dropDownAnchor", 3}, + {"imeOptions", 3}, + {"imeActionLabel", 3}, + {"imeActionId", 3}, + {"imeExtractEnterAnimation", 3}, + {"imeExtractExitAnimation", 3}, + {"tension", 4}, + {"extraTension", 4}, + {"anyDensity", 4}, + {"searchSuggestThreshold", 4}, + {"includeInGlobalSearch", 4}, + {"onClick", 4}, + {"targetSdkVersion", 4}, + {"maxSdkVersion", 4}, + {"testOnly", 4}, + {"contentDescription", 4}, + {"gestureStrokeWidth", 4}, + {"gestureColor", 4}, + {"uncertainGestureColor", 4}, + {"fadeOffset", 4}, + {"fadeDuration", 4}, + {"gestureStrokeType", 4}, + {"gestureStrokeLengthThreshold", 4}, + {"gestureStrokeSquarenessThreshold", 4}, + {"gestureStrokeAngleThreshold", 4}, + {"eventsInterceptionEnabled", 4}, + {"fadeEnabled", 4}, + {"backupAgent", 4}, + {"allowBackup", 4}, + {"glEsVersion", 4}, + {"queryAfterZeroResults", 4}, + {"dropDownHeight", 4}, + {"smallScreens", 4}, + {"normalScreens", 4}, + {"largeScreens", 4}, + {"progressBarStyleInverse", 4}, + {"progressBarStyleSmallInverse", 4}, + {"progressBarStyleLargeInverse", 4}, + {"searchSettingsDescription", 4}, + {"textColorPrimaryInverseDisableOnly", 4}, + {"autoUrlDetect", 4}, + {"resizeable", 4}, + {"required", 5}, + {"accountType", 5}, + {"contentAuthority", 5}, + {"userVisible", 5}, + {"windowShowWallpaper", 5}, + {"wallpaperOpenEnterAnimation", 5}, + {"wallpaperOpenExitAnimation", 5}, + {"wallpaperCloseEnterAnimation", 5}, + {"wallpaperCloseExitAnimation", 5}, + {"wallpaperIntraOpenEnterAnimation", 5}, + {"wallpaperIntraOpenExitAnimation", 5}, + {"wallpaperIntraCloseEnterAnimation", 5}, + {"wallpaperIntraCloseExitAnimation", 5}, + {"supportsUploading", 5}, + {"killAfterRestore", 5}, + {"restoreNeedsApplication", 5}, + {"smallIcon", 5}, + {"accountPreferences", 5}, + {"textAppearanceSearchResultSubtitle", 5}, + {"textAppearanceSearchResultTitle", 5}, + {"summaryColumn", 5}, + {"detailColumn", 5}, + {"detailSocialSummary", 5}, + {"thumbnail", 5}, + {"detachWallpaper", 5}, + {"finishOnCloseSystemDialogs", 5}, + {"scrollbarFadeDuration", 5}, + {"scrollbarDefaultDelayBeforeFade", 5}, + {"fadeScrollbars", 5}, + {"colorBackgroundCacheHint", 5}, + {"dropDownHorizontalOffset", 5}, + {"dropDownVerticalOffset", 5}, + {"quickContactBadgeStyleWindowSmall", 6}, + {"quickContactBadgeStyleWindowMedium", 6}, + {"quickContactBadgeStyleWindowLarge", 6}, + {"quickContactBadgeStyleSmallWindowSmall", 6}, + {"quickContactBadgeStyleSmallWindowMedium", 6}, + {"quickContactBadgeStyleSmallWindowLarge", 6}, + {"author", 7}, + {"autoStart", 7}, + {"expandableListViewWhiteStyle", 8}, + {"installLocation", 8}, + {"vmSafeMode", 8}, + {"webTextViewStyle", 8}, + {"restoreAnyVersion", 8}, + {"tabStripLeft", 8}, + {"tabStripRight", 8}, + {"tabStripEnabled", 8}, + {"logo", 9}, + {"xlargeScreens", 9}, + {"immersive", 9}, + {"overScrollMode", 9}, + {"overScrollHeader", 9}, + {"overScrollFooter", 9}, + {"filterTouchesWhenObscured", 9}, + {"textSelectHandleLeft", 9}, + {"textSelectHandleRight", 9}, + {"textSelectHandle", 9}, + {"textSelectHandleWindowStyle", 9}, + {"popupAnimationStyle", 9}, + {"screenSize", 9}, + {"screenDensity", 9}, + {"allContactsName", 11}, + {"windowActionBar", 11}, + {"actionBarStyle", 11}, + {"navigationMode", 11}, + {"displayOptions", 11}, + {"subtitle", 11}, + {"customNavigationLayout", 11}, + {"hardwareAccelerated", 11}, + {"measureWithLargestChild", 11}, + {"animateFirstView", 11}, + {"dropDownSpinnerStyle", 11}, + {"actionDropDownStyle", 11}, + {"actionButtonStyle", 11}, + {"showAsAction", 11}, + {"previewImage", 11}, + {"actionModeBackground", 11}, + {"actionModeCloseDrawable", 11}, + {"windowActionModeOverlay", 11}, + {"valueFrom", 11}, + {"valueTo", 11}, + {"valueType", 11}, + {"propertyName", 11}, + {"ordering", 11}, + {"fragment", 11}, + {"windowActionBarOverlay", 11}, + {"fragmentOpenEnterAnimation", 11}, + {"fragmentOpenExitAnimation", 11}, + {"fragmentCloseEnterAnimation", 11}, + {"fragmentCloseExitAnimation", 11}, + {"fragmentFadeEnterAnimation", 11}, + {"fragmentFadeExitAnimation", 11}, + {"actionBarSize", 11}, + {"imeSubtypeLocale", 11}, + {"imeSubtypeMode", 11}, + {"imeSubtypeExtraValue", 11}, + {"splitMotionEvents", 11}, + {"listChoiceBackgroundIndicator", 11}, + {"spinnerMode", 11}, + {"animateLayoutChanges", 11}, + {"actionBarTabStyle", 11}, + {"actionBarTabBarStyle", 11}, + {"actionBarTabTextStyle", 11}, + {"actionOverflowButtonStyle", 11}, + {"actionModeCloseButtonStyle", 11}, + {"titleTextStyle", 11}, + {"subtitleTextStyle", 11}, + {"iconifiedByDefault", 11}, + {"actionLayout", 11}, + {"actionViewClass", 11}, + {"activatedBackgroundIndicator", 11}, + {"state_activated", 11}, + {"listPopupWindowStyle", 11}, + {"popupMenuStyle", 11}, + {"textAppearanceLargePopupMen", 11}, + {"textAppearanceSmallPopupMen", 11}, + {"breadCrumbTitle", 11}, + {"breadCrumbShortTitle", 11}, + {"listDividerAlertDialog", 11}, + {"textColorAlertDialogListItem", 11}, + {"loopViews", 11}, + {"dialogTheme", 11}, + {"alertDialogTheme", 11}, + {"dividerVertical", 11}, + {"homeAsUpIndicator", 11}, + {"enterFadeDuration", 11}, + {"exitFadeDuration", 11}, + {"selectableItemBackground", 11}, + {"autoAdvanceViewId", 11}, + {"useIntrinsicSizeAsMinimum", 11}, + {"actionModeCutDrawable", 11}, + {"actionModeCopyDrawable", 11}, + {"actionModePasteDrawable", 11}, + {"textEditPasteWindowLayout", 11}, + {"textEditNoPasteWindowLayout", 11}, + {"textIsSelectable", 11}, + {"windowEnableSplitTouch", 11}, + {"indeterminateProgressStyle", 11}, + {"progressBarPadding", 11}, + {"animationResolution", 11}, + {"state_accelerated", 11}, + {"baseline", 11}, + {"homeLayout", 11}, + {"opacity", 11}, + {"alpha", 11}, + {"transformPivotX", 11}, + {"transformPivotY", 11}, + {"translationX", 11}, + {"translationY", 11}, + {"scaleX", 11}, + {"scaleY", 11}, + {"rotation", 11}, + {"rotationX", 11}, + {"rotationY", 11}, + {"showDividers", 11}, + {"dividerPadding", 11}, + {"borderlessButtonStyle", 11}, + {"dividerHorizontal", 11}, + {"itemPadding", 11}, + {"buttonBarStyle", 11}, + {"buttonBarButtonStyle", 11}, + {"segmentedButtonStyle", 11}, + {"staticWallpaperPreview", 11}, + {"allowParallelSyncs", 11}, + {"isAlwaysSyncable", 11}, + {"verticalScrollbarPosition", 11}, + {"fastScrollAlwaysVisible", 11}, + {"fastScrollThumbDrawable", 11}, + {"fastScrollPreviewBackgroundLeft", 11}, + {"fastScrollPreviewBackgroundRight", 11}, + {"fastScrollTrackDrawable", 11}, + {"fastScrollOverlayPosition", 11}, + {"customTokens", 11}, + {"nextFocusForward", 11}, + {"firstDayOfWeek", 11}, + {"showWeekNumber", 11}, + {"minDate", 11}, + {"maxDate", 11}, + {"shownWeekCount", 11}, + {"selectedWeekBackgroundColor", 11}, + {"focusedMonthDateColor", 11}, + {"unfocusedMonthDateColor", 11}, + {"weekNumberColor", 11}, + {"weekSeparatorLineColor", 11}, + {"selectedDateVerticalBar", 11}, + {"weekDayTextAppearance", 11}, + {"dateTextAppearance", 11}, + {"solidColor", 11}, + {"spinnersShown", 11}, + {"calendarViewShown", 11}, + {"state_multiline", 11}, + {"detailsElementBackground", 11}, + {"textColorHighlightInverse", 11}, + {"textColorLinkInverse", 11}, + {"editTextColor", 11}, + {"editTextBackground", 11}, + {"horizontalScrollViewStyle", 11}, + {"layerType", 11}, + {"alertDialogIcon", 11}, + {"windowMinWidthMajor", 11}, + {"windowMinWidthMinor", 11}, + {"queryHint", 11}, + {"fastScrollTextColor", 11}, + {"largeHeap", 11}, + {"windowCloseOnTouchOutside", 11}, + {"datePickerStyle", 11}, + {"calendarViewStyle", 11}, + {"textEditSidePasteWindowLayout", 11}, + {"textEditSideNoPasteWindowLayout", 11}, + {"actionMenuTextAppearance", 11}, + {"actionMenuTextColor", 11}, + {"textCursorDrawable", 12}, + {"resizeMode", 12}, + {"requiresSmallestWidthDp", 12}, + {"compatibleWidthLimitDp", 12}, + {"largestWidthLimitDp", 12}, + {"state_hovered", 13}, + {"state_drag_can_accept", 13}, + {"state_drag_hovered", 13}, + {"stopWithTask", 13}, + {"switchTextOn", 13}, + {"switchTextOff", 13}, + {"switchPreferenceStyle", 13}, + {"switchTextAppearance", 13}, + {"track", 13}, + {"switchMinWidth", 13}, + {"switchPadding", 13}, + {"thumbTextPadding", 13}, + {"textSuggestionsWindowStyle", 13}, + {"textEditSuggestionItemLayout", 13}, + {"rowCount", 13}, + {"rowOrderPreserved", 13}, + {"columnCount", 13}, + {"columnOrderPreserved", 13}, + {"useDefaultMargins", 13}, + {"alignmentMode", 13}, + {"layout_row", 13}, + {"layout_rowSpan", 13}, + {"layout_columnSpan", 13}, + {"actionModeSelectAllDrawable", 13}, + {"isAuxiliary", 13}, + {"accessibilityEventTypes", 13}, + {"packageNames", 13}, + {"accessibilityFeedbackType", 13}, + {"notificationTimeout", 13}, + {"accessibilityFlags", 13}, + {"canRetrieveWindowContent", 13}, + {"listPreferredItemHeightLarge", 13}, + {"listPreferredItemHeightSmall", 13}, + {"actionBarSplitStyle", 13}, + {"actionProviderClass", 13}, + {"backgroundStacked", 13}, + {"backgroundSplit", 13}, + {"textAllCaps", 13}, + {"colorPressedHighlight", 13}, + {"colorLongPressedHighlight", 13}, + {"colorFocusedHighlight", 13}, + {"colorActivatedHighlight", 13}, + {"colorMultiSelectHighlight", 13}, + {"drawableStart", 13}, + {"drawableEnd", 13}, + {"actionModeStyle", 13}, + {"minResizeWidth", 13}, + {"minResizeHeight", 13}, + {"actionBarWidgetTheme", 13}, + {"uiOptions", 13}, + {"subtypeLocale", 13}, + {"subtypeExtraValue", 13}, + {"actionBarDivider", 13}, + {"actionBarItemBackground", 13}, + {"actionModeSplitBackground", 13}, + {"textAppearanceListItem", 13}, + {"textAppearanceListItemSmall", 13}, + {"targetDescriptions", 13}, + {"directionDescriptions", 13}, + {"overridesImplicitlyEnabledSubtype", 13}, + {"listPreferredItemPaddingLeft", 13}, + {"listPreferredItemPaddingRight", 13}, + {"requiresFadingEdge", 13}, + {"publicKey", 13}, + {"parentActivityName", 16}, + {"isolatedProcess", 16}, + {"importantForAccessibility", 16}, + {"keyboardLayout", 16}, + {"fontFamily", 16}, + {"mediaRouteButtonStyle", 16}, + {"mediaRouteTypes", 16}, + {"supportsRtl", 17}, + {"textDirection", 17}, + {"textAlignment", 17}, + {"layoutDirection", 17}, + {"paddingStart", 17}, + {"paddingEnd", 17}, + {"layout_marginStart", 17}, + {"layout_marginEnd", 17}, + {"layout_toStartOf", 17}, + {"layout_toEndOf", 17}, + {"layout_alignStart", 17}, + {"layout_alignEnd", 17}, + {"layout_alignParentStart", 17}, + {"layout_alignParentEnd", 17}, + {"listPreferredItemPaddingStart", 17}, + {"listPreferredItemPaddingEnd", 17}, + {"singleUser", 17}, + {"presentationTheme", 17}, + {"subtypeId", 17}, + {"initialKeyguardLayout", 17}, + {"widgetCategory", 17}, + {"permissionGroupFlags", 17}, + {"labelFor", 17}, + {"permissionFlags", 17}, + {"checkedTextViewStyle", 17}, + {"showOnLockScreen", 17}, + {"format12Hour", 17}, + {"format24Hour", 17}, + {"timeZone", 17}, + {"mipMap", 18}, + {"mirrorForRtl", 18}, + {"windowOverscan", 18}, + {"requiredForAllUsers", 18}, + {"indicatorStart", 18}, + {"indicatorEnd", 18}, + {"childIndicatorStart", 18}, + {"childIndicatorEnd", 18}, + {"restrictedAccountType", 18}, + {"requiredAccountType", 18}, + {"canRequestTouchExplorationMode", 18}, + {"canRequestEnhancedWebAccessibility", 18}, + {"canRequestFilterKeyEvents", 18}, + {"layoutMode", 18}, + {"keySet", 19}, + {"targetId", 19}, + {"fromScene", 19}, + {"toScene", 19}, + {"transition", 19}, + {"transitionOrdering", 19}, + {"fadingMode", 19}, + {"startDelay", 19}, + {"ssp", 19}, + {"sspPrefix", 19}, + {"sspPattern", 19}, + {"addPrintersActivity", 19}, + {"vendor", 19}, + {"category", 19}, + {"isAsciiCapable", 19}, + {"autoMirrored", 19}, + {"supportsSwitchingToNextInputMethod", 19}, + {"requireDeviceUnlock", 19}, + {"apduServiceBanner", 19}, + {"accessibilityLiveRegion", 19}, + {"windowTranslucentStatus", 19}, + {"windowTranslucentNavigation", 19}, + {"advancedPrintOptionsActivity", 19}, + {"banner", 20}, + {"windowSwipeToDismiss", 20}, + {"isGame", 20}, + {"allowEmbedded", 20}, + {"setupActivity", 20}, + {"fastScrollStyle", 21}, + {"windowContentTransitions", 21}, + {"windowContentTransitionManager", 21}, + {"translationZ", 21}, + {"tintMode", 21}, + {"controlX1", 21}, + {"controlY1", 21}, + {"controlX2", 21}, + {"controlY2", 21}, + {"transitionName", 21}, + {"transitionGroup", 21}, + {"viewportWidth", 21}, + {"viewportHeight", 21}, + {"fillColor", 21}, + {"pathData", 21}, + {"strokeColor", 21}, + {"strokeWidth", 21}, + {"trimPathStart", 21}, + {"trimPathEnd", 21}, + {"trimPathOffset", 21}, + {"strokeLineCap", 21}, + {"strokeLineJoin", 21}, + {"strokeMiterLimit", 21}, + {"colorControlNormal", 21}, + {"colorControlActivated", 21}, + {"colorButtonNormal", 21}, + {"colorControlHighlight", 21}, + {"persistableMode", 21}, + {"titleTextAppearance", 21}, + {"subtitleTextAppearance", 21}, + {"slideEdge", 21}, + {"actionBarTheme", 21}, + {"textAppearanceListItemSecondary", 21}, + {"colorPrimary", 21}, + {"colorPrimaryDark", 21}, + {"colorAccent", 21}, + {"nestedScrollingEnabled", 21}, + {"windowEnterTransition", 21}, + {"windowExitTransition", 21}, + {"windowSharedElementEnterTransition", 21}, + {"windowSharedElementExitTransition", 21}, + {"windowAllowReturnTransitionOverlap", 21}, + {"windowAllowEnterTransitionOverlap", 21}, + {"sessionService", 21}, + {"stackViewStyle", 21}, + {"switchStyle", 21}, + {"elevation", 21}, + {"excludeId", 21}, + {"excludeClass", 21}, + {"hideOnContentScroll", 21}, + {"actionOverflowMenuStyle", 21}, + {"documentLaunchMode", 21}, + {"maxRecents", 21}, + {"autoRemoveFromRecents", 21}, + {"stateListAnimator", 21}, + {"toId", 21}, + {"fromId", 21}, + {"reversible", 21}, + {"splitTrack", 21}, + {"targetName", 21}, + {"excludeName", 21}, + {"matchOrder", 21}, + {"windowDrawsSystemBarBackgrounds", 21}, + {"statusBarColor", 21}, + {"navigationBarColor", 21}, + {"contentInsetStart", 21}, + {"contentInsetEnd", 21}, + {"contentInsetLeft", 21}, + {"contentInsetRight", 21}, + {"paddingMode", 21}, + {"layout_rowWeight", 21}, + {"layout_columnWeight", 21}, + {"translateX", 21}, + {"translateY", 21}, + {"selectableItemBackgroundBorderless", 21}, + {"elegantTextHeight", 21}, + {"searchKeyphraseId", 21}, + {"searchKeyphrase", 21}, + {"searchKeyphraseSupportedLocales", 21}, + {"windowTransitionBackgroundFadeDuration", 21}, + {"overlapAnchor", 21}, + {"progressTint", 21}, + {"progressTintMode", 21}, + {"progressBackgroundTint", 21}, + {"progressBackgroundTintMode", 21}, + {"secondaryProgressTint", 21}, + {"secondaryProgressTintMode", 21}, + {"indeterminateTint", 21}, + {"indeterminateTintMode", 21}, + {"backgroundTint", 21}, + {"backgroundTintMode", 21}, + {"foregroundTint", 21}, + {"foregroundTintMode", 21}, + {"buttonTint", 21}, + {"buttonTintMode", 21}, + {"thumbTint", 21}, + {"thumbTintMode", 21}, + {"fullBackupOnly", 21}, + {"propertyXName", 21}, + {"propertyYName", 21}, + {"relinquishTaskIdentity", 21}, + {"tileModeX", 21}, + {"tileModeY", 21}, + {"actionModeShareDrawable", 21}, + {"actionModeFindDrawable", 21}, + {"actionModeWebSearchDrawable", 21}, + {"transitionVisibilityMode", 21}, + {"minimumHorizontalAngle", 21}, + {"minimumVerticalAngle", 21}, + {"maximumAngle", 21}, + {"searchViewStyle", 21}, + {"closeIcon", 21}, + {"goIcon", 21}, + {"searchIcon", 21}, + {"voiceIcon", 21}, + {"commitIcon", 21}, + {"suggestionRowLayout", 21}, + {"queryBackground", 21}, + {"submitBackground", 21}, + {"buttonBarPositiveButtonStyle", 21}, + {"buttonBarNeutralButtonStyle", 21}, + {"buttonBarNegativeButtonStyle", 21}, + {"popupElevation", 21}, + {"actionBarPopupTheme", 21}, + {"multiArch", 21}, + {"touchscreenBlocksFocus", 21}, + {"windowElevation", 21}, + {"launchTaskBehindTargetAnimation", 21}, + {"launchTaskBehindSourceAnimation", 21}, + {"restrictionType", 21}, + {"dayOfWeekBackground", 21}, + {"dayOfWeekTextAppearance", 21}, + {"headerMonthTextAppearance", 21}, + {"headerDayOfMonthTextAppearance", 21}, + {"headerYearTextAppearance", 21}, + {"yearListItemTextAppearance", 21}, + {"yearListSelectorColor", 21}, + {"calendarTextColor", 21}, + {"recognitionService", 21}, + {"timePickerStyle", 21}, + {"timePickerDialogTheme", 21}, + {"headerTimeTextAppearance", 21}, + {"headerAmPmTextAppearance", 21}, + {"numbersTextColor", 21}, + {"numbersBackgroundColor", 21}, + {"numbersSelectorColor", 21}, + {"amPmTextColor", 21}, + {"amPmBackgroundColor", 21}, + {"searchKeyphraseRecognitionFlags", 21}, + {"checkMarkTint", 21}, + {"checkMarkTintMode", 21}, + {"popupTheme", 21}, + {"toolbarStyle", 21}, + {"windowClipToOutline", 21}, + {"datePickerDialogTheme", 21}, + {"showText", 21}, + {"windowReturnTransition", 21}, + {"windowReenterTransition", 21}, + {"windowSharedElementReturnTransition", 21}, + {"windowSharedElementReenterTransition", 21}, + {"resumeWhilePausing", 21}, + {"datePickerMode", 21}, + {"timePickerMode", 21}, + {"inset", 21}, + {"letterSpacing", 21}, + {"fontFeatureSettings", 21}, + {"outlineProvider", 21}, + {"contentAgeHint", 21}, + {"country", 21}, + {"windowSharedElementsUseOverlay", 21}, + {"reparent", 21}, + {"reparentWithOverlay", 21}, + {"ambientShadowAlpha", 21}, + {"spotShadowAlpha", 21}, + {"navigationIcon", 21}, + {"navigationContentDescription", 21}, + {"fragmentExitTransition", 21}, + {"fragmentEnterTransition", 21}, + {"fragmentSharedElementEnterTransition", 21}, + {"fragmentReturnTransition", 21}, + {"fragmentSharedElementReturnTransition", 21}, + {"fragmentReenterTransition", 21}, + {"fragmentAllowEnterTransitionOverlap", 21}, + {"fragmentAllowReturnTransitionOverlap", 21}, + {"patternPathData", 21}, + {"strokeAlpha", 21}, + {"fillAlpha", 21}, + {"windowActivityTransitions", 21}, + {"colorEdgeEffect", 21}}; -size_t findAttributeSdkLevel(const ResourceName& name) { - if (name.package != u"android" && name.type != ResourceType::kAttr) { - return 0; - } +size_t FindAttributeSdkLevel(const ResourceName& name) { + if (name.package != "android" && name.type != ResourceType::kAttr) { + return 0; + } - auto iter = sAttrMap.find(name.entry); - if (iter != sAttrMap.end()) { - return iter->second; - } - return SDK_LOLLIPOP_MR1; + auto iter = sAttrMap.find(name.entry); + if (iter != sAttrMap.end()) { + return iter->second; + } + return SDK_LOLLIPOP_MR1; +} + +std::pair<StringPiece, int> GetDevelopmentSdkCodeNameAndVersion() { + return std::make_pair(StringPiece(sDevelopmentSdkCodeName), + sDevelopmentSdkLevel); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h index 8a7e3436ed6e..9b38ecbeae99 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -17,37 +17,40 @@ #ifndef AAPT_SDK_CONSTANTS_H #define AAPT_SDK_CONSTANTS_H +#include <utility> + #include "Resource.h" namespace aapt { enum { - SDK_CUPCAKE = 3, - SDK_DONUT = 4, - SDK_ECLAIR = 5, - SDK_ECLAIR_0_1 = 6, - SDK_ECLAIR_MR1 = 7, - SDK_FROYO = 8, - SDK_GINGERBREAD = 9, - SDK_GINGERBREAD_MR1 = 10, - SDK_HONEYCOMB = 11, - SDK_HONEYCOMB_MR1 = 12, - SDK_HONEYCOMB_MR2 = 13, - SDK_ICE_CREAM_SANDWICH = 14, - SDK_ICE_CREAM_SANDWICH_MR1 = 15, - SDK_JELLY_BEAN = 16, - SDK_JELLY_BEAN_MR1 = 17, - SDK_JELLY_BEAN_MR2 = 18, - SDK_KITKAT = 19, - SDK_KITKAT_WATCH = 20, - SDK_LOLLIPOP = 21, - SDK_LOLLIPOP_MR1 = 22, - SDK_MARSHMALLOW = 23, + SDK_CUPCAKE = 3, + SDK_DONUT = 4, + SDK_ECLAIR = 5, + SDK_ECLAIR_0_1 = 6, + SDK_ECLAIR_MR1 = 7, + SDK_FROYO = 8, + SDK_GINGERBREAD = 9, + SDK_GINGERBREAD_MR1 = 10, + SDK_HONEYCOMB = 11, + SDK_HONEYCOMB_MR1 = 12, + SDK_HONEYCOMB_MR2 = 13, + SDK_ICE_CREAM_SANDWICH = 14, + SDK_ICE_CREAM_SANDWICH_MR1 = 15, + SDK_JELLY_BEAN = 16, + SDK_JELLY_BEAN_MR1 = 17, + SDK_JELLY_BEAN_MR2 = 18, + SDK_KITKAT = 19, + SDK_KITKAT_WATCH = 20, + SDK_LOLLIPOP = 21, + SDK_LOLLIPOP_MR1 = 22, + SDK_MARSHMALLOW = 23, }; -size_t findAttributeSdkLevel(const ResourceId& id); -size_t findAttributeSdkLevel(const ResourceName& name); +size_t FindAttributeSdkLevel(const ResourceId& id); +size_t FindAttributeSdkLevel(const ResourceName& name); +std::pair<StringPiece, int> GetDevelopmentSdkCodeNameAndVersion(); -} // namespace aapt +} // namespace aapt -#endif // AAPT_SDK_CONSTANTS_H +#endif // AAPT_SDK_CONSTANTS_H diff --git a/tools/aapt2/SdkConstants_test.cpp b/tools/aapt2/SdkConstants_test.cpp index e81f412dda15..716d922fb5a0 100644 --- a/tools/aapt2/SdkConstants_test.cpp +++ b/tools/aapt2/SdkConstants_test.cpp @@ -16,23 +16,23 @@ #include "SdkConstants.h" -#include <gtest/gtest.h> +#include "gtest/gtest.h" namespace aapt { TEST(SdkConstantsTest, FirstAttributeIsSdk1) { - EXPECT_EQ(1u, findAttributeSdkLevel(ResourceId(0x01010000))); + EXPECT_EQ(1u, FindAttributeSdkLevel(ResourceId(0x01010000))); } TEST(SdkConstantsTest, AllAttributesAfterLollipopAreLollipopMR1) { - EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010103f7))); - EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010104ce))); + EXPECT_EQ(SDK_LOLLIPOP, FindAttributeSdkLevel(ResourceId(0x010103f7))); + EXPECT_EQ(SDK_LOLLIPOP, FindAttributeSdkLevel(ResourceId(0x010104ce))); - EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104cf))); - EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d8))); + EXPECT_EQ(SDK_LOLLIPOP_MR1, FindAttributeSdkLevel(ResourceId(0x010104cf))); + EXPECT_EQ(SDK_LOLLIPOP_MR1, FindAttributeSdkLevel(ResourceId(0x010104d8))); - EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d9))); - EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x0101ffff))); + EXPECT_EQ(SDK_LOLLIPOP_MR1, FindAttributeSdkLevel(ResourceId(0x010104d9))); + EXPECT_EQ(SDK_LOLLIPOP_MR1, FindAttributeSdkLevel(ResourceId(0x0101ffff))); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h index 319528e0ea1b..459a8e603b6b 100644 --- a/tools/aapt2/Source.h +++ b/tools/aapt2/Source.h @@ -17,12 +17,12 @@ #ifndef AAPT_SOURCE_H #define AAPT_SOURCE_H -#include "util/Maybe.h" -#include "util/StringPiece.h" - #include <ostream> #include <string> +#include "util/Maybe.h" +#include "util/StringPiece.h" + namespace aapt { /** @@ -30,20 +30,19 @@ namespace aapt { * showing errors. */ struct Source { - std::string path; - Maybe<size_t> line; + std::string path; + Maybe<size_t> line; - Source() = default; + Source() = default; - inline Source(const StringPiece& path) : path(path.toString()) { - } + inline Source(const StringPiece& path) + : path(path.ToString()) { // NOLINT(implicit) + } - inline Source(const StringPiece& path, size_t line) : path(path.toString()), line(line) { - } + inline Source(const StringPiece& path, size_t line) + : path(path.ToString()), line(line) {} - inline Source withLine(size_t line) const { - return Source(path, line); - } + inline Source WithLine(size_t line) const { return Source(path, line); } }; // @@ -51,30 +50,30 @@ struct Source { // inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) { - out << source.path; - if (source.line) { - out << ":" << source.line.value(); - } - return out; + out << source.path; + if (source.line) { + out << ":" << source.line.value(); + } + return out; } inline bool operator==(const Source& lhs, const Source& rhs) { - return lhs.path == rhs.path && lhs.line == rhs.line; + return lhs.path == rhs.path && lhs.line == rhs.line; } inline bool operator<(const Source& lhs, const Source& rhs) { - int cmp = lhs.path.compare(rhs.path); - if (cmp < 0) return true; - if (cmp > 0) return false; - if (lhs.line) { - if (rhs.line) { - return lhs.line.value() < rhs.line.value(); - } - return false; + int cmp = lhs.path.compare(rhs.path); + if (cmp < 0) return true; + if (cmp > 0) return false; + if (lhs.line) { + if (rhs.line) { + return lhs.line.value() < rhs.line.value(); } - return bool(rhs.line); + return false; + } + return bool(rhs.line); } -} // namespace aapt +} // namespace aapt -#endif // AAPT_SOURCE_H +#endif // AAPT_SOURCE_H diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp index aadb00b6be2a..30328299bb84 100644 --- a/tools/aapt2/StringPool.cpp +++ b/tools/aapt2/StringPool.cpp @@ -15,393 +15,405 @@ */ #include "StringPool.h" -#include "util/BigBuffer.h" -#include "util/StringPiece.h" -#include "util/Util.h" #include <algorithm> -#include <androidfw/ResourceTypes.h> #include <memory> #include <string> +#include "android-base/logging.h" +#include "androidfw/ResourceTypes.h" + +#include "util/BigBuffer.h" +#include "util/StringPiece.h" +#include "util/Util.h" + namespace aapt { -StringPool::Ref::Ref() : mEntry(nullptr) { -} +StringPool::Ref::Ref() : entry_(nullptr) {} -StringPool::Ref::Ref(const StringPool::Ref& rhs) : mEntry(rhs.mEntry) { - if (mEntry != nullptr) { - mEntry->ref++; - } +StringPool::Ref::Ref(const StringPool::Ref& rhs) : entry_(rhs.entry_) { + if (entry_ != nullptr) { + entry_->ref_++; + } } -StringPool::Ref::Ref(StringPool::Entry* entry) : mEntry(entry) { - if (mEntry != nullptr) { - mEntry->ref++; - } +StringPool::Ref::Ref(StringPool::Entry* entry) : entry_(entry) { + if (entry_ != nullptr) { + entry_->ref_++; + } } StringPool::Ref::~Ref() { - if (mEntry != nullptr) { - mEntry->ref--; - } + if (entry_ != nullptr) { + entry_->ref_--; + } } StringPool::Ref& StringPool::Ref::operator=(const StringPool::Ref& rhs) { - if (rhs.mEntry != nullptr) { - rhs.mEntry->ref++; - } + if (rhs.entry_ != nullptr) { + rhs.entry_->ref_++; + } - if (mEntry != nullptr) { - mEntry->ref--; - } - mEntry = rhs.mEntry; - return *this; + if (entry_ != nullptr) { + entry_->ref_--; + } + entry_ = rhs.entry_; + return *this; } -const std::u16string* StringPool::Ref::operator->() const { - return &mEntry->value; +const std::string* StringPool::Ref::operator->() const { + return &entry_->value; } -const std::u16string& StringPool::Ref::operator*() const { - return mEntry->value; -} +const std::string& StringPool::Ref::operator*() const { return entry_->value; } -size_t StringPool::Ref::getIndex() const { - return mEntry->index; -} +size_t StringPool::Ref::index() const { return entry_->index; } -const StringPool::Context& StringPool::Ref::getContext() const { - return mEntry->context; +const StringPool::Context& StringPool::Ref::GetContext() const { + return entry_->context; } -StringPool::StyleRef::StyleRef() : mEntry(nullptr) { -} +StringPool::StyleRef::StyleRef() : entry_(nullptr) {} -StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs) : mEntry(rhs.mEntry) { - if (mEntry != nullptr) { - mEntry->ref++; - } +StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs) + : entry_(rhs.entry_) { + if (entry_ != nullptr) { + entry_->ref_++; + } } -StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : mEntry(entry) { - if (mEntry != nullptr) { - mEntry->ref++; - } +StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : entry_(entry) { + if (entry_ != nullptr) { + entry_->ref_++; + } } StringPool::StyleRef::~StyleRef() { - if (mEntry != nullptr) { - mEntry->ref--; - } + if (entry_ != nullptr) { + entry_->ref_--; + } } -StringPool::StyleRef& StringPool::StyleRef::operator=(const StringPool::StyleRef& rhs) { - if (rhs.mEntry != nullptr) { - rhs.mEntry->ref++; - } +StringPool::StyleRef& StringPool::StyleRef::operator=( + const StringPool::StyleRef& rhs) { + if (rhs.entry_ != nullptr) { + rhs.entry_->ref_++; + } - if (mEntry != nullptr) { - mEntry->ref--; - } - mEntry = rhs.mEntry; - return *this; + if (entry_ != nullptr) { + entry_->ref_--; + } + entry_ = rhs.entry_; + return *this; } const StringPool::StyleEntry* StringPool::StyleRef::operator->() const { - return mEntry; + return entry_; } const StringPool::StyleEntry& StringPool::StyleRef::operator*() const { - return *mEntry; + return *entry_; } -size_t StringPool::StyleRef::getIndex() const { - return mEntry->str.getIndex(); -} +size_t StringPool::StyleRef::index() const { return entry_->str.index(); } -const StringPool::Context& StringPool::StyleRef::getContext() const { - return mEntry->str.getContext(); +const StringPool::Context& StringPool::StyleRef::GetContext() const { + return entry_->str.GetContext(); } -StringPool::Ref StringPool::makeRef(const StringPiece16& str) { - return makeRefImpl(str, Context{}, true); +StringPool::Ref StringPool::MakeRef(const StringPiece& str) { + return MakeRefImpl(str, Context{}, true); } -StringPool::Ref StringPool::makeRef(const StringPiece16& str, const Context& context) { - return makeRefImpl(str, context, true); +StringPool::Ref StringPool::MakeRef(const StringPiece& str, + const Context& context) { + return MakeRefImpl(str, context, true); } -StringPool::Ref StringPool::makeRefImpl(const StringPiece16& str, const Context& context, - bool unique) { - if (unique) { - auto iter = mIndexedStrings.find(str); - if (iter != std::end(mIndexedStrings)) { - return Ref(iter->second); - } +StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, + const Context& context, bool unique) { + if (unique) { + auto iter = indexed_strings_.find(str); + if (iter != std::end(indexed_strings_)) { + return Ref(iter->second); } - - Entry* entry = new Entry(); - entry->value = str.toString(); - entry->context = context; - entry->index = mStrings.size(); - entry->ref = 0; - mStrings.emplace_back(entry); - mIndexedStrings.insert(std::make_pair(StringPiece16(entry->value), entry)); - return Ref(entry); -} - -StringPool::StyleRef StringPool::makeRef(const StyleString& str) { - return makeRef(str, Context{}); -} - -StringPool::StyleRef StringPool::makeRef(const StyleString& str, const Context& context) { - Entry* entry = new Entry(); - entry->value = str.str; - entry->context = context; - entry->index = mStrings.size(); - entry->ref = 0; - mStrings.emplace_back(entry); - mIndexedStrings.insert(std::make_pair(StringPiece16(entry->value), entry)); - - StyleEntry* styleEntry = new StyleEntry(); - styleEntry->str = Ref(entry); - for (const aapt::Span& span : str.spans) { - styleEntry->spans.emplace_back(Span{makeRef(span.name), - span.firstChar, span.lastChar}); - } - styleEntry->ref = 0; - mStyles.emplace_back(styleEntry); - return StyleRef(styleEntry); -} - -StringPool::StyleRef StringPool::makeRef(const StyleRef& ref) { - Entry* entry = new Entry(); - entry->value = *ref.mEntry->str; - entry->context = ref.mEntry->str.mEntry->context; - entry->index = mStrings.size(); - entry->ref = 0; - mStrings.emplace_back(entry); - mIndexedStrings.insert(std::make_pair(StringPiece16(entry->value), entry)); - - StyleEntry* styleEntry = new StyleEntry(); - styleEntry->str = Ref(entry); - for (const Span& span : ref.mEntry->spans) { - styleEntry->spans.emplace_back(Span{ makeRef(*span.name), span.firstChar, span.lastChar }); - } - styleEntry->ref = 0; - mStyles.emplace_back(styleEntry); - return StyleRef(styleEntry); -} - -void StringPool::merge(StringPool&& pool) { - mIndexedStrings.insert(pool.mIndexedStrings.begin(), pool.mIndexedStrings.end()); - pool.mIndexedStrings.clear(); - std::move(pool.mStrings.begin(), pool.mStrings.end(), std::back_inserter(mStrings)); - pool.mStrings.clear(); - std::move(pool.mStyles.begin(), pool.mStyles.end(), std::back_inserter(mStyles)); - pool.mStyles.clear(); - - // Assign the indices. - const size_t len = mStrings.size(); - for (size_t index = 0; index < len; index++) { - mStrings[index]->index = index; - } -} - -void StringPool::hintWillAdd(size_t stringCount, size_t styleCount) { - mStrings.reserve(mStrings.size() + stringCount); - mStyles.reserve(mStyles.size() + styleCount); -} - -void StringPool::prune() { - const auto iterEnd = std::end(mIndexedStrings); - auto indexIter = std::begin(mIndexedStrings); - while (indexIter != iterEnd) { - if (indexIter->second->ref <= 0) { - indexIter = mIndexedStrings.erase(indexIter); - } else { - ++indexIter; - } - } - - auto endIter2 = std::remove_if(std::begin(mStrings), std::end(mStrings), - [](const std::unique_ptr<Entry>& entry) -> bool { - return entry->ref <= 0; - } - ); - - auto endIter3 = std::remove_if(std::begin(mStyles), std::end(mStyles), - [](const std::unique_ptr<StyleEntry>& entry) -> bool { - return entry->ref <= 0; - } - ); - - // Remove the entries at the end or else we'll be accessing - // a deleted string from the StyleEntry. - mStrings.erase(endIter2, std::end(mStrings)); - mStyles.erase(endIter3, std::end(mStyles)); - - // Reassign the indices. - const size_t len = mStrings.size(); - for (size_t index = 0; index < len; index++) { - mStrings[index]->index = index; - } -} - -void StringPool::sort(const std::function<bool(const Entry&, const Entry&)>& cmp) { - std::sort(std::begin(mStrings), std::end(mStrings), - [&cmp](const std::unique_ptr<Entry>& a, const std::unique_ptr<Entry>& b) -> bool { - return cmp(*a, *b); - } - ); - - // Assign the indices. - const size_t len = mStrings.size(); - for (size_t index = 0; index < len; index++) { - mStrings[index]->index = index; + } + + Entry* entry = new Entry(); + entry->value = str.ToString(); + entry->context = context; + entry->index = strings_.size(); + entry->ref_ = 0; + strings_.emplace_back(entry); + indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry)); + return Ref(entry); +} + +StringPool::StyleRef StringPool::MakeRef(const StyleString& str) { + return MakeRef(str, Context{}); +} + +StringPool::StyleRef StringPool::MakeRef(const StyleString& str, + const Context& context) { + Entry* entry = new Entry(); + entry->value = str.str; + entry->context = context; + entry->index = strings_.size(); + entry->ref_ = 0; + strings_.emplace_back(entry); + indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry)); + + StyleEntry* style_entry = new StyleEntry(); + style_entry->str = Ref(entry); + for (const aapt::Span& span : str.spans) { + style_entry->spans.emplace_back( + Span{MakeRef(span.name), span.first_char, span.last_char}); + } + style_entry->ref_ = 0; + styles_.emplace_back(style_entry); + return StyleRef(style_entry); +} + +StringPool::StyleRef StringPool::MakeRef(const StyleRef& ref) { + Entry* entry = new Entry(); + entry->value = *ref.entry_->str; + entry->context = ref.entry_->str.entry_->context; + entry->index = strings_.size(); + entry->ref_ = 0; + strings_.emplace_back(entry); + indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry)); + + StyleEntry* style_entry = new StyleEntry(); + style_entry->str = Ref(entry); + for (const Span& span : ref.entry_->spans) { + style_entry->spans.emplace_back( + Span{MakeRef(*span.name), span.first_char, span.last_char}); + } + style_entry->ref_ = 0; + styles_.emplace_back(style_entry); + return StyleRef(style_entry); +} + +void StringPool::Merge(StringPool&& pool) { + indexed_strings_.insert(pool.indexed_strings_.begin(), + pool.indexed_strings_.end()); + pool.indexed_strings_.clear(); + std::move(pool.strings_.begin(), pool.strings_.end(), + std::back_inserter(strings_)); + pool.strings_.clear(); + std::move(pool.styles_.begin(), pool.styles_.end(), + std::back_inserter(styles_)); + pool.styles_.clear(); + + // Assign the indices. + const size_t len = strings_.size(); + for (size_t index = 0; index < len; index++) { + strings_[index]->index = index; + } +} + +void StringPool::HintWillAdd(size_t stringCount, size_t styleCount) { + strings_.reserve(strings_.size() + stringCount); + styles_.reserve(styles_.size() + styleCount); +} + +void StringPool::Prune() { + const auto iter_end = indexed_strings_.end(); + auto index_iter = indexed_strings_.begin(); + while (index_iter != iter_end) { + if (index_iter->second->ref_ <= 0) { + index_iter = indexed_strings_.erase(index_iter); + } else { + ++index_iter; } - - // Reorder the styles. - std::sort(std::begin(mStyles), std::end(mStyles), + } + + auto end_iter2 = + std::remove_if(strings_.begin(), strings_.end(), + [](const std::unique_ptr<Entry>& entry) -> bool { + return entry->ref_ <= 0; + }); + + auto end_iter3 = + std::remove_if(styles_.begin(), styles_.end(), + [](const std::unique_ptr<StyleEntry>& entry) -> bool { + return entry->ref_ <= 0; + }); + + // Remove the entries at the end or else we'll be accessing + // a deleted string from the StyleEntry. + strings_.erase(end_iter2, strings_.end()); + styles_.erase(end_iter3, styles_.end()); + + // Reassign the indices. + const size_t len = strings_.size(); + for (size_t index = 0; index < len; index++) { + strings_[index]->index = index; + } +} + +void StringPool::Sort( + const std::function<bool(const Entry&, const Entry&)>& cmp) { + std::sort( + strings_.begin(), strings_.end(), + [&cmp](const std::unique_ptr<Entry>& a, + const std::unique_ptr<Entry>& b) -> bool { return cmp(*a, *b); }); + + // Assign the indices. + const size_t len = strings_.size(); + for (size_t index = 0; index < len; index++) { + strings_[index]->index = index; + } + + // Reorder the styles. + std::sort(styles_.begin(), styles_.end(), [](const std::unique_ptr<StyleEntry>& lhs, const std::unique_ptr<StyleEntry>& rhs) -> bool { - return lhs->str.getIndex() < rhs->str.getIndex(); - } - ); + return lhs->str.index() < rhs->str.index(); + }); } template <typename T> -static T* encodeLength(T* data, size_t length) { - static_assert(std::is_integral<T>::value, "wat."); +static T* EncodeLength(T* data, size_t length) { + static_assert(std::is_integral<T>::value, "wat."); - constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1); - constexpr size_t kMaxSize = kMask - 1; - if (length > kMaxSize) { - *data++ = kMask | (kMaxSize & (length >> (sizeof(T) * 8))); - } - *data++ = length; - return data; + constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1); + constexpr size_t kMaxSize = kMask - 1; + if (length > kMaxSize) { + *data++ = kMask | (kMaxSize & (length >> (sizeof(T) * 8))); + } + *data++ = length; + return data; } template <typename T> -static size_t encodedLengthUnits(size_t length) { - static_assert(std::is_integral<T>::value, "wat."); +static size_t EncodedLengthUnits(size_t length) { + static_assert(std::is_integral<T>::value, "wat."); - constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1); - constexpr size_t kMaxSize = kMask - 1; - return length > kMaxSize ? 2 : 1; + constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1); + constexpr size_t kMaxSize = kMask - 1; + return length > kMaxSize ? 2 : 1; } +bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) { + const size_t start_index = out->size(); + android::ResStringPool_header* header = + out->NextBlock<android::ResStringPool_header>(); + header->header.type = android::RES_STRING_POOL_TYPE; + header->header.headerSize = sizeof(*header); + header->stringCount = pool.size(); + if (utf8) { + header->flags |= android::ResStringPool_header::UTF8_FLAG; + } -bool StringPool::flatten(BigBuffer* out, const StringPool& pool, bool utf8) { - const size_t startIndex = out->size(); - android::ResStringPool_header* header = out->nextBlock<android::ResStringPool_header>(); - header->header.type = android::RES_STRING_POOL_TYPE; - header->header.headerSize = sizeof(*header); - header->stringCount = pool.size(); - if (utf8) { - header->flags |= android::ResStringPool_header::UTF8_FLAG; - } + uint32_t* indices = + pool.size() != 0 ? out->NextBlock<uint32_t>(pool.size()) : nullptr; - uint32_t* indices = pool.size() != 0 ? out->nextBlock<uint32_t>(pool.size()) : nullptr; + uint32_t* style_indices = nullptr; + if (!pool.styles_.empty()) { + header->styleCount = pool.styles_.back()->str.index() + 1; + style_indices = out->NextBlock<uint32_t>(header->styleCount); + } - uint32_t* styleIndices = nullptr; - if (!pool.mStyles.empty()) { - header->styleCount = pool.mStyles.back()->str.getIndex() + 1; - styleIndices = out->nextBlock<uint32_t>(header->styleCount); - } + const size_t before_strings_index = out->size(); + header->stringsStart = before_strings_index - start_index; + + for (const auto& entry : pool) { + *indices = out->size() - before_strings_index; + indices++; - const size_t beforeStringsIndex = out->size(); - header->stringsStart = beforeStringsIndex - startIndex; + if (utf8) { + const std::string& encoded = entry->value; + const ssize_t utf16_length = utf8_to_utf16_length( + reinterpret_cast<const uint8_t*>(entry->value.data()), + entry->value.size()); + CHECK(utf16_length >= 0); - for (const auto& entry : pool) { - *indices = out->size() - beforeStringsIndex; - indices++; + const size_t total_size = EncodedLengthUnits<char>(utf16_length) + + EncodedLengthUnits<char>(encoded.length()) + + encoded.size() + 1; - if (utf8) { - std::string encoded = util::utf16ToUtf8(entry->value); + char* data = out->NextBlock<char>(total_size); - const size_t totalSize = encodedLengthUnits<char>(entry->value.size()) - + encodedLengthUnits<char>(encoded.length()) - + encoded.size() + 1; + // First encode the UTF16 string length. + data = EncodeLength(data, utf16_length); - char* data = out->nextBlock<char>(totalSize); + // Now encode the size of the real UTF8 string. + data = EncodeLength(data, encoded.length()); + strncpy(data, encoded.data(), encoded.size()); - // First encode the actual UTF16 string length. - data = encodeLength(data, entry->value.size()); + } else { + const std::u16string encoded = util::Utf8ToUtf16(entry->value); + const ssize_t utf16_length = encoded.size(); - // Now encode the size of the converted UTF8 string. - data = encodeLength(data, encoded.length()); - strncpy(data, encoded.data(), encoded.size()); - } else { - const size_t totalSize = encodedLengthUnits<char16_t>(entry->value.size()) - + entry->value.size() + 1; + // Total number of 16-bit words to write. + const size_t total_size = + EncodedLengthUnits<char16_t>(utf16_length) + encoded.size() + 1; - char16_t* data = out->nextBlock<char16_t>(totalSize); + char16_t* data = out->NextBlock<char16_t>(total_size); - // Encode the actual UTF16 string length. - data = encodeLength(data, entry->value.size()); - const size_t byteLength = entry->value.size() * sizeof(char16_t); + // Encode the actual UTF16 string length. + data = EncodeLength(data, utf16_length); + const size_t byte_length = encoded.size() * sizeof(char16_t); - // NOTE: For some reason, strncpy16(data, entry->value.data(), entry->value.size()) - // truncates the string. - memcpy(data, entry->value.data(), byteLength); + // NOTE: For some reason, strncpy16(data, entry->value.data(), + // entry->value.size()) truncates the string. + memcpy(data, encoded.data(), byte_length); - // The null-terminating character is already here due to the block of data being set - // to 0s on allocation. - } + // The null-terminating character is already here due to the block of data + // being set to 0s on allocation. } - - out->align4(); - - if (!pool.mStyles.empty()) { - const size_t beforeStylesIndex = out->size(); - header->stylesStart = beforeStylesIndex - startIndex; - - size_t currentIndex = 0; - for (const auto& entry : pool.mStyles) { - while (entry->str.getIndex() > currentIndex) { - styleIndices[currentIndex++] = out->size() - beforeStylesIndex; - - uint32_t* spanOffset = out->nextBlock<uint32_t>(); - *spanOffset = android::ResStringPool_span::END; - } - styleIndices[currentIndex++] = out->size() - beforeStylesIndex; - - android::ResStringPool_span* span = - out->nextBlock<android::ResStringPool_span>(entry->spans.size()); - for (const auto& s : entry->spans) { - span->name.index = s.name.getIndex(); - span->firstChar = s.firstChar; - span->lastChar = s.lastChar; - span++; - } - - uint32_t* spanEnd = out->nextBlock<uint32_t>(); - *spanEnd = android::ResStringPool_span::END; - } - - // The error checking code in the platform looks for an entire - // ResStringPool_span structure worth of 0xFFFFFFFF at the end - // of the style block, so fill in the remaining 2 32bit words - // with 0xFFFFFFFF. - const size_t paddingLength = sizeof(android::ResStringPool_span) - - sizeof(android::ResStringPool_span::name); - uint8_t* padding = out->nextBlock<uint8_t>(paddingLength); - memset(padding, 0xff, paddingLength); - out->align4(); + } + + out->Align4(); + + if (!pool.styles_.empty()) { + const size_t before_styles_index = out->size(); + header->stylesStart = before_styles_index - start_index; + + size_t current_index = 0; + for (const auto& entry : pool.styles_) { + while (entry->str.index() > current_index) { + style_indices[current_index++] = out->size() - before_styles_index; + + uint32_t* span_offset = out->NextBlock<uint32_t>(); + *span_offset = android::ResStringPool_span::END; + } + style_indices[current_index++] = out->size() - before_styles_index; + + android::ResStringPool_span* span = + out->NextBlock<android::ResStringPool_span>(entry->spans.size()); + for (const auto& s : entry->spans) { + span->name.index = s.name.index(); + span->firstChar = s.first_char; + span->lastChar = s.last_char; + span++; + } + + uint32_t* spanEnd = out->NextBlock<uint32_t>(); + *spanEnd = android::ResStringPool_span::END; } - header->header.size = out->size() - startIndex; - return true; + + // The error checking code in the platform looks for an entire + // ResStringPool_span structure worth of 0xFFFFFFFF at the end + // of the style block, so fill in the remaining 2 32bit words + // with 0xFFFFFFFF. + const size_t padding_length = sizeof(android::ResStringPool_span) - + sizeof(android::ResStringPool_span::name); + uint8_t* padding = out->NextBlock<uint8_t>(padding_length); + memset(padding, 0xff, padding_length); + out->Align4(); + } + header->header.size = out->size() - start_index; + return true; } -bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) { - return flatten(out, pool, true); +bool StringPool::FlattenUtf8(BigBuffer* out, const StringPool& pool) { + return Flatten(out, pool, true); } -bool StringPool::flattenUtf16(BigBuffer* out, const StringPool& pool) { - return flatten(out, pool, false); +bool StringPool::FlattenUtf16(BigBuffer* out, const StringPool& pool) { + return Flatten(out, pool, false); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h index 9e1ca912796a..a4f556ca52e4 100644 --- a/tools/aapt2/StringPool.h +++ b/tools/aapt2/StringPool.h @@ -17,207 +17,218 @@ #ifndef AAPT_STRING_POOL_H #define AAPT_STRING_POOL_H -#include "util/BigBuffer.h" -#include "ConfigDescription.h" -#include "util/StringPiece.h" - #include <functional> -#include <map> #include <memory> #include <string> +#include <unordered_map> #include <vector> +#include "ConfigDescription.h" +#include "util/BigBuffer.h" +#include "util/StringPiece.h" + namespace aapt { struct Span { - std::u16string name; - uint32_t firstChar; - uint32_t lastChar; + std::string name; + uint32_t first_char; + uint32_t last_char; }; struct StyleString { - std::u16string str; - std::vector<Span> spans; + std::string str; + std::vector<Span> spans; }; class StringPool { -public: - struct Context { - uint32_t priority; - ConfigDescription config; + public: + class Context { + public: + enum : uint32_t { + kStylePriority = 0u, + kHighPriority = 1u, + kNormalPriority = 0x7fffffffu, + kLowPriority = 0xffffffffu, }; + uint32_t priority = kNormalPriority; + ConfigDescription config; - class Entry; + Context() = default; + Context(uint32_t p, const ConfigDescription& c) : priority(p), config(c) {} + explicit Context(uint32_t p) : priority(p) {} + explicit Context(const ConfigDescription& c) + : priority(kNormalPriority), config(c) {} + }; - class Ref { - public: - Ref(); - Ref(const Ref&); - ~Ref(); + class Entry; - Ref& operator=(const Ref& rhs); - const std::u16string* operator->() const; - const std::u16string& operator*() const; + class Ref { + public: + Ref(); + Ref(const Ref&); + ~Ref(); - size_t getIndex() const; - const Context& getContext() const; + Ref& operator=(const Ref& rhs); + const std::string* operator->() const; + const std::string& operator*() const; - private: - friend class StringPool; + size_t index() const; + const Context& GetContext() const; - explicit Ref(Entry* entry); + private: + friend class StringPool; - Entry* mEntry; - }; - - class StyleEntry; + explicit Ref(Entry* entry); - class StyleRef { - public: - StyleRef(); - StyleRef(const StyleRef&); - ~StyleRef(); + Entry* entry_; + }; - StyleRef& operator=(const StyleRef& rhs); - const StyleEntry* operator->() const; - const StyleEntry& operator*() const; + class StyleEntry; - size_t getIndex() const; - const Context& getContext() const; + class StyleRef { + public: + StyleRef(); + StyleRef(const StyleRef&); + ~StyleRef(); - private: - friend class StringPool; + StyleRef& operator=(const StyleRef& rhs); + const StyleEntry* operator->() const; + const StyleEntry& operator*() const; - explicit StyleRef(StyleEntry* entry); + size_t index() const; + const Context& GetContext() const; - StyleEntry* mEntry; - }; + private: + friend class StringPool; - class Entry { - public: - std::u16string value; - Context context; - size_t index; + explicit StyleRef(StyleEntry* entry); - private: - friend class StringPool; - friend class Ref; + StyleEntry* entry_; + }; - int ref; - }; + class Entry { + public: + std::string value; + Context context; + size_t index; - struct Span { - Ref name; - uint32_t firstChar; - uint32_t lastChar; - }; + private: + friend class StringPool; + friend class Ref; - class StyleEntry { - public: - Ref str; - std::vector<Span> spans; + int ref_; + }; - private: - friend class StringPool; - friend class StyleRef; + struct Span { + Ref name; + uint32_t first_char; + uint32_t last_char; + }; - int ref; - }; + class StyleEntry { + public: + Ref str; + std::vector<Span> spans; - using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator; - - static bool flattenUtf8(BigBuffer* out, const StringPool& pool); - static bool flattenUtf16(BigBuffer* out, const StringPool& pool); - - StringPool() = default; - StringPool(const StringPool&) = delete; - - /** - * Adds a string to the pool, unless it already exists. Returns - * a reference to the string in the pool. - */ - Ref makeRef(const StringPiece16& str); - - /** - * Adds a string to the pool, unless it already exists, with a context - * object that can be used when sorting the string pool. Returns - * a reference to the string in the pool. - */ - Ref makeRef(const StringPiece16& str, const Context& context); - - /** - * Adds a style to the string pool and returns a reference to it. - */ - StyleRef makeRef(const StyleString& str); - - /** - * Adds a style to the string pool with a context object that - * can be used when sorting the string pool. Returns a reference - * to the style in the string pool. - */ - StyleRef makeRef(const StyleString& str, const Context& context); - - /** - * Adds a style from another string pool. Returns a reference to the - * style in the string pool. - */ - StyleRef makeRef(const StyleRef& ref); - - /** - * Moves pool into this one without coalescing strings. When this - * function returns, pool will be empty. - */ - void merge(StringPool&& pool); - - /** - * Retuns the number of strings in the table. - */ - inline size_t size() const; - - /** - * Reserves space for strings and styles as an optimization. - */ - void hintWillAdd(size_t stringCount, size_t styleCount); - - /** - * Sorts the strings according to some comparison function. - */ - void sort(const std::function<bool(const Entry&, const Entry&)>& cmp); - - /** - * Removes any strings that have no references. - */ - void prune(); - -private: - friend const_iterator begin(const StringPool& pool); - friend const_iterator end(const StringPool& pool); - - static bool flatten(BigBuffer* out, const StringPool& pool, bool utf8); - - Ref makeRefImpl(const StringPiece16& str, const Context& context, bool unique); - - std::vector<std::unique_ptr<Entry>> mStrings; - std::vector<std::unique_ptr<StyleEntry>> mStyles; - std::multimap<StringPiece16, Entry*> mIndexedStrings; + private: + friend class StringPool; + friend class StyleRef; + + int ref_; + }; + + using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator; + + static bool FlattenUtf8(BigBuffer* out, const StringPool& pool); + static bool FlattenUtf16(BigBuffer* out, const StringPool& pool); + + StringPool() = default; + StringPool(const StringPool&) = delete; + + /** + * Adds a string to the pool, unless it already exists. Returns + * a reference to the string in the pool. + */ + Ref MakeRef(const StringPiece& str); + + /** + * Adds a string to the pool, unless it already exists, with a context + * object that can be used when sorting the string pool. Returns + * a reference to the string in the pool. + */ + Ref MakeRef(const StringPiece& str, const Context& context); + + /** + * Adds a style to the string pool and returns a reference to it. + */ + StyleRef MakeRef(const StyleString& str); + + /** + * Adds a style to the string pool with a context object that + * can be used when sorting the string pool. Returns a reference + * to the style in the string pool. + */ + StyleRef MakeRef(const StyleString& str, const Context& context); + + /** + * Adds a style from another string pool. Returns a reference to the + * style in the string pool. + */ + StyleRef MakeRef(const StyleRef& ref); + + /** + * Moves pool into this one without coalescing strings. When this + * function returns, pool will be empty. + */ + void Merge(StringPool&& pool); + + /** + * Returns the number of strings in the table. + */ + inline size_t size() const; + + /** + * Reserves space for strings and styles as an optimization. + */ + void HintWillAdd(size_t string_count, size_t style_count); + + /** + * Sorts the strings according to some comparison function. + */ + void Sort(const std::function<bool(const Entry&, const Entry&)>& cmp); + + /** + * Removes any strings that have no references. + */ + void Prune(); + + private: + friend const_iterator begin(const StringPool& pool); + friend const_iterator end(const StringPool& pool); + + static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8); + + Ref MakeRefImpl(const StringPiece& str, const Context& context, bool unique); + + std::vector<std::unique_ptr<Entry>> strings_; + std::vector<std::unique_ptr<StyleEntry>> styles_; + std::unordered_multimap<StringPiece, Entry*> indexed_strings_; }; // // Inline implementation // -inline size_t StringPool::size() const { - return mStrings.size(); -} +inline size_t StringPool::size() const { return strings_.size(); } inline StringPool::const_iterator begin(const StringPool& pool) { - return pool.mStrings.begin(); + return pool.strings_.begin(); } inline StringPool::const_iterator end(const StringPool& pool) { - return pool.mStrings.end(); + return pool.strings_.end(); } -} // namespace aapt +} // namespace aapt -#endif // AAPT_STRING_POOL_H +#endif // AAPT_STRING_POOL_H diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp index 2b2d348fd17c..e1394fc0221f 100644 --- a/tools/aapt2/StringPool_test.cpp +++ b/tools/aapt2/StringPool_test.cpp @@ -15,237 +15,254 @@ */ #include "StringPool.h" -#include "util/Util.h" -#include <gtest/gtest.h> #include <string> +#include "test/Test.h" +#include "util/Util.h" + namespace aapt { TEST(StringPoolTest, InsertOneString) { - StringPool pool; + StringPool pool; - StringPool::Ref ref = pool.makeRef(u"wut"); - EXPECT_EQ(*ref, u"wut"); + StringPool::Ref ref = pool.MakeRef("wut"); + EXPECT_EQ(*ref, "wut"); } TEST(StringPoolTest, InsertTwoUniqueStrings) { - StringPool pool; + StringPool pool; - StringPool::Ref ref = pool.makeRef(u"wut"); - StringPool::Ref ref2 = pool.makeRef(u"hey"); + StringPool::Ref ref = pool.MakeRef("wut"); + StringPool::Ref ref2 = pool.MakeRef("hey"); - EXPECT_EQ(*ref, u"wut"); - EXPECT_EQ(*ref2, u"hey"); + EXPECT_EQ(*ref, "wut"); + EXPECT_EQ(*ref2, "hey"); } TEST(StringPoolTest, DoNotInsertNewDuplicateString) { - StringPool pool; + StringPool pool; - StringPool::Ref ref = pool.makeRef(u"wut"); - StringPool::Ref ref2 = pool.makeRef(u"wut"); + StringPool::Ref ref = pool.MakeRef("wut"); + StringPool::Ref ref2 = pool.MakeRef("wut"); - EXPECT_EQ(*ref, u"wut"); - EXPECT_EQ(*ref2, u"wut"); - EXPECT_EQ(1u, pool.size()); + EXPECT_EQ(*ref, "wut"); + EXPECT_EQ(*ref2, "wut"); + EXPECT_EQ(1u, pool.size()); } TEST(StringPoolTest, MaintainInsertionOrderIndex) { - StringPool pool; + StringPool pool; - StringPool::Ref ref = pool.makeRef(u"z"); - StringPool::Ref ref2 = pool.makeRef(u"a"); - StringPool::Ref ref3 = pool.makeRef(u"m"); + StringPool::Ref ref = pool.MakeRef("z"); + StringPool::Ref ref2 = pool.MakeRef("a"); + StringPool::Ref ref3 = pool.MakeRef("m"); - EXPECT_EQ(0u, ref.getIndex()); - EXPECT_EQ(1u, ref2.getIndex()); - EXPECT_EQ(2u, ref3.getIndex()); + EXPECT_EQ(0u, ref.index()); + EXPECT_EQ(1u, ref2.index()); + EXPECT_EQ(2u, ref3.index()); } TEST(StringPoolTest, PruneStringsWithNoReferences) { - StringPool pool; - - StringPool::Ref refA = pool.makeRef(u"foo"); - { - StringPool::Ref ref = pool.makeRef(u"wut"); - EXPECT_EQ(*ref, u"wut"); - EXPECT_EQ(2u, pool.size()); - } - StringPool::Ref refB = pool.makeRef(u"bar"); - - EXPECT_EQ(3u, pool.size()); - pool.prune(); + StringPool pool; + + StringPool::Ref refA = pool.MakeRef("foo"); + { + StringPool::Ref ref = pool.MakeRef("wut"); + EXPECT_EQ(*ref, "wut"); EXPECT_EQ(2u, pool.size()); - StringPool::const_iterator iter = begin(pool); - EXPECT_EQ((*iter)->value, u"foo"); - EXPECT_LT((*iter)->index, 2u); - ++iter; - EXPECT_EQ((*iter)->value, u"bar"); - EXPECT_LT((*iter)->index, 2u); + } + StringPool::Ref refB = pool.MakeRef("bar"); + + EXPECT_EQ(3u, pool.size()); + pool.Prune(); + EXPECT_EQ(2u, pool.size()); + StringPool::const_iterator iter = begin(pool); + EXPECT_EQ((*iter)->value, "foo"); + EXPECT_LT((*iter)->index, 2u); + ++iter; + EXPECT_EQ((*iter)->value, "bar"); + EXPECT_LT((*iter)->index, 2u); } TEST(StringPoolTest, SortAndMaintainIndexesInReferences) { - StringPool pool; - - StringPool::Ref ref = pool.makeRef(u"z"); - StringPool::StyleRef ref2 = pool.makeRef(StyleString{ {u"a"} }); - StringPool::Ref ref3 = pool.makeRef(u"m"); + StringPool pool; - EXPECT_EQ(*ref, u"z"); - EXPECT_EQ(0u, ref.getIndex()); + StringPool::Ref ref = pool.MakeRef("z"); + StringPool::StyleRef ref2 = pool.MakeRef(StyleString{{"a"}}); + StringPool::Ref ref3 = pool.MakeRef("m"); - EXPECT_EQ(*(ref2->str), u"a"); - EXPECT_EQ(1u, ref2.getIndex()); + EXPECT_EQ(*ref, "z"); + EXPECT_EQ(0u, ref.index()); - EXPECT_EQ(*ref3, u"m"); - EXPECT_EQ(2u, ref3.getIndex()); + EXPECT_EQ(*(ref2->str), "a"); + EXPECT_EQ(1u, ref2.index()); - pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - return a.value < b.value; - }); + EXPECT_EQ(*ref3, "m"); + EXPECT_EQ(2u, ref3.index()); + pool.Sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { + return a.value < b.value; + }); - EXPECT_EQ(*ref, u"z"); - EXPECT_EQ(2u, ref.getIndex()); + EXPECT_EQ(*ref, "z"); + EXPECT_EQ(2u, ref.index()); - EXPECT_EQ(*(ref2->str), u"a"); - EXPECT_EQ(0u, ref2.getIndex()); + EXPECT_EQ(*(ref2->str), "a"); + EXPECT_EQ(0u, ref2.index()); - EXPECT_EQ(*ref3, u"m"); - EXPECT_EQ(1u, ref3.getIndex()); + EXPECT_EQ(*ref3, "m"); + EXPECT_EQ(1u, ref3.index()); } TEST(StringPoolTest, SortAndStillDedupe) { - StringPool pool; + StringPool pool; - StringPool::Ref ref = pool.makeRef(u"z"); - StringPool::Ref ref2 = pool.makeRef(u"a"); - StringPool::Ref ref3 = pool.makeRef(u"m"); + StringPool::Ref ref = pool.MakeRef("z"); + StringPool::Ref ref2 = pool.MakeRef("a"); + StringPool::Ref ref3 = pool.MakeRef("m"); - pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - return a.value < b.value; - }); + pool.Sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { + return a.value < b.value; + }); - StringPool::Ref ref4 = pool.makeRef(u"z"); - StringPool::Ref ref5 = pool.makeRef(u"a"); - StringPool::Ref ref6 = pool.makeRef(u"m"); + StringPool::Ref ref4 = pool.MakeRef("z"); + StringPool::Ref ref5 = pool.MakeRef("a"); + StringPool::Ref ref6 = pool.MakeRef("m"); - EXPECT_EQ(ref4.getIndex(), ref.getIndex()); - EXPECT_EQ(ref5.getIndex(), ref2.getIndex()); - EXPECT_EQ(ref6.getIndex(), ref3.getIndex()); + EXPECT_EQ(ref4.index(), ref.index()); + EXPECT_EQ(ref5.index(), ref2.index()); + EXPECT_EQ(ref6.index(), ref3.index()); } TEST(StringPoolTest, AddStyles) { - StringPool pool; + StringPool pool; - StyleString str { - { u"android" }, - { - Span{ { u"b" }, 2, 6 } - } - }; + StyleString str{{"android"}, {Span{{"b"}, 2, 6}}}; - StringPool::StyleRef ref = pool.makeRef(str); + StringPool::StyleRef ref = pool.MakeRef(str); - EXPECT_EQ(0u, ref.getIndex()); - EXPECT_EQ(std::u16string(u"android"), *(ref->str)); - ASSERT_EQ(1u, ref->spans.size()); + EXPECT_EQ(0u, ref.index()); + EXPECT_EQ(std::string("android"), *(ref->str)); + ASSERT_EQ(1u, ref->spans.size()); - const StringPool::Span& span = ref->spans.front(); - EXPECT_EQ(*(span.name), u"b"); - EXPECT_EQ(2u, span.firstChar); - EXPECT_EQ(6u, span.lastChar); + const StringPool::Span& span = ref->spans.front(); + EXPECT_EQ(*(span.name), "b"); + EXPECT_EQ(2u, span.first_char); + EXPECT_EQ(6u, span.last_char); } TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) { - StringPool pool; + StringPool pool; - StringPool::Ref ref = pool.makeRef(u"android"); + StringPool::Ref ref = pool.MakeRef("android"); - StyleString str { { u"android" } }; - StringPool::StyleRef styleRef = pool.makeRef(str); + StyleString str{{"android"}}; + StringPool::StyleRef styleRef = pool.MakeRef(str); - EXPECT_NE(ref.getIndex(), styleRef.getIndex()); + EXPECT_NE(ref.index(), styleRef.index()); } TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { - using namespace android; // For NO_ERROR on Windows. + using namespace android; // For NO_ERROR on Windows. - StringPool pool; - BigBuffer buffer(1024); - StringPool::flattenUtf8(&buffer, pool); + StringPool pool; + BigBuffer buffer(1024); + StringPool::FlattenUtf8(&buffer, pool); - std::unique_ptr<uint8_t[]> data = util::copy(buffer); - ResStringPool test; - ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); + std::unique_ptr<uint8_t[]> data = util::Copy(buffer); + ResStringPool test; + ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); } TEST(StringPoolTest, FlattenOddCharactersUtf16) { - using namespace android; // For NO_ERROR on Windows. + using namespace android; // For NO_ERROR on Windows. + + StringPool pool; + pool.MakeRef("\u093f"); + BigBuffer buffer(1024); + StringPool::FlattenUtf16(&buffer, pool); + + std::unique_ptr<uint8_t[]> data = util::Copy(buffer); + ResStringPool test; + ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); + size_t len = 0; + const char16_t* str = test.stringAt(0, &len); + EXPECT_EQ(1u, len); + EXPECT_EQ(u'\u093f', *str); + EXPECT_EQ(0u, str[1]); +} + +constexpr const char* sLongString = + "バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑" + "え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限" + "します。メール、SMSや、同期を使 " + "用するその他のアプリは、起動しても更新されないことがあります。バッテリーセ" + "ーバーは端末の充電中は自動的にOFFになります。"; + +TEST(StringPoolTest, Flatten) { + using namespace android; // For NO_ERROR on Windows. + + StringPool pool; - StringPool pool; - pool.makeRef(u"\u093f"); - BigBuffer buffer(1024); - StringPool::flattenUtf16(&buffer, pool); + StringPool::Ref ref1 = pool.MakeRef("hello"); + StringPool::Ref ref2 = pool.MakeRef("goodbye"); + StringPool::Ref ref3 = pool.MakeRef(sLongString); + StringPool::Ref ref4 = pool.MakeRef(""); + StringPool::StyleRef ref5 = pool.MakeRef( + StyleString{{"style"}, {Span{{"b"}, 0, 1}, Span{{"i"}, 2, 3}}}); + + EXPECT_EQ(0u, ref1.index()); + EXPECT_EQ(1u, ref2.index()); + EXPECT_EQ(2u, ref3.index()); + EXPECT_EQ(3u, ref4.index()); + EXPECT_EQ(4u, ref5.index()); + + BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)}; + StringPool::FlattenUtf8(&buffers[0], pool); + StringPool::FlattenUtf16(&buffers[1], pool); + + // Test both UTF-8 and UTF-16 buffers. + for (const BigBuffer& buffer : buffers) { + std::unique_ptr<uint8_t[]> data = util::Copy(buffer); - std::unique_ptr<uint8_t[]> data = util::copy(buffer); ResStringPool test; ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); - size_t len = 0; - const char16_t* str = test.stringAt(0, &len); - EXPECT_EQ(1u, len); - EXPECT_EQ(u'\u093f', *str); - EXPECT_EQ(0u, str[1]); -} -constexpr const char16_t* sLongString = u"バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限します。メール、SMSや、同期を使 用するその他のアプリは、起動しても更新されないことがあります。バッテリーセーバーは端末の充電中は自動的にOFFになります。"; - -TEST(StringPoolTest, FlattenUtf8) { - using namespace android; // For NO_ERROR on Windows. - - StringPool pool; - - StringPool::Ref ref1 = pool.makeRef(u"hello"); - StringPool::Ref ref2 = pool.makeRef(u"goodbye"); - StringPool::Ref ref3 = pool.makeRef(sLongString); - StringPool::StyleRef ref4 = pool.makeRef(StyleString{ - { u"style" }, - { Span{ { u"b" }, 0, 1 }, Span{ { u"i" }, 2, 3 } } - }); - - EXPECT_EQ(0u, ref1.getIndex()); - EXPECT_EQ(1u, ref2.getIndex()); - EXPECT_EQ(2u, ref3.getIndex()); - EXPECT_EQ(3u, ref4.getIndex()); - - BigBuffer buffer(1024); - StringPool::flattenUtf8(&buffer, pool); - - std::unique_ptr<uint8_t[]> data = util::copy(buffer); - { - ResStringPool test; - ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); - - EXPECT_EQ(util::getString(test, 0), u"hello"); - EXPECT_EQ(util::getString(test, 1), u"goodbye"); - EXPECT_EQ(util::getString(test, 2), sLongString); - EXPECT_EQ(util::getString(test, 3), u"style"); - - const ResStringPool_span* span = test.styleAt(3); - ASSERT_NE(nullptr, span); - EXPECT_EQ(util::getString(test, span->name.index), u"b"); - EXPECT_EQ(0u, span->firstChar); - EXPECT_EQ(1u, span->lastChar); - span++; - - ASSERT_NE(ResStringPool_span::END, span->name.index); - EXPECT_EQ(util::getString(test, span->name.index), u"i"); - EXPECT_EQ(2u, span->firstChar); - EXPECT_EQ(3u, span->lastChar); - span++; - - EXPECT_EQ(ResStringPool_span::END, span->name.index); - } + EXPECT_EQ(std::string("hello"), util::GetString(test, 0)); + EXPECT_EQ(StringPiece16(u"hello"), util::GetString16(test, 0)); + + EXPECT_EQ(std::string("goodbye"), util::GetString(test, 1)); + EXPECT_EQ(StringPiece16(u"goodbye"), util::GetString16(test, 1)); + + EXPECT_EQ(StringPiece(sLongString), util::GetString(test, 2)); + EXPECT_EQ(util::Utf8ToUtf16(sLongString), + util::GetString16(test, 2).ToString()); + + size_t len; + EXPECT_TRUE(test.stringAt(3, &len) != nullptr || + test.string8At(3, &len) != nullptr); + + EXPECT_EQ(std::string("style"), util::GetString(test, 4)); + EXPECT_EQ(StringPiece16(u"style"), util::GetString16(test, 4)); + + const ResStringPool_span* span = test.styleAt(4); + ASSERT_NE(nullptr, span); + EXPECT_EQ(std::string("b"), util::GetString(test, span->name.index)); + EXPECT_EQ(StringPiece16(u"b"), util::GetString16(test, span->name.index)); + EXPECT_EQ(0u, span->firstChar); + EXPECT_EQ(1u, span->lastChar); + span++; + + ASSERT_NE(ResStringPool_span::END, span->name.index); + EXPECT_EQ(std::string("i"), util::GetString(test, span->name.index)); + EXPECT_EQ(StringPiece16(u"i"), util::GetString16(test, span->name.index)); + EXPECT_EQ(2u, span->firstChar); + EXPECT_EQ(3u, span->lastChar); + span++; + + EXPECT_EQ(ResStringPool_span::END, span->name.index); + } } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h index b8bc5db7d6e4..1cb6aa13f336 100644 --- a/tools/aapt2/ValueVisitor.h +++ b/tools/aapt2/ValueVisitor.h @@ -17,90 +17,92 @@ #ifndef AAPT_VALUE_VISITOR_H #define AAPT_VALUE_VISITOR_H -#include "ResourceValues.h" #include "ResourceTable.h" +#include "ResourceValues.h" namespace aapt { /** - * Visits a value and invokes the appropriate method based on its type. Does not traverse - * into compound types. Use ValueVisitor for that. + * Visits a value and invokes the appropriate method based on its type. Does not + * traverse into compound types. Use ValueVisitor for that. */ struct RawValueVisitor { - virtual ~RawValueVisitor() = default; - - virtual void visitItem(Item* value) {} - virtual void visit(Reference* value) { visitItem(value); } - virtual void visit(RawString* value) { visitItem(value); } - virtual void visit(String* value) { visitItem(value); } - virtual void visit(StyledString* value) { visitItem(value); } - virtual void visit(FileReference* value) { visitItem(value); } - virtual void visit(Id* value) { visitItem(value); } - virtual void visit(BinaryPrimitive* value) { visitItem(value); } - - virtual void visit(Attribute* value) {} - virtual void visit(Style* value) {} - virtual void visit(Array* value) {} - virtual void visit(Plural* value) {} - virtual void visit(Styleable* value) {} + virtual ~RawValueVisitor() = default; + + virtual void VisitItem(Item* value) {} + virtual void Visit(Reference* value) { VisitItem(value); } + virtual void Visit(RawString* value) { VisitItem(value); } + virtual void Visit(String* value) { VisitItem(value); } + virtual void Visit(StyledString* value) { VisitItem(value); } + virtual void Visit(FileReference* value) { VisitItem(value); } + virtual void Visit(Id* value) { VisitItem(value); } + virtual void Visit(BinaryPrimitive* value) { VisitItem(value); } + + virtual void Visit(Attribute* value) {} + virtual void Visit(Style* value) {} + virtual void Visit(Array* value) {} + virtual void Visit(Plural* value) {} + virtual void Visit(Styleable* value) {} }; -#define DECL_VISIT_COMPOUND_VALUE(T) \ - virtual void visit(T* value) { \ - visitSubValues(value); \ - } +// NOLINT, do not add parentheses around T. +#define DECL_VISIT_COMPOUND_VALUE(T) \ + virtual void Visit(T* value) override { /* NOLINT */ \ + VisitSubValues(value); \ + } /** - * Visits values, and if they are compound values, visits the components as well. + * Visits values, and if they are compound values, visits the components as + * well. */ struct ValueVisitor : public RawValueVisitor { - // The compiler will think we're hiding an overload, when we actually intend - // to call into RawValueVisitor. This will expose the visit methods in the super - // class so the compiler knows we are trying to call them. - using RawValueVisitor::visit; - - void visitSubValues(Attribute* attribute) { - for (Attribute::Symbol& symbol : attribute->symbols) { - visit(&symbol.symbol); - } + // The compiler will think we're hiding an overload, when we actually intend + // to call into RawValueVisitor. This will expose the visit methods in the + // super class so the compiler knows we are trying to call them. + using RawValueVisitor::Visit; + + void VisitSubValues(Attribute* attribute) { + for (Attribute::Symbol& symbol : attribute->symbols) { + Visit(&symbol.symbol); } + } - void visitSubValues(Style* style) { - if (style->parent) { - visit(&style->parent.value()); - } + void VisitSubValues(Style* style) { + if (style->parent) { + Visit(&style->parent.value()); + } - for (Style::Entry& entry : style->entries) { - visit(&entry.key); - entry.value->accept(this); - } + for (Style::Entry& entry : style->entries) { + Visit(&entry.key); + entry.value->Accept(this); } + } - void visitSubValues(Array* array) { - for (std::unique_ptr<Item>& item : array->items) { - item->accept(this); - } + void VisitSubValues(Array* array) { + for (std::unique_ptr<Item>& item : array->items) { + item->Accept(this); } + } - void visitSubValues(Plural* plural) { - for (std::unique_ptr<Item>& item : plural->values) { - if (item) { - item->accept(this); - } - } + void VisitSubValues(Plural* plural) { + for (std::unique_ptr<Item>& item : plural->values) { + if (item) { + item->Accept(this); + } } + } - void visitSubValues(Styleable* styleable) { - for (Reference& reference : styleable->entries) { - visit(&reference); - } + void VisitSubValues(Styleable* styleable) { + for (Reference& reference : styleable->entries) { + Visit(&reference); } + } - DECL_VISIT_COMPOUND_VALUE(Attribute); - DECL_VISIT_COMPOUND_VALUE(Style); - DECL_VISIT_COMPOUND_VALUE(Array); - DECL_VISIT_COMPOUND_VALUE(Plural); - DECL_VISIT_COMPOUND_VALUE(Styleable); + DECL_VISIT_COMPOUND_VALUE(Attribute); + DECL_VISIT_COMPOUND_VALUE(Style); + DECL_VISIT_COMPOUND_VALUE(Array); + DECL_VISIT_COMPOUND_VALUE(Plural); + DECL_VISIT_COMPOUND_VALUE(Styleable); }; /** @@ -108,11 +110,9 @@ struct ValueVisitor : public RawValueVisitor { */ template <typename T> struct DynCastVisitor : public RawValueVisitor { - T* value = nullptr; + T* value = nullptr; - void visit(T* v) override { - value = v; - } + void Visit(T* v) override { value = v; } }; /** @@ -120,16 +120,14 @@ struct DynCastVisitor : public RawValueVisitor { */ template <> struct DynCastVisitor<Item> : public RawValueVisitor { - Item* value = nullptr; + Item* value = nullptr; - void visitItem(Item* item) override { - value = item; - } + void VisitItem(Item* item) override { value = item; } }; template <typename T> -const T* valueCast(const Value* value) { - return valueCast<T>(const_cast<Value*>(value)); +const T* ValueCast(const Value* value) { + return ValueCast<T>(const_cast<Value*>(value)); } /** @@ -137,31 +135,33 @@ const T* valueCast(const Value* value) { * Otherwise, returns nullptr. */ template <typename T> -T* valueCast(Value* value) { - if (!value) { - return nullptr; - } - DynCastVisitor<T> visitor; - value->accept(&visitor); - return visitor.value; +T* ValueCast(Value* value) { + if (!value) { + return nullptr; + } + DynCastVisitor<T> visitor; + value->Accept(&visitor); + return visitor.value; } -inline void visitAllValuesInPackage(ResourceTablePackage* pkg, RawValueVisitor* visitor) { - for (auto& type : pkg->types) { - for (auto& entry : type->entries) { - for (auto& configValue : entry->values) { - configValue->value->accept(visitor); - } - } +inline void VisitAllValuesInPackage(ResourceTablePackage* pkg, + RawValueVisitor* visitor) { + for (auto& type : pkg->types) { + for (auto& entry : type->entries) { + for (auto& config_value : entry->values) { + config_value->value->Accept(visitor); + } } + } } -inline void visitAllValuesInTable(ResourceTable* table, RawValueVisitor* visitor) { - for (auto& pkg : table->packages) { - visitAllValuesInPackage(pkg.get(), visitor); - } +inline void VisitAllValuesInTable(ResourceTable* table, + RawValueVisitor* visitor) { + for (auto& pkg : table->packages) { + VisitAllValuesInPackage(pkg.get(), visitor); + } } -} // namespace aapt +} // namespace aapt -#endif // AAPT_VALUE_VISITOR_H +#endif // AAPT_VALUE_VISITOR_H diff --git a/tools/aapt2/ValueVisitor_test.cpp b/tools/aapt2/ValueVisitor_test.cpp index 1624079727bb..eb75b102e427 100644 --- a/tools/aapt2/ValueVisitor_test.cpp +++ b/tools/aapt2/ValueVisitor_test.cpp @@ -14,74 +14,74 @@ * limitations under the License. */ -#include <gtest/gtest.h> +#include "ValueVisitor.h" + #include <string> #include "ResourceValues.h" +#include "test/Test.h" #include "util/Util.h" -#include "ValueVisitor.h" -#include "test/Builders.h" namespace aapt { struct SingleReferenceVisitor : public ValueVisitor { - using ValueVisitor::visit; + using ValueVisitor::Visit; - Reference* visited = nullptr; + Reference* visited = nullptr; - void visit(Reference* ref) override { - visited = ref; - } + void Visit(Reference* ref) override { visited = ref; } }; struct StyleVisitor : public ValueVisitor { - using ValueVisitor::visit; + using ValueVisitor::Visit; - std::list<Reference*> visitedRefs; - Style* visitedStyle = nullptr; + std::list<Reference*> visited_refs; + Style* visited_style = nullptr; - void visit(Reference* ref) override { - visitedRefs.push_back(ref); - } + void Visit(Reference* ref) override { visited_refs.push_back(ref); } - void visit(Style* style) override { - visitedStyle = style; - ValueVisitor::visit(style); - } + void Visit(Style* style) override { + visited_style = style; + ValueVisitor::Visit(style); + } }; TEST(ValueVisitorTest, VisitsReference) { - Reference ref(ResourceName{u"android", ResourceType::kAttr, u"foo"}); - SingleReferenceVisitor visitor; - ref.accept(&visitor); + Reference ref(ResourceName{"android", ResourceType::kAttr, "foo"}); + SingleReferenceVisitor visitor; + ref.Accept(&visitor); - EXPECT_EQ(visitor.visited, &ref); + EXPECT_EQ(visitor.visited, &ref); } TEST(ValueVisitorTest, VisitsReferencesInStyle) { - std::unique_ptr<Style> style = test::StyleBuilder() - .setParent(u"@android:style/foo") - .addItem(u"@android:attr/one", test::buildReference(u"@android:id/foo")) - .build(); + std::unique_ptr<Style> style = + test::StyleBuilder() + .SetParent("android:style/foo") + .AddItem("android:attr/one", test::BuildReference("android:id/foo")) + .Build(); - StyleVisitor visitor; - style->accept(&visitor); + StyleVisitor visitor; + style->Accept(&visitor); - ASSERT_EQ(style.get(), visitor.visitedStyle); + ASSERT_EQ(style.get(), visitor.visited_style); - // Entry attribute references, plus the parent reference, plus one value reference. - ASSERT_EQ(style->entries.size() + 2, visitor.visitedRefs.size()); + // Entry attribute references, plus the parent reference, plus one value + // reference. + ASSERT_EQ(style->entries.size() + 2, visitor.visited_refs.size()); } TEST(ValueVisitorTest, ValueCast) { - std::unique_ptr<Reference> ref = test::buildReference(u"@android:color/white"); - EXPECT_NE(valueCast<Reference>(ref.get()), nullptr); - - std::unique_ptr<Style> style = test::StyleBuilder() - .addItem(u"@android:attr/foo", test::buildReference(u"@android:color/black")) - .build(); - EXPECT_NE(valueCast<Style>(style.get()), nullptr); - EXPECT_EQ(valueCast<Reference>(style.get()), nullptr); + std::unique_ptr<Reference> ref = test::BuildReference("android:color/white"); + EXPECT_NE(ValueCast<Reference>(ref.get()), nullptr); + + std::unique_ptr<Style> style = + test::StyleBuilder() + .AddItem("android:attr/foo", + test::BuildReference("android:color/black")) + .Build(); + EXPECT_NE(ValueCast<Style>(style.get()), nullptr); + EXPECT_EQ(ValueCast<Reference>(style.get()), nullptr); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp index 2452a1d29410..f0b18e65cc1a 100644 --- a/tools/aapt2/compile/Compile.cpp +++ b/tools/aapt2/compile/Compile.cpp @@ -14,12 +14,18 @@ * limitations under the License. */ +#include <dirent.h> + +#include <fstream> +#include <string> + #include "ConfigDescription.h" #include "Diagnostics.h" #include "Flags.h" #include "ResourceParser.h" #include "ResourceTable.h" #include "compile/IdAssigner.h" +#include "compile/InlineXmlFormatParser.h" #include "compile/Png.h" #include "compile/PseudolocaleGenerator.h" #include "compile/XmlIdCollector.h" @@ -32,547 +38,771 @@ #include "xml/XmlDom.h" #include "xml/XmlPullParser.h" -#include <google/protobuf/io/zero_copy_stream_impl_lite.h> -#include <google/protobuf/io/coded_stream.h> +#include "android-base/errors.h" +#include "android-base/file.h" +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/io/zero_copy_stream_impl_lite.h" -#include <dirent.h> -#include <fstream> -#include <string> +using google::protobuf::io::CopyingOutputStreamAdaptor; +using google::protobuf::io::ZeroCopyOutputStream; namespace aapt { struct ResourcePathData { - Source source; - std::u16string resourceDir; - std::u16string name; - std::string extension; - - // Original config str. We keep this because when we parse the config, we may add on - // version qualifiers. We want to preserve the original input so the output is easily - // computed before hand. - std::string configStr; - ConfigDescription config; + Source source; + std::string resource_dir; + std::string name; + std::string extension; + + // Original config str. We keep this because when we parse the config, we may + // add on + // version qualifiers. We want to preserve the original input so the output is + // easily + // computed before hand. + std::string config_str; + ConfigDescription config; }; /** * Resource file paths are expected to look like: * [--/res/]type[-config]/name */ -static Maybe<ResourcePathData> extractResourcePathData(const std::string& path, - std::string* outError) { - std::vector<std::string> parts = util::split(path, file::sDirSep); - if (parts.size() < 2) { - if (outError) *outError = "bad resource path"; - return {}; - } - - std::string& dir = parts[parts.size() - 2]; - StringPiece dirStr = dir; - - StringPiece configStr; - ConfigDescription config; - size_t dashPos = dir.find('-'); - if (dashPos != std::string::npos) { - configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1)); - if (!ConfigDescription::parse(configStr, &config)) { - if (outError) { - std::stringstream errStr; - errStr << "invalid configuration '" << configStr << "'"; - *outError = errStr.str(); - } - return {}; - } - dirStr = dirStr.substr(0, dashPos); - } - - std::string& filename = parts[parts.size() - 1]; - StringPiece name = filename; - StringPiece extension; - size_t dotPos = filename.find('.'); - if (dotPos != std::string::npos) { - extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1)); - name = name.substr(0, dotPos); - } - - return ResourcePathData{ - Source(path), - util::utf8ToUtf16(dirStr), - util::utf8ToUtf16(name), - extension.toString(), - configStr.toString(), - config - }; +static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, + std::string* out_error) { + std::vector<std::string> parts = util::Split(path, file::sDirSep); + if (parts.size() < 2) { + if (out_error) *out_error = "bad resource path"; + return {}; + } + + std::string& dir = parts[parts.size() - 2]; + StringPiece dir_str = dir; + + StringPiece config_str; + ConfigDescription config; + size_t dash_pos = dir.find('-'); + if (dash_pos != std::string::npos) { + config_str = dir_str.substr(dash_pos + 1, dir.size() - (dash_pos + 1)); + if (!ConfigDescription::Parse(config_str, &config)) { + if (out_error) { + std::stringstream err_str; + err_str << "invalid configuration '" << config_str << "'"; + *out_error = err_str.str(); + } + return {}; + } + dir_str = dir_str.substr(0, dash_pos); + } + + std::string& filename = parts[parts.size() - 1]; + StringPiece name = filename; + StringPiece extension; + size_t dot_pos = filename.find('.'); + if (dot_pos != std::string::npos) { + extension = name.substr(dot_pos + 1, filename.size() - (dot_pos + 1)); + name = name.substr(0, dot_pos); + } + + return ResourcePathData{Source(path), dir_str.ToString(), + name.ToString(), extension.ToString(), + config_str.ToString(), config}; } struct CompileOptions { - std::string outputPath; - Maybe<std::string> resDir; - bool pseudolocalize = false; - bool legacyMode = false; - bool verbose = false; + std::string output_path; + Maybe<std::string> res_dir; + bool pseudolocalize = false; + bool legacy_mode = false; + bool verbose = false; }; -static std::string buildIntermediateFilename(const ResourcePathData& data) { - std::stringstream name; - name << data.resourceDir; - if (!data.configStr.empty()) { - name << "-" << data.configStr; - } - name << "_" << data.name; - if (!data.extension.empty()) { - name << "." << data.extension; - } - name << ".flat"; - return name.str(); +static std::string BuildIntermediateFilename(const ResourcePathData& data) { + std::stringstream name; + name << data.resource_dir; + if (!data.config_str.empty()) { + name << "-" << data.config_str; + } + name << "_" << data.name; + if (!data.extension.empty()) { + name << "." << data.extension; + } + name << ".flat"; + return name.str(); } -static bool isHidden(const StringPiece& filename) { - return util::stringStartsWith<char>(filename, "."); +static bool IsHidden(const StringPiece& filename) { + return util::StartsWith(filename, "."); } /** * Walks the res directory structure, looking for resource files. */ -static bool loadInputFilesFromDir(IAaptContext* context, const CompileOptions& options, - std::vector<ResourcePathData>* outPathData) { - const std::string& rootDir = options.resDir.value(); - std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()), closedir); - if (!d) { - context->getDiagnostics()->error(DiagMessage() << strerror(errno)); - return false; - } - - while (struct dirent* entry = readdir(d.get())) { - if (isHidden(entry->d_name)) { - continue; - } - - std::string prefixPath = rootDir; - file::appendPath(&prefixPath, entry->d_name); - - if (file::getFileType(prefixPath) != file::FileType::kDirectory) { - continue; - } - - std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()), closedir); - if (!subDir) { - context->getDiagnostics()->error(DiagMessage() << strerror(errno)); - return false; - } - - while (struct dirent* leafEntry = readdir(subDir.get())) { - if (isHidden(leafEntry->d_name)) { - continue; - } - - std::string fullPath = prefixPath; - file::appendPath(&fullPath, leafEntry->d_name); - - std::string errStr; - Maybe<ResourcePathData> pathData = extractResourcePathData(fullPath, &errStr); - if (!pathData) { - context->getDiagnostics()->error(DiagMessage() << errStr); - return false; - } +static bool LoadInputFilesFromDir( + IAaptContext* context, const CompileOptions& options, + std::vector<ResourcePathData>* out_path_data) { + const std::string& root_dir = options.res_dir.value(); + std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), + closedir); + if (!d) { + context->GetDiagnostics()->Error(DiagMessage() << strerror(errno)); + return false; + } - outPathData->push_back(std::move(pathData.value())); - } + while (struct dirent* entry = readdir(d.get())) { + if (IsHidden(entry->d_name)) { + continue; } - return true; -} - -static bool compileTable(IAaptContext* context, const CompileOptions& options, - const ResourcePathData& pathData, IArchiveWriter* writer, - const std::string& outputPath) { - ResourceTable table; - { - std::ifstream fin(pathData.source.path, std::ifstream::binary); - if (!fin) { - context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno)); - return false; - } - - - // Parse the values file from XML. - xml::XmlPullParser xmlParser(fin); - ResourceParserOptions parserOptions; - parserOptions.errorOnPositionalArguments = !options.legacyMode; + std::string prefix_path = root_dir; + file::AppendPath(&prefix_path, entry->d_name); - // If the filename includes donottranslate, then the default translatable is false. - parserOptions.translatable = pathData.name.find(u"donottranslate") == std::string::npos; - - ResourceParser resParser(context->getDiagnostics(), &table, pathData.source, - pathData.config, parserOptions); - if (!resParser.parse(&xmlParser)) { - return false; - } - - fin.close(); + if (file::GetFileType(prefix_path) != file::FileType::kDirectory) { + continue; } - if (options.pseudolocalize) { - // Generate pseudo-localized strings (en-XA and ar-XB). - // These are created as weak symbols, and are only generated from default configuration - // strings and plurals. - PseudolocaleGenerator pseudolocaleGenerator; - if (!pseudolocaleGenerator.consume(context, &table)) { - return false; - } + std::unique_ptr<DIR, decltype(closedir)*> subdir( + opendir(prefix_path.data()), closedir); + if (!subdir) { + context->GetDiagnostics()->Error(DiagMessage() << strerror(errno)); + return false; } - // Ensure we have the compilation package at least. - table.createPackage(context->getCompilationPackage()); + while (struct dirent* leaf_entry = readdir(subdir.get())) { + if (IsHidden(leaf_entry->d_name)) { + continue; + } - // Assign an ID to any package that has resources. - for (auto& pkg : table.packages) { - if (!pkg->id) { - // If no package ID was set while parsing (public identifiers), auto assign an ID. - pkg->id = context->getPackageId(); - } - } + std::string full_path = prefix_path; + file::AppendPath(&full_path, leaf_entry->d_name); - // Create the file/zip entry. - if (!writer->startEntry(outputPath, 0)) { - context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open"); + std::string err_str; + Maybe<ResourcePathData> path_data = + ExtractResourcePathData(full_path, &err_str); + if (!path_data) { + context->GetDiagnostics()->Error(DiagMessage() << err_str); return false; - } - - std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table); + } - // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface. - { - google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); - - if (!pbTable->SerializeToZeroCopyStream(&adaptor)) { - context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write"); - return false; - } + out_path_data->push_back(std::move(path_data.value())); } + } + return true; +} - if (!writer->finishEntry()) { - context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to finish entry"); - return false; - } - return true; +static bool CompileTable(IAaptContext* context, const CompileOptions& options, + const ResourcePathData& path_data, + IArchiveWriter* writer, + const std::string& output_path) { + ResourceTable table; + { + std::ifstream fin(path_data.source.path, std::ifstream::binary); + if (!fin) { + context->GetDiagnostics()->Error(DiagMessage(path_data.source) + << strerror(errno)); + return false; + } + + // Parse the values file from XML. + xml::XmlPullParser xml_parser(fin); + + ResourceParserOptions parser_options; + parser_options.error_on_positional_arguments = !options.legacy_mode; + + // If the filename includes donottranslate, then the default translatable is + // false. + parser_options.translatable = + path_data.name.find("donottranslate") == std::string::npos; + + ResourceParser res_parser(context->GetDiagnostics(), &table, + path_data.source, path_data.config, + parser_options); + if (!res_parser.Parse(&xml_parser)) { + return false; + } + + fin.close(); + } + + if (options.pseudolocalize) { + // Generate pseudo-localized strings (en-XA and ar-XB). + // These are created as weak symbols, and are only generated from default + // configuration + // strings and plurals. + PseudolocaleGenerator pseudolocale_generator; + if (!pseudolocale_generator.Consume(context, &table)) { + return false; + } + } + + // Ensure we have the compilation package at least. + table.CreatePackage(context->GetCompilationPackage()); + + // Assign an ID to any package that has resources. + for (auto& pkg : table.packages) { + if (!pkg->id) { + // If no package ID was set while parsing (public identifiers), auto + // assign an ID. + pkg->id = context->GetPackageId(); + } + } + + // Create the file/zip entry. + if (!writer->StartEntry(output_path, 0)) { + context->GetDiagnostics()->Error(DiagMessage(output_path) + << "failed to open"); + return false; + } + + // Make sure CopyingOutputStreamAdaptor is deleted before we call + // writer->FinishEntry(). + { + // Wrap our IArchiveWriter with an adaptor that implements the + // ZeroCopyOutputStream + // interface. + CopyingOutputStreamAdaptor copying_adaptor(writer); + + std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(&table); + if (!pb_table->SerializeToZeroCopyStream(©ing_adaptor)) { + context->GetDiagnostics()->Error(DiagMessage(output_path) + << "failed to write"); + return false; + } + } + + if (!writer->FinishEntry()) { + context->GetDiagnostics()->Error(DiagMessage(output_path) + << "failed to finish entry"); + return false; + } + return true; } -static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath, const ResourceFile& file, - const BigBuffer& buffer, IArchiveWriter* writer, +static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path, + const ResourceFile& file, + const BigBuffer& buffer, + IArchiveWriter* writer, IDiagnostics* diag) { - // Start the entry so we can write the header. - if (!writer->startEntry(outputPath, 0)) { - diag->error(DiagMessage(outputPath) << "failed to open file"); - return false; - } - - // Create the header. - std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(file); - - { - // The stream must be destroyed before we finish the entry, or else - // some data won't be flushed. - // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream - // interface. - google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); - CompiledFileOutputStream outputStream(&adaptor, pbCompiledFile.get()); - for (const BigBuffer::Block& block : buffer) { - if (!outputStream.Write(block.buffer.get(), block.size)) { - diag->error(DiagMessage(outputPath) << "failed to write data"); - return false; - } - } - } - - if (!writer->finishEntry()) { - diag->error(DiagMessage(outputPath) << "failed to finish writing data"); - return false; - } - return true; + // Start the entry so we can write the header. + if (!writer->StartEntry(output_path, 0)) { + diag->Error(DiagMessage(output_path) << "failed to open file"); + return false; + } + + // Make sure CopyingOutputStreamAdaptor is deleted before we call + // writer->FinishEntry(). + { + // Wrap our IArchiveWriter with an adaptor that implements the + // ZeroCopyOutputStream + // interface. + CopyingOutputStreamAdaptor copying_adaptor(writer); + CompiledFileOutputStream output_stream(©ing_adaptor); + + // Number of CompiledFiles. + output_stream.WriteLittleEndian32(1); + + std::unique_ptr<pb::CompiledFile> compiled_file = + SerializeCompiledFileToPb(file); + output_stream.WriteCompiledFile(compiled_file.get()); + output_stream.WriteData(&buffer); + + if (output_stream.HadError()) { + diag->Error(DiagMessage(output_path) << "failed to write data"); + return false; + } + } + + if (!writer->FinishEntry()) { + diag->Error(DiagMessage(output_path) << "failed to finish writing data"); + return false; + } + return true; } -static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, const ResourceFile& file, - const android::FileMap& map, IArchiveWriter* writer, +static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, + const ResourceFile& file, + const android::FileMap& map, + IArchiveWriter* writer, IDiagnostics* diag) { - // Start the entry so we can write the header. - if (!writer->startEntry(outputPath, 0)) { - diag->error(DiagMessage(outputPath) << "failed to open file"); - return false; - } + // Start the entry so we can write the header. + if (!writer->StartEntry(output_path, 0)) { + diag->Error(DiagMessage(output_path) << "failed to open file"); + return false; + } + + // Make sure CopyingOutputStreamAdaptor is deleted before we call + // writer->FinishEntry(). + { + // Wrap our IArchiveWriter with an adaptor that implements the + // ZeroCopyOutputStream interface. + CopyingOutputStreamAdaptor copying_adaptor(writer); + CompiledFileOutputStream output_stream(©ing_adaptor); + + // Number of CompiledFiles. + output_stream.WriteLittleEndian32(1); + + std::unique_ptr<pb::CompiledFile> compiled_file = + SerializeCompiledFileToPb(file); + output_stream.WriteCompiledFile(compiled_file.get()); + output_stream.WriteData(map.getDataPtr(), map.getDataLength()); + + if (output_stream.HadError()) { + diag->Error(DiagMessage(output_path) << "failed to write data"); + return false; + } + } + + if (!writer->FinishEntry()) { + diag->Error(DiagMessage(output_path) << "failed to finish writing data"); + return false; + } + return true; +} - // Create the header. - std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(file); - - { - // The stream must be destroyed before we finish the entry, or else - // some data won't be flushed. - // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream - // interface. - google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); - CompiledFileOutputStream outputStream(&adaptor, pbCompiledFile.get()); - if (!outputStream.Write(map.getDataPtr(), map.getDataLength())) { - diag->error(DiagMessage(outputPath) << "failed to write data"); - return false; - } - } +static bool FlattenXmlToOutStream(IAaptContext* context, + const StringPiece& output_path, + xml::XmlResource* xmlres, + CompiledFileOutputStream* out) { + BigBuffer buffer(1024); + XmlFlattenerOptions xml_flattener_options; + xml_flattener_options.keep_raw_values = true; + XmlFlattener flattener(&buffer, xml_flattener_options); + if (!flattener.Consume(context, xmlres)) { + return false; + } + + std::unique_ptr<pb::CompiledFile> pb_compiled_file = + SerializeCompiledFileToPb(xmlres->file); + out->WriteCompiledFile(pb_compiled_file.get()); + out->WriteData(&buffer); + + if (out->HadError()) { + context->GetDiagnostics()->Error(DiagMessage(output_path) + << "failed to write data"); + return false; + } + return true; +} - if (!writer->finishEntry()) { - diag->error(DiagMessage(outputPath) << "failed to finish writing data"); +static bool CompileXml(IAaptContext* context, const CompileOptions& options, + const ResourcePathData& path_data, + IArchiveWriter* writer, const std::string& output_path) { + if (context->IsVerbose()) { + context->GetDiagnostics()->Note(DiagMessage(path_data.source) + << "compiling XML"); + } + + std::unique_ptr<xml::XmlResource> xmlres; + { + std::ifstream fin(path_data.source.path, std::ifstream::binary); + if (!fin) { + context->GetDiagnostics()->Error(DiagMessage(path_data.source) + << strerror(errno)); + return false; + } + + xmlres = xml::Inflate(&fin, context->GetDiagnostics(), path_data.source); + + fin.close(); + } + + if (!xmlres) { + return false; + } + + xmlres->file.name = ResourceName( + {}, *ParseResourceType(path_data.resource_dir), path_data.name); + xmlres->file.config = path_data.config; + xmlres->file.source = path_data.source; + + // Collect IDs that are defined here. + XmlIdCollector collector; + if (!collector.Consume(context, xmlres.get())) { + return false; + } + + // Look for and process any <aapt:attr> tags and create sub-documents. + InlineXmlFormatParser inline_xml_format_parser; + if (!inline_xml_format_parser.Consume(context, xmlres.get())) { + return false; + } + + // Start the entry so we can write the header. + if (!writer->StartEntry(output_path, 0)) { + context->GetDiagnostics()->Error(DiagMessage(output_path) + << "failed to open file"); + return false; + } + + // Make sure CopyingOutputStreamAdaptor is deleted before we call + // writer->FinishEntry(). + { + // Wrap our IArchiveWriter with an adaptor that implements the + // ZeroCopyOutputStream + // interface. + CopyingOutputStreamAdaptor copying_adaptor(writer); + CompiledFileOutputStream output_stream(©ing_adaptor); + + std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents = + inline_xml_format_parser.GetExtractedInlineXmlDocuments(); + + // Number of CompiledFiles. + output_stream.WriteLittleEndian32(1 + inline_documents.size()); + + if (!FlattenXmlToOutStream(context, output_path, xmlres.get(), + &output_stream)) { + return false; + } + + for (auto& inline_xml_doc : inline_documents) { + if (!FlattenXmlToOutStream(context, output_path, inline_xml_doc.get(), + &output_stream)) { return false; + } } - return true; + } + + if (!writer->FinishEntry()) { + context->GetDiagnostics()->Error(DiagMessage(output_path) + << "failed to finish writing data"); + return false; + } + return true; } -static bool compileXml(IAaptContext* context, const CompileOptions& options, - const ResourcePathData& pathData, IArchiveWriter* writer, - const std::string& outputPath) { +class BigBufferOutputStream : public io::OutputStream { + public: + explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {} - std::unique_ptr<xml::XmlResource> xmlRes; - { - std::ifstream fin(pathData.source.path, std::ifstream::binary); - if (!fin) { - context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno)); - return false; - } + bool Next(void** data, int* len) override { + size_t count; + *data = buffer_->NextBlock(&count); + *len = static_cast<int>(count); + return true; + } - xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source); + void BackUp(int count) override { buffer_->BackUp(count); } - fin.close(); - } + google::protobuf::int64 ByteCount() const override { + return buffer_->size(); + } - if (!xmlRes) { - return false; - } + bool HadError() const override { return false; } - // Collect IDs that are defined here. - XmlIdCollector collector; - if (!collector.consume(context, xmlRes.get())) { - return false; - } + private: + BigBuffer* buffer_; - xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); - xmlRes->file.config = pathData.config; - xmlRes->file.source = pathData.source; + DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream); +}; - BigBuffer buffer(1024); - XmlFlattenerOptions xmlFlattenerOptions; - xmlFlattenerOptions.keepRawValues = true; - XmlFlattener flattener(&buffer, xmlFlattenerOptions); - if (!flattener.consume(context, xmlRes.get())) { +static bool CompilePng(IAaptContext* context, const CompileOptions& options, + const ResourcePathData& path_data, + IArchiveWriter* writer, const std::string& output_path) { + if (context->IsVerbose()) { + context->GetDiagnostics()->Note(DiagMessage(path_data.source) + << "compiling PNG"); + } + + BigBuffer buffer(4096); + ResourceFile res_file; + res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), + path_data.name); + res_file.config = path_data.config; + res_file.source = path_data.source; + + { + std::string content; + if (!android::base::ReadFileToString(path_data.source.path, &content)) { + context->GetDiagnostics()->Error( + DiagMessage(path_data.source) + << android::base::SystemErrorCodeToString(errno)); + return false; + } + + BigBuffer crunched_png_buffer(4096); + BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer); + + // Ensure that we only keep the chunks we care about if we end up + // using the original PNG instead of the crunched one. + PngChunkFilter png_chunk_filter(content); + std::unique_ptr<Image> image = ReadPng(context, &png_chunk_filter); + if (!image) { + return false; + } + + std::unique_ptr<NinePatch> nine_patch; + if (path_data.extension == "9.png") { + std::string err; + nine_patch = NinePatch::Create(image->rows.get(), image->width, + image->height, &err); + if (!nine_patch) { + context->GetDiagnostics()->Error(DiagMessage() << err); return false; - } - - if (!writeHeaderAndBufferToWriter(outputPath, xmlRes->file, buffer, writer, - context->getDiagnostics())) { + } + + // Remove the 1px border around the NinePatch. + // Basically the row array is shifted up by 1, and the length is treated + // as height - 2. + // For each row, shift the array to the left by 1, and treat the length as + // width - 2. + image->width -= 2; + image->height -= 2; + memmove(image->rows.get(), image->rows.get() + 1, + image->height * sizeof(uint8_t**)); + for (int32_t h = 0; h < image->height; h++) { + memmove(image->rows[h], image->rows[h] + 4, image->width * 4); + } + + if (context->IsVerbose()) { + context->GetDiagnostics()->Note(DiagMessage(path_data.source) + << "9-patch: " << *nine_patch); + } + } + + // Write the crunched PNG. + if (!WritePng(context, image.get(), nine_patch.get(), + &crunched_png_buffer_out, {})) { + return false; + } + + if (nine_patch != nullptr || + crunched_png_buffer_out.ByteCount() <= png_chunk_filter.ByteCount()) { + // No matter what, we must use the re-encoded PNG, even if it is larger. + // 9-patch images must be re-encoded since their borders are stripped. + buffer.AppendBuffer(std::move(crunched_png_buffer)); + } else { + // The re-encoded PNG is larger than the original, and there is + // no mandatory transformation. Use the original. + if (context->IsVerbose()) { + context->GetDiagnostics()->Note( + DiagMessage(path_data.source) + << "original PNG is smaller than crunched PNG" + << ", using original"); + } + + PngChunkFilter png_chunk_filter_again(content); + BigBuffer filtered_png_buffer(4096); + BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer); + io::Copy(&filtered_png_buffer_out, &png_chunk_filter_again); + buffer.AppendBuffer(std::move(filtered_png_buffer)); + } + + if (context->IsVerbose()) { + // For debugging only, use the legacy PNG cruncher and compare the + // resulting file sizes. + // This will help catch exotic cases where the new code may generate + // larger PNGs. + std::stringstream legacy_stream(content); + BigBuffer legacy_buffer(4096); + Png png(context->GetDiagnostics()); + if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) { return false; - } - return true; -} + } -static bool compilePng(IAaptContext* context, const CompileOptions& options, - const ResourcePathData& pathData, IArchiveWriter* writer, - const std::string& outputPath) { - BigBuffer buffer(4096); - ResourceFile resFile; - resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); - resFile.config = pathData.config; - resFile.source = pathData.source; - - { - std::ifstream fin(pathData.source.path, std::ifstream::binary); - if (!fin) { - context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno)); - return false; - } - - Png png(context->getDiagnostics()); - if (!png.process(pathData.source, &fin, &buffer, {})) { - return false; - } + context->GetDiagnostics()->Note(DiagMessage(path_data.source) + << "legacy=" << legacy_buffer.size() + << " new=" << buffer.size()); } + } - if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer, - context->getDiagnostics())) { - return false; - } - return true; + if (!WriteHeaderAndBufferToWriter(output_path, res_file, buffer, writer, + context->GetDiagnostics())) { + return false; + } + return true; } -static bool compileFile(IAaptContext* context, const CompileOptions& options, - const ResourcePathData& pathData, IArchiveWriter* writer, - const std::string& outputPath) { - BigBuffer buffer(256); - ResourceFile resFile; - resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); - resFile.config = pathData.config; - resFile.source = pathData.source; - - std::string errorStr; - Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr); - if (!f) { - context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr); - return false; - } - - if (!writeHeaderAndMmapToWriter(outputPath, resFile, f.value(), writer, - context->getDiagnostics())) { - return false; - } - return true; +static bool CompileFile(IAaptContext* context, const CompileOptions& options, + const ResourcePathData& path_data, + IArchiveWriter* writer, + const std::string& output_path) { + if (context->IsVerbose()) { + context->GetDiagnostics()->Note(DiagMessage(path_data.source) + << "compiling file"); + } + + BigBuffer buffer(256); + ResourceFile res_file; + res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), + path_data.name); + res_file.config = path_data.config; + res_file.source = path_data.source; + + std::string error_str; + Maybe<android::FileMap> f = file::MmapPath(path_data.source.path, &error_str); + if (!f) { + context->GetDiagnostics()->Error(DiagMessage(path_data.source) + << error_str); + return false; + } + + if (!WriteHeaderAndMmapToWriter(output_path, res_file, f.value(), writer, + context->GetDiagnostics())) { + return false; + } + return true; } class CompileContext : public IAaptContext { -public: - void setVerbose(bool val) { - mVerbose = val; - } + public: + void SetVerbose(bool val) { verbose_ = val; } - bool verbose() override { - return mVerbose; - } + bool IsVerbose() override { return verbose_; } - IDiagnostics* getDiagnostics() override { - return &mDiagnostics; - } + IDiagnostics* GetDiagnostics() override { return &diagnostics_; } - NameMangler* getNameMangler() override { - abort(); - return nullptr; - } + NameMangler* GetNameMangler() override { + abort(); + return nullptr; + } - const std::u16string& getCompilationPackage() override { - static std::u16string empty; - return empty; - } + const std::string& GetCompilationPackage() override { + static std::string empty; + return empty; + } - uint8_t getPackageId() override { - return 0x0; - } + uint8_t GetPackageId() override { return 0x0; } - SymbolTable* getExternalSymbols() override { - abort(); - return nullptr; - } + SymbolTable* GetExternalSymbols() override { + abort(); + return nullptr; + } -private: - StdErrDiagnostics mDiagnostics; - bool mVerbose = false; + int GetMinSdkVersion() override { return 0; } + private: + StdErrDiagnostics diagnostics_; + bool verbose_ = false; }; /** - * Entry point for compilation phase. Parses arguments and dispatches to the correct steps. + * Entry point for compilation phase. Parses arguments and dispatches to the + * correct steps. */ -int compile(const std::vector<StringPiece>& args) { - CompileContext context; - CompileOptions options; - - bool verbose = false; - Flags flags = Flags() - .requiredFlag("-o", "Output path", &options.outputPath) - .optionalFlag("--dir", "Directory to scan for resources", &options.resDir) - .optionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales " - "(en-XA and ar-XB)", &options.pseudolocalize) - .optionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings", - &options.legacyMode) - .optionalSwitch("-v", "Enables verbose logging", &verbose); - if (!flags.parse("aapt2 compile", args, &std::cerr)) { +int Compile(const std::vector<StringPiece>& args) { + CompileContext context; + CompileOptions options; + + bool verbose = false; + Flags flags = + Flags() + .RequiredFlag("-o", "Output path", &options.output_path) + .OptionalFlag("--dir", "Directory to scan for resources", + &options.res_dir) + .OptionalSwitch("--pseudo-localize", + "Generate resources for pseudo-locales " + "(en-XA and ar-XB)", + &options.pseudolocalize) + .OptionalSwitch( + "--legacy", + "Treat errors that used to be valid in AAPT as warnings", + &options.legacy_mode) + .OptionalSwitch("-v", "Enables verbose logging", &verbose); + if (!flags.Parse("aapt2 compile", args, &std::cerr)) { + return 1; + } + + context.SetVerbose(verbose); + + std::unique_ptr<IArchiveWriter> archive_writer; + + std::vector<ResourcePathData> input_data; + if (options.res_dir) { + if (!flags.GetArgs().empty()) { + // Can't have both files and a resource directory. + context.GetDiagnostics()->Error(DiagMessage() + << "files given but --dir specified"); + flags.Usage("aapt2 compile", &std::cerr); + return 1; + } + + if (!LoadInputFilesFromDir(&context, options, &input_data)) { + return 1; + } + + archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), + options.output_path); + + } else { + input_data.reserve(flags.GetArgs().size()); + + // Collect data from the path for each input file. + for (const std::string& arg : flags.GetArgs()) { + std::string error_str; + if (Maybe<ResourcePathData> path_data = + ExtractResourcePathData(arg, &error_str)) { + input_data.push_back(std::move(path_data.value())); + } else { + context.GetDiagnostics()->Error(DiagMessage() << error_str << " (" + << arg << ")"); return 1; + } } - context.setVerbose(verbose); + archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), + options.output_path); + } - std::unique_ptr<IArchiveWriter> archiveWriter; + if (!archive_writer) { + return 1; + } - std::vector<ResourcePathData> inputData; - if (options.resDir) { - if (!flags.getArgs().empty()) { - // Can't have both files and a resource directory. - context.getDiagnostics()->error(DiagMessage() << "files given but --dir specified"); - flags.usage("aapt2 compile", &std::cerr); - return 1; - } + bool error = false; + for (ResourcePathData& path_data : input_data) { + if (options.verbose) { + context.GetDiagnostics()->Note(DiagMessage(path_data.source) + << "processing"); + } - if (!loadInputFilesFromDir(&context, options, &inputData)) { - return 1; - } + if (path_data.resource_dir == "values") { + // Overwrite the extension. + path_data.extension = "arsc"; - archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath); + const std::string output_filename = BuildIntermediateFilename(path_data); + if (!CompileTable(&context, options, path_data, archive_writer.get(), + output_filename)) { + error = true; + } } else { - inputData.reserve(flags.getArgs().size()); - - // Collect data from the path for each input file. - for (const std::string& arg : flags.getArgs()) { - std::string errorStr; - if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) { - inputData.push_back(std::move(pathData.value())); - } else { - context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")"); - return 1; + const std::string output_filename = BuildIntermediateFilename(path_data); + if (const ResourceType* type = + ParseResourceType(path_data.resource_dir)) { + if (*type != ResourceType::kRaw) { + if (path_data.extension == "xml") { + if (!CompileXml(&context, options, path_data, archive_writer.get(), + output_filename)) { + error = true; } - } - - archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(), options.outputPath); - } - - if (!archiveWriter) { - return false; - } - - bool error = false; - for (ResourcePathData& pathData : inputData) { - if (options.verbose) { - context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing"); - } - - if (pathData.resourceDir == u"values") { - // Overwrite the extension. - pathData.extension = "arsc"; - - const std::string outputFilename = buildIntermediateFilename(pathData); - if (!compileTable(&context, options, pathData, archiveWriter.get(), outputFilename)) { - error = true; + } else if (path_data.extension == "png" || + path_data.extension == "9.png") { + if (!CompilePng(&context, options, path_data, archive_writer.get(), + output_filename)) { + error = true; } - - } else { - const std::string outputFilename = buildIntermediateFilename(pathData); - if (const ResourceType* type = parseResourceType(pathData.resourceDir)) { - if (*type != ResourceType::kRaw) { - if (pathData.extension == "xml") { - if (!compileXml(&context, options, pathData, archiveWriter.get(), - outputFilename)) { - error = true; - } - } else if (pathData.extension == "png" || pathData.extension == "9.png") { - if (!compilePng(&context, options, pathData, archiveWriter.get(), - outputFilename)) { - error = true; - } - } else { - if (!compileFile(&context, options, pathData, archiveWriter.get(), - outputFilename)) { - error = true; - } - } - } else { - if (!compileFile(&context, options, pathData, archiveWriter.get(), - outputFilename)) { - error = true; - } - } - } else { - context.getDiagnostics()->error( - DiagMessage() << "invalid file path '" << pathData.source << "'"); - error = true; + } else { + if (!CompileFile(&context, options, path_data, archive_writer.get(), + output_filename)) { + error = true; } + } + } else { + if (!CompileFile(&context, options, path_data, archive_writer.get(), + output_filename)) { + error = true; + } } - } - - if (error) { - return 1; - } - return 0; + } else { + context.GetDiagnostics()->Error( + DiagMessage() << "invalid file path '" << path_data.source << "'"); + error = true; + } + } + } + + if (error) { + return 1; + } + return 0; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp index aa4a5803b8df..17c22c5aac6b 100644 --- a/tools/aapt2/compile/IdAssigner.cpp +++ b/tools/aapt2/compile/IdAssigner.cpp @@ -14,97 +14,205 @@ * limitations under the License. */ -#include "ResourceTable.h" - #include "compile/IdAssigner.h" + +#include <map> + +#include "android-base/logging.h" + +#include "ResourceTable.h" #include "process/IResourceTableConsumer.h" #include "util/Util.h" -#include <bitset> -#include <cassert> -#include <set> - namespace aapt { -bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) { - std::bitset<256> usedTypeIds; - std::set<uint16_t> usedEntryIds; +/** + * Assigns the intended ID to the ResourceTablePackage, ResourceTableType, and + * ResourceEntry, + * as long as there is no existing ID or the ID is the same. + */ +static bool AssignId(IDiagnostics* diag, const ResourceId& id, + const ResourceName& name, ResourceTablePackage* pkg, + ResourceTableType* type, ResourceEntry* entry) { + if (pkg->id.value() == id.package_id()) { + if (!type->id || type->id.value() == id.type_id()) { + type->id = id.type_id(); - for (auto& package : table->packages) { - assert(package->id && "packages must have manually assigned IDs"); + if (!entry->id || entry->id.value() == id.entry_id()) { + entry->id = id.entry_id(); + return true; + } + } + } - usedTypeIds.reset(); + const ResourceId existing_id(pkg->id.value(), type->id ? type->id.value() : 0, + entry->id ? entry->id.value() : 0); + diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " + << name << " with conflicting ID " << existing_id); + return false; +} - // Type ID 0 is invalid, reserve it. - usedTypeIds.set(0); +bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) { + std::map<ResourceId, ResourceName> assigned_ids; - // Collect used type IDs. - for (auto& type : package->types) { - if (type->id) { - usedEntryIds.clear(); + for (auto& package : table->packages) { + CHECK(bool(package->id)) << "packages must have manually assigned IDs"; - if (usedTypeIds[type->id.value()]) { - // This ID is already taken! - context->getDiagnostics()->error(DiagMessage() - << "type '" << type->type << "' in " - << "package '" << package->name << "' has " - << "duplicate ID " - << std::hex << (int) type->id.value() - << std::dec); - return false; - } + for (auto& type : package->types) { + for (auto& entry : type->entries) { + const ResourceName name(package->name, type->type, entry->name); - // Mark the type ID as taken. - usedTypeIds.set(type->id.value()); + if (assigned_id_map_) { + // Assign the pre-assigned stable ID meant for this resource. + const auto iter = assigned_id_map_->find(name); + if (iter != assigned_id_map_->end()) { + const ResourceId assigned_id = iter->second; + const bool result = + AssignId(context->GetDiagnostics(), assigned_id, name, + package.get(), type.get(), entry.get()); + if (!result) { + return false; } + } + } - // Collect used entry IDs. - for (auto& entry : type->entries) { - if (entry->id) { - // Mark entry ID as taken. - if (!usedEntryIds.insert(entry->id.value()).second) { - // This ID existed before! - ResourceNameRef nameRef(package->name, type->type, entry->name); - context->getDiagnostics()->error(DiagMessage() - << "resource '" << nameRef << "' " - << "has duplicate entry ID " - << std::hex << (int) entry->id.value() - << std::dec); - return false; - } - } - } + if (package->id && type->id && entry->id) { + // If the ID is set for this resource, then reserve it. + ResourceId resource_id(package->id.value(), type->id.value(), + entry->id.value()); + auto result = assigned_ids.insert({resource_id, name}); + const ResourceName& existing_name = result.first->second; + if (!result.second) { + context->GetDiagnostics()->Error( + DiagMessage() << "resource " << name << " has same ID " + << resource_id << " as " << existing_name); + return false; + } + } + } + } + } - // Assign unused entry IDs. - const auto endUsedEntryIter = usedEntryIds.end(); - auto nextUsedEntryIter = usedEntryIds.begin(); - uint16_t nextId = 0; - for (auto& entry : type->entries) { - if (!entry->id) { - // Assign the next available entryID. - while (nextUsedEntryIter != endUsedEntryIter && - nextId == *nextUsedEntryIter) { - nextId++; - ++nextUsedEntryIter; - } - entry->id = nextId++; - } - } + if (assigned_id_map_) { + // 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; + const ResourceId& pre_assigned_id = stable_id_entry.second; + auto result = assigned_ids.insert({pre_assigned_id, pre_assigned_name}); + const ResourceName& existing_name = result.first->second; + if (!result.second && existing_name != pre_assigned_name) { + context->GetDiagnostics()->Error( + DiagMessage() << "stable ID " << pre_assigned_id << " for resource " + << pre_assigned_name + << " is already taken by resource " << existing_name); + return false; + } + } + } + + // Assign any resources without IDs the next available ID. Gaps will be filled + // if possible, + // unless those IDs have been reserved. + + const auto assigned_ids_iter_end = assigned_ids.end(); + for (auto& package : table->packages) { + CHECK(bool(package->id)) << "packages must have manually assigned IDs"; + + // Build a half filled ResourceId object, which will be used to find the + // closest matching + // reserved ID in the assignedId map. From that point the next available + // type ID can be + // found. + ResourceId resource_id(package->id.value(), 0, 0); + uint8_t next_expected_type_id = 1; + + // Find the closest matching ResourceId that is <= the one with only the + // package set. + auto next_type_iter = assigned_ids.lower_bound(resource_id); + for (auto& type : package->types) { + if (!type->id) { + // We need to assign a type ID. Iterate over the reserved IDs until we + // find + // some type ID that is a distance of 2 greater than the last one we've + // seen. + // That means there is an available type ID between these reserved IDs. + while (next_type_iter != assigned_ids_iter_end) { + if (next_type_iter->first.package_id() != package->id.value()) { + break; + } + + const uint8_t type_id = next_type_iter->first.type_id(); + if (type_id > next_expected_type_id) { + // There is a gap in the type IDs, so use the missing one. + type->id = next_expected_type_id++; + break; + } + + // Set our expectation to be the next type ID after the reserved one + // we + // just saw. + next_expected_type_id = type_id + 1; + + // Move to the next reserved ID. + ++next_type_iter; } - // Assign unused type IDs. - size_t nextTypeId = 0; - for (auto& type : package->types) { - if (!type->id) { - while (nextTypeId < usedTypeIds.size() && usedTypeIds[nextTypeId]) { - nextTypeId++; - } - type->id = static_cast<uint8_t>(nextTypeId); - nextTypeId++; + if (!type->id) { + // We must have hit the end of the reserved IDs and not found a gap. + // That means the next ID is available. + type->id = next_expected_type_id++; + } + } + + resource_id = ResourceId(package->id.value(), type->id.value(), 0); + uint16_t next_expected_entry_id = 0; + + // Find the closest matching ResourceId that is <= the one with only the + // package + // and type set. + auto next_entry_iter = assigned_ids.lower_bound(resource_id); + for (auto& entry : type->entries) { + if (!entry->id) { + // We need to assign an entry ID. Iterate over the reserved IDs until + // we find + // some entry ID that is a distance of 2 greater than the last one + // we've seen. + // That means there is an available entry ID between these reserved + // IDs. + while (next_entry_iter != assigned_ids_iter_end) { + if (next_entry_iter->first.package_id() != package->id.value() || + next_entry_iter->first.type_id() != type->id.value()) { + break; + } + + const uint16_t entry_id = next_entry_iter->first.entry_id(); + if (entry_id > next_expected_entry_id) { + // There is a gap in the entry IDs, so use the missing one. + entry->id = next_expected_entry_id++; + break; } + + // Set our expectation to be the next type ID after the reserved one + // we + // just saw. + next_expected_entry_id = entry_id + 1; + + // Move to the next reserved entry ID. + ++next_entry_iter; + } + + if (!entry->id) { + // We must have hit the end of the reserved IDs and not found a gap. + // That means the next ID is available. + entry->id = next_expected_entry_id++; + } } + } } - return true; + } + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/IdAssigner.h b/tools/aapt2/compile/IdAssigner.h index 514df3ad3861..371ec01818cd 100644 --- a/tools/aapt2/compile/IdAssigner.h +++ b/tools/aapt2/compile/IdAssigner.h @@ -17,18 +17,33 @@ #ifndef AAPT_COMPILE_IDASSIGNER_H #define AAPT_COMPILE_IDASSIGNER_H +#include <unordered_map> + +#include "Resource.h" #include "process/IResourceTableConsumer.h" +#include "android-base/macros.h" + namespace aapt { /** - * Assigns IDs to each resource in the table, respecting existing IDs and filling in gaps + * Assigns IDs to each resource in the table, respecting existing IDs and + * filling in gaps * in between fixed ID assignments. */ -struct IdAssigner : public IResourceTableConsumer { - bool consume(IAaptContext* context, ResourceTable* table) override; +class IdAssigner : public IResourceTableConsumer { + public: + IdAssigner() = default; + explicit IdAssigner(const std::unordered_map<ResourceName, ResourceId>* map) + : assigned_id_map_(map) {} + + bool Consume(IAaptContext* context, ResourceTable* table) override; + + private: + const std::unordered_map<ResourceName, ResourceId>* assigned_id_map_ = + nullptr; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_COMPILE_IDASSIGNER_H */ diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp index e25a17ab125e..d465091d224e 100644 --- a/tools/aapt2/compile/IdAssigner_test.cpp +++ b/tools/aapt2/compile/IdAssigner_test.cpp @@ -16,108 +16,170 @@ #include "compile/IdAssigner.h" -#include "test/Context.h" -#include "test/Builders.h" - -#include <gtest/gtest.h> +#include "test/Test.h" namespace aapt { -::testing::AssertionResult verifyIds(ResourceTable* table); +::testing::AssertionResult VerifyIds(ResourceTable* table); TEST(IdAssignerTest, AssignIds) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addSimple(u"@android:attr/foo") - .addSimple(u"@android:attr/bar") - .addSimple(u"@android:id/foo") - .setPackageId(u"android", 0x01) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - IdAssigner assigner; - - ASSERT_TRUE(assigner.consume(context.get(), table.get())); - ASSERT_TRUE(verifyIds(table.get())); + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .AddSimple("android:attr/foo") + .AddSimple("android:attr/bar") + .AddSimple("android:id/foo") + .SetPackageId("android", 0x01) + .Build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + IdAssigner assigner; + + ASSERT_TRUE(assigner.Consume(context.get(), table.get())); + ASSERT_TRUE(VerifyIds(table.get())); } TEST(IdAssignerTest, AssignIdsWithReservedIds) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addSimple(u"@android:attr/foo", ResourceId(0x01040006)) - .addSimple(u"@android:attr/bar") - .addSimple(u"@android:id/foo") - .addSimple(u"@app:id/biz") - .setPackageId(u"android", 0x01) - .setPackageId(u"app", 0x7f) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - IdAssigner assigner; - - ASSERT_TRUE(assigner.consume(context.get(), table.get())); - ASSERT_TRUE(verifyIds(table.get())); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddSimple("android:id/foo", ResourceId(0x01010000)) + .AddSimple("android:dimen/two") + .AddSimple("android:integer/three") + .AddSimple("android:string/five") + .AddSimple("android:attr/fun", ResourceId(0x01040000)) + .AddSimple("android:attr/foo", ResourceId(0x01040006)) + .AddSimple("android:attr/bar") + .AddSimple("android:attr/baz") + .AddSimple("app:id/biz") + .SetPackageId("android", 0x01) + .SetPackageId("app", 0x7f) + .Build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + IdAssigner assigner; + + ASSERT_TRUE(assigner.Consume(context.get(), table.get())); + ASSERT_TRUE(VerifyIds(table.get())); + + Maybe<ResourceTable::SearchResult> maybe_result; + + // Expect to fill in the gaps between 0x0101XXXX and 0x0104XXXX. + + maybe_result = table->FindResource(test::ParseNameOrDie("android:dimen/two")); + AAPT_ASSERT_TRUE(maybe_result); + EXPECT_EQ(make_value<uint8_t>(2), maybe_result.value().type->id); + + maybe_result = + table->FindResource(test::ParseNameOrDie("android:integer/three")); + AAPT_ASSERT_TRUE(maybe_result); + EXPECT_EQ(make_value<uint8_t>(3), maybe_result.value().type->id); + + // Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX + // IDs. + + maybe_result = + table->FindResource(test::ParseNameOrDie("android:string/five")); + AAPT_ASSERT_TRUE(maybe_result); + EXPECT_EQ(make_value<uint8_t>(5), maybe_result.value().type->id); + + // Expect to fill in the gaps between 0x01040000 and 0x01040006. + + maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/bar")); + AAPT_ASSERT_TRUE(maybe_result); + EXPECT_EQ(make_value<uint16_t>(1), maybe_result.value().entry->id); + + maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/baz")); + AAPT_ASSERT_TRUE(maybe_result); + EXPECT_EQ(make_value<uint16_t>(2), maybe_result.value().entry->id); } TEST(IdAssignerTest, FailWhenNonUniqueIdsAssigned) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addSimple(u"@android:attr/foo", ResourceId(0x01040006)) - .addSimple(u"@android:attr/bar", ResourceId(0x01040006)) - .setPackageId(u"android", 0x01) - .setPackageId(u"app", 0x7f) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - IdAssigner assigner; + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddSimple("android:attr/foo", ResourceId(0x01040006)) + .AddSimple("android:attr/bar", ResourceId(0x01040006)) + .SetPackageId("android", 0x01) + .SetPackageId("app", 0x7f) + .Build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + IdAssigner assigner; + + ASSERT_FALSE(assigner.Consume(context.get(), table.get())); +} - ASSERT_FALSE(assigner.consume(context.get(), table.get())); +TEST(IdAssignerTest, AssignIdsWithIdMap) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .AddSimple("android:attr/foo") + .AddSimple("android:attr/bar") + .SetPackageId("android", 0x01) + .Build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unordered_map<ResourceName, ResourceId> id_map = { + {test::ParseNameOrDie("android:attr/foo"), ResourceId(0x01010002)}}; + IdAssigner assigner(&id_map); + ASSERT_TRUE(assigner.Consume(context.get(), table.get())); + ASSERT_TRUE(VerifyIds(table.get())); + Maybe<ResourceTable::SearchResult> result = + table->FindResource(test::ParseNameOrDie("android:attr/foo")); + AAPT_ASSERT_TRUE(result); + + const ResourceTable::SearchResult& search_result = result.value(); + EXPECT_EQ(make_value<uint8_t>(0x01), search_result.package->id); + EXPECT_EQ(make_value<uint8_t>(0x01), search_result.type->id); + EXPECT_EQ(make_value<uint16_t>(0x0002), search_result.entry->id); } -::testing::AssertionResult verifyIds(ResourceTable* table) { - std::set<uint8_t> packageIds; - for (auto& package : table->packages) { - if (!package->id) { - return ::testing::AssertionFailure() << "package " << package->name << " has no ID"; - } +::testing::AssertionResult VerifyIds(ResourceTable* table) { + std::set<uint8_t> package_ids; + for (auto& package : table->packages) { + if (!package->id) { + return ::testing::AssertionFailure() << "package " << package->name + << " has no ID"; + } - if (!packageIds.insert(package->id.value()).second) { - return ::testing::AssertionFailure() << "package " << package->name - << " has non-unique ID " << std::hex << (int) package->id.value() << std::dec; - } + if (!package_ids.insert(package->id.value()).second) { + return ::testing::AssertionFailure() + << "package " << package->name << " has non-unique ID " << std::hex + << (int)package->id.value() << std::dec; + } + } + + for (auto& package : table->packages) { + std::set<uint8_t> type_ids; + for (auto& type : package->types) { + if (!type->id) { + return ::testing::AssertionFailure() << "type " << type->type + << " of package " << package->name + << " has no ID"; + } + + if (!type_ids.insert(type->id.value()).second) { + return ::testing::AssertionFailure() + << "type " << type->type << " of package " << package->name + << " has non-unique ID " << std::hex << (int)type->id.value() + << std::dec; + } } - for (auto& package : table->packages) { - std::set<uint8_t> typeIds; - for (auto& type : package->types) { - if (!type->id) { - return ::testing::AssertionFailure() << "type " << type->type << " of package " - << package->name << " has no ID"; - } - - if (!typeIds.insert(type->id.value()).second) { - return ::testing::AssertionFailure() << "type " << type->type - << " of package " << package->name << " has non-unique ID " - << std::hex << (int) type->id.value() << std::dec; - } + for (auto& type : package->types) { + std::set<uint16_t> entry_ids; + for (auto& entry : type->entries) { + if (!entry->id) { + return ::testing::AssertionFailure() + << "entry " << entry->name << " of type " << type->type + << " of package " << package->name << " has no ID"; } - - for (auto& type : package->types) { - std::set<uint16_t> entryIds; - for (auto& entry : type->entries) { - if (!entry->id) { - return ::testing::AssertionFailure() << "entry " << entry->name << " of type " - << type->type << " of package " << package->name << " has no ID"; - } - - if (!entryIds.insert(entry->id.value()).second) { - return ::testing::AssertionFailure() << "entry " << entry->name - << " of type " << type->type << " of package " << package->name - << " has non-unique ID " - << std::hex << (int) entry->id.value() << std::dec; - } - } + if (!entry_ids.insert(entry->id.value()).second) { + return ::testing::AssertionFailure() + << "entry " << entry->name << " of type " << type->type + << " of package " << package->name << " has non-unique ID " + << std::hex << (int)entry->id.value() << std::dec; } + } } - return ::testing::AssertionSuccess() << "all IDs are unique and assigned"; + } + return ::testing::AssertionSuccess() << "all IDs are unique and assigned"; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/Image.h b/tools/aapt2/compile/Image.h new file mode 100644 index 000000000000..db0b945e1f18 --- /dev/null +++ b/tools/aapt2/compile/Image.h @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef AAPT_COMPILE_IMAGE_H +#define AAPT_COMPILE_IMAGE_H + +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +#include "android-base/macros.h" + +namespace aapt { + +/** + * An in-memory image, loaded from disk, with pixels in RGBA_8888 format. + */ +class Image { + public: + explicit Image() = default; + + /** + * A `height` sized array of pointers, where each element points to a + * `width` sized row of RGBA_8888 pixels. + */ + std::unique_ptr<uint8_t* []> rows; + + /** + * The width of the image in RGBA_8888 pixels. This is int32_t because of + * 9-patch data + * format limitations. + */ + int32_t width = 0; + + /** + * The height of the image in RGBA_8888 pixels. This is int32_t because of + * 9-patch data + * format limitations. + */ + int32_t height = 0; + + /** + * Buffer to the raw image data stored sequentially. + * Use `rows` to access the data on a row-by-row basis. + */ + std::unique_ptr<uint8_t[]> data; + + private: + DISALLOW_COPY_AND_ASSIGN(Image); +}; + +/** + * A range of pixel values, starting at 'start' and ending before 'end' + * exclusive. Or rather [a, b). + */ +struct Range { + int32_t start = 0; + int32_t end = 0; + + explicit Range() = default; + inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {} +}; + +inline bool operator==(const Range& left, const Range& right) { + return left.start == right.start && left.end == right.end; +} + +/** + * Inset lengths from all edges of a rectangle. `left` and `top` are measured + * from the left and top + * edges, while `right` and `bottom` are measured from the right and bottom + * edges, respectively. + */ +struct Bounds { + int32_t left = 0; + int32_t top = 0; + int32_t right = 0; + int32_t bottom = 0; + + explicit Bounds() = default; + inline explicit Bounds(int32_t l, int32_t t, int32_t r, int32_t b) + : left(l), top(t), right(r), bottom(b) {} + + bool nonZero() const; +}; + +inline bool Bounds::nonZero() const { + return left != 0 || top != 0 || right != 0 || bottom != 0; +} + +inline bool operator==(const Bounds& left, const Bounds& right) { + return left.left == right.left && left.top == right.top && + left.right == right.right && left.bottom == right.bottom; +} + +/** + * Contains 9-patch data from a source image. All measurements exclude the 1px + * border of the + * source 9-patch image. + */ +class NinePatch { + public: + static std::unique_ptr<NinePatch> Create(uint8_t** rows, const int32_t width, + const int32_t height, + std::string* err_out); + + /** + * Packs the RGBA_8888 data pointed to by pixel into a uint32_t + * with format 0xAARRGGBB (the way 9-patch expects it). + */ + static uint32_t PackRGBA(const uint8_t* pixel); + + /** + * 9-patch content padding/insets. All positions are relative to the 9-patch + * NOT including the 1px thick source border. + */ + Bounds padding; + + /** + * Optical layout bounds/insets. This overrides the padding for + * layout purposes. All positions are relative to the 9-patch + * NOT including the 1px thick source border. + * See + * https://developer.android.com/about/versions/android-4.3.html#OpticalBounds + */ + Bounds layout_bounds; + + /** + * Outline of the image, calculated based on opacity. + */ + Bounds outline; + + /** + * The computed radius of the outline. If non-zero, the outline is a + * rounded-rect. + */ + float outline_radius = 0.0f; + + /** + * The largest alpha value within the outline. + */ + uint32_t outline_alpha = 0x000000ffu; + + /** + * Horizontal regions of the image that are stretchable. + * All positions are relative to the 9-patch + * NOT including the 1px thick source border. + */ + std::vector<Range> horizontal_stretch_regions; + + /** + * Vertical regions of the image that are stretchable. + * All positions are relative to the 9-patch + * NOT including the 1px thick source border. + */ + std::vector<Range> vertical_stretch_regions; + + /** + * The colors within each region, fixed or stretchable. + * For w*h regions, the color of region (x,y) is addressable + * via index y*w + x. + */ + std::vector<uint32_t> region_colors; + + /** + * Returns serialized data containing the original basic 9-patch meta data. + * Optical layout bounds and round rect outline data must be serialized + * separately using SerializeOpticalLayoutBounds() and + * SerializeRoundedRectOutline(). + */ + std::unique_ptr<uint8_t[]> SerializeBase(size_t* out_len) const; + + /** + * Serializes the layout bounds. + */ + std::unique_ptr<uint8_t[]> SerializeLayoutBounds(size_t* out_len) const; + + /** + * Serializes the rounded-rect outline. + */ + std::unique_ptr<uint8_t[]> SerializeRoundedRectOutline(size_t* out_len) const; + + private: + explicit NinePatch() = default; + + DISALLOW_COPY_AND_ASSIGN(NinePatch); +}; + +::std::ostream& operator<<(::std::ostream& out, const Range& range); +::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds); +::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch); + +} // namespace aapt + +#endif /* AAPT_COMPILE_IMAGE_H */ diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp new file mode 100644 index 000000000000..786494b6ad1c --- /dev/null +++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "compile/InlineXmlFormatParser.h" + +#include <sstream> +#include <string> + +#include "android-base/macros.h" + +#include "Debug.h" +#include "ResourceUtils.h" +#include "util/Util.h" +#include "xml/XmlDom.h" +#include "xml/XmlUtil.h" + +namespace aapt { + +namespace { + +/** + * XML Visitor that will find all <aapt:attr> elements for extraction. + */ +class Visitor : public xml::PackageAwareVisitor { + public: + using xml::PackageAwareVisitor::Visit; + + struct InlineDeclaration { + xml::Element* el; + std::string attr_namespace_uri; + std::string attr_name; + }; + + explicit Visitor(IAaptContext* context, xml::XmlResource* xml_resource) + : context_(context), xml_resource_(xml_resource) {} + + void Visit(xml::Element* el) override { + if (el->namespace_uri != xml::kSchemaAapt || el->name != "attr") { + xml::PackageAwareVisitor::Visit(el); + return; + } + + const Source& src = xml_resource_->file.source.WithLine(el->line_number); + + xml::Attribute* attr = el->FindAttribute({}, "name"); + if (!attr) { + context_->GetDiagnostics()->Error(DiagMessage(src) + << "missing 'name' attribute"); + error_ = true; + return; + } + + Maybe<Reference> ref = ResourceUtils::ParseXmlAttributeName(attr->value); + if (!ref) { + context_->GetDiagnostics()->Error( + DiagMessage(src) << "invalid XML attribute '" << attr->value << "'"); + error_ = true; + return; + } + + const ResourceName& name = ref.value().name.value(); + + // Use an empty string for the compilation package because we don't want to + // default to + // the local package if the user specified name="style" or something. This + // should just + // be the default namespace. + Maybe<xml::ExtractedPackage> maybe_pkg = + TransformPackageAlias(name.package, {}); + if (!maybe_pkg) { + context_->GetDiagnostics()->Error(DiagMessage(src) + << "invalid namespace prefix '" + << name.package << "'"); + error_ = true; + return; + } + + const xml::ExtractedPackage& pkg = maybe_pkg.value(); + const bool private_namespace = + pkg.private_namespace || ref.value().private_reference; + + InlineDeclaration decl; + decl.el = el; + decl.attr_name = name.entry; + if (!pkg.package.empty()) { + decl.attr_namespace_uri = + xml::BuildPackageNamespace(pkg.package, private_namespace); + } + + inline_declarations_.push_back(std::move(decl)); + } + + const std::vector<InlineDeclaration>& GetInlineDeclarations() const { + return inline_declarations_; + } + + bool HasError() const { return error_; } + + private: + DISALLOW_COPY_AND_ASSIGN(Visitor); + + IAaptContext* context_; + xml::XmlResource* xml_resource_; + std::vector<InlineDeclaration> inline_declarations_; + bool error_ = false; +}; + +} // namespace + +bool InlineXmlFormatParser::Consume(IAaptContext* context, + xml::XmlResource* doc) { + Visitor visitor(context, doc); + doc->root->Accept(&visitor); + if (visitor.HasError()) { + return false; + } + + size_t name_suffix_counter = 0; + for (const Visitor::InlineDeclaration& decl : + visitor.GetInlineDeclarations()) { + auto new_doc = util::make_unique<xml::XmlResource>(); + new_doc->file.config = doc->file.config; + new_doc->file.source = doc->file.source.WithLine(decl.el->line_number); + new_doc->file.name = doc->file.name; + + // Modify the new entry name. We need to suffix the entry with a number to + // avoid + // local collisions, then mangle it with the empty package, such that it + // won't show up + // in R.java. + + new_doc->file.name.entry = + NameMangler::MangleEntry({}, new_doc->file.name.entry + "__" + + std::to_string(name_suffix_counter)); + + // Extracted elements must be the only child of <aapt:attr>. + // Make sure there is one root node in the children (ignore empty text). + for (auto& child : decl.el->children) { + const Source child_source = doc->file.source.WithLine(child->line_number); + if (xml::Text* t = xml::NodeCast<xml::Text>(child.get())) { + if (!util::TrimWhitespace(t->text).empty()) { + context->GetDiagnostics()->Error( + DiagMessage(child_source) + << "can't extract text into its own resource"); + return false; + } + } else if (new_doc->root) { + context->GetDiagnostics()->Error( + DiagMessage(child_source) + << "inline XML resources must have a single root"); + return false; + } else { + new_doc->root = std::move(child); + new_doc->root->parent = nullptr; + } + } + + // Walk up and find the parent element. + xml::Node* node = decl.el; + xml::Element* parent_el = nullptr; + while (node->parent && + (parent_el = xml::NodeCast<xml::Element>(node->parent)) == nullptr) { + node = node->parent; + } + + if (!parent_el) { + context->GetDiagnostics()->Error( + DiagMessage(new_doc->file.source) + << "no suitable parent for inheriting attribute"); + return false; + } + + // Add the inline attribute to the parent. + parent_el->attributes.push_back( + xml::Attribute{decl.attr_namespace_uri, decl.attr_name, + "@" + new_doc->file.name.ToString()}); + + // Delete the subtree. + for (auto iter = parent_el->children.begin(); + iter != parent_el->children.end(); ++iter) { + if (iter->get() == node) { + parent_el->children.erase(iter); + break; + } + } + + queue_.push_back(std::move(new_doc)); + + name_suffix_counter++; + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/compile/InlineXmlFormatParser.h b/tools/aapt2/compile/InlineXmlFormatParser.h new file mode 100644 index 000000000000..1a658fd6a180 --- /dev/null +++ b/tools/aapt2/compile/InlineXmlFormatParser.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef AAPT_COMPILE_INLINEXMLFORMATPARSER_H +#define AAPT_COMPILE_INLINEXMLFORMATPARSER_H + +#include <memory> +#include <vector> + +#include "android-base/macros.h" + +#include "process/IResourceTableConsumer.h" + +namespace aapt { + +/** + * Extracts Inline XML definitions into their own xml::XmlResource objects. + * + * Inline XML looks like: + * + * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + * xmlns:aapt="http://schemas.android.com/aapt" > + * <aapt:attr name="android:drawable" > + * <vector + * android:height="64dp" + * android:width="64dp" + * android:viewportHeight="600" + * android:viewportWidth="600"/> + * </aapt:attr> + * </animated-vector> + * + * The <vector> will be extracted into its own XML file and <animated-vector> + * will + * gain an attribute 'android:drawable' set to a reference to the extracted + * <vector> resource. + */ +class InlineXmlFormatParser : public IXmlResourceConsumer { + public: + explicit InlineXmlFormatParser() = default; + + bool Consume(IAaptContext* context, xml::XmlResource* doc) override; + + std::vector<std::unique_ptr<xml::XmlResource>>& + GetExtractedInlineXmlDocuments() { + return queue_; + } + + private: + DISALLOW_COPY_AND_ASSIGN(InlineXmlFormatParser); + + std::vector<std::unique_ptr<xml::XmlResource>> queue_; +}; + +} // namespace aapt + +#endif /* AAPT_COMPILE_INLINEXMLFORMATPARSER_H */ diff --git a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp new file mode 100644 index 000000000000..348796c98c22 --- /dev/null +++ b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "compile/InlineXmlFormatParser.h" + +#include "test/Test.h" + +namespace aapt { + +TEST(InlineXmlFormatParserTest, PassThrough) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android"> + <View android:text="hey"> + <View android:id="hi" /> + </View> + </View>)EOF"); + + InlineXmlFormatParser parser; + ASSERT_TRUE(parser.Consume(context.get(), doc.get())); + EXPECT_EQ(0u, parser.GetExtractedInlineXmlDocuments().size()); +} + +TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( + <View1 xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <aapt:attr name="android:text"> + <View2 android:text="hey"> + <View3 android:id="hi" /> + </View2> + </aapt:attr> + </View1>)EOF"); + + doc->file.name = test::ParseNameOrDie("layout/main"); + + InlineXmlFormatParser parser; + ASSERT_TRUE(parser.Consume(context.get(), doc.get())); + + // One XML resource should have been extracted. + EXPECT_EQ(1u, parser.GetExtractedInlineXmlDocuments().size()); + + xml::Element* el = xml::FindRootElement(doc.get()); + ASSERT_NE(nullptr, el); + + EXPECT_EQ("View1", el->name); + + // The <aapt:attr> tag should be extracted. + EXPECT_EQ(nullptr, el->FindChild(xml::kSchemaAapt, "attr")); + + // The 'android:text' attribute should be set with a reference. + xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "text"); + ASSERT_NE(nullptr, attr); + + ResourceNameRef name_ref; + ASSERT_TRUE(ResourceUtils::ParseReference(attr->value, &name_ref)); + + xml::XmlResource* extracted_doc = + parser.GetExtractedInlineXmlDocuments()[0].get(); + ASSERT_NE(nullptr, extracted_doc); + + // Make sure the generated reference is correct. + EXPECT_EQ(name_ref.package, extracted_doc->file.name.package); + EXPECT_EQ(name_ref.type, extracted_doc->file.name.type); + EXPECT_EQ(name_ref.entry, extracted_doc->file.name.entry); + + // Verify the structure of the extracted XML. + el = xml::FindRootElement(extracted_doc); + ASSERT_NE(nullptr, el); + EXPECT_EQ("View2", el->name); + EXPECT_NE(nullptr, el->FindChild({}, "View3")); +} + +TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( + <View1 xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <aapt:attr name="android:text"> + <View2 android:text="hey"> + <View3 android:id="hi" /> + </View2> + </aapt:attr> + + <aapt:attr name="android:drawable"> + <vector /> + </aapt:attr> + </View1>)EOF"); + + doc->file.name = test::ParseNameOrDie("layout/main"); + + InlineXmlFormatParser parser; + ASSERT_TRUE(parser.Consume(context.get(), doc.get())); + ASSERT_EQ(2u, parser.GetExtractedInlineXmlDocuments().size()); + + xml::Element* el = xml::FindRootElement(doc.get()); + ASSERT_NE(nullptr, el); + + EXPECT_EQ("View1", el->name); + + xml::Attribute* attr_text = el->FindAttribute(xml::kSchemaAndroid, "text"); + ASSERT_NE(nullptr, attr_text); + + xml::Attribute* attr_drawable = + el->FindAttribute(xml::kSchemaAndroid, "drawable"); + ASSERT_NE(nullptr, attr_drawable); + + // The two extracted resources should have different names. + EXPECT_NE(attr_text->value, attr_drawable->value); + + // The child <aapt:attr> elements should be gone. + EXPECT_EQ(nullptr, el->FindChild(xml::kSchemaAapt, "attr")); + + xml::XmlResource* extracted_doc_text = + parser.GetExtractedInlineXmlDocuments()[0].get(); + ASSERT_NE(nullptr, extracted_doc_text); + el = xml::FindRootElement(extracted_doc_text); + ASSERT_NE(nullptr, el); + EXPECT_EQ("View2", el->name); + + xml::XmlResource* extracted_doc_drawable = + parser.GetExtractedInlineXmlDocuments()[1].get(); + ASSERT_NE(nullptr, extracted_doc_drawable); + el = xml::FindRootElement(extracted_doc_drawable); + ASSERT_NE(nullptr, el); + EXPECT_EQ("vector", el->name); +} + +} // namespace aapt diff --git a/tools/aapt2/compile/NinePatch.cpp b/tools/aapt2/compile/NinePatch.cpp new file mode 100644 index 000000000000..eab5c97c437c --- /dev/null +++ b/tools/aapt2/compile/NinePatch.cpp @@ -0,0 +1,698 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "compile/Image.h" + +#include <sstream> +#include <string> +#include <vector> + +#include "androidfw/ResourceTypes.h" + +#include "util/StringPiece.h" +#include "util/Util.h" + +namespace aapt { + +// Colors in the format 0xAARRGGBB (the way 9-patch expects it). +constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu; +constexpr static const uint32_t kColorOpaqueBlack = 0xff000000u; +constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u; + +constexpr static const uint32_t kPrimaryColor = kColorOpaqueBlack; +constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed; + +/** + * Returns the alpha value encoded in the 0xAARRGBB encoded pixel. + */ +static uint32_t get_alpha(uint32_t color); + +/** + * Determines whether a color on an ImageLine is valid. + * A 9patch image may use a transparent color as neutral, + * or a fully opaque white color as neutral, based on the + * pixel color at (0,0) of the image. One or the other is fine, + * but we need to ensure consistency throughout the image. + */ +class ColorValidator { + public: + virtual ~ColorValidator() = default; + + /** + * Returns true if the color specified is a neutral color + * (no padding, stretching, or optical bounds). + */ + virtual bool IsNeutralColor(uint32_t color) const = 0; + + /** + * Returns true if the color is either a neutral color + * or one denoting padding, stretching, or optical bounds. + */ + bool IsValidColor(uint32_t color) const { + switch (color) { + case kPrimaryColor: + case kSecondaryColor: + return true; + } + return IsNeutralColor(color); + } +}; + +// Walks an ImageLine and records Ranges of primary and secondary colors. +// The primary color is black and is used to denote a padding or stretching +// range, +// depending on which border we're iterating over. +// The secondary color is red and is used to denote optical bounds. +// +// An ImageLine is a templated-interface that would look something like this if +// it +// were polymorphic: +// +// class ImageLine { +// public: +// virtual int32_t GetLength() const = 0; +// virtual uint32_t GetColor(int32_t idx) const = 0; +// }; +// +template <typename ImageLine> +static bool FillRanges(const ImageLine* image_line, + const ColorValidator* color_validator, + std::vector<Range>* primary_ranges, + std::vector<Range>* secondary_ranges, + std::string* out_err) { + const int32_t length = image_line->GetLength(); + + uint32_t last_color = 0xffffffffu; + for (int32_t idx = 1; idx < length - 1; idx++) { + const uint32_t color = image_line->GetColor(idx); + if (!color_validator->IsValidColor(color)) { + *out_err = "found an invalid color"; + return false; + } + + if (color != last_color) { + // We are ending a range. Which range? + // note: encode the x offset without the final 1 pixel border. + if (last_color == kPrimaryColor) { + primary_ranges->back().end = idx - 1; + } else if (last_color == kSecondaryColor) { + secondary_ranges->back().end = idx - 1; + } + + // We are starting a range. Which range? + // note: encode the x offset without the final 1 pixel border. + if (color == kPrimaryColor) { + primary_ranges->push_back(Range(idx - 1, length - 2)); + } else if (color == kSecondaryColor) { + secondary_ranges->push_back(Range(idx - 1, length - 2)); + } + last_color = color; + } + } + return true; +} + +/** + * Iterates over a row in an image. Implements the templated ImageLine + * interface. + */ +class HorizontalImageLine { + public: + explicit HorizontalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, + int32_t length) + : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {} + + inline int32_t GetLength() const { return length_; } + + inline uint32_t GetColor(int32_t idx) const { + return NinePatch::PackRGBA(rows_[yoffset_] + (idx + xoffset_) * 4); + } + + private: + uint8_t** rows_; + int32_t xoffset_, yoffset_, length_; + + DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine); +}; + +/** + * Iterates over a column in an image. Implements the templated ImageLine + * interface. + */ +class VerticalImageLine { + public: + explicit VerticalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, + int32_t length) + : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {} + + inline int32_t GetLength() const { return length_; } + + inline uint32_t GetColor(int32_t idx) const { + return NinePatch::PackRGBA(rows_[yoffset_ + idx] + (xoffset_ * 4)); + } + + private: + uint8_t** rows_; + int32_t xoffset_, yoffset_, length_; + + DISALLOW_COPY_AND_ASSIGN(VerticalImageLine); +}; + +class DiagonalImageLine { + public: + explicit DiagonalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, + int32_t xstep, int32_t ystep, int32_t length) + : rows_(rows), + xoffset_(xoffset), + yoffset_(yoffset), + xstep_(xstep), + ystep_(ystep), + length_(length) {} + + inline int32_t GetLength() const { return length_; } + + inline uint32_t GetColor(int32_t idx) const { + return NinePatch::PackRGBA(rows_[yoffset_ + (idx * ystep_)] + + ((idx + xoffset_) * xstep_) * 4); + } + + private: + uint8_t** rows_; + int32_t xoffset_, yoffset_, xstep_, ystep_, length_; + + DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine); +}; + +class TransparentNeutralColorValidator : public ColorValidator { + public: + bool IsNeutralColor(uint32_t color) const override { + return get_alpha(color) == 0; + } +}; + +class WhiteNeutralColorValidator : public ColorValidator { + public: + bool IsNeutralColor(uint32_t color) const override { + return color == kColorOpaqueWhite; + } +}; + +inline static uint32_t get_alpha(uint32_t color) { + return (color & 0xff000000u) >> 24; +} + +static bool PopulateBounds(const std::vector<Range>& padding, + const std::vector<Range>& layout_bounds, + const std::vector<Range>& stretch_regions, + const int32_t length, int32_t* padding_start, + int32_t* padding_end, int32_t* layout_start, + int32_t* layout_end, const StringPiece& edge_name, + std::string* out_err) { + if (padding.size() > 1) { + std::stringstream err_stream; + err_stream << "too many padding sections on " << edge_name << " border"; + *out_err = err_stream.str(); + return false; + } + + *padding_start = 0; + *padding_end = 0; + if (!padding.empty()) { + const Range& range = padding.front(); + *padding_start = range.start; + *padding_end = length - range.end; + } else if (!stretch_regions.empty()) { + // No padding was defined. Compute the padding from the first and last + // stretch regions. + *padding_start = stretch_regions.front().start; + *padding_end = length - stretch_regions.back().end; + } + + if (layout_bounds.size() > 2) { + std::stringstream err_stream; + err_stream << "too many layout bounds sections on " << edge_name + << " border"; + *out_err = err_stream.str(); + return false; + } + + *layout_start = 0; + *layout_end = 0; + if (layout_bounds.size() >= 1) { + const Range& range = layout_bounds.front(); + // If there is only one layout bound segment, it might not start at 0, but + // then it should + // end at length. + if (range.start != 0 && range.end != length) { + std::stringstream err_stream; + err_stream << "layout bounds on " << edge_name + << " border must start at edge"; + *out_err = err_stream.str(); + return false; + } + *layout_start = range.end; + + if (layout_bounds.size() >= 2) { + const Range& range = layout_bounds.back(); + if (range.end != length) { + std::stringstream err_stream; + err_stream << "layout bounds on " << edge_name + << " border must start at edge"; + *out_err = err_stream.str(); + return false; + } + *layout_end = length - range.start; + } + } + return true; +} + +static int32_t CalculateSegmentCount(const std::vector<Range>& stretch_regions, + int32_t length) { + if (stretch_regions.size() == 0) { + return 0; + } + + const bool start_is_fixed = stretch_regions.front().start != 0; + const bool end_is_fixed = stretch_regions.back().end != length; + int32_t modifier = 0; + if (start_is_fixed && end_is_fixed) { + modifier = 1; + } else if (!start_is_fixed && !end_is_fixed) { + modifier = -1; + } + return static_cast<int32_t>(stretch_regions.size()) * 2 + modifier; +} + +static uint32_t GetRegionColor(uint8_t** rows, const Bounds& region) { + // Sample the first pixel to compare against. + const uint32_t expected_color = + NinePatch::PackRGBA(rows[region.top] + region.left * 4); + for (int32_t y = region.top; y < region.bottom; y++) { + const uint8_t* row = rows[y]; + for (int32_t x = region.left; x < region.right; x++) { + const uint32_t color = NinePatch::PackRGBA(row + x * 4); + if (get_alpha(color) == 0) { + // The color is transparent. + // If the expectedColor is not transparent, NO_COLOR. + if (get_alpha(expected_color) != 0) { + return android::Res_png_9patch::NO_COLOR; + } + } else if (color != expected_color) { + return android::Res_png_9patch::NO_COLOR; + } + } + } + + if (get_alpha(expected_color) == 0) { + return android::Res_png_9patch::TRANSPARENT_COLOR; + } + return expected_color; +} + +// Fills out_colors with each 9-patch section's color. If the whole section is +// transparent, +// it gets the special TRANSPARENT color. If the whole section is the same +// color, it is assigned +// that color. Otherwise it gets the special NO_COLOR color. +// +// Note that the rows contain the 9-patch 1px border, and the indices in the +// stretch regions are +// already offset to exclude the border. This means that each time the rows are +// accessed, +// the indices must be offset by 1. +// +// width and height also include the 9-patch 1px border. +static void CalculateRegionColors( + uint8_t** rows, const std::vector<Range>& horizontal_stretch_regions, + const std::vector<Range>& vertical_stretch_regions, const int32_t width, + const int32_t height, std::vector<uint32_t>* out_colors) { + int32_t next_top = 0; + Bounds bounds; + auto row_iter = vertical_stretch_regions.begin(); + while (next_top != height) { + if (row_iter != vertical_stretch_regions.end()) { + if (next_top != row_iter->start) { + // This is a fixed segment. + // Offset the bounds by 1 to accommodate the border. + bounds.top = next_top + 1; + bounds.bottom = row_iter->start + 1; + next_top = row_iter->start; + } else { + // This is a stretchy segment. + // Offset the bounds by 1 to accommodate the border. + bounds.top = row_iter->start + 1; + bounds.bottom = row_iter->end + 1; + next_top = row_iter->end; + ++row_iter; + } + } else { + // This is the end, fixed section. + // Offset the bounds by 1 to accommodate the border. + bounds.top = next_top + 1; + bounds.bottom = height + 1; + next_top = height; + } + + int32_t next_left = 0; + auto col_iter = horizontal_stretch_regions.begin(); + while (next_left != width) { + if (col_iter != horizontal_stretch_regions.end()) { + if (next_left != col_iter->start) { + // This is a fixed segment. + // Offset the bounds by 1 to accommodate the border. + bounds.left = next_left + 1; + bounds.right = col_iter->start + 1; + next_left = col_iter->start; + } else { + // This is a stretchy segment. + // Offset the bounds by 1 to accommodate the border. + bounds.left = col_iter->start + 1; + bounds.right = col_iter->end + 1; + next_left = col_iter->end; + ++col_iter; + } + } else { + // This is the end, fixed section. + // Offset the bounds by 1 to accommodate the border. + bounds.left = next_left + 1; + bounds.right = width + 1; + next_left = width; + } + out_colors->push_back(GetRegionColor(rows, bounds)); + } + } +} + +// Calculates the insets of a row/column of pixels based on where the largest +// alpha value begins +// (on both sides). +template <typename ImageLine> +static void FindOutlineInsets(const ImageLine* image_line, int32_t* out_start, + int32_t* out_end) { + *out_start = 0; + *out_end = 0; + + const int32_t length = image_line->GetLength(); + if (length < 3) { + return; + } + + // If the length is odd, we want both sides to process the center pixel, + // so we use two different midpoints (to account for < and <= in the different + // loops). + const int32_t mid2 = length / 2; + const int32_t mid1 = mid2 + (length % 2); + + uint32_t max_alpha = 0; + for (int32_t i = 0; i < mid1 && max_alpha != 0xff; i++) { + uint32_t alpha = get_alpha(image_line->GetColor(i)); + if (alpha > max_alpha) { + max_alpha = alpha; + *out_start = i; + } + } + + max_alpha = 0; + for (int32_t i = length - 1; i >= mid2 && max_alpha != 0xff; i--) { + uint32_t alpha = get_alpha(image_line->GetColor(i)); + if (alpha > max_alpha) { + max_alpha = alpha; + *out_end = length - (i + 1); + } + } + return; +} + +template <typename ImageLine> +static uint32_t FindMaxAlpha(const ImageLine* image_line) { + const int32_t length = image_line->GetLength(); + uint32_t max_alpha = 0; + for (int32_t idx = 0; idx < length && max_alpha != 0xff; idx++) { + uint32_t alpha = get_alpha(image_line->GetColor(idx)); + if (alpha > max_alpha) { + max_alpha = alpha; + } + } + return max_alpha; +} + +// Pack the pixels in as 0xAARRGGBB (as 9-patch expects it). +uint32_t NinePatch::PackRGBA(const uint8_t* pixel) { + return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2]; +} + +std::unique_ptr<NinePatch> NinePatch::Create(uint8_t** rows, + const int32_t width, + const int32_t height, + std::string* out_err) { + if (width < 3 || height < 3) { + *out_err = "image must be at least 3x3 (1x1 image with 1 pixel border)"; + return {}; + } + + std::vector<Range> horizontal_padding; + std::vector<Range> horizontal_layout_bounds; + std::vector<Range> vertical_padding; + std::vector<Range> vertical_layout_bounds; + std::vector<Range> unexpected_ranges; + std::unique_ptr<ColorValidator> color_validator; + + if (rows[0][3] == 0) { + color_validator = util::make_unique<TransparentNeutralColorValidator>(); + } else if (PackRGBA(rows[0]) == kColorOpaqueWhite) { + color_validator = util::make_unique<WhiteNeutralColorValidator>(); + } else { + *out_err = + "top-left corner pixel must be either opaque white or transparent"; + return {}; + } + + // Private constructor, can't use make_unique. + auto nine_patch = std::unique_ptr<NinePatch>(new NinePatch()); + + HorizontalImageLine top_row(rows, 0, 0, width); + if (!FillRanges(&top_row, color_validator.get(), + &nine_patch->horizontal_stretch_regions, &unexpected_ranges, + out_err)) { + return {}; + } + + if (!unexpected_ranges.empty()) { + const Range& range = unexpected_ranges[0]; + std::stringstream err_stream; + err_stream << "found unexpected optical bounds (red pixel) on top border " + << "at x=" << range.start + 1; + *out_err = err_stream.str(); + return {}; + } + + VerticalImageLine left_col(rows, 0, 0, height); + if (!FillRanges(&left_col, color_validator.get(), + &nine_patch->vertical_stretch_regions, &unexpected_ranges, + out_err)) { + return {}; + } + + if (!unexpected_ranges.empty()) { + const Range& range = unexpected_ranges[0]; + std::stringstream err_stream; + err_stream << "found unexpected optical bounds (red pixel) on left border " + << "at y=" << range.start + 1; + return {}; + } + + HorizontalImageLine bottom_row(rows, 0, height - 1, width); + if (!FillRanges(&bottom_row, color_validator.get(), &horizontal_padding, + &horizontal_layout_bounds, out_err)) { + return {}; + } + + if (!PopulateBounds(horizontal_padding, horizontal_layout_bounds, + nine_patch->horizontal_stretch_regions, width - 2, + &nine_patch->padding.left, &nine_patch->padding.right, + &nine_patch->layout_bounds.left, + &nine_patch->layout_bounds.right, "bottom", out_err)) { + return {}; + } + + VerticalImageLine right_col(rows, width - 1, 0, height); + if (!FillRanges(&right_col, color_validator.get(), &vertical_padding, + &vertical_layout_bounds, out_err)) { + return {}; + } + + if (!PopulateBounds(vertical_padding, vertical_layout_bounds, + nine_patch->vertical_stretch_regions, height - 2, + &nine_patch->padding.top, &nine_patch->padding.bottom, + &nine_patch->layout_bounds.top, + &nine_patch->layout_bounds.bottom, "right", out_err)) { + return {}; + } + + // Fill the region colors of the 9-patch. + const int32_t num_rows = + CalculateSegmentCount(nine_patch->horizontal_stretch_regions, width - 2); + const int32_t num_cols = + CalculateSegmentCount(nine_patch->vertical_stretch_regions, height - 2); + if ((int64_t)num_rows * (int64_t)num_cols > 0x7f) { + *out_err = "too many regions in 9-patch"; + return {}; + } + + nine_patch->region_colors.reserve(num_rows * num_cols); + CalculateRegionColors(rows, nine_patch->horizontal_stretch_regions, + nine_patch->vertical_stretch_regions, width - 2, + height - 2, &nine_patch->region_colors); + + // Compute the outline based on opacity. + + // Find left and right extent of 9-patch content on center row. + HorizontalImageLine mid_row(rows, 1, height / 2, width - 2); + FindOutlineInsets(&mid_row, &nine_patch->outline.left, + &nine_patch->outline.right); + + // Find top and bottom extent of 9-patch content on center column. + VerticalImageLine mid_col(rows, width / 2, 1, height - 2); + FindOutlineInsets(&mid_col, &nine_patch->outline.top, + &nine_patch->outline.bottom); + + const int32_t outline_width = + (width - 2) - nine_patch->outline.left - nine_patch->outline.right; + const int32_t outline_height = + (height - 2) - nine_patch->outline.top - nine_patch->outline.bottom; + + // Find the largest alpha value within the outline area. + HorizontalImageLine outline_mid_row( + rows, 1 + nine_patch->outline.left, + 1 + nine_patch->outline.top + (outline_height / 2), outline_width); + VerticalImageLine outline_mid_col( + rows, 1 + nine_patch->outline.left + (outline_width / 2), + 1 + nine_patch->outline.top, outline_height); + nine_patch->outline_alpha = + std::max(FindMaxAlpha(&outline_mid_row), FindMaxAlpha(&outline_mid_col)); + + // Assuming the image is a round rect, compute the radius by marching + // diagonally from the top left corner towards the center. + DiagonalImageLine diagonal(rows, 1 + nine_patch->outline.left, + 1 + nine_patch->outline.top, 1, 1, + std::min(outline_width, outline_height)); + int32_t top_left, bottom_right; + FindOutlineInsets(&diagonal, &top_left, &bottom_right); + + /* Determine source radius based upon inset: + * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r + * sqrt(2) * r = sqrt(2) * i + r + * (sqrt(2) - 1) * r = sqrt(2) * i + * r = sqrt(2) / (sqrt(2) - 1) * i + */ + nine_patch->outline_radius = 3.4142f * top_left; + return nine_patch; +} + +std::unique_ptr<uint8_t[]> NinePatch::SerializeBase(size_t* outLen) const { + android::Res_png_9patch data; + data.numXDivs = static_cast<uint8_t>(horizontal_stretch_regions.size()) * 2; + data.numYDivs = static_cast<uint8_t>(vertical_stretch_regions.size()) * 2; + data.numColors = static_cast<uint8_t>(region_colors.size()); + data.paddingLeft = padding.left; + data.paddingRight = padding.right; + data.paddingTop = padding.top; + data.paddingBottom = padding.bottom; + + auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]); + android::Res_png_9patch::serialize( + data, (const int32_t*)horizontal_stretch_regions.data(), + (const int32_t*)vertical_stretch_regions.data(), region_colors.data(), + buffer.get()); + // Convert to file endianness. + reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile(); + + *outLen = data.serializedSize(); + return buffer; +} + +std::unique_ptr<uint8_t[]> NinePatch::SerializeLayoutBounds( + size_t* out_len) const { + size_t chunk_len = sizeof(uint32_t) * 4; + auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]); + uint8_t* cursor = buffer.get(); + + memcpy(cursor, &layout_bounds.left, sizeof(layout_bounds.left)); + cursor += sizeof(layout_bounds.left); + + memcpy(cursor, &layout_bounds.top, sizeof(layout_bounds.top)); + cursor += sizeof(layout_bounds.top); + + memcpy(cursor, &layout_bounds.right, sizeof(layout_bounds.right)); + cursor += sizeof(layout_bounds.right); + + memcpy(cursor, &layout_bounds.bottom, sizeof(layout_bounds.bottom)); + cursor += sizeof(layout_bounds.bottom); + + *out_len = chunk_len; + return buffer; +} + +std::unique_ptr<uint8_t[]> NinePatch::SerializeRoundedRectOutline( + size_t* out_len) const { + size_t chunk_len = sizeof(uint32_t) * 6; + auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]); + uint8_t* cursor = buffer.get(); + + memcpy(cursor, &outline.left, sizeof(outline.left)); + cursor += sizeof(outline.left); + + memcpy(cursor, &outline.top, sizeof(outline.top)); + cursor += sizeof(outline.top); + + memcpy(cursor, &outline.right, sizeof(outline.right)); + cursor += sizeof(outline.right); + + memcpy(cursor, &outline.bottom, sizeof(outline.bottom)); + cursor += sizeof(outline.bottom); + + *((float*)cursor) = outline_radius; + cursor += sizeof(outline_radius); + + *((uint32_t*)cursor) = outline_alpha; + + *out_len = chunk_len; + return buffer; +} + +::std::ostream& operator<<(::std::ostream& out, const Range& range) { + return out << "[" << range.start << ", " << range.end << ")"; +} + +::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) { + return out << "l=" << bounds.left << " t=" << bounds.top + << " r=" << bounds.right << " b=" << bounds.bottom; +} + +::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch) { + return out << "horizontalStretch:" + << util::Joiner(nine_patch.horizontal_stretch_regions, " ") + << " verticalStretch:" + << util::Joiner(nine_patch.vertical_stretch_regions, " ") + << " padding: " << nine_patch.padding + << ", bounds: " << nine_patch.layout_bounds + << ", outline: " << nine_patch.outline + << " rad=" << nine_patch.outline_radius + << " alpha=" << nine_patch.outline_alpha; +} + +} // namespace aapt diff --git a/tools/aapt2/compile/NinePatch_test.cpp b/tools/aapt2/compile/NinePatch_test.cpp new file mode 100644 index 000000000000..f54bb2e62842 --- /dev/null +++ b/tools/aapt2/compile/NinePatch_test.cpp @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "compile/Image.h" + +#include "test/Test.h" + +namespace aapt { + +// Pixels are in RGBA_8888 packing. + +#define RED "\xff\x00\x00\xff" +#define BLUE "\x00\x00\xff\xff" +#define GREEN "\xff\x00\x00\xff" +#define GR_70 "\xff\x00\x00\xb3" +#define GR_50 "\xff\x00\x00\x80" +#define GR_20 "\xff\x00\x00\x33" +#define BLACK "\x00\x00\x00\xff" +#define WHITE "\xff\xff\xff\xff" +#define TRANS "\x00\x00\x00\x00" + +static uint8_t* k2x2[] = { + (uint8_t*)WHITE WHITE, (uint8_t*)WHITE WHITE, +}; + +static uint8_t* kMixedNeutralColor3x3[] = { + (uint8_t*)WHITE BLACK TRANS, (uint8_t*)TRANS RED TRANS, + (uint8_t*)WHITE WHITE WHITE, +}; + +static uint8_t* kTransparentNeutralColor3x3[] = { + (uint8_t*)TRANS BLACK TRANS, (uint8_t*)BLACK RED BLACK, + (uint8_t*)TRANS BLACK TRANS, +}; + +static uint8_t* kSingleStretch7x6[] = { + (uint8_t*)WHITE WHITE BLACK BLACK BLACK WHITE WHITE, + (uint8_t*)WHITE RED RED RED RED RED WHITE, + (uint8_t*)BLACK RED RED RED RED RED WHITE, + (uint8_t*)BLACK RED RED RED RED RED WHITE, + (uint8_t*)WHITE RED RED RED RED RED WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE, +}; + +static uint8_t* kMultipleStretch10x7[] = { + (uint8_t*)WHITE WHITE BLACK WHITE BLACK BLACK WHITE BLACK WHITE WHITE, + (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE, + (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE, + (uint8_t*)WHITE RED BLUE RED BLUE BLUE RED BLUE RED WHITE, + (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE, + (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE, +}; + +static uint8_t* kPadding6x5[] = { + (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE BLACK, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE BLACK BLACK WHITE WHITE, +}; + +static uint8_t* kLayoutBoundsWrongEdge3x3[] = { + (uint8_t*)WHITE RED WHITE, (uint8_t*)RED WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE, +}; + +static uint8_t* kLayoutBoundsNotEdgeAligned5x5[] = { + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE RED, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE RED WHITE WHITE, +}; + +static uint8_t* kLayoutBounds5x5[] = { + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE RED, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE RED, + (uint8_t*)WHITE RED WHITE RED WHITE, +}; + +static uint8_t* kAsymmetricLayoutBounds5x5[] = { + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE RED, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE RED WHITE WHITE WHITE, +}; + +static uint8_t* kPaddingAndLayoutBounds5x5[] = { + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE RED, + (uint8_t*)WHITE WHITE WHITE WHITE BLACK, + (uint8_t*)WHITE WHITE WHITE WHITE RED, + (uint8_t*)WHITE RED BLACK RED WHITE, +}; + +static uint8_t* kColorfulImage5x5[] = { + (uint8_t*)WHITE BLACK WHITE BLACK WHITE, + (uint8_t*)BLACK RED BLUE GREEN WHITE, + (uint8_t*)BLACK RED GREEN GREEN WHITE, + (uint8_t*)WHITE TRANS BLUE GREEN WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, +}; + +static uint8_t* kOutlineOpaque10x10[] = { + (uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE, + (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE, +}; + +static uint8_t* kOutlineTranslucent10x10[] = { + (uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE, + (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE, + (uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE, +}; + +static uint8_t* kOutlineOffsetTranslucent12x10[] = { + (uint8_t*) + WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*) + WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE, +}; + +static uint8_t* kOutlineRadius5x5[] = { + (uint8_t*)WHITE BLACK BLACK BLACK WHITE, + (uint8_t*)BLACK TRANS GREEN TRANS WHITE, + (uint8_t*)BLACK GREEN GREEN GREEN WHITE, + (uint8_t*)BLACK TRANS GREEN TRANS WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, +}; + +static uint8_t* kStretchAndPadding5x5[] = { + (uint8_t*)WHITE WHITE BLACK WHITE WHITE, (uint8_t*)WHITE RED RED RED WHITE, + (uint8_t*)BLACK RED RED RED BLACK, (uint8_t*)WHITE RED RED RED WHITE, + (uint8_t*)WHITE WHITE BLACK WHITE WHITE, +}; + +TEST(NinePatchTest, Minimum3x3) { + std::string err; + EXPECT_EQ(nullptr, NinePatch::Create(k2x2, 2, 2, &err)); + EXPECT_FALSE(err.empty()); +} + +TEST(NinePatchTest, MixedNeutralColors) { + std::string err; + EXPECT_EQ(nullptr, NinePatch::Create(kMixedNeutralColor3x3, 3, 3, &err)); + EXPECT_FALSE(err.empty()); +} + +TEST(NinePatchTest, TransparentNeutralColor) { + std::string err; + EXPECT_NE(nullptr, + NinePatch::Create(kTransparentNeutralColor3x3, 3, 3, &err)); +} + +TEST(NinePatchTest, SingleStretchRegion) { + std::string err; + std::unique_ptr<NinePatch> nine_patch = + NinePatch::Create(kSingleStretch7x6, 7, 6, &err); + ASSERT_NE(nullptr, nine_patch); + + ASSERT_EQ(1u, nine_patch->horizontal_stretch_regions.size()); + ASSERT_EQ(1u, nine_patch->vertical_stretch_regions.size()); + + EXPECT_EQ(Range(1, 4), nine_patch->horizontal_stretch_regions.front()); + EXPECT_EQ(Range(1, 3), nine_patch->vertical_stretch_regions.front()); +} + +TEST(NinePatchTest, MultipleStretchRegions) { + std::string err; + std::unique_ptr<NinePatch> nine_patch = + NinePatch::Create(kMultipleStretch10x7, 10, 7, &err); + ASSERT_NE(nullptr, nine_patch); + + ASSERT_EQ(3u, nine_patch->horizontal_stretch_regions.size()); + ASSERT_EQ(2u, nine_patch->vertical_stretch_regions.size()); + + EXPECT_EQ(Range(1, 2), nine_patch->horizontal_stretch_regions[0]); + EXPECT_EQ(Range(3, 5), nine_patch->horizontal_stretch_regions[1]); + EXPECT_EQ(Range(6, 7), nine_patch->horizontal_stretch_regions[2]); + + EXPECT_EQ(Range(0, 2), nine_patch->vertical_stretch_regions[0]); + EXPECT_EQ(Range(3, 5), nine_patch->vertical_stretch_regions[1]); +} + +TEST(NinePatchTest, InferPaddingFromStretchRegions) { + std::string err; + std::unique_ptr<NinePatch> nine_patch = + NinePatch::Create(kMultipleStretch10x7, 10, 7, &err); + ASSERT_NE(nullptr, nine_patch); + EXPECT_EQ(Bounds(1, 0, 1, 0), nine_patch->padding); +} + +TEST(NinePatchTest, Padding) { + std::string err; + std::unique_ptr<NinePatch> nine_patch = + NinePatch::Create(kPadding6x5, 6, 5, &err); + ASSERT_NE(nullptr, nine_patch); + EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding); +} + +TEST(NinePatchTest, LayoutBoundsAreOnWrongEdge) { + std::string err; + EXPECT_EQ(nullptr, NinePatch::Create(kLayoutBoundsWrongEdge3x3, 3, 3, &err)); + EXPECT_FALSE(err.empty()); +} + +TEST(NinePatchTest, LayoutBoundsMustTouchEdges) { + std::string err; + EXPECT_EQ(nullptr, + NinePatch::Create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err)); + EXPECT_FALSE(err.empty()); +} + +TEST(NinePatchTest, LayoutBounds) { + std::string err; + std::unique_ptr<NinePatch> nine_patch = + NinePatch::Create(kLayoutBounds5x5, 5, 5, &err); + ASSERT_NE(nullptr, nine_patch); + EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds); + + nine_patch = NinePatch::Create(kAsymmetricLayoutBounds5x5, 5, 5, &err); + ASSERT_NE(nullptr, nine_patch); + EXPECT_EQ(Bounds(1, 1, 0, 0), nine_patch->layout_bounds); +} + +TEST(NinePatchTest, PaddingAndLayoutBounds) { + std::string err; + std::unique_ptr<NinePatch> nine_patch = + NinePatch::Create(kPaddingAndLayoutBounds5x5, 5, 5, &err); + ASSERT_NE(nullptr, nine_patch); + EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding); + EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds); +} + +TEST(NinePatchTest, RegionColorsAreCorrect) { + std::string err; + std::unique_ptr<NinePatch> nine_patch = + NinePatch::Create(kColorfulImage5x5, 5, 5, &err); + ASSERT_NE(nullptr, nine_patch); + + std::vector<uint32_t> expected_colors = { + NinePatch::PackRGBA((uint8_t*)RED), + (uint32_t)android::Res_png_9patch::NO_COLOR, + NinePatch::PackRGBA((uint8_t*)GREEN), + (uint32_t)android::Res_png_9patch::TRANSPARENT_COLOR, + NinePatch::PackRGBA((uint8_t*)BLUE), + NinePatch::PackRGBA((uint8_t*)GREEN), + }; + EXPECT_EQ(expected_colors, nine_patch->region_colors); +} + +TEST(NinePatchTest, OutlineFromOpaqueImage) { + std::string err; + std::unique_ptr<NinePatch> nine_patch = + NinePatch::Create(kOutlineOpaque10x10, 10, 10, &err); + ASSERT_NE(nullptr, nine_patch); + EXPECT_EQ(Bounds(2, 2, 2, 2), nine_patch->outline); + EXPECT_EQ(0x000000ffu, nine_patch->outline_alpha); + EXPECT_EQ(0.0f, nine_patch->outline_radius); +} + +TEST(NinePatchTest, OutlineFromTranslucentImage) { + std::string err; + std::unique_ptr<NinePatch> nine_patch = + NinePatch::Create(kOutlineTranslucent10x10, 10, 10, &err); + ASSERT_NE(nullptr, nine_patch); + EXPECT_EQ(Bounds(3, 3, 3, 3), nine_patch->outline); + EXPECT_EQ(0x000000b3u, nine_patch->outline_alpha); + EXPECT_EQ(0.0f, nine_patch->outline_radius); +} + +TEST(NinePatchTest, OutlineFromOffCenterImage) { + std::string err; + std::unique_ptr<NinePatch> nine_patch = + NinePatch::Create(kOutlineOffsetTranslucent12x10, 12, 10, &err); + ASSERT_NE(nullptr, nine_patch); + + // TODO(adamlesinski): The old AAPT algorithm searches from the outside to the + // middle for each inset. If the outline is shifted, the search may not find a + // closer bounds. + // This check should be: + // EXPECT_EQ(Bounds(5, 3, 3, 3), ninePatch->outline); + // but until I know what behavior I'm breaking, I will leave it at the + // incorrect: + EXPECT_EQ(Bounds(4, 3, 3, 3), nine_patch->outline); + + EXPECT_EQ(0x000000b3u, nine_patch->outline_alpha); + EXPECT_EQ(0.0f, nine_patch->outline_radius); +} + +TEST(NinePatchTest, OutlineRadius) { + std::string err; + std::unique_ptr<NinePatch> nine_patch = + NinePatch::Create(kOutlineRadius5x5, 5, 5, &err); + ASSERT_NE(nullptr, nine_patch); + EXPECT_EQ(Bounds(0, 0, 0, 0), nine_patch->outline); + EXPECT_EQ(3.4142f, nine_patch->outline_radius); +} + +::testing::AssertionResult BigEndianOne(uint8_t* cursor) { + if (cursor[0] == 0 && cursor[1] == 0 && cursor[2] == 0 && cursor[3] == 1) { + return ::testing::AssertionSuccess(); + } + return ::testing::AssertionFailure() << "Not BigEndian 1"; +} + +TEST(NinePatchTest, SerializePngEndianness) { + std::string err; + std::unique_ptr<NinePatch> nine_patch = + NinePatch::Create(kStretchAndPadding5x5, 5, 5, &err); + ASSERT_NE(nullptr, nine_patch); + + size_t len; + std::unique_ptr<uint8_t[]> data = nine_patch->SerializeBase(&len); + ASSERT_NE(nullptr, data); + ASSERT_NE(0u, len); + + // Skip past wasDeserialized + numXDivs + numYDivs + numColors + xDivsOffset + + // yDivsOffset + // (12 bytes) + uint8_t* cursor = data.get() + 12; + + // Check that padding is big-endian. Expecting value 1. + EXPECT_TRUE(BigEndianOne(cursor)); + EXPECT_TRUE(BigEndianOne(cursor + 4)); + EXPECT_TRUE(BigEndianOne(cursor + 8)); + EXPECT_TRUE(BigEndianOne(cursor + 12)); +} + +} // namespace aapt diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp index bbf7f411d07e..7ab05b58b8b9 100644 --- a/tools/aapt2/compile/Png.cpp +++ b/tools/aapt2/compile/Png.cpp @@ -14,18 +14,18 @@ * limitations under the License. */ -#include "util/BigBuffer.h" #include "Png.h" #include "Source.h" +#include "util/BigBuffer.h" #include "util/Util.h" #include <androidfw/ResourceTypes.h> -#include <iostream> #include <png.h> +#include <zlib.h> +#include <iostream> #include <sstream> #include <string> #include <vector> -#include <zlib.h> namespace aapt { @@ -33,158 +33,166 @@ constexpr bool kDebug = false; constexpr size_t kPngSignatureSize = 8u; struct PngInfo { - ~PngInfo() { - for (png_bytep row : rows) { - if (row != nullptr) { - delete[] row; - } - } - - delete[] xDivs; - delete[] yDivs; - } - - void* serialize9Patch() { - void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs, yDivs, - colors.data()); - reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile(); - return serialized; - } - - uint32_t width = 0; - uint32_t height = 0; - std::vector<png_bytep> rows; - - bool is9Patch = false; - android::Res_png_9patch info9Patch; - int32_t* xDivs = nullptr; - int32_t* yDivs = nullptr; - std::vector<uint32_t> colors; - - // Layout padding. - bool haveLayoutBounds = false; - int32_t layoutBoundsLeft; - int32_t layoutBoundsTop; - int32_t layoutBoundsRight; - int32_t layoutBoundsBottom; - - // Round rect outline description. - int32_t outlineInsetsLeft; - int32_t outlineInsetsTop; - int32_t outlineInsetsRight; - int32_t outlineInsetsBottom; - float outlineRadius; - uint8_t outlineAlpha; + ~PngInfo() { + for (png_bytep row : rows) { + if (row != nullptr) { + delete[] row; + } + } + + delete[] xDivs; + delete[] yDivs; + } + + void* serialize9Patch() { + void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs, + yDivs, colors.data()); + reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile(); + return serialized; + } + + uint32_t width = 0; + uint32_t height = 0; + std::vector<png_bytep> rows; + + bool is9Patch = false; + android::Res_png_9patch info9Patch; + int32_t* xDivs = nullptr; + int32_t* yDivs = nullptr; + std::vector<uint32_t> colors; + + // Layout padding. + bool haveLayoutBounds = false; + int32_t layoutBoundsLeft; + int32_t layoutBoundsTop; + int32_t layoutBoundsRight; + int32_t layoutBoundsBottom; + + // Round rect outline description. + int32_t outlineInsetsLeft; + int32_t outlineInsetsTop; + int32_t outlineInsetsRight; + int32_t outlineInsetsBottom; + float outlineRadius; + uint8_t outlineAlpha; }; -static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t length) { - std::istream* input = reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr)); - if (!input->read(reinterpret_cast<char*>(data), length)) { - png_error(readPtr, strerror(errno)); - } +static void readDataFromStream(png_structp readPtr, png_bytep data, + png_size_t length) { + std::istream* input = + reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr)); + if (!input->read(reinterpret_cast<char*>(data), length)) { + png_error(readPtr, strerror(errno)); + } } -static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) { - BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr)); - png_bytep buf = outBuffer->nextBlock<png_byte>(length); - memcpy(buf, data, length); +static void writeDataToStream(png_structp writePtr, png_bytep data, + png_size_t length) { + BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr)); + png_bytep buf = outBuffer->NextBlock<png_byte>(length); + memcpy(buf, data, length); } -static void flushDataToStream(png_structp /*writePtr*/) { -} +static void flushDataToStream(png_structp /*writePtr*/) {} static void logWarning(png_structp readPtr, png_const_charp warningMessage) { - IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr)); - diag->warn(DiagMessage() << warningMessage); + IDiagnostics* diag = + reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr)); + diag->Warn(DiagMessage() << warningMessage); } - -static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) { - if (setjmp(png_jmpbuf(readPtr))) { - diag->error(DiagMessage() << "failed reading png"); - return false; - } - - png_set_sig_bytes(readPtr, kPngSignatureSize); - png_read_info(readPtr, infoPtr); - - int colorType, bitDepth, interlaceType, compressionType; - png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, &colorType, - &interlaceType, &compressionType, nullptr); - - if (colorType == PNG_COLOR_TYPE_PALETTE) { - png_set_palette_to_rgb(readPtr); - } - - if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) { - png_set_expand_gray_1_2_4_to_8(readPtr); - } - - if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) { - png_set_tRNS_to_alpha(readPtr); - } - - if (bitDepth == 16) { - png_set_strip_16(readPtr); - } - - if (!(colorType & PNG_COLOR_MASK_ALPHA)) { - png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER); - } - - if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { - png_set_gray_to_rgb(readPtr); - } - - png_set_interlace_handling(readPtr); - png_read_update_info(readPtr, infoPtr); - - const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr); - outInfo->rows.resize(outInfo->height); - for (size_t i = 0; i < outInfo->height; i++) { - outInfo->rows[i] = new png_byte[rowBytes]; - } - - png_read_image(readPtr, outInfo->rows.data()); - png_read_end(readPtr, infoPtr); - return true; +static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, + PngInfo* outInfo) { + if (setjmp(png_jmpbuf(readPtr))) { + diag->Error(DiagMessage() << "failed reading png"); + return false; + } + + png_set_sig_bytes(readPtr, kPngSignatureSize); + png_read_info(readPtr, infoPtr); + + int colorType, bitDepth, interlaceType, compressionType; + png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, + &colorType, &interlaceType, &compressionType, nullptr); + + if (colorType == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(readPtr); + } + + if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) { + png_set_expand_gray_1_2_4_to_8(readPtr); + } + + if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(readPtr); + } + + if (bitDepth == 16) { + png_set_strip_16(readPtr); + } + + if (!(colorType & PNG_COLOR_MASK_ALPHA)) { + png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER); + } + + if (colorType == PNG_COLOR_TYPE_GRAY || + colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(readPtr); + } + + png_set_interlace_handling(readPtr); + png_read_update_info(readPtr, infoPtr); + + const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr); + outInfo->rows.resize(outInfo->height); + for (size_t i = 0; i < outInfo->height; i++) { + outInfo->rows[i] = new png_byte[rowBytes]; + } + + png_read_image(readPtr, outInfo->rows.data()); + png_read_end(readPtr, infoPtr); + return true; } -static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, void* data) { - size_t patchSize = inPatch->serializedSize(); - void* newData = malloc(patchSize); - memcpy(newData, data, patchSize); - android::Res_png_9patch* outPatch = inPatch->deserialize(newData); - outPatch->fileToDevice(); - // deserialization is done in place, so outPatch == newData - assert(outPatch == newData); - assert(outPatch->numXDivs == inPatch->numXDivs); - assert(outPatch->numYDivs == inPatch->numYDivs); - assert(outPatch->paddingLeft == inPatch->paddingLeft); - assert(outPatch->paddingRight == inPatch->paddingRight); - assert(outPatch->paddingTop == inPatch->paddingTop); - assert(outPatch->paddingBottom == inPatch->paddingBottom); -/* for (int i = 0; i < outPatch->numXDivs; i++) { - assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]); - } - for (int i = 0; i < outPatch->numYDivs; i++) { - assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]); - } - for (int i = 0; i < outPatch->numColors; i++) { - assert(outPatch->getColors()[i] == inPatch->getColors()[i]); - }*/ - free(newData); +static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, + void* data) { + size_t patchSize = inPatch->serializedSize(); + void* newData = malloc(patchSize); + memcpy(newData, data, patchSize); + android::Res_png_9patch* outPatch = inPatch->deserialize(newData); + outPatch->fileToDevice(); + // deserialization is done in place, so outPatch == newData + assert(outPatch == newData); + assert(outPatch->numXDivs == inPatch->numXDivs); + assert(outPatch->numYDivs == inPatch->numYDivs); + assert(outPatch->paddingLeft == inPatch->paddingLeft); + assert(outPatch->paddingRight == inPatch->paddingRight); + assert(outPatch->paddingTop == inPatch->paddingTop); + assert(outPatch->paddingBottom == inPatch->paddingBottom); + /* for (int i = 0; i < outPatch->numXDivs; i++) { + assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]); + } + for (int i = 0; i < outPatch->numYDivs; i++) { + assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]); + } + for (int i = 0; i < outPatch->numColors; i++) { + assert(outPatch->getColors()[i] == inPatch->getColors()[i]); + }*/ + free(newData); } -/*static void dump_image(int w, int h, const png_byte* const* rows, int color_type) { +/*static void dump_image(int w, int h, const png_byte* const* rows, int +color_type) { int i, j, rr, gg, bb, aa; int bpp; - if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) { + if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == +PNG_COLOR_TYPE_GRAY) { bpp = 1; } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { bpp = 2; - } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) { + } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == +PNG_COLOR_TYPE_RGB_ALPHA) { // We use a padding byte even when there is no alpha bpp = 4; } else { @@ -224,1055 +232,1090 @@ static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, void* } }*/ -#define MAX(a,b) ((a)>(b)?(a):(b)) -#define ABS(a) ((a)<0?-(a):(a)) - -static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance, - png_colorp rgbPalette, png_bytep alphaPalette, - int *paletteEntries, bool *hasTransparency, int *colorType, - png_bytepp outRows) { - int w = imageInfo.width; - int h = imageInfo.height; - int i, j, rr, gg, bb, aa, idx; - uint32_t colors[256], col; - int num_colors = 0; - int maxGrayDeviation = 0; - - bool isOpaque = true; - bool isPalette = true; - bool isGrayscale = true; - - // Scan the entire image and determine if: - // 1. Every pixel has R == G == B (grayscale) - // 2. Every pixel has A == 255 (opaque) - // 3. There are no more than 256 distinct RGBA colors - - if (kDebug) { - printf("Initial image data:\n"); - //dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA); - } - - for (j = 0; j < h; j++) { - const png_byte* row = imageInfo.rows[j]; - png_bytep out = outRows[j]; - for (i = 0; i < w; i++) { - rr = *row++; - gg = *row++; - bb = *row++; - aa = *row++; - - int odev = maxGrayDeviation; - maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation); - maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation); - maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation); - if (maxGrayDeviation > odev) { - if (kDebug) { - printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n", - maxGrayDeviation, i, j, rr, gg, bb, aa); - } - } - - // Check if image is really grayscale - if (isGrayscale) { - if (rr != gg || rr != bb) { - if (kDebug) { - printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", - i, j, rr, gg, bb, aa); - } - isGrayscale = false; - } - } - - // Check if image is really opaque - if (isOpaque) { - if (aa != 0xff) { - if (kDebug) { - printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", - i, j, rr, gg, bb, aa); - } - isOpaque = false; - } - } - - // Check if image is really <= 256 colors - if (isPalette) { - col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa); - bool match = false; - for (idx = 0; idx < num_colors; idx++) { - if (colors[idx] == col) { - match = true; - break; - } - } - - // Write the palette index for the pixel to outRows optimistically - // We might overwrite it later if we decide to encode as gray or - // gray + alpha - *out++ = idx; - if (!match) { - if (num_colors == 256) { - if (kDebug) { - printf("Found 257th color at %d, %d\n", i, j); - } - isPalette = false; - } else { - colors[num_colors++] = col; - } - } - } - } - } - - *paletteEntries = 0; - *hasTransparency = !isOpaque; - int bpp = isOpaque ? 3 : 4; - int paletteSize = w * h + bpp * num_colors; - - if (kDebug) { - printf("isGrayscale = %s\n", isGrayscale ? "true" : "false"); - printf("isOpaque = %s\n", isOpaque ? "true" : "false"); - printf("isPalette = %s\n", isPalette ? "true" : "false"); - printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", - paletteSize, 2 * w * h, bpp * w * h); - printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance); - } - - // Choose the best color type for the image. - // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel - // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations - // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA - // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently - // small, otherwise use COLOR_TYPE_RGB{_ALPHA} - if (isGrayscale) { - if (isOpaque) { - *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel - } else { - // Use a simple heuristic to determine whether using a palette will - // save space versus using gray + alpha for each pixel. - // This doesn't take into account chunk overhead, filtering, LZ - // compression, etc. - if (isPalette && (paletteSize < 2 * w * h)) { - *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color - } else { - *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel - } - } - } else if (isPalette && (paletteSize < bpp * w * h)) { - *colorType = PNG_COLOR_TYPE_PALETTE; - } else { - if (maxGrayDeviation <= grayscaleTolerance) { - diag->note(DiagMessage() - << "forcing image to gray (max deviation = " - << maxGrayDeviation << ")"); - *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA; - } else { - *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; - } - } - - // Perform postprocessing of the image or palette data based on the final - // color type chosen +#ifdef MAX +#undef MAX +#endif +#ifdef ABS +#undef ABS +#endif - if (*colorType == PNG_COLOR_TYPE_PALETTE) { - // Create separate RGB and Alpha palettes and set the number of colors - *paletteEntries = num_colors; +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define ABS(a) ((a) < 0 ? -(a) : (a)) - // Create the RGB and alpha palettes - for (int idx = 0; idx < num_colors; idx++) { - col = colors[idx]; - rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff); - rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff); - rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff); - alphaPalette[idx] = (png_byte) (col & 0xff); +static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, + int grayscaleTolerance, png_colorp rgbPalette, + png_bytep alphaPalette, int* paletteEntries, + bool* hasTransparency, int* colorType, + png_bytepp outRows) { + int w = imageInfo.width; + int h = imageInfo.height; + int i, j, rr, gg, bb, aa, idx; + uint32_t colors[256], col; + int num_colors = 0; + int maxGrayDeviation = 0; + + bool isOpaque = true; + bool isPalette = true; + bool isGrayscale = true; + + // Scan the entire image and determine if: + // 1. Every pixel has R == G == B (grayscale) + // 2. Every pixel has A == 255 (opaque) + // 3. There are no more than 256 distinct RGBA colors + + if (kDebug) { + printf("Initial image data:\n"); + // dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA); + } + + for (j = 0; j < h; j++) { + const png_byte* row = imageInfo.rows[j]; + png_bytep out = outRows[j]; + for (i = 0; i < w; i++) { + rr = *row++; + gg = *row++; + bb = *row++; + aa = *row++; + + int odev = maxGrayDeviation; + maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation); + maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation); + maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation); + if (maxGrayDeviation > odev) { + if (kDebug) { + printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n", + maxGrayDeviation, i, j, rr, gg, bb, aa); } - } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { - // If the image is gray or gray + alpha, compact the pixels into outRows - for (j = 0; j < h; j++) { - const png_byte* row = imageInfo.rows[j]; - png_bytep out = outRows[j]; - for (i = 0; i < w; i++) { - rr = *row++; - gg = *row++; - bb = *row++; - aa = *row++; - - if (isGrayscale) { - *out++ = rr; - } else { - *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f); - } - if (!isOpaque) { - *out++ = aa; - } - } + } + + // Check if image is really grayscale + if (isGrayscale) { + if (rr != gg || rr != bb) { + if (kDebug) { + printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", i, j, + rr, gg, bb, aa); + } + isGrayscale = false; } - } -} - -static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info, - int grayScaleTolerance) { - if (setjmp(png_jmpbuf(writePtr))) { - diag->error(DiagMessage() << "failed to write png"); - return false; - } - - uint32_t width, height; - int colorType, bitDepth, interlaceType, compressionType; - - png_unknown_chunk unknowns[3]; - unknowns[0].data = nullptr; - unknowns[1].data = nullptr; - unknowns[2].data = nullptr; - - png_bytepp outRows = (png_bytepp) malloc((int) info->height * sizeof(png_bytep)); - if (outRows == (png_bytepp) 0) { - printf("Can't allocate output buffer!\n"); - exit(1); - } - for (uint32_t i = 0; i < info->height; i++) { - outRows[i] = (png_bytep) malloc(2 * (int) info->width); - if (outRows[i] == (png_bytep) 0) { - printf("Can't allocate output buffer!\n"); - exit(1); + } + + // Check if image is really opaque + if (isOpaque) { + if (aa != 0xff) { + if (kDebug) { + printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", i, j, + rr, gg, bb, aa); + } + isOpaque = false; } - } - - png_set_compression_level(writePtr, Z_BEST_COMPRESSION); - - if (kDebug) { - diag->note(DiagMessage() - << "writing image: w = " << info->width - << ", h = " << info->height); - } - - png_color rgbPalette[256]; - png_byte alphaPalette[256]; - bool hasTransparency; - int paletteEntries; - - analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette, - &paletteEntries, &hasTransparency, &colorType, outRows); - - // If the image is a 9-patch, we need to preserve it as a ARGB file to make - // sure the pixels will not be pre-dithered/clamped until we decide they are - if (info->is9Patch && (colorType == PNG_COLOR_TYPE_RGB || - colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_PALETTE)) { - colorType = PNG_COLOR_TYPE_RGB_ALPHA; - } - - if (kDebug) { - switch (colorType) { - case PNG_COLOR_TYPE_PALETTE: - diag->note(DiagMessage() - << "has " << paletteEntries - << " colors" << (hasTransparency ? " (with alpha)" : "") - << ", using PNG_COLOR_TYPE_PALLETTE"); - break; - case PNG_COLOR_TYPE_GRAY: - diag->note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY"); - break; - case PNG_COLOR_TYPE_GRAY_ALPHA: - diag->note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA"); - break; - case PNG_COLOR_TYPE_RGB: - diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB"); - break; - case PNG_COLOR_TYPE_RGB_ALPHA: - diag->note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA"); + } + + // Check if image is really <= 256 colors + if (isPalette) { + col = (uint32_t)((rr << 24) | (gg << 16) | (bb << 8) | aa); + bool match = false; + for (idx = 0; idx < num_colors; idx++) { + if (colors[idx] == col) { + match = true; break; + } } - } - - png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType, - PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - if (colorType == PNG_COLOR_TYPE_PALETTE) { - png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries); - if (hasTransparency) { - png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, (png_color_16p) 0); + // Write the palette index for the pixel to outRows optimistically + // We might overwrite it later if we decide to encode as gray or + // gray + alpha + *out++ = idx; + if (!match) { + if (num_colors == 256) { + if (kDebug) { + printf("Found 257th color at %d, %d\n", i, j); + } + isPalette = false; + } else { + colors[num_colors++] = col; + } } - png_set_filter(writePtr, 0, PNG_NO_FILTERS); + } + } + } + + *paletteEntries = 0; + *hasTransparency = !isOpaque; + int bpp = isOpaque ? 3 : 4; + int paletteSize = w * h + bpp * num_colors; + + if (kDebug) { + printf("isGrayscale = %s\n", isGrayscale ? "true" : "false"); + printf("isOpaque = %s\n", isOpaque ? "true" : "false"); + printf("isPalette = %s\n", isPalette ? "true" : "false"); + printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", paletteSize, + 2 * w * h, bpp * w * h); + printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, + grayscaleTolerance); + } + + // Choose the best color type for the image. + // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel + // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct + // combinations + // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA + // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is + // sufficiently + // small, otherwise use COLOR_TYPE_RGB{_ALPHA} + if (isGrayscale) { + if (isOpaque) { + *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel } else { - png_set_filter(writePtr, 0, PNG_ALL_FILTERS); + // Use a simple heuristic to determine whether using a palette will + // save space versus using gray + alpha for each pixel. + // This doesn't take into account chunk overhead, filtering, LZ + // compression, etc. + if (isPalette && (paletteSize < 2 * w * h)) { + *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color + } else { + *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel + } + } + } else if (isPalette && (paletteSize < bpp * w * h)) { + *colorType = PNG_COLOR_TYPE_PALETTE; + } else { + if (maxGrayDeviation <= grayscaleTolerance) { + diag->Note(DiagMessage() << "forcing image to gray (max deviation = " + << maxGrayDeviation << ")"); + *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA; + } else { + *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; } + } - if (info->is9Patch) { - int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0); - int pIndex = info->haveLayoutBounds ? 2 : 1; - int bIndex = 1; - int oIndex = 0; - - // Chunks ordered thusly because older platforms depend on the base 9 patch data being last - png_bytep chunkNames = info->haveLayoutBounds - ? (png_bytep)"npOl\0npLb\0npTc\0" - : (png_bytep)"npOl\0npTc"; + // Perform postprocessing of the image or palette data based on the final + // color type chosen - // base 9 patch data - if (kDebug) { - diag->note(DiagMessage() << "adding 9-patch info.."); - } - strcpy((char*)unknowns[pIndex].name, "npTc"); - unknowns[pIndex].data = (png_byte*) info->serialize9Patch(); - unknowns[pIndex].size = info->info9Patch.serializedSize(); - // TODO: remove the check below when everything works - checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data); - - // automatically generated 9 patch outline data - int chunkSize = sizeof(png_uint_32) * 6; - strcpy((char*)unknowns[oIndex].name, "npOl"); - unknowns[oIndex].data = (png_byte*) calloc(chunkSize, 1); - png_byte outputData[chunkSize]; - memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32)); - ((float*) outputData)[4] = info->outlineRadius; - ((png_uint_32*) outputData)[5] = info->outlineAlpha; - memcpy(unknowns[oIndex].data, &outputData, chunkSize); - unknowns[oIndex].size = chunkSize; - - // optional optical inset / layout bounds data - if (info->haveLayoutBounds) { - int chunkSize = sizeof(png_uint_32) * 4; - strcpy((char*)unknowns[bIndex].name, "npLb"); - unknowns[bIndex].data = (png_byte*) calloc(chunkSize, 1); - memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize); - unknowns[bIndex].size = chunkSize; - } + if (*colorType == PNG_COLOR_TYPE_PALETTE) { + // Create separate RGB and Alpha palettes and set the number of colors + *paletteEntries = num_colors; - for (int i = 0; i < chunkCount; i++) { - unknowns[i].location = PNG_HAVE_PLTE; - } - png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, - chunkNames, chunkCount); - png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount); - -#if PNG_LIBPNG_VER < 10600 - // Deal with unknown chunk location bug in 1.5.x and earlier. - png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE); - if (info->haveLayoutBounds) { - png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE); - } -#endif + // Create the RGB and alpha palettes + for (int idx = 0; idx < num_colors; idx++) { + col = colors[idx]; + rgbPalette[idx].red = (png_byte)((col >> 24) & 0xff); + rgbPalette[idx].green = (png_byte)((col >> 16) & 0xff); + rgbPalette[idx].blue = (png_byte)((col >> 8) & 0xff); + alphaPalette[idx] = (png_byte)(col & 0xff); } - - png_write_info(writePtr, infoPtr); - - png_bytepp rows; - if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) { - if (colorType == PNG_COLOR_TYPE_RGB) { - png_set_filler(writePtr, 0, PNG_FILLER_AFTER); + } else if (*colorType == PNG_COLOR_TYPE_GRAY || + *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { + // If the image is gray or gray + alpha, compact the pixels into outRows + for (j = 0; j < h; j++) { + const png_byte* row = imageInfo.rows[j]; + png_bytep out = outRows[j]; + for (i = 0; i < w; i++) { + rr = *row++; + gg = *row++; + bb = *row++; + aa = *row++; + + if (isGrayscale) { + *out++ = rr; + } else { + *out++ = (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f); } - rows = info->rows.data(); - } else { - rows = outRows; + if (!isOpaque) { + *out++ = aa; + } + } } - png_write_image(writePtr, rows); + } +} +static bool writePng(IDiagnostics* diag, png_structp writePtr, + png_infop infoPtr, PngInfo* info, int grayScaleTolerance) { + if (setjmp(png_jmpbuf(writePtr))) { + diag->Error(DiagMessage() << "failed to write png"); + return false; + } + + uint32_t width, height; + int colorType, bitDepth, interlaceType, compressionType; + + png_unknown_chunk unknowns[3]; + unknowns[0].data = nullptr; + unknowns[1].data = nullptr; + unknowns[2].data = nullptr; + + png_bytepp outRows = + (png_bytepp)malloc((int)info->height * sizeof(png_bytep)); + if (outRows == (png_bytepp)0) { + printf("Can't allocate output buffer!\n"); + exit(1); + } + for (uint32_t i = 0; i < info->height; i++) { + outRows[i] = (png_bytep)malloc(2 * (int)info->width); + if (outRows[i] == (png_bytep)0) { + printf("Can't allocate output buffer!\n"); + exit(1); + } + } + + png_set_compression_level(writePtr, Z_BEST_COMPRESSION); + + if (kDebug) { + diag->Note(DiagMessage() << "writing image: w = " << info->width + << ", h = " << info->height); + } + + png_color rgbPalette[256]; + png_byte alphaPalette[256]; + bool hasTransparency; + int paletteEntries; + + analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette, + &paletteEntries, &hasTransparency, &colorType, outRows); + + // If the image is a 9-patch, we need to preserve it as a ARGB file to make + // sure the pixels will not be pre-dithered/clamped until we decide they are + if (info->is9Patch && + (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY || + colorType == PNG_COLOR_TYPE_PALETTE)) { + colorType = PNG_COLOR_TYPE_RGB_ALPHA; + } + + if (kDebug) { + switch (colorType) { + case PNG_COLOR_TYPE_PALETTE: + diag->Note(DiagMessage() << "has " << paletteEntries << " colors" + << (hasTransparency ? " (with alpha)" : "") + << ", using PNG_COLOR_TYPE_PALLETTE"); + break; + case PNG_COLOR_TYPE_GRAY: + diag->Note(DiagMessage() + << "is opaque gray, using PNG_COLOR_TYPE_GRAY"); + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + diag->Note(DiagMessage() + << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA"); + break; + case PNG_COLOR_TYPE_RGB: + diag->Note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB"); + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + diag->Note(DiagMessage() + << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA"); + break; + } + } + + png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + if (colorType == PNG_COLOR_TYPE_PALETTE) { + png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries); + if (hasTransparency) { + png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, + (png_color_16p)0); + } + png_set_filter(writePtr, 0, PNG_NO_FILTERS); + } else { + png_set_filter(writePtr, 0, PNG_ALL_FILTERS); + } + + if (info->is9Patch) { + int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0); + int pIndex = info->haveLayoutBounds ? 2 : 1; + int bIndex = 1; + int oIndex = 0; + + // Chunks ordered thusly because older platforms depend on the base 9 patch + // data being last + png_bytep chunkNames = info->haveLayoutBounds + ? (png_bytep) "npOl\0npLb\0npTc\0" + : (png_bytep) "npOl\0npTc"; + + // base 9 patch data if (kDebug) { - printf("Final image data:\n"); - //dump_image(info->width, info->height, rows, colorType); - } - - png_write_end(writePtr, infoPtr); - - for (uint32_t i = 0; i < info->height; i++) { - free(outRows[i]); - } - free(outRows); - free(unknowns[0].data); - free(unknowns[1].data); - free(unknowns[2].data); + diag->Note(DiagMessage() << "adding 9-patch info.."); + } + strcpy((char*)unknowns[pIndex].name, "npTc"); + unknowns[pIndex].data = (png_byte*)info->serialize9Patch(); + unknowns[pIndex].size = info->info9Patch.serializedSize(); + // TODO: remove the check below when everything works + checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data); + + // automatically generated 9 patch outline data + int chunkSize = sizeof(png_uint_32) * 6; + strcpy((char*)unknowns[oIndex].name, "npOl"); + unknowns[oIndex].data = (png_byte*)calloc(chunkSize, 1); + png_byte outputData[chunkSize]; + memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32)); + ((float*)outputData)[4] = info->outlineRadius; + ((png_uint_32*)outputData)[5] = info->outlineAlpha; + memcpy(unknowns[oIndex].data, &outputData, chunkSize); + unknowns[oIndex].size = chunkSize; + + // optional optical inset / layout bounds data + if (info->haveLayoutBounds) { + int chunkSize = sizeof(png_uint_32) * 4; + strcpy((char*)unknowns[bIndex].name, "npLb"); + unknowns[bIndex].data = (png_byte*)calloc(chunkSize, 1); + memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize); + unknowns[bIndex].size = chunkSize; + } + + for (int i = 0; i < chunkCount; i++) { + unknowns[i].location = PNG_HAVE_PLTE; + } + png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, chunkNames, + chunkCount); + png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount); - png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceType, - &compressionType, nullptr); - - if (kDebug) { - diag->note(DiagMessage() - << "image written: w = " << width << ", h = " << height - << ", d = " << bitDepth << ", colors = " << colorType - << ", inter = " << interlaceType << ", comp = " << compressionType); +#if PNG_LIBPNG_VER < 10600 + // Deal with unknown chunk location bug in 1.5.x and earlier. + png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE); + if (info->haveLayoutBounds) { + png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE); } - return true; +#endif + } + + png_write_info(writePtr, infoPtr); + + png_bytepp rows; + if (colorType == PNG_COLOR_TYPE_RGB || + colorType == PNG_COLOR_TYPE_RGB_ALPHA) { + if (colorType == PNG_COLOR_TYPE_RGB) { + png_set_filler(writePtr, 0, PNG_FILLER_AFTER); + } + rows = info->rows.data(); + } else { + rows = outRows; + } + png_write_image(writePtr, rows); + + if (kDebug) { + printf("Final image data:\n"); + // dump_image(info->width, info->height, rows, colorType); + } + + png_write_end(writePtr, infoPtr); + + for (uint32_t i = 0; i < info->height; i++) { + free(outRows[i]); + } + free(outRows); + free(unknowns[0].data); + free(unknowns[1].data); + free(unknowns[2].data); + + png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, + &interlaceType, &compressionType, nullptr); + + if (kDebug) { + diag->Note(DiagMessage() << "image written: w = " << width + << ", h = " << height << ", d = " << bitDepth + << ", colors = " << colorType + << ", inter = " << interlaceType + << ", comp = " << compressionType); + } + return true; } constexpr uint32_t kColorWhite = 0xffffffffu; constexpr uint32_t kColorTick = 0xff000000u; constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu; -enum class TickType { - kNone, - kTick, - kLayoutBounds, - kBoth -}; +enum class TickType { kNone, kTick, kLayoutBounds, kBoth }; static TickType tickType(png_bytep p, bool transparent, const char** outError) { - png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); - - if (transparent) { - if (p[3] == 0) { - return TickType::kNone; - } - if (color == kColorLayoutBoundsTick) { - return TickType::kLayoutBounds; - } - if (color == kColorTick) { - return TickType::kTick; - } - - // Error cases - if (p[3] != 0xff) { - *outError = "Frame pixels must be either solid or transparent " - "(not intermediate alphas)"; - return TickType::kNone; - } + png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); - if (p[0] != 0 || p[1] != 0 || p[2] != 0) { - *outError = "Ticks in transparent frame must be black or red"; - } - return TickType::kTick; + if (transparent) { + if (p[3] == 0) { + return TickType::kNone; } - - if (p[3] != 0xFF) { - *outError = "White frame must be a solid color (no alpha)"; - } - if (color == kColorWhite) { - return TickType::kNone; + if (color == kColorLayoutBoundsTick) { + return TickType::kLayoutBounds; } if (color == kColorTick) { - return TickType::kTick; + return TickType::kTick; } - if (color == kColorLayoutBoundsTick) { - return TickType::kLayoutBounds; + + // Error cases + if (p[3] != 0xff) { + *outError = + "Frame pixels must be either solid or transparent " + "(not intermediate alphas)"; + return TickType::kNone; } if (p[0] != 0 || p[1] != 0 || p[2] != 0) { - *outError = "Ticks in white frame must be black or red"; - return TickType::kNone; + *outError = "Ticks in transparent frame must be black or red"; } return TickType::kTick; + } + + if (p[3] != 0xFF) { + *outError = "White frame must be a solid color (no alpha)"; + } + if (color == kColorWhite) { + return TickType::kNone; + } + if (color == kColorTick) { + return TickType::kTick; + } + if (color == kColorLayoutBoundsTick) { + return TickType::kLayoutBounds; + } + + if (p[0] != 0 || p[1] != 0 || p[2] != 0) { + *outError = "Ticks in white frame must be black or red"; + return TickType::kNone; + } + return TickType::kTick; } -enum class TickState { - kStart, - kInside1, - kOutside1 -}; +enum class TickState { kStart, kInside1, kOutside1 }; -static bool getHorizontalTicks(png_bytep row, int width, bool transparent, bool required, - int32_t* outLeft, int32_t* outRight, const char** outError, +static bool getHorizontalTicks(png_bytep row, int width, bool transparent, + bool required, int32_t* outLeft, + int32_t* outRight, const char** outError, uint8_t* outDivs, bool multipleAllowed) { - *outLeft = *outRight = -1; - TickState state = TickState::kStart; - bool found = false; - - for (int i = 1; i < width - 1; i++) { - if (tickType(row+i*4, transparent, outError) == TickType::kTick) { - if (state == TickState::kStart || - (state == TickState::kOutside1 && multipleAllowed)) { - *outLeft = i-1; - *outRight = width-2; - found = true; - if (outDivs != NULL) { - *outDivs += 2; - } - state = TickState::kInside1; - } else if (state == TickState::kOutside1) { - *outError = "Can't have more than one marked region along edge"; - *outLeft = i; - return false; - } - } else if (!*outError) { - if (state == TickState::kInside1) { - // We're done with this div. Move on to the next. - *outRight = i-1; - outRight += 2; - outLeft += 2; - state = TickState::kOutside1; - } - } else { - *outLeft = i; - return false; + *outLeft = *outRight = -1; + TickState state = TickState::kStart; + bool found = false; + + for (int i = 1; i < width - 1; i++) { + if (tickType(row + i * 4, transparent, outError) == TickType::kTick) { + if (state == TickState::kStart || + (state == TickState::kOutside1 && multipleAllowed)) { + *outLeft = i - 1; + *outRight = width - 2; + found = true; + if (outDivs != NULL) { + *outDivs += 2; } - } - - if (required && !found) { - *outError = "No marked region found along edge"; - *outLeft = -1; + state = TickState::kInside1; + } else if (state == TickState::kOutside1) { + *outError = "Can't have more than one marked region along edge"; + *outLeft = i; return false; + } + } else if (!*outError) { + if (state == TickState::kInside1) { + // We're done with this div. Move on to the next. + *outRight = i - 1; + outRight += 2; + outLeft += 2; + state = TickState::kOutside1; + } + } else { + *outLeft = i; + return false; } - return true; + } + + if (required && !found) { + *outError = "No marked region found along edge"; + *outLeft = -1; + return false; + } + return true; } -static bool getVerticalTicks(png_bytepp rows, int offset, int height, bool transparent, - bool required, int32_t* outTop, int32_t* outBottom, - const char** outError, uint8_t* outDivs, bool multipleAllowed) { - *outTop = *outBottom = -1; - TickState state = TickState::kStart; - bool found = false; - - for (int i = 1; i < height - 1; i++) { - if (tickType(rows[i]+offset, transparent, outError) == TickType::kTick) { - if (state == TickState::kStart || - (state == TickState::kOutside1 && multipleAllowed)) { - *outTop = i-1; - *outBottom = height-2; - found = true; - if (outDivs != NULL) { - *outDivs += 2; - } - state = TickState::kInside1; - } else if (state == TickState::kOutside1) { - *outError = "Can't have more than one marked region along edge"; - *outTop = i; - return false; - } - } else if (!*outError) { - if (state == TickState::kInside1) { - // We're done with this div. Move on to the next. - *outBottom = i-1; - outTop += 2; - outBottom += 2; - state = TickState::kOutside1; - } - } else { - *outTop = i; - return false; +static bool getVerticalTicks(png_bytepp rows, int offset, int height, + bool transparent, bool required, int32_t* outTop, + int32_t* outBottom, const char** outError, + uint8_t* outDivs, bool multipleAllowed) { + *outTop = *outBottom = -1; + TickState state = TickState::kStart; + bool found = false; + + for (int i = 1; i < height - 1; i++) { + if (tickType(rows[i] + offset, transparent, outError) == TickType::kTick) { + if (state == TickState::kStart || + (state == TickState::kOutside1 && multipleAllowed)) { + *outTop = i - 1; + *outBottom = height - 2; + found = true; + if (outDivs != NULL) { + *outDivs += 2; } - } - - if (required && !found) { - *outError = "No marked region found along edge"; - *outTop = -1; + state = TickState::kInside1; + } else if (state == TickState::kOutside1) { + *outError = "Can't have more than one marked region along edge"; + *outTop = i; return false; + } + } else if (!*outError) { + if (state == TickState::kInside1) { + // We're done with this div. Move on to the next. + *outBottom = i - 1; + outTop += 2; + outBottom += 2; + state = TickState::kOutside1; + } + } else { + *outTop = i; + return false; } - return true; -} + } -static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, bool transparent, - bool /* required */, int32_t* outLeft, - int32_t* outRight, const char** outError) { - *outLeft = *outRight = 0; - - // Look for left tick - if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) { - // Starting with a layout padding tick - int i = 1; - while (i < width - 1) { - (*outLeft)++; - i++; - if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) { - break; - } - } - } + if (required && !found) { + *outError = "No marked region found along edge"; + *outTop = -1; + return false; + } + return true; +} - // Look for right tick - if (tickType(row + (width - 2) * 4, transparent, outError) == TickType::kLayoutBounds) { - // Ending with a layout padding tick - int i = width - 2; - while (i > 1) { - (*outRight)++; - i--; - if (tickType(row+i*4, transparent, outError) != TickType::kLayoutBounds) { - break; - } - } - } - return true; +static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, + bool transparent, + bool /* required */, + int32_t* outLeft, int32_t* outRight, + const char** outError) { + *outLeft = *outRight = 0; + + // Look for left tick + if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) { + // Starting with a layout padding tick + int i = 1; + while (i < width - 1) { + (*outLeft)++; + i++; + if (tickType(row + i * 4, transparent, outError) != + TickType::kLayoutBounds) { + break; + } + } + } + + // Look for right tick + if (tickType(row + (width - 2) * 4, transparent, outError) == + TickType::kLayoutBounds) { + // Ending with a layout padding tick + int i = width - 2; + while (i > 1) { + (*outRight)++; + i--; + if (tickType(row + i * 4, transparent, outError) != + TickType::kLayoutBounds) { + break; + } + } + } + return true; } -static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, int height, bool transparent, - bool /* required */, int32_t* outTop, int32_t* outBottom, +static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, + int height, bool transparent, + bool /* required */, int32_t* outTop, + int32_t* outBottom, const char** outError) { - *outTop = *outBottom = 0; - - // Look for top tick - if (tickType(rows[1] + offset, transparent, outError) == TickType::kLayoutBounds) { - // Starting with a layout padding tick - int i = 1; - while (i < height - 1) { - (*outTop)++; - i++; - if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) { - break; - } - } - } - - // Look for bottom tick - if (tickType(rows[height - 2] + offset, transparent, outError) == TickType::kLayoutBounds) { - // Ending with a layout padding tick - int i = height - 2; - while (i > 1) { - (*outBottom)++; - i--; - if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) { - break; - } - } - } - return true; + *outTop = *outBottom = 0; + + // Look for top tick + if (tickType(rows[1] + offset, transparent, outError) == + TickType::kLayoutBounds) { + // Starting with a layout padding tick + int i = 1; + while (i < height - 1) { + (*outTop)++; + i++; + if (tickType(rows[i] + offset, transparent, outError) != + TickType::kLayoutBounds) { + break; + } + } + } + + // Look for bottom tick + if (tickType(rows[height - 2] + offset, transparent, outError) == + TickType::kLayoutBounds) { + // Ending with a layout padding tick + int i = height - 2; + while (i > 1) { + (*outBottom)++; + i--; + if (tickType(rows[i] + offset, transparent, outError) != + TickType::kLayoutBounds) { + break; + } + } + } + return true; } -static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, int endY, - int dX, int dY, int* outInset) { - uint8_t maxOpacity = 0; - int inset = 0; - *outInset = 0; - for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) { - png_byte* color = rows[y] + x * 4; - uint8_t opacity = color[3]; - if (opacity > maxOpacity) { - maxOpacity = opacity; - *outInset = inset; - } - if (opacity == 0xff) return; - } +static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, + int endY, int dX, int dY, int* outInset) { + uint8_t maxOpacity = 0; + int inset = 0; + *outInset = 0; + for (int x = startX, y = startY; x != endX && y != endY; + x += dX, y += dY, inset++) { + png_byte* color = rows[y] + x * 4; + uint8_t opacity = color[3]; + if (opacity > maxOpacity) { + maxOpacity = opacity; + *outInset = inset; + } + if (opacity == 0xff) return; + } } static uint8_t maxAlphaOverRow(png_bytep row, int startX, int endX) { - uint8_t maxAlpha = 0; - for (int x = startX; x < endX; x++) { - uint8_t alpha = (row + x * 4)[3]; - if (alpha > maxAlpha) maxAlpha = alpha; - } - return maxAlpha; + uint8_t maxAlpha = 0; + for (int x = startX; x < endX; x++) { + uint8_t alpha = (row + x * 4)[3]; + if (alpha > maxAlpha) maxAlpha = alpha; + } + return maxAlpha; } -static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, int endY) { - uint8_t maxAlpha = 0; - for (int y = startY; y < endY; y++) { - uint8_t alpha = (rows[y] + offsetX * 4)[3]; - if (alpha > maxAlpha) maxAlpha = alpha; - } - return maxAlpha; +static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, + int endY) { + uint8_t maxAlpha = 0; + for (int y = startY; y < endY; y++) { + uint8_t alpha = (rows[y] + offsetX * 4)[3]; + if (alpha > maxAlpha) maxAlpha = alpha; + } + return maxAlpha; } static void getOutline(PngInfo* image) { - int midX = image->width / 2; - int midY = image->height / 2; - int endX = image->width - 2; - int endY = image->height - 2; - - // find left and right extent of nine patch content on center row - if (image->width > 4) { - findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft); - findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0, - &image->outlineInsetsRight); - } else { - image->outlineInsetsLeft = 0; - image->outlineInsetsRight = 0; - } - - // find top and bottom extent of nine patch content on center column - if (image->height > 4) { - findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop); - findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1, - &image->outlineInsetsBottom); - } else { - image->outlineInsetsTop = 0; - image->outlineInsetsBottom = 0; - } - - int innerStartX = 1 + image->outlineInsetsLeft; - int innerStartY = 1 + image->outlineInsetsTop; - int innerEndX = endX - image->outlineInsetsRight; - int innerEndY = endY - image->outlineInsetsBottom; - int innerMidX = (innerEndX + innerStartX) / 2; - int innerMidY = (innerEndY + innerStartY) / 2; - - // assuming the image is a round rect, compute the radius by marching - // diagonally from the top left corner towards the center - image->outlineAlpha = std::max( - maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX), - maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY)); - - int diagonalInset = 0; - findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, innerMidY, 1, 1, - &diagonalInset); - - /* Determine source radius based upon inset: - * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r - * sqrt(2) * r = sqrt(2) * i + r - * (sqrt(2) - 1) * r = sqrt(2) * i - * r = sqrt(2) / (sqrt(2) - 1) * i - */ - image->outlineRadius = 3.4142f * diagonalInset; - - if (kDebug) { - printf("outline insets %d %d %d %d, rad %f, alpha %x\n", - image->outlineInsetsLeft, - image->outlineInsetsTop, - image->outlineInsetsRight, - image->outlineInsetsBottom, - image->outlineRadius, - image->outlineAlpha); - } + int midX = image->width / 2; + int midY = image->height / 2; + int endX = image->width - 2; + int endY = image->height - 2; + + // find left and right extent of nine patch content on center row + if (image->width > 4) { + findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, + &image->outlineInsetsLeft); + findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0, + &image->outlineInsetsRight); + } else { + image->outlineInsetsLeft = 0; + image->outlineInsetsRight = 0; + } + + // find top and bottom extent of nine patch content on center column + if (image->height > 4) { + findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, + &image->outlineInsetsTop); + findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1, + &image->outlineInsetsBottom); + } else { + image->outlineInsetsTop = 0; + image->outlineInsetsBottom = 0; + } + + int innerStartX = 1 + image->outlineInsetsLeft; + int innerStartY = 1 + image->outlineInsetsTop; + int innerEndX = endX - image->outlineInsetsRight; + int innerEndY = endY - image->outlineInsetsBottom; + int innerMidX = (innerEndX + innerStartX) / 2; + int innerMidY = (innerEndY + innerStartY) / 2; + + // assuming the image is a round rect, compute the radius by marching + // diagonally from the top left corner towards the center + image->outlineAlpha = std::max( + maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX), + maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY)); + + int diagonalInset = 0; + findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, + innerMidY, 1, 1, &diagonalInset); + + /* Determine source radius based upon inset: + * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r + * sqrt(2) * r = sqrt(2) * i + r + * (sqrt(2) - 1) * r = sqrt(2) * i + * r = sqrt(2) / (sqrt(2) - 1) * i + */ + image->outlineRadius = 3.4142f * diagonalInset; + + if (kDebug) { + printf("outline insets %d %d %d %d, rad %f, alpha %x\n", + image->outlineInsetsLeft, image->outlineInsetsTop, + image->outlineInsetsRight, image->outlineInsetsBottom, + image->outlineRadius, image->outlineAlpha); + } } -static uint32_t getColor(png_bytepp rows, int left, int top, int right, int bottom) { - png_bytep color = rows[top] + left*4; +static uint32_t getColor(png_bytepp rows, int left, int top, int right, + int bottom) { + png_bytep color = rows[top] + left * 4; - if (left > right || top > bottom) { - return android::Res_png_9patch::TRANSPARENT_COLOR; - } + if (left > right || top > bottom) { + return android::Res_png_9patch::TRANSPARENT_COLOR; + } - while (top <= bottom) { - for (int i = left; i <= right; i++) { - png_bytep p = rows[top]+i*4; - if (color[3] == 0) { - if (p[3] != 0) { - return android::Res_png_9patch::NO_COLOR; - } - } else if (p[0] != color[0] || p[1] != color[1] || - p[2] != color[2] || p[3] != color[3]) { - return android::Res_png_9patch::NO_COLOR; - } + while (top <= bottom) { + for (int i = left; i <= right; i++) { + png_bytep p = rows[top] + i * 4; + if (color[3] == 0) { + if (p[3] != 0) { + return android::Res_png_9patch::NO_COLOR; } - top++; - } - - if (color[3] == 0) { - return android::Res_png_9patch::TRANSPARENT_COLOR; - } - return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2]; + } else if (p[0] != color[0] || p[1] != color[1] || p[2] != color[2] || + p[3] != color[3]) { + return android::Res_png_9patch::NO_COLOR; + } + } + top++; + } + + if (color[3] == 0) { + return android::Res_png_9patch::TRANSPARENT_COLOR; + } + return (color[3] << 24) | (color[0] << 16) | (color[1] << 8) | color[2]; } static bool do9Patch(PngInfo* image, std::string* outError) { - image->is9Patch = true; - - int W = image->width; - int H = image->height; - int i, j; - - const int maxSizeXDivs = W * sizeof(int32_t); - const int maxSizeYDivs = H * sizeof(int32_t); - int32_t* xDivs = image->xDivs = new int32_t[W]; - int32_t* yDivs = image->yDivs = new int32_t[H]; - uint8_t numXDivs = 0; - uint8_t numYDivs = 0; - - int8_t numColors; - int numRows; - int numCols; - int top; - int left; - int right; - int bottom; - memset(xDivs, -1, maxSizeXDivs); - memset(yDivs, -1, maxSizeYDivs); - image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1; - image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1; - image->layoutBoundsLeft = image->layoutBoundsRight = 0; - image->layoutBoundsTop = image->layoutBoundsBottom = 0; - - png_bytep p = image->rows[0]; - bool transparent = p[3] == 0; - bool hasColor = false; - - const char* errorMsg = nullptr; - int errorPixel = -1; - const char* errorEdge = nullptr; - - int colorIndex = 0; - std::vector<png_bytep> newRows; - - // Validate size... - if (W < 3 || H < 3) { - errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels"; - goto getout; - } - - // Validate frame... - if (!transparent && - (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) { - errorMsg = "Must have one-pixel frame that is either transparent or white"; - goto getout; - } - - // Find left and right of sizing areas... - if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], &errorMsg, &numXDivs, - true)) { - errorPixel = xDivs[0]; - errorEdge = "top"; - goto getout; - } - - // Find top and bottom of sizing areas... - if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], &yDivs[1], - &errorMsg, &numYDivs, true)) { - errorPixel = yDivs[0]; - errorEdge = "left"; - goto getout; - } - - // Copy patch size data into image... - image->info9Patch.numXDivs = numXDivs; - image->info9Patch.numYDivs = numYDivs; - - // Find left and right of padding area... - if (!getHorizontalTicks(image->rows[H-1], W, transparent, false, - &image->info9Patch.paddingLeft, &image->info9Patch.paddingRight, - &errorMsg, nullptr, false)) { - errorPixel = image->info9Patch.paddingLeft; - errorEdge = "bottom"; - goto getout; - } - - // Find top and bottom of padding area... - if (!getVerticalTicks(image->rows.data(), (W-1)*4, H, transparent, false, - &image->info9Patch.paddingTop, &image->info9Patch.paddingBottom, - &errorMsg, nullptr, false)) { - errorPixel = image->info9Patch.paddingTop; - errorEdge = "right"; - goto getout; - } - - // Find left and right of layout padding... - getHorizontalLayoutBoundsTicks(image->rows[H-1], W, transparent, false, - &image->layoutBoundsLeft, &image->layoutBoundsRight, &errorMsg); - - getVerticalLayoutBoundsTicks(image->rows.data(), (W-1)*4, H, transparent, false, - &image->layoutBoundsTop, &image->layoutBoundsBottom, &errorMsg); - - image->haveLayoutBounds = image->layoutBoundsLeft != 0 - || image->layoutBoundsRight != 0 - || image->layoutBoundsTop != 0 - || image->layoutBoundsBottom != 0; - - if (image->haveLayoutBounds) { - if (kDebug) { - printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop, - image->layoutBoundsRight, image->layoutBoundsBottom); - } - } - - // use opacity of pixels to estimate the round rect outline - getOutline(image); - - // If padding is not yet specified, take values from size. - if (image->info9Patch.paddingLeft < 0) { - image->info9Patch.paddingLeft = xDivs[0]; - image->info9Patch.paddingRight = W - 2 - xDivs[1]; - } else { - // Adjust value to be correct! - image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight; - } - if (image->info9Patch.paddingTop < 0) { - image->info9Patch.paddingTop = yDivs[0]; - image->info9Patch.paddingBottom = H - 2 - yDivs[1]; + image->is9Patch = true; + + int W = image->width; + int H = image->height; + int i, j; + + const int maxSizeXDivs = W * sizeof(int32_t); + const int maxSizeYDivs = H * sizeof(int32_t); + int32_t* xDivs = image->xDivs = new int32_t[W]; + int32_t* yDivs = image->yDivs = new int32_t[H]; + uint8_t numXDivs = 0; + uint8_t numYDivs = 0; + + int8_t numColors; + int numRows; + int numCols; + int top; + int left; + int right; + int bottom; + memset(xDivs, -1, maxSizeXDivs); + memset(yDivs, -1, maxSizeYDivs); + image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1; + image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1; + image->layoutBoundsLeft = image->layoutBoundsRight = 0; + image->layoutBoundsTop = image->layoutBoundsBottom = 0; + + png_bytep p = image->rows[0]; + bool transparent = p[3] == 0; + bool hasColor = false; + + const char* errorMsg = nullptr; + int errorPixel = -1; + const char* errorEdge = nullptr; + + int colorIndex = 0; + std::vector<png_bytep> newRows; + + // Validate size... + if (W < 3 || H < 3) { + errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels"; + goto getout; + } + + // Validate frame... + if (!transparent && + (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) { + errorMsg = "Must have one-pixel frame that is either transparent or white"; + goto getout; + } + + // Find left and right of sizing areas... + if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], + &errorMsg, &numXDivs, true)) { + errorPixel = xDivs[0]; + errorEdge = "top"; + goto getout; + } + + // Find top and bottom of sizing areas... + if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], + &yDivs[1], &errorMsg, &numYDivs, true)) { + errorPixel = yDivs[0]; + errorEdge = "left"; + goto getout; + } + + // Copy patch size data into image... + image->info9Patch.numXDivs = numXDivs; + image->info9Patch.numYDivs = numYDivs; + + // Find left and right of padding area... + if (!getHorizontalTicks(image->rows[H - 1], W, transparent, false, + &image->info9Patch.paddingLeft, + &image->info9Patch.paddingRight, &errorMsg, nullptr, + false)) { + errorPixel = image->info9Patch.paddingLeft; + errorEdge = "bottom"; + goto getout; + } + + // Find top and bottom of padding area... + if (!getVerticalTicks(image->rows.data(), (W - 1) * 4, H, transparent, false, + &image->info9Patch.paddingTop, + &image->info9Patch.paddingBottom, &errorMsg, nullptr, + false)) { + errorPixel = image->info9Patch.paddingTop; + errorEdge = "right"; + goto getout; + } + + // Find left and right of layout padding... + getHorizontalLayoutBoundsTicks(image->rows[H - 1], W, transparent, false, + &image->layoutBoundsLeft, + &image->layoutBoundsRight, &errorMsg); + + getVerticalLayoutBoundsTicks(image->rows.data(), (W - 1) * 4, H, transparent, + false, &image->layoutBoundsTop, + &image->layoutBoundsBottom, &errorMsg); + + image->haveLayoutBounds = + image->layoutBoundsLeft != 0 || image->layoutBoundsRight != 0 || + image->layoutBoundsTop != 0 || image->layoutBoundsBottom != 0; + + if (image->haveLayoutBounds) { + if (kDebug) { + printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, + image->layoutBoundsTop, image->layoutBoundsRight, + image->layoutBoundsBottom); + } + } + + // use opacity of pixels to estimate the round rect outline + getOutline(image); + + // If padding is not yet specified, take values from size. + if (image->info9Patch.paddingLeft < 0) { + image->info9Patch.paddingLeft = xDivs[0]; + image->info9Patch.paddingRight = W - 2 - xDivs[1]; + } else { + // Adjust value to be correct! + image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight; + } + if (image->info9Patch.paddingTop < 0) { + image->info9Patch.paddingTop = yDivs[0]; + image->info9Patch.paddingBottom = H - 2 - yDivs[1]; + } else { + // Adjust value to be correct! + image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom; + } + + /* if (kDebug) { + printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName, + xDivs[0], xDivs[1], + yDivs[0], yDivs[1]); + printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName, + image->info9Patch.paddingLeft, image->info9Patch.paddingRight, + image->info9Patch.paddingTop, + image->info9Patch.paddingBottom); + }*/ + + // Remove frame from image. + newRows.resize(H - 2); + for (i = 0; i < H - 2; i++) { + newRows[i] = image->rows[i + 1]; + memmove(newRows[i], newRows[i] + 4, (W - 2) * 4); + } + image->rows.swap(newRows); + + image->width -= 2; + W = image->width; + image->height -= 2; + H = image->height; + + // Figure out the number of rows and columns in the N-patch + numCols = numXDivs + 1; + if (xDivs[0] == 0) { // Column 1 is strechable + numCols--; + } + if (xDivs[numXDivs - 1] == W) { + numCols--; + } + numRows = numYDivs + 1; + if (yDivs[0] == 0) { // Row 1 is strechable + numRows--; + } + if (yDivs[numYDivs - 1] == H) { + numRows--; + } + + // Make sure the amount of rows and columns will fit in the number of + // colors we can use in the 9-patch format. + if (numRows * numCols > 0x7F) { + errorMsg = "Too many rows and columns in 9-patch perimeter"; + goto getout; + } + + numColors = numRows * numCols; + image->info9Patch.numColors = numColors; + image->colors.resize(numColors); + + // Fill in color information for each patch. + + uint32_t c; + top = 0; + + // The first row always starts with the top being at y=0 and the bottom + // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case + // the first row is stretchable along the Y axis, otherwise it is fixed. + // The last row always ends with the bottom being bitmap.height and the top + // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or + // yDivs[numYDivs-1]. In the former case the last row is stretchable along + // the Y axis, otherwise it is fixed. + // + // The first and last columns are similarly treated with respect to the X + // axis. + // + // The above is to help explain some of the special casing that goes on the + // code below. + + // The initial yDiv and whether the first row is considered stretchable or + // not depends on whether yDiv[0] was zero or not. + for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) { + if (j == numYDivs) { + bottom = H; } else { - // Adjust value to be correct! - image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom; - } - -/* if (kDebug) { - printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName, - xDivs[0], xDivs[1], - yDivs[0], yDivs[1]); - printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName, - image->info9Patch.paddingLeft, image->info9Patch.paddingRight, - image->info9Patch.paddingTop, image->info9Patch.paddingBottom); - }*/ - - // Remove frame from image. - newRows.resize(H - 2); - for (i = 0; i < H - 2; i++) { - newRows[i] = image->rows[i + 1]; - memmove(newRows[i], newRows[i] + 4, (W - 2) * 4); - } - image->rows.swap(newRows); - - image->width -= 2; - W = image->width; - image->height -= 2; - H = image->height; - - // Figure out the number of rows and columns in the N-patch - numCols = numXDivs + 1; - if (xDivs[0] == 0) { // Column 1 is strechable - numCols--; - } - if (xDivs[numXDivs - 1] == W) { - numCols--; - } - numRows = numYDivs + 1; - if (yDivs[0] == 0) { // Row 1 is strechable - numRows--; - } - if (yDivs[numYDivs - 1] == H) { - numRows--; - } - - // Make sure the amount of rows and columns will fit in the number of - // colors we can use in the 9-patch format. - if (numRows * numCols > 0x7F) { - errorMsg = "Too many rows and columns in 9-patch perimeter"; - goto getout; - } - - numColors = numRows * numCols; - image->info9Patch.numColors = numColors; - image->colors.resize(numColors); - - // Fill in color information for each patch. - - uint32_t c; - top = 0; - - // The first row always starts with the top being at y=0 and the bottom - // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case - // the first row is stretchable along the Y axis, otherwise it is fixed. - // The last row always ends with the bottom being bitmap.height and the top - // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or - // yDivs[numYDivs-1]. In the former case the last row is stretchable along - // the Y axis, otherwise it is fixed. - // - // The first and last columns are similarly treated with respect to the X - // axis. - // - // The above is to help explain some of the special casing that goes on the - // code below. - - // The initial yDiv and whether the first row is considered stretchable or - // not depends on whether yDiv[0] was zero or not. - for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) { - if (j == numYDivs) { - bottom = H; - } else { - bottom = yDivs[j]; - } - left = 0; - // The initial xDiv and whether the first column is considered - // stretchable or not depends on whether xDiv[0] was zero or not. - for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) { - if (i == numXDivs) { - right = W; - } else { - right = xDivs[i]; - } - c = getColor(image->rows.data(), left, top, right - 1, bottom - 1); - image->colors[colorIndex++] = c; - if (kDebug) { - if (c != android::Res_png_9patch::NO_COLOR) { - hasColor = true; - } - } - left = right; + bottom = yDivs[j]; + } + left = 0; + // The initial xDiv and whether the first column is considered + // stretchable or not depends on whether xDiv[0] was zero or not. + for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) { + if (i == numXDivs) { + right = W; + } else { + right = xDivs[i]; + } + c = getColor(image->rows.data(), left, top, right - 1, bottom - 1); + image->colors[colorIndex++] = c; + if (kDebug) { + if (c != android::Res_png_9patch::NO_COLOR) { + hasColor = true; } - top = bottom; + } + left = right; } + top = bottom; + } - assert(colorIndex == numColors); + assert(colorIndex == numColors); - if (kDebug && hasColor) { - for (i = 0; i < numColors; i++) { - if (i == 0) printf("Colors:\n"); - printf(" #%08x", image->colors[i]); - if (i == numColors - 1) printf("\n"); - } + if (kDebug && hasColor) { + for (i = 0; i < numColors; i++) { + if (i == 0) printf("Colors:\n"); + printf(" #%08x", image->colors[i]); + if (i == numColors - 1) printf("\n"); } + } getout: - if (errorMsg) { - std::stringstream err; - err << "9-patch malformed: " << errorMsg; - if (errorEdge) { - err << "." << std::endl; - if (errorPixel >= 0) { - err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge"; - } else { - err << "Found along " << errorEdge << " edge"; - } - } - *outError = err.str(); - return false; - } - return true; + if (errorMsg) { + std::stringstream err; + err << "9-patch malformed: " << errorMsg; + if (errorEdge) { + err << "." << std::endl; + if (errorPixel >= 0) { + err << "Found at pixel #" << errorPixel << " along " << errorEdge + << " edge"; + } else { + err << "Found along " << errorEdge << " edge"; + } + } + *outError = err.str(); + return false; + } + return true; } - -bool Png::process(const Source& source, std::istream* input, BigBuffer* outBuffer, - const PngOptions& options) { - png_byte signature[kPngSignatureSize]; - - // Read the PNG signature first. - if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) { - mDiag->error(DiagMessage() << strerror(errno)); - return false; - } - - // If the PNG signature doesn't match, bail early. - if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) { - mDiag->error(DiagMessage() << "not a valid png file"); - return false; - } - - bool result = false; - png_structp readPtr = nullptr; - png_infop infoPtr = nullptr; - png_structp writePtr = nullptr; - png_infop writeInfoPtr = nullptr; - PngInfo pngInfo = {}; - - readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); - if (!readPtr) { - mDiag->error(DiagMessage() << "failed to allocate read ptr"); - goto bail; - } - - infoPtr = png_create_info_struct(readPtr); - if (!infoPtr) { - mDiag->error(DiagMessage() << "failed to allocate info ptr"); - goto bail; - } - - png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning); - - // Set the read function to read from std::istream. - png_set_read_fn(readPtr, (png_voidp) input, readDataFromStream); - - if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) { - goto bail; - } - - if (util::stringEndsWith<char>(source.path, ".9.png")) { - std::string errorMsg; - if (!do9Patch(&pngInfo, &errorMsg)) { - mDiag->error(DiagMessage() << errorMsg); - goto bail; - } - } - - writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); - if (!writePtr) { - mDiag->error(DiagMessage() << "failed to allocate write ptr"); - goto bail; - } - - writeInfoPtr = png_create_info_struct(writePtr); - if (!writeInfoPtr) { - mDiag->error(DiagMessage() << "failed to allocate write info ptr"); - goto bail; - } - - png_set_error_fn(writePtr, nullptr, nullptr, logWarning); - - // Set the write function to write to std::ostream. - png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream); - - if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance)) { - goto bail; - } - - result = true; +bool Png::process(const Source& source, std::istream* input, + BigBuffer* outBuffer, const PngOptions& options) { + png_byte signature[kPngSignatureSize]; + + // Read the PNG signature first. + if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) { + mDiag->Error(DiagMessage() << strerror(errno)); + return false; + } + + // If the PNG signature doesn't match, bail early. + if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) { + mDiag->Error(DiagMessage() << "not a valid png file"); + return false; + } + + bool result = false; + png_structp readPtr = nullptr; + png_infop infoPtr = nullptr; + png_structp writePtr = nullptr; + png_infop writeInfoPtr = nullptr; + PngInfo pngInfo = {}; + + readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); + if (!readPtr) { + mDiag->Error(DiagMessage() << "failed to allocate read ptr"); + goto bail; + } + + infoPtr = png_create_info_struct(readPtr); + if (!infoPtr) { + mDiag->Error(DiagMessage() << "failed to allocate info ptr"); + goto bail; + } + + png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, + logWarning); + + // Set the read function to read from std::istream. + png_set_read_fn(readPtr, (png_voidp)input, readDataFromStream); + + if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) { + goto bail; + } + + if (util::EndsWith(source.path, ".9.png")) { + std::string errorMsg; + if (!do9Patch(&pngInfo, &errorMsg)) { + mDiag->Error(DiagMessage() << errorMsg); + goto bail; + } + } + + writePtr = + png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); + if (!writePtr) { + mDiag->Error(DiagMessage() << "failed to allocate write ptr"); + goto bail; + } + + writeInfoPtr = png_create_info_struct(writePtr); + if (!writeInfoPtr) { + mDiag->Error(DiagMessage() << "failed to allocate write info ptr"); + goto bail; + } + + png_set_error_fn(writePtr, nullptr, nullptr, logWarning); + + // Set the write function to write to std::ostream. + png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, + flushDataToStream); + + if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, + options.grayscale_tolerance)) { + goto bail; + } + + result = true; bail: - if (readPtr) { - png_destroy_read_struct(&readPtr, &infoPtr, nullptr); - } - - if (writePtr) { - png_destroy_write_struct(&writePtr, &writeInfoPtr); - } - return result; + if (readPtr) { + png_destroy_read_struct(&readPtr, &infoPtr, nullptr); + } + + if (writePtr) { + png_destroy_write_struct(&writePtr, &writeInfoPtr); + } + return result; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h index 345ff6c56870..aff1da3f05d2 100644 --- a/tools/aapt2/compile/Png.h +++ b/tools/aapt2/compile/Png.h @@ -17,31 +17,81 @@ #ifndef AAPT_PNG_H #define AAPT_PNG_H -#include "util/BigBuffer.h" -#include "Diagnostics.h" -#include "Source.h" - #include <iostream> #include <string> +#include "android-base/macros.h" + +#include "Diagnostics.h" +#include "Source.h" +#include "compile/Image.h" +#include "io/Io.h" +#include "process/IResourceTableConsumer.h" +#include "util/BigBuffer.h" + namespace aapt { struct PngOptions { - int grayScaleTolerance = 0; + int grayscale_tolerance = 0; }; +/** + * Deprecated. Removing once new PNG crunching code is proved to be correct. + */ class Png { -public: - Png(IDiagnostics* diag) : mDiag(diag) { - } + public: + explicit Png(IDiagnostics* diag) : mDiag(diag) {} + + bool process(const Source& source, std::istream* input, BigBuffer* outBuffer, + const PngOptions& options); + + private: + IDiagnostics* mDiag; - bool process(const Source& source, std::istream* input, BigBuffer* outBuffer, - const PngOptions& options); + DISALLOW_COPY_AND_ASSIGN(Png); +}; + +/** + * An InputStream that filters out unimportant PNG chunks. + */ +class PngChunkFilter : public io::InputStream { + public: + explicit PngChunkFilter(const StringPiece& data); + + bool Next(const void** buffer, int* len) override; + void BackUp(int count) override; + bool Skip(int count) override; + + google::protobuf::int64 ByteCount() const override { + return static_cast<google::protobuf::int64>(window_start_); + } + + bool HadError() const override { return error_; } -private: - IDiagnostics* mDiag; + private: + bool ConsumeWindow(const void** buffer, int* len); + + StringPiece data_; + size_t window_start_ = 0; + size_t window_end_ = 0; + bool error_ = false; + + DISALLOW_COPY_AND_ASSIGN(PngChunkFilter); }; -} // namespace aapt +/** + * Reads a PNG from the InputStream into memory as an RGBA Image. + */ +std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in); + +/** + * Writes the RGBA Image, with optional 9-patch meta-data, into the OutputStream + * as a PNG. + */ +bool WritePng(IAaptContext* context, const Image* image, + const NinePatch* nine_patch, io::OutputStream* out, + const PngOptions& options); + +} // namespace aapt -#endif // AAPT_PNG_H +#endif // AAPT_PNG_H diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp new file mode 100644 index 000000000000..4cbefb9496ae --- /dev/null +++ b/tools/aapt2/compile/PngChunkFilter.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "compile/Png.h" + +#include "io/Io.h" +#include "util/StringPiece.h" + +namespace aapt { + +static constexpr const char* kPngSignature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"; + +// Useful helper function that encodes individual bytes into a uint32 +// at compile time. +constexpr uint32_t u32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { + return (((uint32_t)a) << 24) | (((uint32_t)b) << 16) | (((uint32_t)c) << 8) | + ((uint32_t)d); +} + +// Whitelist of PNG chunk types that we want to keep in the resulting PNG. +enum PngChunkTypes { + kPngChunkIHDR = u32(73, 72, 68, 82), + kPngChunkIDAT = u32(73, 68, 65, 84), + kPngChunkIEND = u32(73, 69, 78, 68), + kPngChunkPLTE = u32(80, 76, 84, 69), + kPngChunktRNS = u32(116, 82, 78, 83), + kPngChunksRGB = u32(115, 82, 71, 66), +}; + +static uint32_t Peek32LE(const char* data) { + uint32_t word = ((uint32_t)data[0]) & 0x000000ff; + word <<= 8; + word |= ((uint32_t)data[1]) & 0x000000ff; + word <<= 8; + word |= ((uint32_t)data[2]) & 0x000000ff; + word <<= 8; + word |= ((uint32_t)data[3]) & 0x000000ff; + return word; +} + +static bool IsPngChunkWhitelisted(uint32_t type) { + switch (type) { + case kPngChunkIHDR: + case kPngChunkIDAT: + case kPngChunkIEND: + case kPngChunkPLTE: + case kPngChunktRNS: + case kPngChunksRGB: + return true; + default: + return false; + } +} + +PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) { + if (util::StartsWith(data_, kPngSignature)) { + window_start_ = 0; + window_end_ = strlen(kPngSignature); + } else { + error_ = true; + } +} + +bool PngChunkFilter::ConsumeWindow(const void** buffer, int* len) { + if (window_start_ != window_end_) { + // We have bytes to give from our window. + const int bytes_read = (int)(window_end_ - window_start_); + *buffer = data_.data() + window_start_; + *len = bytes_read; + window_start_ = window_end_; + return true; + } + return false; +} + +bool PngChunkFilter::Next(const void** buffer, int* len) { + if (error_) { + return false; + } + + // In case BackUp was called, we must consume the window. + if (ConsumeWindow(buffer, len)) { + return true; + } + + // Advance the window as far as possible (until we meet a chunk that + // we want to strip). + while (window_end_ < data_.size()) { + // Chunk length (4 bytes) + type (4 bytes) + crc32 (4 bytes) = 12 bytes. + const size_t kMinChunkHeaderSize = 3 * sizeof(uint32_t); + + // Is there enough room for a chunk header? + if (data_.size() - window_start_ < kMinChunkHeaderSize) { + error_ = true; + return false; + } + + // Verify the chunk length. + const uint32_t chunk_len = Peek32LE(data_.data() + window_end_); + if (((uint64_t)chunk_len) + ((uint64_t)window_end_) + sizeof(uint32_t) > + data_.size()) { + // Overflow. + error_ = true; + return false; + } + + // Do we strip this chunk? + const uint32_t chunk_type = + Peek32LE(data_.data() + window_end_ + sizeof(uint32_t)); + if (IsPngChunkWhitelisted(chunk_type)) { + // Advance the window to include this chunk. + window_end_ += kMinChunkHeaderSize + chunk_len; + } else { + // We want to strip this chunk. If we accumulated a window, + // we must return the window now. + if (window_start_ != window_end_) { + break; + } + + // The window is empty, so we can advance past this chunk + // and keep looking for the next good chunk, + window_end_ += kMinChunkHeaderSize + chunk_len; + window_start_ = window_end_; + } + } + + if (ConsumeWindow(buffer, len)) { + return true; + } + return false; +} + +void PngChunkFilter::BackUp(int count) { + if (error_) { + return; + } + window_start_ -= count; +} + +bool PngChunkFilter::Skip(int count) { + if (error_) { + return false; + } + + const void* buffer; + int len; + while (count > 0) { + if (!Next(&buffer, &len)) { + return false; + } + if (len > count) { + BackUp(len - count); + count = 0; + } else { + count -= len; + } + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/compile/PngCrunch.cpp b/tools/aapt2/compile/PngCrunch.cpp new file mode 100644 index 000000000000..3b46d8b4c782 --- /dev/null +++ b/tools/aapt2/compile/PngCrunch.cpp @@ -0,0 +1,767 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "compile/Png.h" + +#include <png.h> +#include <zlib.h> + +#include <algorithm> +#include <unordered_map> +#include <unordered_set> + +#include "android-base/errors.h" +#include "android-base/logging.h" +#include "android-base/macros.h" + +namespace aapt { + +// Size in bytes of the PNG signature. +constexpr size_t kPngSignatureSize = 8u; + +/** + * Custom deleter that destroys libpng read and info structs. + */ +class PngReadStructDeleter { + public: + PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr) + : read_ptr_(read_ptr), info_ptr_(info_ptr) {} + + ~PngReadStructDeleter() { + png_destroy_read_struct(&read_ptr_, &info_ptr_, nullptr); + } + + private: + png_structp read_ptr_; + png_infop info_ptr_; + + DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter); +}; + +/** + * Custom deleter that destroys libpng write and info structs. + */ +class PngWriteStructDeleter { + public: + PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr) + : write_ptr_(write_ptr), info_ptr_(info_ptr) {} + + ~PngWriteStructDeleter() { + png_destroy_write_struct(&write_ptr_, &info_ptr_); + } + + private: + png_structp write_ptr_; + png_infop info_ptr_; + + DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter); +}; + +// Custom warning logging method that uses IDiagnostics. +static void LogWarning(png_structp png_ptr, png_const_charp warning_msg) { + IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(png_ptr); + diag->Warn(DiagMessage() << warning_msg); +} + +// Custom error logging method that uses IDiagnostics. +static void LogError(png_structp png_ptr, png_const_charp error_msg) { + IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(png_ptr); + diag->Error(DiagMessage() << error_msg); +} + +static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer, + png_size_t len) { + io::InputStream* in = (io::InputStream*)png_get_io_ptr(png_ptr); + + const void* in_buffer; + int in_len; + if (!in->Next(&in_buffer, &in_len)) { + if (in->HadError()) { + std::string err = in->GetError(); + png_error(png_ptr, err.c_str()); + } + return; + } + + const size_t bytes_read = std::min(static_cast<size_t>(in_len), len); + memcpy(buffer, in_buffer, bytes_read); + if (bytes_read != static_cast<size_t>(in_len)) { + in->BackUp(in_len - static_cast<int>(bytes_read)); + } +} + +static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, + png_size_t len) { + io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(png_ptr); + + void* out_buffer; + int out_len; + while (len > 0) { + if (!out->Next(&out_buffer, &out_len)) { + if (out->HadError()) { + std::string err = out->GetError(); + png_error(png_ptr, err.c_str()); + } + return; + } + + const size_t bytes_written = std::min(static_cast<size_t>(out_len), len); + memcpy(out_buffer, buffer, bytes_written); + + // Advance the input buffer. + buffer += bytes_written; + len -= bytes_written; + + // Advance the output buffer. + out_len -= static_cast<int>(bytes_written); + } + + // If the entire output buffer wasn't used, backup. + if (out_len > 0) { + out->BackUp(out_len); + } +} + +std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) { + // Read the first 8 bytes of the file looking for the PNG signature. + // Bail early if it does not match. + const png_byte* signature; + int buffer_size; + if (!in->Next((const void**)&signature, &buffer_size)) { + context->GetDiagnostics()->Error( + DiagMessage() << android::base::SystemErrorCodeToString(errno)); + return {}; + } + + if (static_cast<size_t>(buffer_size) < kPngSignatureSize || + png_sig_cmp(signature, 0, kPngSignatureSize) != 0) { + context->GetDiagnostics()->Error( + DiagMessage() << "file signature does not match PNG signature"); + return {}; + } + + // Start at the beginning of the first chunk. + in->BackUp(buffer_size - static_cast<int>(kPngSignatureSize)); + + // Create and initialize the png_struct with the default error and warning + // handlers. + // The header version is also passed in to ensure that this was built against + // the same + // version of libpng. + png_structp read_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (read_ptr == nullptr) { + context->GetDiagnostics()->Error( + DiagMessage() << "failed to create libpng read png_struct"); + return {}; + } + + // Create and initialize the memory for image header and data. + png_infop info_ptr = png_create_info_struct(read_ptr); + if (info_ptr == nullptr) { + context->GetDiagnostics()->Error( + DiagMessage() << "failed to create libpng read png_info"); + png_destroy_read_struct(&read_ptr, nullptr, nullptr); + return {}; + } + + // Automatically release PNG resources at end of scope. + PngReadStructDeleter png_read_deleter(read_ptr, info_ptr); + + // libpng uses longjmp to jump to an error handling routine. + // setjmp will only return true if it was jumped to, aka there was + // an error. + if (setjmp(png_jmpbuf(read_ptr))) { + return {}; + } + + // Handle warnings ourselves via IDiagnostics. + png_set_error_fn(read_ptr, (png_voidp)context->GetDiagnostics(), LogError, + LogWarning); + + // Set up the read functions which read from our custom data sources. + png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream); + + // Skip the signature that we already read. + png_set_sig_bytes(read_ptr, kPngSignatureSize); + + // Read the chunk headers. + png_read_info(read_ptr, info_ptr); + + // Extract image meta-data from the various chunk headers. + uint32_t width, height; + int bit_depth, color_type, interlace_method, compression_method, + filter_method; + png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type, + &interlace_method, &compression_method, &filter_method); + + // When the image is read, expand it so that it is in RGBA 8888 format + // so that image handling is uniform. + + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(read_ptr); + } + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { + png_set_expand_gray_1_2_4_to_8(read_ptr); + } + + if (png_get_valid(read_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(read_ptr); + } + + if (bit_depth == 16) { + png_set_strip_16(read_ptr); + } + + if (!(color_type & PNG_COLOR_MASK_ALPHA)) { + png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER); + } + + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(read_ptr); + } + + if (interlace_method != PNG_INTERLACE_NONE) { + png_set_interlace_handling(read_ptr); + } + + // Once all the options for reading have been set, we need to flush + // them to libpng. + png_read_update_info(read_ptr, info_ptr); + + // 9-patch uses int32_t to index images, so we cap the image dimensions to + // something + // that can always be represented by 9-patch. + if (width > std::numeric_limits<int32_t>::max() || + height > std::numeric_limits<int32_t>::max()) { + context->GetDiagnostics()->Error(DiagMessage() + << "PNG image dimensions are too large: " + << width << "x" << height); + return {}; + } + + std::unique_ptr<Image> output_image = util::make_unique<Image>(); + output_image->width = static_cast<int32_t>(width); + output_image->height = static_cast<int32_t>(height); + + const size_t row_bytes = png_get_rowbytes(read_ptr, info_ptr); + CHECK(row_bytes == 4 * width); // RGBA + + // Allocate one large block to hold the image. + output_image->data = + std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]); + + // Create an array of rows that index into the data block. + output_image->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]); + for (uint32_t h = 0; h < height; h++) { + output_image->rows[h] = output_image->data.get() + (h * row_bytes); + } + + // Actually read the image pixels. + png_read_image(read_ptr, output_image->rows.get()); + + // Finish reading. This will read any other chunks after the image data. + png_read_end(read_ptr, info_ptr); + + return output_image; +} + +/** + * Experimentally chosen constant to be added to the overhead of using color + * type + * PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette + * chunk. + * Without this, many small PNGs encoded with palettes are larger after + * compression than + * the same PNGs encoded as RGBA. + */ +constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u; + +// Pick a color type by which to encode the image, based on which color type +// will take +// the least amount of disk space. +// +// 9-patch images traditionally have not been encoded with palettes. +// The original rationale was to avoid dithering until after scaling, +// but I don't think this would be an issue with palettes. Either way, +// our naive size estimation tends to be wrong for small images like 9-patches +// and using palettes balloons the size of the resulting 9-patch. +// In order to not regress in size, restrict 9-patch to not use palettes. + +// The options are: +// +// - RGB +// - RGBA +// - RGB + cheap alpha +// - Color palette +// - Color palette + cheap alpha +// - Color palette + alpha palette +// - Grayscale +// - Grayscale + cheap alpha +// - Grayscale + alpha +// +static int PickColorType(int32_t width, int32_t height, bool grayscale, + bool convertible_to_grayscale, bool has_nine_patch, + size_t color_palette_size, size_t alpha_palette_size) { + const size_t palette_chunk_size = 16 + color_palette_size * 3; + const size_t alpha_chunk_size = 16 + alpha_palette_size; + const size_t color_alpha_data_chunk_size = 16 + 4 * width * height; + const size_t color_data_chunk_size = 16 + 3 * width * height; + const size_t grayscale_alpha_data_chunk_size = 16 + 2 * width * height; + const size_t palette_data_chunk_size = 16 + width * height; + + if (grayscale) { + if (alpha_palette_size == 0) { + // This is the smallest the data can be. + return PNG_COLOR_TYPE_GRAY; + } else if (color_palette_size <= 256 && !has_nine_patch) { + // This grayscale has alpha and can fit within a palette. + // See if it is worth fitting into a palette. + const size_t palette_threshold = palette_chunk_size + alpha_chunk_size + + palette_data_chunk_size + + kPaletteOverheadConstant; + if (grayscale_alpha_data_chunk_size > palette_threshold) { + return PNG_COLOR_TYPE_PALETTE; + } + } + return PNG_COLOR_TYPE_GRAY_ALPHA; + } + + if (color_palette_size <= 256 && !has_nine_patch) { + // This image can fit inside a palette. Let's see if it is worth it. + size_t total_size_with_palette = + palette_data_chunk_size + palette_chunk_size; + size_t total_size_without_palette = color_data_chunk_size; + if (alpha_palette_size > 0) { + total_size_with_palette += alpha_palette_size; + total_size_without_palette = color_alpha_data_chunk_size; + } + + if (total_size_without_palette > + total_size_with_palette + kPaletteOverheadConstant) { + return PNG_COLOR_TYPE_PALETTE; + } + } + + if (convertible_to_grayscale) { + if (alpha_palette_size == 0) { + return PNG_COLOR_TYPE_GRAY; + } else { + return PNG_COLOR_TYPE_GRAY_ALPHA; + } + } + + if (alpha_palette_size == 0) { + return PNG_COLOR_TYPE_RGB; + } + return PNG_COLOR_TYPE_RGBA; +} + +// Assigns indices to the color and alpha palettes, encodes them, and then +// invokes +// png_set_PLTE/png_set_tRNS. +// This must be done before writing image data. +// Image data must be transformed to use the indices assigned within the +// palette. +static void WritePalette(png_structp write_ptr, png_infop write_info_ptr, + std::unordered_map<uint32_t, int>* color_palette, + std::unordered_set<uint32_t>* alpha_palette) { + CHECK(color_palette->size() <= 256); + CHECK(alpha_palette->size() <= 256); + + // Populate the PNG palette struct and assign indices to the color + // palette. + + // Colors in the alpha palette should have smaller indices. + // This will ensure that we can truncate the alpha palette if it is + // smaller than the color palette. + int index = 0; + for (uint32_t color : *alpha_palette) { + (*color_palette)[color] = index++; + } + + // Assign the rest of the entries. + for (auto& entry : *color_palette) { + if (entry.second == -1) { + entry.second = index++; + } + } + + // Create the PNG color palette struct. + auto color_palette_bytes = + std::unique_ptr<png_color[]>(new png_color[color_palette->size()]); + + std::unique_ptr<png_byte[]> alpha_palette_bytes; + if (!alpha_palette->empty()) { + alpha_palette_bytes = + std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]); + } + + for (const auto& entry : *color_palette) { + const uint32_t color = entry.first; + const int index = entry.second; + CHECK(index >= 0); + CHECK(static_cast<size_t>(index) < color_palette->size()); + + png_colorp slot = color_palette_bytes.get() + index; + slot->red = color >> 24; + slot->green = color >> 16; + slot->blue = color >> 8; + + const png_byte alpha = color & 0x000000ff; + if (alpha != 0xff && alpha_palette_bytes) { + CHECK(static_cast<size_t>(index) < alpha_palette->size()); + alpha_palette_bytes[index] = alpha; + } + } + + // The bytes get copied here, so it is safe to release color_palette_bytes at + // the end of function + // scope. + png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(), + color_palette->size()); + + if (alpha_palette_bytes) { + png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(), + alpha_palette->size(), nullptr); + } +} + +// Write the 9-patch custom PNG chunks to write_info_ptr. This must be done +// before +// writing image data. +static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr, + const NinePatch* nine_patch) { + // The order of the chunks is important. + // 9-patch code in older platforms expects the 9-patch chunk to + // be last. + + png_unknown_chunk unknown_chunks[3]; + memset(unknown_chunks, 0, sizeof(unknown_chunks)); + + size_t index = 0; + size_t chunk_len = 0; + + std::unique_ptr<uint8_t[]> serialized_outline = + nine_patch->SerializeRoundedRectOutline(&chunk_len); + strcpy((char*)unknown_chunks[index].name, "npOl"); + unknown_chunks[index].size = chunk_len; + unknown_chunks[index].data = (png_bytep)serialized_outline.get(); + unknown_chunks[index].location = PNG_HAVE_PLTE; + index++; + + std::unique_ptr<uint8_t[]> serialized_layout_bounds; + if (nine_patch->layout_bounds.nonZero()) { + serialized_layout_bounds = nine_patch->SerializeLayoutBounds(&chunk_len); + strcpy((char*)unknown_chunks[index].name, "npLb"); + unknown_chunks[index].size = chunk_len; + unknown_chunks[index].data = (png_bytep)serialized_layout_bounds.get(); + unknown_chunks[index].location = PNG_HAVE_PLTE; + index++; + } + + std::unique_ptr<uint8_t[]> serialized_nine_patch = + nine_patch->SerializeBase(&chunk_len); + strcpy((char*)unknown_chunks[index].name, "npTc"); + unknown_chunks[index].size = chunk_len; + unknown_chunks[index].data = (png_bytep)serialized_nine_patch.get(); + unknown_chunks[index].location = PNG_HAVE_PLTE; + index++; + + // Handle all unknown chunks. We are manually setting the chunks here, + // so we will only ever handle our custom chunks. + png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0); + + // Set the actual chunks here. The data gets copied, so our buffers can + // safely go out of scope. + png_set_unknown_chunks(write_ptr, write_info_ptr, unknown_chunks, index); +} + +bool WritePng(IAaptContext* context, const Image* image, + const NinePatch* nine_patch, io::OutputStream* out, + const PngOptions& options) { + // Create and initialize the write png_struct with the default error and + // warning handlers. + // The header version is also passed in to ensure that this was built against + // the same + // version of libpng. + png_structp write_ptr = + png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (write_ptr == nullptr) { + context->GetDiagnostics()->Error( + DiagMessage() << "failed to create libpng write png_struct"); + return false; + } + + // Allocate memory to store image header data. + png_infop write_info_ptr = png_create_info_struct(write_ptr); + if (write_info_ptr == nullptr) { + context->GetDiagnostics()->Error( + DiagMessage() << "failed to create libpng write png_info"); + png_destroy_write_struct(&write_ptr, nullptr); + return false; + } + + // Automatically release PNG resources at end of scope. + PngWriteStructDeleter png_write_deleter(write_ptr, write_info_ptr); + + // libpng uses longjmp to jump to error handling routines. + // setjmp will return true only if it was jumped to, aka, there was an error. + if (setjmp(png_jmpbuf(write_ptr))) { + return false; + } + + // Handle warnings with our IDiagnostics. + png_set_error_fn(write_ptr, (png_voidp)context->GetDiagnostics(), LogError, + LogWarning); + + // Set up the write functions which write to our custom data sources. + png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr); + + // We want small files and can take the performance hit to achieve this goal. + png_set_compression_level(write_ptr, Z_BEST_COMPRESSION); + + // Begin analysis of the image data. + // Scan the entire image and determine if: + // 1. Every pixel has R == G == B (grayscale) + // 2. Every pixel has A == 255 (opaque) + // 3. There are no more than 256 distinct RGBA colors (palette). + std::unordered_map<uint32_t, int> color_palette; + std::unordered_set<uint32_t> alpha_palette; + bool needs_to_zero_rgb_channels_of_transparent_pixels = false; + bool grayscale = true; + int max_gray_deviation = 0; + + for (int32_t y = 0; y < image->height; y++) { + const uint8_t* row = image->rows[y]; + for (int32_t x = 0; x < image->width; x++) { + int red = *row++; + int green = *row++; + int blue = *row++; + int alpha = *row++; + + if (alpha == 0) { + // The color is completely transparent. + // For purposes of palettes and grayscale optimization, + // treat all channels as 0x00. + needs_to_zero_rgb_channels_of_transparent_pixels = + needs_to_zero_rgb_channels_of_transparent_pixels || + (red != 0 || green != 0 || blue != 0); + red = green = blue = 0; + } + + // Insert the color into the color palette. + const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha; + color_palette[color] = -1; + + // If the pixel has non-opaque alpha, insert it into the + // alpha palette. + if (alpha != 0xff) { + alpha_palette.insert(color); + } + + // Check if the image is indeed grayscale. + if (grayscale) { + if (red != green || red != blue) { + grayscale = false; + } + } + + // Calculate the gray scale deviation so that it can be compared + // with the threshold. + max_gray_deviation = std::max(std::abs(red - green), max_gray_deviation); + max_gray_deviation = std::max(std::abs(green - blue), max_gray_deviation); + max_gray_deviation = std::max(std::abs(blue - red), max_gray_deviation); + } + } + + if (context->IsVerbose()) { + DiagMessage msg; + msg << " paletteSize=" << color_palette.size() + << " alphaPaletteSize=" << alpha_palette.size() + << " maxGrayDeviation=" << max_gray_deviation + << " grayScale=" << (grayscale ? "true" : "false"); + context->GetDiagnostics()->Note(msg); + } + + const bool convertible_to_grayscale = + max_gray_deviation <= options.grayscale_tolerance; + + const int new_color_type = PickColorType( + image->width, image->height, grayscale, convertible_to_grayscale, + nine_patch != nullptr, color_palette.size(), alpha_palette.size()); + + if (context->IsVerbose()) { + DiagMessage msg; + msg << "encoding PNG "; + if (nine_patch) { + msg << "(with 9-patch) as "; + } + switch (new_color_type) { + case PNG_COLOR_TYPE_GRAY: + msg << "GRAY"; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + msg << "GRAY + ALPHA"; + break; + case PNG_COLOR_TYPE_RGB: + msg << "RGB"; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + msg << "RGBA"; + break; + case PNG_COLOR_TYPE_PALETTE: + msg << "PALETTE"; + break; + default: + msg << "unknown type " << new_color_type; + break; + } + context->GetDiagnostics()->Note(msg); + } + + png_set_IHDR(write_ptr, write_info_ptr, image->width, image->height, 8, + new_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + if (new_color_type & PNG_COLOR_MASK_PALETTE) { + // Assigns indices to the palette, and writes the encoded palette to the + // libpng writePtr. + WritePalette(write_ptr, write_info_ptr, &color_palette, &alpha_palette); + png_set_filter(write_ptr, 0, PNG_NO_FILTERS); + } else { + png_set_filter(write_ptr, 0, PNG_ALL_FILTERS); + } + + if (nine_patch) { + WriteNinePatch(write_ptr, write_info_ptr, nine_patch); + } + + // Flush our updates to the header. + png_write_info(write_ptr, write_info_ptr); + + // Write out each row of image data according to its encoding. + if (new_color_type == PNG_COLOR_TYPE_PALETTE) { + // 1 byte/pixel. + auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width]); + + for (int32_t y = 0; y < image->height; y++) { + png_const_bytep in_row = image->rows[y]; + for (int32_t x = 0; x < image->width; x++) { + int rr = *in_row++; + int gg = *in_row++; + int bb = *in_row++; + int aa = *in_row++; + if (aa == 0) { + // Zero out color channels when transparent. + rr = gg = bb = 0; + } + + const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa; + const int idx = color_palette[color]; + CHECK(idx != -1); + out_row[x] = static_cast<png_byte>(idx); + } + png_write_row(write_ptr, out_row.get()); + } + } else if (new_color_type == PNG_COLOR_TYPE_GRAY || + new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + const size_t bpp = new_color_type == PNG_COLOR_TYPE_GRAY ? 1 : 2; + auto out_row = + std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]); + + for (int32_t y = 0; y < image->height; y++) { + png_const_bytep in_row = image->rows[y]; + for (int32_t x = 0; x < image->width; x++) { + int rr = in_row[x * 4]; + int gg = in_row[x * 4 + 1]; + int bb = in_row[x * 4 + 2]; + int aa = in_row[x * 4 + 3]; + if (aa == 0) { + // Zero out the gray channel when transparent. + rr = gg = bb = 0; + } + + if (grayscale) { + // The image was already grayscale, red == green == blue. + out_row[x * bpp] = in_row[x * 4]; + } else { + // The image is convertible to grayscale, use linear-luminance of + // sRGB colorspace: + // https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale + out_row[x * bpp] = + (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f); + } + + if (bpp == 2) { + // Write out alpha if we have it. + out_row[x * bpp + 1] = aa; + } + } + png_write_row(write_ptr, out_row.get()); + } + } else if (new_color_type == PNG_COLOR_TYPE_RGB || + new_color_type == PNG_COLOR_TYPE_RGBA) { + const size_t bpp = new_color_type == PNG_COLOR_TYPE_RGB ? 3 : 4; + if (needs_to_zero_rgb_channels_of_transparent_pixels) { + // The source RGBA data can't be used as-is, because we need to zero out + // the RGB + // values of transparent pixels. + auto out_row = + std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]); + + for (int32_t y = 0; y < image->height; y++) { + png_const_bytep in_row = image->rows[y]; + for (int32_t x = 0; x < image->width; x++) { + int rr = *in_row++; + int gg = *in_row++; + int bb = *in_row++; + int aa = *in_row++; + if (aa == 0) { + // Zero out the RGB channels when transparent. + rr = gg = bb = 0; + } + out_row[x * bpp] = rr; + out_row[x * bpp + 1] = gg; + out_row[x * bpp + 2] = bb; + if (bpp == 4) { + out_row[x * bpp + 3] = aa; + } + } + png_write_row(write_ptr, out_row.get()); + } + } else { + // The source image can be used as-is, just tell libpng whether or not to + // ignore + // the alpha channel. + if (new_color_type == PNG_COLOR_TYPE_RGB) { + // Delete the extraneous alpha values that we appended to our buffer + // when reading the original values. + png_set_filler(write_ptr, 0, PNG_FILLER_AFTER); + } + png_write_image(write_ptr, image->rows.get()); + } + } else { + LOG(FATAL) << "unreachable"; + } + + png_write_end(write_ptr, write_info_ptr); + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp index d080e16c520b..055a725213b7 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp @@ -14,235 +14,249 @@ * limitations under the License. */ +#include "compile/PseudolocaleGenerator.h" + +#include <algorithm> + #include "ResourceTable.h" #include "ResourceValues.h" #include "ValueVisitor.h" -#include "compile/PseudolocaleGenerator.h" #include "compile/Pseudolocalizer.h" -#include <algorithm> - namespace aapt { -std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string, - Pseudolocalizer::Method method, - StringPool* pool) { - Pseudolocalizer localizer(method); - - const StringPiece16 originalText = *string->value->str; - - StyleString localized; - - // Copy the spans. We will update their offsets when we localize. - localized.spans.reserve(string->value->spans.size()); - for (const StringPool::Span& span : string->value->spans) { - localized.spans.push_back(Span{ *span.name, span.firstChar, span.lastChar }); +std::unique_ptr<StyledString> PseudolocalizeStyledString( + StyledString* string, Pseudolocalizer::Method method, StringPool* pool) { + Pseudolocalizer localizer(method); + + const StringPiece original_text = *string->value->str; + + StyleString localized; + + // Copy the spans. We will update their offsets when we localize. + localized.spans.reserve(string->value->spans.size()); + for (const StringPool::Span& span : string->value->spans) { + localized.spans.push_back( + Span{*span.name, span.first_char, span.last_char}); + } + + // The ranges are all represented with a single value. This is the start of + // one range and + // end of another. + struct Range { + size_t start; + + // Once the new string is localized, these are the pointers to the spans to + // adjust. + // Since this struct represents the start of one range and end of another, + // we have + // the two pointers respectively. + uint32_t* update_start; + uint32_t* update_end; + }; + + auto cmp = [](const Range& r, size_t index) -> bool { + return r.start < index; + }; + + // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7] + // The ranges are the spaces in between. In this example, with a total string + // length of 9, + // the vector represents: (0,1], (2,4], (5,6], (7,9] + // + std::vector<Range> ranges; + ranges.push_back(Range{0}); + ranges.push_back(Range{original_text.size() - 1}); + for (size_t i = 0; i < string->value->spans.size(); i++) { + const StringPool::Span& span = string->value->spans[i]; + + // Insert or update the Range marker for the start of this span. + auto iter = + std::lower_bound(ranges.begin(), ranges.end(), span.first_char, cmp); + if (iter != ranges.end() && iter->start == span.first_char) { + iter->update_start = &localized.spans[i].first_char; + } else { + ranges.insert(iter, Range{span.first_char, &localized.spans[i].first_char, + nullptr}); } - // The ranges are all represented with a single value. This is the start of one range and - // end of another. - struct Range { - size_t start; - - // Once the new string is localized, these are the pointers to the spans to adjust. - // Since this struct represents the start of one range and end of another, we have - // the two pointers respectively. - uint32_t* updateStart; - uint32_t* updateEnd; - }; - - auto cmp = [](const Range& r, size_t index) -> bool { - return r.start < index; - }; - - // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7] - // The ranges are the spaces in between. In this example, with a total string length of 9, - // the vector represents: (0,1], (2,4], (5,6], (7,9] - // - std::vector<Range> ranges; - ranges.push_back(Range{ 0 }); - ranges.push_back(Range{ originalText.size() - 1 }); - for (size_t i = 0; i < string->value->spans.size(); i++) { - const StringPool::Span& span = string->value->spans[i]; - - // Insert or update the Range marker for the start of this span. - auto iter = std::lower_bound(ranges.begin(), ranges.end(), span.firstChar, cmp); - if (iter != ranges.end() && iter->start == span.firstChar) { - iter->updateStart = &localized.spans[i].firstChar; - } else { - ranges.insert(iter, - Range{ span.firstChar, &localized.spans[i].firstChar, nullptr }); - } - - // Insert or update the Range marker for the end of this span. - iter = std::lower_bound(ranges.begin(), ranges.end(), span.lastChar, cmp); - if (iter != ranges.end() && iter->start == span.lastChar) { - iter->updateEnd = &localized.spans[i].lastChar; - } else { - ranges.insert(iter, - Range{ span.lastChar, nullptr, &localized.spans[i].lastChar }); - } + // Insert or update the Range marker for the end of this span. + iter = std::lower_bound(ranges.begin(), ranges.end(), span.last_char, cmp); + if (iter != ranges.end() && iter->start == span.last_char) { + iter->update_end = &localized.spans[i].last_char; + } else { + ranges.insert( + iter, Range{span.last_char, nullptr, &localized.spans[i].last_char}); } + } - localized.str += localizer.start(); + localized.str += localizer.Start(); - // Iterate over the ranges and localize each section. - for (size_t i = 0; i < ranges.size(); i++) { - const size_t start = ranges[i].start; - size_t len = originalText.size() - start; - if (i + 1 < ranges.size()) { - len = ranges[i + 1].start - start; - } - - if (ranges[i].updateStart) { - *ranges[i].updateStart = localized.str.size(); - } + // Iterate over the ranges and localize each section. + for (size_t i = 0; i < ranges.size(); i++) { + const size_t start = ranges[i].start; + size_t len = original_text.size() - start; + if (i + 1 < ranges.size()) { + len = ranges[i + 1].start - start; + } - if (ranges[i].updateEnd) { - *ranges[i].updateEnd = localized.str.size(); - } + if (ranges[i].update_start) { + *ranges[i].update_start = localized.str.size(); + } - localized.str += localizer.text(originalText.substr(start, len)); + if (ranges[i].update_end) { + *ranges[i].update_end = localized.str.size(); } - localized.str += localizer.end(); + localized.str += localizer.Text(original_text.substr(start, len)); + } + + localized.str += localizer.End(); - std::unique_ptr<StyledString> localizedString = util::make_unique<StyledString>( - pool->makeRef(localized)); - localizedString->setSource(string->getSource()); - return localizedString; + std::unique_ptr<StyledString> localized_string = + util::make_unique<StyledString>(pool->MakeRef(localized)); + localized_string->SetSource(string->GetSource()); + return localized_string; } namespace { -struct Visitor : public RawValueVisitor { - StringPool* mPool; - Pseudolocalizer::Method mMethod; - Pseudolocalizer mLocalizer; - - // Either value or item will be populated upon visiting the value. - std::unique_ptr<Value> mValue; - std::unique_ptr<Item> mItem; - - Visitor(StringPool* pool, Pseudolocalizer::Method method) : - mPool(pool), mMethod(method), mLocalizer(method) { - } - - void visit(Plural* plural) override { - std::unique_ptr<Plural> localized = util::make_unique<Plural>(); - for (size_t i = 0; i < plural->values.size(); i++) { - Visitor subVisitor(mPool, mMethod); - if (plural->values[i]) { - plural->values[i]->accept(&subVisitor); - if (subVisitor.mValue) { - localized->values[i] = std::move(subVisitor.mItem); - } else { - localized->values[i] = std::unique_ptr<Item>(plural->values[i]->clone(mPool)); - } - } +class Visitor : public RawValueVisitor { + public: + // Either value or item will be populated upon visiting the value. + std::unique_ptr<Value> value; + std::unique_ptr<Item> item; + + Visitor(StringPool* pool, Pseudolocalizer::Method method) + : pool_(pool), method_(method), localizer_(method) {} + + void Visit(Plural* plural) override { + std::unique_ptr<Plural> localized = util::make_unique<Plural>(); + for (size_t i = 0; i < plural->values.size(); i++) { + Visitor sub_visitor(pool_, method_); + if (plural->values[i]) { + plural->values[i]->Accept(&sub_visitor); + if (sub_visitor.value) { + localized->values[i] = std::move(sub_visitor.item); + } else { + localized->values[i] = + std::unique_ptr<Item>(plural->values[i]->Clone(pool_)); } - localized->setSource(plural->getSource()); - localized->setWeak(true); - mValue = std::move(localized); - } - - void visit(String* string) override { - std::u16string result = mLocalizer.start() + mLocalizer.text(*string->value) + - mLocalizer.end(); - std::unique_ptr<String> localized = util::make_unique<String>(mPool->makeRef(result)); - localized->setSource(string->getSource()); - localized->setWeak(true); - mItem = std::move(localized); - } - - void visit(StyledString* string) override { - mItem = pseudolocalizeStyledString(string, mMethod, mPool); - mItem->setWeak(true); + } } + localized->SetSource(plural->GetSource()); + localized->SetWeak(true); + value = std::move(localized); + } + + void Visit(String* string) override { + std::string result = + localizer_.Start() + localizer_.Text(*string->value) + localizer_.End(); + std::unique_ptr<String> localized = + util::make_unique<String>(pool_->MakeRef(result)); + localized->SetSource(string->GetSource()); + localized->SetWeak(true); + item = std::move(localized); + } + + void Visit(StyledString* string) override { + item = PseudolocalizeStyledString(string, method_, pool_); + item->SetWeak(true); + } + + private: + DISALLOW_COPY_AND_ASSIGN(Visitor); + + StringPool* pool_; + Pseudolocalizer::Method method_; + Pseudolocalizer localizer_; }; -ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base, +ConfigDescription ModifyConfigForPseudoLocale(const ConfigDescription& base, Pseudolocalizer::Method m) { - ConfigDescription modified = base; - switch (m) { + ConfigDescription modified = base; + switch (m) { case Pseudolocalizer::Method::kAccent: - modified.language[0] = 'e'; - modified.language[1] = 'n'; - modified.country[0] = 'X'; - modified.country[1] = 'A'; - break; + modified.language[0] = 'e'; + modified.language[1] = 'n'; + modified.country[0] = 'X'; + modified.country[1] = 'A'; + break; case Pseudolocalizer::Method::kBidi: - modified.language[0] = 'a'; - modified.language[1] = 'r'; - modified.country[0] = 'X'; - modified.country[1] = 'B'; - break; + modified.language[0] = 'a'; + modified.language[1] = 'r'; + modified.country[0] = 'X'; + modified.country[1] = 'B'; + break; default: - break; - } - return modified; + break; + } + return modified; } -void pseudolocalizeIfNeeded(const Pseudolocalizer::Method method, - ResourceConfigValue* originalValue, - StringPool* pool, - ResourceEntry* entry) { - Visitor visitor(pool, method); - originalValue->value->accept(&visitor); - - std::unique_ptr<Value> localizedValue; - if (visitor.mValue) { - localizedValue = std::move(visitor.mValue); - } else if (visitor.mItem) { - localizedValue = std::move(visitor.mItem); - } - - if (!localizedValue) { - return; - } - - ConfigDescription configWithAccent = modifyConfigForPseudoLocale( - originalValue->config, method); - - ResourceConfigValue* newConfigValue = entry->findOrCreateValue( - configWithAccent, originalValue->product); - if (!newConfigValue->value) { - // Only use auto-generated pseudo-localization if none is defined. - newConfigValue->value = std::move(localizedValue); - } +void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method, + ResourceConfigValue* original_value, + StringPool* pool, ResourceEntry* entry) { + Visitor visitor(pool, method); + original_value->value->Accept(&visitor); + + std::unique_ptr<Value> localized_value; + if (visitor.value) { + localized_value = std::move(visitor.value); + } else if (visitor.item) { + localized_value = std::move(visitor.item); + } + + if (!localized_value) { + return; + } + + ConfigDescription config_with_accent = + ModifyConfigForPseudoLocale(original_value->config, method); + + ResourceConfigValue* new_config_value = + entry->FindOrCreateValue(config_with_accent, original_value->product); + if (!new_config_value->value) { + // Only use auto-generated pseudo-localization if none is defined. + new_config_value->value = std::move(localized_value); + } } /** - * A value is pseudolocalizable if it does not define a locale (or is the default locale) + * A value is pseudolocalizable if it does not define a locale (or is the + * default locale) * and is translateable. */ -static bool isPseudolocalizable(ResourceConfigValue* configValue) { - const int diff = configValue->config.diff(ConfigDescription::defaultConfig()); - if (diff & ConfigDescription::CONFIG_LOCALE) { - return false; - } - return configValue->value->isTranslateable(); +static bool IsPseudolocalizable(ResourceConfigValue* config_value) { + const int diff = + config_value->config.diff(ConfigDescription::DefaultConfig()); + if (diff & ConfigDescription::CONFIG_LOCALE) { + return false; + } + return config_value->value->IsTranslateable(); } -} // namespace - -bool PseudolocaleGenerator::consume(IAaptContext* context, ResourceTable* table) { - for (auto& package : table->packages) { - for (auto& type : package->types) { - for (auto& entry : type->entries) { - std::vector<ResourceConfigValue*> values = entry->findValuesIf(isPseudolocalizable); - - for (ResourceConfigValue* value : values) { - pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, - &table->stringPool, entry.get()); - pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, - &table->stringPool, entry.get()); - } - } +} // namespace + +bool PseudolocaleGenerator::Consume(IAaptContext* context, + ResourceTable* table) { + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + std::vector<ResourceConfigValue*> values = + entry->FindValuesIf(IsPseudolocalizable); + + for (ResourceConfigValue* value : values) { + PseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, + &table->string_pool, entry.get()); + PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, + &table->string_pool, entry.get()); } + } } - return true; + } + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/PseudolocaleGenerator.h b/tools/aapt2/compile/PseudolocaleGenerator.h index 4fbc51607595..ace378603f65 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.h +++ b/tools/aapt2/compile/PseudolocaleGenerator.h @@ -23,14 +23,13 @@ namespace aapt { -std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string, - Pseudolocalizer::Method method, - StringPool* pool); +std::unique_ptr<StyledString> PseudolocalizeStyledString( + StyledString* string, Pseudolocalizer::Method method, StringPool* pool); struct PseudolocaleGenerator : public IResourceTableConsumer { - bool consume(IAaptContext* context, ResourceTable* table) override; + bool Consume(IAaptContext* context, ResourceTable* table) override; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H */ diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp index 4cb6ea2db565..5a9884d34dd1 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp @@ -15,109 +15,119 @@ */ #include "compile/PseudolocaleGenerator.h" -#include "test/Builders.h" -#include "test/Common.h" -#include "test/Context.h" -#include "util/Util.h" -#include <androidfw/ResourceTypes.h> -#include <gtest/gtest.h> +#include "test/Test.h" +#include "util/Util.h" namespace aapt { TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { - StringPool pool; - StyleString originalStyle; - originalStyle.str = u"Hello world!"; - originalStyle.spans = { Span{ u"b", 2, 3 }, Span{ u"b", 6, 7 }, Span{ u"i", 1, 10 } }; + StringPool pool; + StyleString original_style; + original_style.str = "Hello world!"; + original_style.spans = {Span{"b", 2, 3}, Span{"b", 6, 7}, Span{"i", 1, 10}}; - std::unique_ptr<StyledString> newString = pseudolocalizeStyledString( - util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(), - Pseudolocalizer::Method::kNone, &pool); + std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString( + util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), + Pseudolocalizer::Method::kNone, &pool); - EXPECT_EQ(originalStyle.str, *newString->value->str); - ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size()); + EXPECT_EQ(original_style.str, *new_string->value->str); + ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size()); - EXPECT_EQ(2u, newString->value->spans[0].firstChar); - EXPECT_EQ(3u, newString->value->spans[0].lastChar); - EXPECT_EQ(std::u16string(u"b"), *newString->value->spans[0].name); + EXPECT_EQ(std::string("He").size(), new_string->value->spans[0].first_char); + EXPECT_EQ(std::string("Hel").size(), new_string->value->spans[0].last_char); + EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name); - EXPECT_EQ(6u, newString->value->spans[1].firstChar); - EXPECT_EQ(7u, newString->value->spans[1].lastChar); - EXPECT_EQ(std::u16string(u"b"), *newString->value->spans[1].name); + EXPECT_EQ(std::string("Hello ").size(), + new_string->value->spans[1].first_char); + EXPECT_EQ(std::string("Hello w").size(), + new_string->value->spans[1].last_char); + EXPECT_EQ(std::string("b"), *new_string->value->spans[1].name); - EXPECT_EQ(1u, newString->value->spans[2].firstChar); - EXPECT_EQ(10u, newString->value->spans[2].lastChar); - EXPECT_EQ(std::u16string(u"i"), *newString->value->spans[2].name); + EXPECT_EQ(std::string("H").size(), new_string->value->spans[2].first_char); + EXPECT_EQ(std::string("Hello worl").size(), + new_string->value->spans[2].last_char); + EXPECT_EQ(std::string("i"), *new_string->value->spans[2].name); - originalStyle.spans.push_back(Span{ u"em", 0, 11u }); + original_style.spans.push_back(Span{"em", 0, 11u}); - newString = pseudolocalizeStyledString( - util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(), - Pseudolocalizer::Method::kAccent, &pool); + new_string = PseudolocalizeStyledString( + util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), + Pseudolocalizer::Method::kAccent, &pool); - EXPECT_EQ(std::u16string(u"[Ĥéļļö ŵöŕļð¡ one two]"), *newString->value->str); - ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size()); + EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *new_string->value->str); + ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size()); - EXPECT_EQ(3u, newString->value->spans[0].firstChar); - EXPECT_EQ(4u, newString->value->spans[0].lastChar); + EXPECT_EQ(std::string("[Ĥé").size(), new_string->value->spans[0].first_char); + EXPECT_EQ(std::string("[Ĥéļ").size(), new_string->value->spans[0].last_char); - EXPECT_EQ(7u, newString->value->spans[1].firstChar); - EXPECT_EQ(8u, newString->value->spans[1].lastChar); + EXPECT_EQ(std::string("[Ĥéļļö ").size(), + new_string->value->spans[1].first_char); + EXPECT_EQ(std::string("[Ĥéļļö ŵ").size(), + new_string->value->spans[1].last_char); - EXPECT_EQ(2u, newString->value->spans[2].firstChar); - EXPECT_EQ(11u, newString->value->spans[2].lastChar); + EXPECT_EQ(std::string("[Ĥ").size(), new_string->value->spans[2].first_char); + EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļ").size(), + new_string->value->spans[2].last_char); - EXPECT_EQ(1u, newString->value->spans[3].firstChar); - EXPECT_EQ(12u, newString->value->spans[3].lastChar); + EXPECT_EQ(std::string("[").size(), new_string->value->spans[3].first_char); + EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð").size(), + new_string->value->spans[3].last_char); } TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addString(u"@android:string/one", u"one") - .addString(u"@android:string/two", ResourceId{}, test::parseConfigOrDie("en"), u"two") - .addString(u"@android:string/three", u"three") - .addString(u"@android:string/three", ResourceId{}, test::parseConfigOrDie("en-rXA"), - u"three") - .addString(u"@android:string/four", u"four") - .build(); - - String* val = test::getValue<String>(table.get(), u"@android:string/four"); - val->setTranslateable(false); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - PseudolocaleGenerator generator; - ASSERT_TRUE(generator.consume(context.get(), table.get())); - - // Normal pseudolocalization should take place. - ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/one", - test::parseConfigOrDie("en-rXA"))); - ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/one", - test::parseConfigOrDie("ar-rXB"))); - - // No default config for android:string/two, so no pseudlocales should exist. - ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/two", - test::parseConfigOrDie("en-rXA"))); - ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/two", - test::parseConfigOrDie("ar-rXB"))); - - - // Check that we didn't override manual pseudolocalization. - val = test::getValueForConfig<String>(table.get(), u"@android:string/three", - test::parseConfigOrDie("en-rXA")); - ASSERT_NE(nullptr, val); - EXPECT_EQ(std::u16string(u"three"), *val->value); - - ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/three", - test::parseConfigOrDie("ar-rXB"))); - - // Check that four's translateable marker was honored. - ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/four", - test::parseConfigOrDie("en-rXA"))); - ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/four", - test::parseConfigOrDie("ar-rXB"))); - + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddString("android:string/one", "one") + .AddString("android:string/two", ResourceId{}, + test::ParseConfigOrDie("en"), "two") + .AddString("android:string/three", "three") + .AddString("android:string/three", ResourceId{}, + test::ParseConfigOrDie("en-rXA"), "three") + .AddString("android:string/four", "four") + .Build(); + + String* val = test::GetValue<String>(table.get(), "android:string/four"); + ASSERT_NE(nullptr, val); + val->SetTranslateable(false); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + PseudolocaleGenerator generator; + ASSERT_TRUE(generator.Consume(context.get(), table.get())); + + // Normal pseudolocalization should take place. + ASSERT_NE(nullptr, + test::GetValueForConfig<String>(table.get(), "android:string/one", + test::ParseConfigOrDie("en-rXA"))); + ASSERT_NE(nullptr, + test::GetValueForConfig<String>(table.get(), "android:string/one", + test::ParseConfigOrDie("ar-rXB"))); + + // No default config for android:string/two, so no pseudlocales should exist. + ASSERT_EQ(nullptr, + test::GetValueForConfig<String>(table.get(), "android:string/two", + test::ParseConfigOrDie("en-rXA"))); + ASSERT_EQ(nullptr, + test::GetValueForConfig<String>(table.get(), "android:string/two", + test::ParseConfigOrDie("ar-rXB"))); + + // Check that we didn't override manual pseudolocalization. + val = test::GetValueForConfig<String>(table.get(), "android:string/three", + test::ParseConfigOrDie("en-rXA")); + ASSERT_NE(nullptr, val); + EXPECT_EQ(std::string("three"), *val->value); + + ASSERT_NE(nullptr, + test::GetValueForConfig<String>(table.get(), "android:string/three", + test::ParseConfigOrDie("ar-rXB"))); + + // Check that four's translateable marker was honored. + ASSERT_EQ(nullptr, + test::GetValueForConfig<String>(table.get(), "android:string/four", + test::ParseConfigOrDie("en-rXA"))); + ASSERT_EQ(nullptr, + test::GetValueForConfig<String>(table.get(), "android:string/four", + test::ParseConfigOrDie("ar-rXB"))); } -} // namespace aapt - +} // namespace aapt diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp index eae52d778744..f89288f73333 100644 --- a/tools/aapt2/compile/Pseudolocalizer.cpp +++ b/tools/aapt2/compile/Pseudolocalizer.cpp @@ -15,251 +15,333 @@ */ #include "compile/Pseudolocalizer.h" + #include "util/Util.h" namespace aapt { // String basis to generate expansion -static const std::u16string k_expansion_string = u"one two three " - "four five six seven eight nine ten eleven twelve thirteen " - "fourteen fiveteen sixteen seventeen nineteen twenty"; +static const std::string kExpansionString = + "one two three " + "four five six seven eight nine ten eleven twelve thirteen " + "fourteen fiveteen sixteen seventeen nineteen twenty"; // Special unicode characters to override directionality of the words -static const std::u16string k_rlm = u"\u200f"; -static const std::u16string k_rlo = u"\u202e"; -static const std::u16string k_pdf = u"\u202c"; +static const std::string kRlm = "\u200f"; +static const std::string kRlo = "\u202e"; +static const std::string kPdf = "\u202c"; // Placeholder marks -static const std::u16string k_placeholder_open = u"\u00bb"; -static const std::u16string k_placeholder_close = u"\u00ab"; +static const std::string kPlaceholderOpen = "\u00bb"; +static const std::string kPlaceholderClose = "\u00ab"; -static const char16_t k_arg_start = u'{'; -static const char16_t k_arg_end = u'}'; +static const char kArgStart = '{'; +static const char kArgEnd = '}'; class PseudoMethodNone : public PseudoMethodImpl { -public: - std::u16string text(const StringPiece16& text) override { return text.toString(); } - std::u16string placeholder(const StringPiece16& text) override { return text.toString(); } + public: + std::string Text(const StringPiece& text) override { return text.ToString(); } + std::string Placeholder(const StringPiece& text) override { + return text.ToString(); + } }; class PseudoMethodBidi : public PseudoMethodImpl { -public: - std::u16string text(const StringPiece16& text) override; - std::u16string placeholder(const StringPiece16& text) override; + public: + std::string Text(const StringPiece& text) override; + std::string Placeholder(const StringPiece& text) override; }; class PseudoMethodAccent : public PseudoMethodImpl { -public: - PseudoMethodAccent() : mDepth(0), mWordCount(0), mLength(0) {} - std::u16string start() override; - std::u16string end() override; - std::u16string text(const StringPiece16& text) override; - std::u16string placeholder(const StringPiece16& text) override; -private: - size_t mDepth; - size_t mWordCount; - size_t mLength; + public: + PseudoMethodAccent() : depth_(0), word_count_(0), length_(0) {} + std::string Start() override; + std::string End() override; + std::string Text(const StringPiece& text) override; + std::string Placeholder(const StringPiece& text) override; + + private: + size_t depth_; + size_t word_count_; + size_t length_; }; -Pseudolocalizer::Pseudolocalizer(Method method) : mLastDepth(0) { - setMethod(method); +Pseudolocalizer::Pseudolocalizer(Method method) : last_depth_(0) { + SetMethod(method); } -void Pseudolocalizer::setMethod(Method method) { - switch (method) { +void Pseudolocalizer::SetMethod(Method method) { + switch (method) { case Method::kNone: - mImpl = util::make_unique<PseudoMethodNone>(); - break; + impl_ = util::make_unique<PseudoMethodNone>(); + break; case Method::kAccent: - mImpl = util::make_unique<PseudoMethodAccent>(); - break; + impl_ = util::make_unique<PseudoMethodAccent>(); + break; case Method::kBidi: - mImpl = util::make_unique<PseudoMethodBidi>(); - break; - } + impl_ = util::make_unique<PseudoMethodBidi>(); + break; + } } -std::u16string Pseudolocalizer::text(const StringPiece16& text) { - std::u16string out; - size_t depth = mLastDepth; - size_t lastpos, pos; - const size_t length = text.size(); - const char16_t* str = text.data(); - bool escaped = false; - for (lastpos = pos = 0; pos < length; pos++) { - char16_t c = str[pos]; - if (escaped) { - escaped = false; - continue; - } - if (c == '\'') { - escaped = true; - continue; - } +std::string Pseudolocalizer::Text(const StringPiece& text) { + std::string out; + size_t depth = last_depth_; + size_t lastpos, pos; + const size_t length = text.size(); + const char* str = text.data(); + bool escaped = false; + for (lastpos = pos = 0; pos < length; pos++) { + char16_t c = str[pos]; + if (escaped) { + escaped = false; + continue; + } + if (c == '\'') { + escaped = true; + continue; + } - if (c == k_arg_start) { - depth++; - } else if (c == k_arg_end && depth) { - depth--; - } + if (c == kArgStart) { + depth++; + } else if (c == kArgEnd && depth) { + depth--; + } - if (mLastDepth != depth || pos == length - 1) { - bool pseudo = ((mLastDepth % 2) == 0); - size_t nextpos = pos; - if (!pseudo || depth == mLastDepth) { - nextpos++; - } - size_t size = nextpos - lastpos; - if (size) { - std::u16string chunk = text.substr(lastpos, size).toString(); - if (pseudo) { - chunk = mImpl->text(chunk); - } else if (str[lastpos] == k_arg_start && str[nextpos - 1] == k_arg_end) { - chunk = mImpl->placeholder(chunk); - } - out.append(chunk); - } - if (pseudo && depth < mLastDepth) { // End of message - out.append(mImpl->end()); - } else if (!pseudo && depth > mLastDepth) { // Start of message - out.append(mImpl->start()); - } - lastpos = nextpos; - mLastDepth = depth; + if (last_depth_ != depth || pos == length - 1) { + bool pseudo = ((last_depth_ % 2) == 0); + size_t nextpos = pos; + if (!pseudo || depth == last_depth_) { + nextpos++; + } + size_t size = nextpos - lastpos; + if (size) { + std::string chunk = text.substr(lastpos, size).ToString(); + if (pseudo) { + chunk = impl_->Text(chunk); + } else if (str[lastpos] == kArgStart && str[nextpos - 1] == kArgEnd) { + chunk = impl_->Placeholder(chunk); } + out.append(chunk); + } + if (pseudo && depth < last_depth_) { // End of message + out.append(impl_->End()); + } else if (!pseudo && depth > last_depth_) { // Start of message + out.append(impl_->Start()); + } + lastpos = nextpos; + last_depth_ = depth; } - return out; + } + return out; } -static const char16_t* pseudolocalizeChar(const char16_t c) { - switch (c) { - case 'a': return u"\u00e5"; - case 'b': return u"\u0253"; - case 'c': return u"\u00e7"; - case 'd': return u"\u00f0"; - case 'e': return u"\u00e9"; - case 'f': return u"\u0192"; - case 'g': return u"\u011d"; - case 'h': return u"\u0125"; - case 'i': return u"\u00ee"; - case 'j': return u"\u0135"; - case 'k': return u"\u0137"; - case 'l': return u"\u013c"; - case 'm': return u"\u1e3f"; - case 'n': return u"\u00f1"; - case 'o': return u"\u00f6"; - case 'p': return u"\u00fe"; - case 'q': return u"\u0051"; - case 'r': return u"\u0155"; - case 's': return u"\u0161"; - case 't': return u"\u0163"; - case 'u': return u"\u00fb"; - case 'v': return u"\u0056"; - case 'w': return u"\u0175"; - case 'x': return u"\u0445"; - case 'y': return u"\u00fd"; - case 'z': return u"\u017e"; - case 'A': return u"\u00c5"; - case 'B': return u"\u03b2"; - case 'C': return u"\u00c7"; - case 'D': return u"\u00d0"; - case 'E': return u"\u00c9"; - case 'G': return u"\u011c"; - case 'H': return u"\u0124"; - case 'I': return u"\u00ce"; - case 'J': return u"\u0134"; - case 'K': return u"\u0136"; - case 'L': return u"\u013b"; - case 'M': return u"\u1e3e"; - case 'N': return u"\u00d1"; - case 'O': return u"\u00d6"; - case 'P': return u"\u00de"; - case 'Q': return u"\u0071"; - case 'R': return u"\u0154"; - case 'S': return u"\u0160"; - case 'T': return u"\u0162"; - case 'U': return u"\u00db"; - case 'V': return u"\u03bd"; - case 'W': return u"\u0174"; - case 'X': return u"\u00d7"; - case 'Y': return u"\u00dd"; - case 'Z': return u"\u017d"; - case '!': return u"\u00a1"; - case '?': return u"\u00bf"; - case '$': return u"\u20ac"; - default: return NULL; - } +static const char* PseudolocalizeChar(const char c) { + switch (c) { + case 'a': + return "\u00e5"; + case 'b': + return "\u0253"; + case 'c': + return "\u00e7"; + case 'd': + return "\u00f0"; + case 'e': + return "\u00e9"; + case 'f': + return "\u0192"; + case 'g': + return "\u011d"; + case 'h': + return "\u0125"; + case 'i': + return "\u00ee"; + case 'j': + return "\u0135"; + case 'k': + return "\u0137"; + case 'l': + return "\u013c"; + case 'm': + return "\u1e3f"; + case 'n': + return "\u00f1"; + case 'o': + return "\u00f6"; + case 'p': + return "\u00fe"; + case 'q': + return "\u0051"; + case 'r': + return "\u0155"; + case 's': + return "\u0161"; + case 't': + return "\u0163"; + case 'u': + return "\u00fb"; + case 'v': + return "\u0056"; + case 'w': + return "\u0175"; + case 'x': + return "\u0445"; + case 'y': + return "\u00fd"; + case 'z': + return "\u017e"; + case 'A': + return "\u00c5"; + case 'B': + return "\u03b2"; + case 'C': + return "\u00c7"; + case 'D': + return "\u00d0"; + case 'E': + return "\u00c9"; + case 'G': + return "\u011c"; + case 'H': + return "\u0124"; + case 'I': + return "\u00ce"; + case 'J': + return "\u0134"; + case 'K': + return "\u0136"; + case 'L': + return "\u013b"; + case 'M': + return "\u1e3e"; + case 'N': + return "\u00d1"; + case 'O': + return "\u00d6"; + case 'P': + return "\u00de"; + case 'Q': + return "\u0071"; + case 'R': + return "\u0154"; + case 'S': + return "\u0160"; + case 'T': + return "\u0162"; + case 'U': + return "\u00db"; + case 'V': + return "\u03bd"; + case 'W': + return "\u0174"; + case 'X': + return "\u00d7"; + case 'Y': + return "\u00dd"; + case 'Z': + return "\u017d"; + case '!': + return "\u00a1"; + case '?': + return "\u00bf"; + case '$': + return "\u20ac"; + default: + return nullptr; + } } -static bool isPossibleNormalPlaceholderEnd(const char16_t c) { - switch (c) { - case 's': return true; - case 'S': return true; - case 'c': return true; - case 'C': return true; - case 'd': return true; - case 'o': return true; - case 'x': return true; - case 'X': return true; - case 'f': return true; - case 'e': return true; - case 'E': return true; - case 'g': return true; - case 'G': return true; - case 'a': return true; - case 'A': return true; - case 'b': return true; - case 'B': return true; - case 'h': return true; - case 'H': return true; - case '%': return true; - case 'n': return true; - default: return false; - } +static bool IsPossibleNormalPlaceholderEnd(const char c) { + switch (c) { + case 's': + return true; + case 'S': + return true; + case 'c': + return true; + case 'C': + return true; + case 'd': + return true; + case 'o': + return true; + case 'x': + return true; + case 'X': + return true; + case 'f': + return true; + case 'e': + return true; + case 'E': + return true; + case 'g': + return true; + case 'G': + return true; + case 'a': + return true; + case 'A': + return true; + case 'b': + return true; + case 'B': + return true; + case 'h': + return true; + case 'H': + return true; + case '%': + return true; + case 'n': + return true; + default: + return false; + } } -static std::u16string pseudoGenerateExpansion(const unsigned int length) { - std::u16string result = k_expansion_string; - const char16_t* s = result.data(); - if (result.size() < length) { - result += u" "; - result += pseudoGenerateExpansion(length - result.size()); - } else { - int ext = 0; - // Should contain only whole words, so looking for a space - for (unsigned int i = length + 1; i < result.size(); ++i) { - ++ext; - if (s[i] == ' ') { - break; - } - } - result = result.substr(0, length + ext); +static std::string PseudoGenerateExpansion(const unsigned int length) { + std::string result = kExpansionString; + const char* s = result.data(); + if (result.size() < length) { + result += " "; + result += PseudoGenerateExpansion(length - result.size()); + } else { + int ext = 0; + // Should contain only whole words, so looking for a space + for (unsigned int i = length + 1; i < result.size(); ++i) { + ++ext; + if (s[i] == ' ') { + break; + } } - return result; + result = result.substr(0, length + ext); + } + return result; } -std::u16string PseudoMethodAccent::start() { - std::u16string result; - if (mDepth == 0) { - result = u"["; - } - mWordCount = mLength = 0; - mDepth++; - return result; +std::string PseudoMethodAccent::Start() { + std::string result; + if (depth_ == 0) { + result = "["; + } + word_count_ = length_ = 0; + depth_++; + return result; } -std::u16string PseudoMethodAccent::end() { - std::u16string result; - if (mLength) { - result += u" "; - result += pseudoGenerateExpansion(mWordCount > 3 ? mLength : mLength / 2); - } - mWordCount = mLength = 0; - mDepth--; - if (mDepth == 0) { - result += u"]"; - } - return result; +std::string PseudoMethodAccent::End() { + std::string result; + if (length_) { + result += " "; + result += PseudoGenerateExpansion(word_count_ > 3 ? length_ : length_ / 2); + } + word_count_ = length_ = 0; + depth_--; + if (depth_ == 0) { + result += "]"; + } + return result; } /** @@ -267,128 +349,125 @@ std::u16string PseudoMethodAccent::end() { * * Note: This leaves placeholder syntax untouched. */ -std::u16string PseudoMethodAccent::text(const StringPiece16& source) -{ - const char16_t* s = source.data(); - std::u16string result; - const size_t I = source.size(); - bool lastspace = true; - for (size_t i = 0; i < I; i++) { - char16_t c = s[i]; - if (c == '%') { - // Placeholder syntax, no need to pseudolocalize - std::u16string chunk; - bool end = false; - chunk.append(&c, 1); - while (!end && i < I) { - ++i; - c = s[i]; - chunk.append(&c, 1); - if (isPossibleNormalPlaceholderEnd(c)) { - end = true; - } else if (c == 't') { - ++i; - c = s[i]; - chunk.append(&c, 1); - end = true; - } - } - // Treat chunk as a placeholder unless it ends with %. - result += ((c == '%') ? chunk : placeholder(chunk)); - } else if (c == '<' || c == '&') { - // html syntax, no need to pseudolocalize - bool tag_closed = false; - while (!tag_closed && i < I) { - if (c == '&') { - std::u16string escapeText; - escapeText.append(&c, 1); - bool end = false; - size_t htmlCodePos = i; - while (!end && htmlCodePos < I) { - ++htmlCodePos; - c = s[htmlCodePos]; - escapeText.append(&c, 1); - // Valid html code - if (c == ';') { - end = true; - i = htmlCodePos; - } - // Wrong html code - else if (!((c == '#' || - (c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9')))) { - end = true; - } - } - result += escapeText; - if (escapeText != u"<") { - tag_closed = true; - } - continue; - } - if (c == '>') { - tag_closed = true; - result.append(&c, 1); - continue; - } - result.append(&c, 1); - i++; - c = s[i]; +std::string PseudoMethodAccent::Text(const StringPiece& source) { + const char* s = source.data(); + std::string result; + const size_t I = source.size(); + bool lastspace = true; + for (size_t i = 0; i < I; i++) { + char c = s[i]; + if (c == '%') { + // Placeholder syntax, no need to pseudolocalize + std::string chunk; + bool end = false; + chunk.append(&c, 1); + while (!end && i + 1 < I) { + ++i; + c = s[i]; + chunk.append(&c, 1); + if (IsPossibleNormalPlaceholderEnd(c)) { + end = true; + } else if (i + 1 < I && c == 't') { + ++i; + c = s[i]; + chunk.append(&c, 1); + end = true; + } + } + // Treat chunk as a placeholder unless it ends with %. + result += ((c == '%') ? chunk : Placeholder(chunk)); + } else if (c == '<' || c == '&') { + // html syntax, no need to pseudolocalize + bool tag_closed = false; + while (!tag_closed && i < I) { + if (c == '&') { + std::string escape_text; + escape_text.append(&c, 1); + bool end = false; + size_t html_code_pos = i; + while (!end && html_code_pos < I) { + ++html_code_pos; + c = s[html_code_pos]; + escape_text.append(&c, 1); + // Valid html code + if (c == ';') { + end = true; + i = html_code_pos; } - } else { - // This is a pure text that should be pseudolocalized - const char16_t* p = pseudolocalizeChar(c); - if (p != nullptr) { - result += p; - } else { - bool space = util::isspace16(c); - if (lastspace && !space) { - mWordCount++; - } - lastspace = space; - result.append(&c, 1); + // Wrong html code + else if (!((c == '#' || (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')))) { + end = true; } - // Count only pseudolocalizable chars and delimiters - mLength++; + } + result += escape_text; + if (escape_text != "<") { + tag_closed = true; + } + continue; } - } - return result; -} - -std::u16string PseudoMethodAccent::placeholder(const StringPiece16& source) { - // Surround a placeholder with brackets - return k_placeholder_open + source.toString() + k_placeholder_close; -} - -std::u16string PseudoMethodBidi::text(const StringPiece16& source) { - const char16_t* s = source.data(); - std::u16string result; - bool lastspace = true; - bool space = true; - for (size_t i = 0; i < source.size(); i++) { - char16_t c = s[i]; - space = util::isspace16(c); + if (c == '>') { + tag_closed = true; + result.append(&c, 1); + continue; + } + result.append(&c, 1); + i++; + c = s[i]; + } + } else { + // This is a pure text that should be pseudolocalized + const char* p = PseudolocalizeChar(c); + if (p != nullptr) { + result += p; + } else { + bool space = isspace(c); if (lastspace && !space) { - // Word start - result += k_rlm + k_rlo; - } else if (!lastspace && space) { - // Word end - result += k_pdf + k_rlm; + word_count_++; } lastspace = space; result.append(&c, 1); + } + // Count only pseudolocalizable chars and delimiters + length_++; } - if (!lastspace) { - // End of last word - result += k_pdf + k_rlm; + } + return result; +} + +std::string PseudoMethodAccent::Placeholder(const StringPiece& source) { + // Surround a placeholder with brackets + return kPlaceholderOpen + source.ToString() + kPlaceholderClose; +} + +std::string PseudoMethodBidi::Text(const StringPiece& source) { + const char* s = source.data(); + std::string result; + bool lastspace = true; + bool space = true; + for (size_t i = 0; i < source.size(); i++) { + char c = s[i]; + space = isspace(c); + if (lastspace && !space) { + // Word start + result += kRlm + kRlo; + } else if (!lastspace && space) { + // Word end + result += kPdf + kRlm; } - return result; + lastspace = space; + result.append(&c, 1); + } + if (!lastspace) { + // End of last word + result += kPdf + kRlm; + } + return result; } -std::u16string PseudoMethodBidi::placeholder(const StringPiece16& source) { - // Surround a placeholder with directionality change sequence - return k_rlm + k_rlo + source.toString() + k_pdf + k_rlm; +std::string PseudoMethodBidi::Placeholder(const StringPiece& source) { + // Surround a placeholder with directionality change sequence + return kRlm + kRlo + source.ToString() + kPdf + kRlm; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h index 8818c1725617..a6d2ad037d50 100644 --- a/tools/aapt2/compile/Pseudolocalizer.h +++ b/tools/aapt2/compile/Pseudolocalizer.h @@ -17,42 +17,44 @@ #ifndef AAPT_COMPILE_PSEUDOLOCALIZE_H #define AAPT_COMPILE_PSEUDOLOCALIZE_H +#include <memory> + +#include "android-base/macros.h" + #include "ResourceValues.h" #include "StringPool.h" #include "util/StringPiece.h" -#include <android-base/macros.h> -#include <memory> - namespace aapt { class PseudoMethodImpl { -public: - virtual ~PseudoMethodImpl() {} - virtual std::u16string start() { return {}; } - virtual std::u16string end() { return {}; } - virtual std::u16string text(const StringPiece16& text) = 0; - virtual std::u16string placeholder(const StringPiece16& text) = 0; + public: + virtual ~PseudoMethodImpl() {} + virtual std::string Start() { return {}; } + virtual std::string End() { return {}; } + virtual std::string Text(const StringPiece& text) = 0; + virtual std::string Placeholder(const StringPiece& text) = 0; }; class Pseudolocalizer { -public: - enum class Method { - kNone, - kAccent, - kBidi, - }; - - Pseudolocalizer(Method method); - void setMethod(Method method); - std::u16string start() { return mImpl->start(); } - std::u16string end() { return mImpl->end(); } - std::u16string text(const StringPiece16& text); -private: - std::unique_ptr<PseudoMethodImpl> mImpl; - size_t mLastDepth; + public: + enum class Method { + kNone, + kAccent, + kBidi, + }; + + explicit Pseudolocalizer(Method method); + void SetMethod(Method method); + std::string Start() { return impl_->Start(); } + std::string End() { return impl_->End(); } + std::string Text(const StringPiece& text); + + private: + std::unique_ptr<PseudoMethodImpl> impl_; + size_t last_depth_; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_COMPILE_PSEUDOLOCALIZE_H */ diff --git a/tools/aapt2/compile/Pseudolocalizer_test.cpp b/tools/aapt2/compile/Pseudolocalizer_test.cpp index b0bc2c10fbe0..92eb3b58515f 100644 --- a/tools/aapt2/compile/Pseudolocalizer_test.cpp +++ b/tools/aapt2/compile/Pseudolocalizer_test.cpp @@ -15,213 +15,216 @@ */ #include "compile/Pseudolocalizer.h" -#include "util/Util.h" -#include <androidfw/ResourceTypes.h> -#include <gtest/gtest.h> +#include "test/Test.h" +#include "util/Util.h" namespace aapt { // In this context, 'Axis' represents a particular field in the configuration, // such as language or density. -static ::testing::AssertionResult simpleHelper(const char* input, const char* expected, +static ::testing::AssertionResult SimpleHelper(const char* input, + const char* expected, Pseudolocalizer::Method method) { - Pseudolocalizer pseudo(method); - std::string result = util::utf16ToUtf8( - pseudo.start() + pseudo.text(util::utf8ToUtf16(input)) + pseudo.end()); - if (StringPiece(expected) != result) { - return ::testing::AssertionFailure() << expected << " != " << result; - } - return ::testing::AssertionSuccess(); + Pseudolocalizer pseudo(method); + std::string result = pseudo.Start() + pseudo.Text(input) + pseudo.End(); + if (result != expected) { + return ::testing::AssertionFailure() << expected << " != " << result; + } + return ::testing::AssertionSuccess(); } -static ::testing::AssertionResult compoundHelper(const char* in1, const char* in2, const char *in3, - const char* expected, - Pseudolocalizer::Method method) { - Pseudolocalizer pseudo(method); - std::string result = util::utf16ToUtf8(pseudo.start() + - pseudo.text(util::utf8ToUtf16(in1)) + - pseudo.text(util::utf8ToUtf16(in2)) + - pseudo.text(util::utf8ToUtf16(in3)) + - pseudo.end()); - if (StringPiece(expected) != result) { - return ::testing::AssertionFailure() << expected << " != " << result; - } - return ::testing::AssertionSuccess(); +static ::testing::AssertionResult CompoundHelper( + const char* in1, const char* in2, const char* in3, const char* expected, + Pseudolocalizer::Method method) { + Pseudolocalizer pseudo(method); + std::string result = pseudo.Start() + pseudo.Text(in1) + pseudo.Text(in2) + + pseudo.Text(in3) + pseudo.End(); + if (result != expected) { + return ::testing::AssertionFailure() << expected << " != " << result; + } + return ::testing::AssertionSuccess(); } TEST(PseudolocalizerTest, NoPseudolocalization) { - EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kNone)); - EXPECT_TRUE(simpleHelper("Hello, world", "Hello, world", Pseudolocalizer::Method::kNone)); + EXPECT_TRUE(SimpleHelper("", "", Pseudolocalizer::Method::kNone)); + EXPECT_TRUE(SimpleHelper("Hello, world", "Hello, world", + Pseudolocalizer::Method::kNone)); - EXPECT_TRUE(compoundHelper("Hello,", " world", "", - "Hello, world", Pseudolocalizer::Method::kNone)); + EXPECT_TRUE(CompoundHelper("Hello,", " world", "", "Hello, world", + Pseudolocalizer::Method::kNone)); } TEST(PseudolocalizerTest, PlaintextAccent) { - EXPECT_TRUE(simpleHelper("", "[]", Pseudolocalizer::Method::kAccent)); - EXPECT_TRUE(simpleHelper("Hello, world", - "[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer::Method::kAccent)); - - EXPECT_TRUE(simpleHelper("Hello, %1d", - "[Ĥéļļö, »%1d« one two]", Pseudolocalizer::Method::kAccent)); - - EXPECT_TRUE(simpleHelper("Battery %1d%%", - "[βåţţéŕý »%1d«%% one two]", Pseudolocalizer::Method::kAccent)); - - EXPECT_TRUE(compoundHelper("", "", "", "[]", Pseudolocalizer::Method::kAccent)); - EXPECT_TRUE(compoundHelper("Hello,", " world", "", - "[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(SimpleHelper("", "[]", Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(SimpleHelper("Hello, world", "[Ĥéļļö, ŵöŕļð one two]", + Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE(SimpleHelper("Hello, %1d", "[Ĥéļļö, »%1d« one two]", + Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE(SimpleHelper("Battery %1d%%", "[βåţţéŕý »%1d«%% one two]", + Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE( + SimpleHelper("^1 %", "[^1 % one]", Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE( + CompoundHelper("", "", "", "[]", Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(CompoundHelper("Hello,", " world", "", "[Ĥéļļö, ŵöŕļð one two]", + Pseudolocalizer::Method::kAccent)); } TEST(PseudolocalizerTest, PlaintextBidi) { - EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kBidi)); - EXPECT_TRUE(simpleHelper("word", - "\xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f", - Pseudolocalizer::Method::kBidi)); - EXPECT_TRUE(simpleHelper(" word ", - " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ", - Pseudolocalizer::Method::kBidi)); - EXPECT_TRUE(simpleHelper(" word ", - " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ", - Pseudolocalizer::Method::kBidi)); - EXPECT_TRUE(simpleHelper("hello\n world\n", - "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \ - " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n", - Pseudolocalizer::Method::kBidi)); - EXPECT_TRUE(compoundHelper("hello", "\n ", " world\n", - "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \ - " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n", - Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(SimpleHelper("", "", Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(SimpleHelper( + "word", "\xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f", + Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(SimpleHelper( + " word ", " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ", + Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(SimpleHelper( + " word ", " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ", + Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE( + SimpleHelper("hello\n world\n", + "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" + " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n", + Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(CompoundHelper( + "hello", "\n ", " world\n", + "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" + " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n", + Pseudolocalizer::Method::kBidi)); } TEST(PseudolocalizerTest, SimpleICU) { - // Single-fragment messages - EXPECT_TRUE(simpleHelper("{placeholder}", "[»{placeholder}«]", + // Single-fragment messages + EXPECT_TRUE(SimpleHelper("{placeholder}", "[»{placeholder}«]", + Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(SimpleHelper("{USER} is offline", "[»{USER}« îš öƒƒļîñé one two]", + Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(SimpleHelper("Copy from {path1} to {path2}", + "[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]", + Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(SimpleHelper("Today is {1,date} {1,time}", + "[Ţöðåý îš »{1,date}« »{1,time}« one two]", + Pseudolocalizer::Method::kAccent)); + + // Multi-fragment messages + EXPECT_TRUE(CompoundHelper("{USER}", " ", "is offline", + "[»{USER}« îš öƒƒļîñé one two]", Pseudolocalizer::Method::kAccent)); - EXPECT_TRUE(simpleHelper("{USER} is offline", - "[»{USER}« îš öƒƒļîñé one two]", Pseudolocalizer::Method::kAccent)); - EXPECT_TRUE(simpleHelper("Copy from {path1} to {path2}", + EXPECT_TRUE(CompoundHelper("Copy from ", "{path1}", " to {path2}", "[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]", Pseudolocalizer::Method::kAccent)); - EXPECT_TRUE(simpleHelper("Today is {1,date} {1,time}", - "[Ţöðåý îš »{1,date}« »{1,time}« one two]", - Pseudolocalizer::Method::kAccent)); - - // Multi-fragment messages - EXPECT_TRUE(compoundHelper("{USER}", " ", "is offline", - "[»{USER}« îš öƒƒļîñé one two]", - Pseudolocalizer::Method::kAccent)); - EXPECT_TRUE(compoundHelper("Copy from ", "{path1}", " to {path2}", - "[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]", - Pseudolocalizer::Method::kAccent)); } TEST(PseudolocalizerTest, ICUBidi) { - // Single-fragment messages - EXPECT_TRUE(simpleHelper("{placeholder}", - "\xe2\x80\x8f\xE2\x80\xae{placeholder}\xE2\x80\xac\xe2\x80\x8f", - Pseudolocalizer::Method::kBidi)); - EXPECT_TRUE(simpleHelper( - "{COUNT, plural, one {one} other {other}}", - "{COUNT, plural, " \ - "one {\xe2\x80\x8f\xE2\x80\xaeone\xE2\x80\xac\xe2\x80\x8f} " \ - "other {\xe2\x80\x8f\xE2\x80\xaeother\xE2\x80\xac\xe2\x80\x8f}}", - Pseudolocalizer::Method::kBidi)); + // Single-fragment messages + EXPECT_TRUE(SimpleHelper( + "{placeholder}", + "\xe2\x80\x8f\xE2\x80\xae{placeholder}\xE2\x80\xac\xe2\x80\x8f", + Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(SimpleHelper( + "{COUNT, plural, one {one} other {other}}", + "{COUNT, plural, " + "one {\xe2\x80\x8f\xE2\x80\xaeone\xE2\x80\xac\xe2\x80\x8f} " + "other {\xe2\x80\x8f\xE2\x80\xaeother\xE2\x80\xac\xe2\x80\x8f}}", + Pseudolocalizer::Method::kBidi)); } TEST(PseudolocalizerTest, Escaping) { - // Single-fragment messages - EXPECT_TRUE(simpleHelper("'{USER'} is offline", - "['{ÛŠÉŔ'} îš öƒƒļîñé one two three]", + // Single-fragment messages + EXPECT_TRUE(SimpleHelper("'{USER'} is offline", + "['{ÛŠÉŔ'} îš öƒƒļîñé one two three]", + Pseudolocalizer::Method::kAccent)); + + // Multi-fragment messages + EXPECT_TRUE(CompoundHelper("'{USER}", " ", "''is offline", + "['{ÛŠÉŔ} ''îš öƒƒļîñé one two three]", Pseudolocalizer::Method::kAccent)); - - // Multi-fragment messages - EXPECT_TRUE(compoundHelper("'{USER}", " ", "''is offline", - "['{ÛŠÉŔ} ''îš öƒƒļîñé one two three]", - Pseudolocalizer::Method::kAccent)); } TEST(PseudolocalizerTest, PluralsAndSelects) { - EXPECT_TRUE(simpleHelper( - "{COUNT, plural, one {Delete a file} other {Delete {COUNT} files}}", - "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " \ + EXPECT_TRUE(SimpleHelper( + "{COUNT, plural, one {Delete a file} other {Delete {COUNT} files}}", + "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " + "other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]", + Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE( + SimpleHelper("Distance is {COUNT, plural, one {# mile} other {# miles}}", + "[Ðîšţåñçé îš {COUNT, plural, one {# ḿîļé one two} " + "other {# ḿîļéš one two}}]", + Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE(SimpleHelper( + "{1, select, female {{1} added you} " + "male {{1} added you} other {{1} added you}}", + "[{1, select, female {»{1}« åððéð ýöû one two} " + "male {»{1}« åððéð ýöû one two} other {»{1}« åððéð ýöû one two}}]", + Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE( + CompoundHelper("{COUNT, plural, one {Delete a file} " + "other {Delete ", + "{COUNT}", " files}}", + "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " "other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]", - Pseudolocalizer::Method::kAccent)); - - EXPECT_TRUE(simpleHelper( - "Distance is {COUNT, plural, one {# mile} other {# miles}}", - "[Ðîšţåñçé îš {COUNT, plural, one {# ḿîļé one two} " \ - "other {# ḿîļéš one two}}]", - Pseudolocalizer::Method::kAccent)); - - EXPECT_TRUE(simpleHelper( - "{1, select, female {{1} added you} " \ - "male {{1} added you} other {{1} added you}}", - "[{1, select, female {»{1}« åððéð ýöû one two} " \ - "male {»{1}« åððéð ýöû one two} other {»{1}« åððéð ýöû one two}}]", - Pseudolocalizer::Method::kAccent)); - - EXPECT_TRUE(compoundHelper( - "{COUNT, plural, one {Delete a file} " \ - "other {Delete ", "{COUNT}", " files}}", - "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " \ - "other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]", - Pseudolocalizer::Method::kAccent)); + Pseudolocalizer::Method::kAccent)); } TEST(PseudolocalizerTest, NestedICU) { - EXPECT_TRUE(simpleHelper( - "{person, select, " \ - "female {" \ - "{num_circles, plural," \ - "=0{{person} didn't add you to any of her circles.}" \ - "=1{{person} added you to one of her circles.}" \ - "other{{person} added you to her # circles.}}}" \ - "male {" \ - "{num_circles, plural," \ - "=0{{person} didn't add you to any of his circles.}" \ - "=1{{person} added you to one of his circles.}" \ - "other{{person} added you to his # circles.}}}" \ - "other {" \ - "{num_circles, plural," \ - "=0{{person} didn't add you to any of their circles.}" \ - "=1{{person} added you to one of their circles.}" \ - "other{{person} added you to their # circles.}}}}", - "[{person, select, " \ - "female {" \ - "{num_circles, plural," \ - "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥéŕ çîŕçļéš." \ - " one two three four five}" \ - "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥéŕ çîŕçļéš." \ - " one two three four}" \ - "other{»{person}« åððéð ýöû ţö ĥéŕ # çîŕçļéš." \ - " one two three four}}}" \ - "male {" \ - "{num_circles, plural," \ - "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥîš çîŕçļéš." \ - " one two three four five}" \ - "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥîš çîŕçļéš." \ - " one two three four}" \ - "other{»{person}« åððéð ýöû ţö ĥîš # çîŕçļéš." \ - " one two three four}}}" \ - "other {{num_circles, plural," \ - "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ţĥéîŕ çîŕçļéš." \ - " one two three four five}" \ - "=1{»{person}« åððéð ýöû ţö öñé öƒ ţĥéîŕ çîŕçļéš." \ - " one two three four}" \ - "other{»{person}« åððéð ýöû ţö ţĥéîŕ # çîŕçļéš." \ - " one two three four}}}}]", - Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE( + SimpleHelper("{person, select, " + "female {" + "{num_circles, plural," + "=0{{person} didn't add you to any of her circles.}" + "=1{{person} added you to one of her circles.}" + "other{{person} added you to her # circles.}}}" + "male {" + "{num_circles, plural," + "=0{{person} didn't add you to any of his circles.}" + "=1{{person} added you to one of his circles.}" + "other{{person} added you to his # circles.}}}" + "other {" + "{num_circles, plural," + "=0{{person} didn't add you to any of their circles.}" + "=1{{person} added you to one of their circles.}" + "other{{person} added you to their # circles.}}}}", + "[{person, select, " + "female {" + "{num_circles, plural," + "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥéŕ çîŕçļéš." + " one two three four five}" + "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥéŕ çîŕçļéš." + " one two three four}" + "other{»{person}« åððéð ýöû ţö ĥéŕ # çîŕçļéš." + " one two three four}}}" + "male {" + "{num_circles, plural," + "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥîš çîŕçļéš." + " one two three four five}" + "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥîš çîŕçļéš." + " one two three four}" + "other{»{person}« åððéð ýöû ţö ĥîš # çîŕçļéš." + " one two three four}}}" + "other {{num_circles, plural," + "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ţĥéîŕ çîŕçļéš." + " one two three four five}" + "=1{»{person}« åððéð ýöû ţö öñé öƒ ţĥéîŕ çîŕçļéš." + " one two three four}" + "other{»{person}« åððéð ýöû ţö ţĥéîŕ # çîŕçļéš." + " one two three four}}}}]", + Pseudolocalizer::Method::kAccent)); } TEST(PseudolocalizerTest, RedefineMethod) { - Pseudolocalizer pseudo(Pseudolocalizer::Method::kAccent); - std::u16string result = pseudo.text(u"Hello, "); - pseudo.setMethod(Pseudolocalizer::Method::kNone); - result += pseudo.text(u"world!"); - ASSERT_EQ(StringPiece("Ĥéļļö, world!"), util::utf16ToUtf8(result)); + Pseudolocalizer pseudo(Pseudolocalizer::Method::kAccent); + std::string result = pseudo.Text("Hello, "); + pseudo.SetMethod(Pseudolocalizer::Method::kNone); + result += pseudo.Text("world!"); + ASSERT_EQ(StringPiece("Ĥéļļö, world!"), result); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp index f40689eaeb47..d61a15af0d85 100644 --- a/tools/aapt2/compile/XmlIdCollector.cpp +++ b/tools/aapt2/compile/XmlIdCollector.cpp @@ -14,57 +14,61 @@ * limitations under the License. */ -#include "ResourceUtils.h" -#include "ResourceValues.h" #include "compile/XmlIdCollector.h" -#include "xml/XmlDom.h" #include <algorithm> #include <vector> +#include "ResourceUtils.h" +#include "ResourceValues.h" +#include "xml/XmlDom.h" + namespace aapt { namespace { -static bool cmpName(const SourcedResourceName& a, const ResourceNameRef& b) { - return a.name < b; +static bool cmp_name(const SourcedResourceName& a, const ResourceNameRef& b) { + return a.name < b; } struct IdCollector : public xml::Visitor { - using xml::Visitor::visit; + public: + using xml::Visitor::Visit; - std::vector<SourcedResourceName>* mOutSymbols; + explicit IdCollector(std::vector<SourcedResourceName>* out_symbols) + : out_symbols_(out_symbols) {} - IdCollector(std::vector<SourcedResourceName>* outSymbols) : mOutSymbols(outSymbols) { + void Visit(xml::Element* element) override { + for (xml::Attribute& attr : element->attributes) { + ResourceNameRef name; + bool create = false; + if (ResourceUtils::ParseReference(attr.value, &name, &create, nullptr)) { + if (create && name.type == ResourceType::kId) { + auto iter = std::lower_bound(out_symbols_->begin(), + out_symbols_->end(), name, cmp_name); + if (iter == out_symbols_->end() || iter->name != name) { + out_symbols_->insert(iter, + SourcedResourceName{name.ToResourceName(), + element->line_number}); + } + } + } } - void visit(xml::Element* element) override { - for (xml::Attribute& attr : element->attributes) { - ResourceNameRef name; - bool create = false; - if (ResourceUtils::tryParseReference(attr.value, &name, &create, nullptr)) { - if (create && name.type == ResourceType::kId) { - auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(), - name, cmpName); - if (iter == mOutSymbols->end() || iter->name != name) { - mOutSymbols->insert(iter, SourcedResourceName{ name.toResourceName(), - element->lineNumber }); - } - } - } - } + xml::Visitor::Visit(element); + } - xml::Visitor::visit(element); - } + private: + std::vector<SourcedResourceName>* out_symbols_; }; -} // namespace +} // namespace -bool XmlIdCollector::consume(IAaptContext* context, xml::XmlResource* xmlRes) { - xmlRes->file.exportedSymbols.clear(); - IdCollector collector(&xmlRes->file.exportedSymbols); - xmlRes->root->accept(&collector); - return true; +bool XmlIdCollector::Consume(IAaptContext* context, xml::XmlResource* xmlRes) { + xmlRes->file.exported_symbols.clear(); + IdCollector collector(&xmlRes->file.exported_symbols); + xmlRes->root->Accept(&collector); + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/XmlIdCollector.h b/tools/aapt2/compile/XmlIdCollector.h index 1b149449de2c..8febf0f250dd 100644 --- a/tools/aapt2/compile/XmlIdCollector.h +++ b/tools/aapt2/compile/XmlIdCollector.h @@ -23,9 +23,9 @@ namespace aapt { struct XmlIdCollector : public IXmlResourceConsumer { - bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override; + bool Consume(IAaptContext* context, xml::XmlResource* xml_res) override; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_XMLIDCOLLECTOR_H */ diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp index a37ea86c317f..98da56d03ae3 100644 --- a/tools/aapt2/compile/XmlIdCollector_test.cpp +++ b/tools/aapt2/compile/XmlIdCollector_test.cpp @@ -15,18 +15,17 @@ */ #include "compile/XmlIdCollector.h" -#include "test/Builders.h" -#include "test/Context.h" #include <algorithm> -#include <gtest/gtest.h> + +#include "test/Test.h" namespace aapt { TEST(XmlIdCollectorTest, CollectsIds) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/foo" text="@+id/bar"> @@ -34,28 +33,35 @@ TEST(XmlIdCollectorTest, CollectsIds) { class="@+id/bar"/> </View>)EOF"); - XmlIdCollector collector; - ASSERT_TRUE(collector.consume(context.get(), doc.get())); + XmlIdCollector collector; + ASSERT_TRUE(collector.Consume(context.get(), doc.get())); - EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(), - SourcedResourceName{ test::parseNameOrDie(u"@id/foo"), 3u })); + EXPECT_EQ( + 1, std::count(doc->file.exported_symbols.begin(), + doc->file.exported_symbols.end(), + SourcedResourceName{test::ParseNameOrDie("id/foo"), 3u})); - EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(), - SourcedResourceName{ test::parseNameOrDie(u"@id/bar"), 3u })); + EXPECT_EQ( + 1, std::count(doc->file.exported_symbols.begin(), + doc->file.exported_symbols.end(), + SourcedResourceName{test::ParseNameOrDie("id/bar"), 3u})); - EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(), - SourcedResourceName{ test::parseNameOrDie(u"@id/car"), 6u })); + EXPECT_EQ( + 1, std::count(doc->file.exported_symbols.begin(), + doc->file.exported_symbols.end(), + SourcedResourceName{test::ParseNameOrDie("id/car"), 6u})); } TEST(XmlIdCollectorTest, DontCollectNonIds) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>"); + std::unique_ptr<xml::XmlResource> doc = + test::BuildXmlDom("<View foo=\"@+string/foo\"/>"); - XmlIdCollector collector; - ASSERT_TRUE(collector.consume(context.get(), doc.get())); + XmlIdCollector collector; + ASSERT_TRUE(collector.Consume(context.get(), doc.get())); - EXPECT_TRUE(doc->file.exportedSymbols.empty()); + EXPECT_TRUE(doc->file.exported_symbols.empty()); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/diff/Diff.cpp b/tools/aapt2/diff/Diff.cpp index 20b7b59642ca..593e7ab119f1 100644 --- a/tools/aapt2/diff/Diff.cpp +++ b/tools/aapt2/diff/Diff.cpp @@ -14,398 +14,419 @@ * limitations under the License. */ +#include "android-base/macros.h" + #include "Flags.h" #include "ResourceTable.h" +#include "ValueVisitor.h" #include "io/ZipArchive.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" #include "unflatten/BinaryResourceParser.h" -#include <android-base/macros.h> - namespace aapt { class DiffContext : public IAaptContext { -public: - const std::u16string& getCompilationPackage() override { - return mEmpty; - } + public: + const std::string& GetCompilationPackage() override { return empty_; } - uint8_t getPackageId() override { - return 0x0; - } + uint8_t GetPackageId() override { return 0x0; } - IDiagnostics* getDiagnostics() override { - return &mDiagnostics; - } + IDiagnostics* GetDiagnostics() override { return &diagnostics_; } - NameMangler* getNameMangler() override { - return &mNameMangler; - } + NameMangler* GetNameMangler() override { return &name_mangler_; } - SymbolTable* getExternalSymbols() override { - return &mSymbolTable; - } + SymbolTable* GetExternalSymbols() override { return &symbol_table_; } - bool verbose() override { - return false; - } + bool IsVerbose() override { return false; } + + int GetMinSdkVersion() override { return 0; } -private: - std::u16string mEmpty; - StdErrDiagnostics mDiagnostics; - NameMangler mNameMangler = NameMangler(NameManglerPolicy{}); - SymbolTable mSymbolTable; + private: + std::string empty_; + StdErrDiagnostics diagnostics_; + NameMangler name_mangler_ = NameMangler(NameManglerPolicy{}); + SymbolTable symbol_table_; }; class LoadedApk { -public: - LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk, - std::unique_ptr<ResourceTable> table) : - mSource(source), mApk(std::move(apk)), mTable(std::move(table)) { - } + public: + LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk, + std::unique_ptr<ResourceTable> table) + : source_(source), apk_(std::move(apk)), table_(std::move(table)) {} - io::IFileCollection* getFileCollection() { - return mApk.get(); - } + io::IFileCollection* GetFileCollection() { return apk_.get(); } - ResourceTable* getResourceTable() { - return mTable.get(); - } + ResourceTable* GetResourceTable() { return table_.get(); } - const Source& getSource() { - return mSource; - } + const Source& GetSource() { return source_; } -private: - Source mSource; - std::unique_ptr<io::IFileCollection> mApk; - std::unique_ptr<ResourceTable> mTable; + private: + Source source_; + std::unique_ptr<io::IFileCollection> apk_; + std::unique_ptr<ResourceTable> table_; - DISALLOW_COPY_AND_ASSIGN(LoadedApk); + DISALLOW_COPY_AND_ASSIGN(LoadedApk); }; -static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context, const StringPiece& path) { - Source source(path); - std::string error; - std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::create(path, &error); - if (!apk) { - context->getDiagnostics()->error(DiagMessage(source) << error); - return {}; - } - - io::IFile* file = apk->findFile("resources.arsc"); - if (!file) { - context->getDiagnostics()->error(DiagMessage(source) << "no resources.arsc found"); - return {}; - } - - std::unique_ptr<io::IData> data = file->openAsData(); - if (!data) { - context->getDiagnostics()->error(DiagMessage(source) << "could not open resources.arsc"); - return {}; - } - - std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); - BinaryResourceParser parser(context, table.get(), source, data->data(), data->size()); - if (!parser.parse()) { - return {}; - } - - return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table)); +static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context, + const StringPiece& path) { + Source source(path); + std::string error; + std::unique_ptr<io::ZipFileCollection> apk = + io::ZipFileCollection::Create(path, &error); + if (!apk) { + context->GetDiagnostics()->Error(DiagMessage(source) << error); + return {}; + } + + io::IFile* file = apk->FindFile("resources.arsc"); + if (!file) { + context->GetDiagnostics()->Error(DiagMessage(source) + << "no resources.arsc found"); + return {}; + } + + std::unique_ptr<io::IData> data = file->OpenAsData(); + if (!data) { + context->GetDiagnostics()->Error(DiagMessage(source) + << "could not open resources.arsc"); + return {}; + } + + std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); + BinaryResourceParser parser(context, table.get(), source, data->data(), + data->size()); + if (!parser.Parse()) { + return {}; + } + + return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table)); } -static void emitDiffLine(const Source& source, const StringPiece& message) { - std::cerr << source << ": " << message << "\n"; +static void EmitDiffLine(const Source& source, const StringPiece& message) { + std::cerr << source << ": " << message << "\n"; } -static bool isSymbolVisibilityDifferent(const Symbol& symbolA, const Symbol& symbolB) { - return symbolA.state != symbolB.state; +static bool IsSymbolVisibilityDifferent(const Symbol& symbol_a, + const Symbol& symbol_b) { + return symbol_a.state != symbol_b.state; } template <typename Id> -static bool isIdDiff(const Symbol& symbolA, const Maybe<Id>& idA, - const Symbol& symbolB, const Maybe<Id>& idB) { - if (symbolA.state == SymbolState::kPublic || symbolB.state == SymbolState::kPublic) { - return idA != idB; - } - return false; +static bool IsIdDiff(const Symbol& symbol_a, const Maybe<Id>& id_a, + const Symbol& symbol_b, const Maybe<Id>& id_b) { + if (symbol_a.state == SymbolState::kPublic || + symbol_b.state == SymbolState::kPublic) { + return id_a != id_b; + } + return false; } -static bool emitResourceConfigValueDiff(IAaptContext* context, - LoadedApk* apkA, - ResourceTablePackage* pkgA, - ResourceTableType* typeA, - ResourceEntry* entryA, - ResourceConfigValue* configValueA, - LoadedApk* apkB, - ResourceTablePackage* pkgB, - ResourceTableType* typeB, - ResourceEntry* entryB, - ResourceConfigValue* configValueB) { - Value* valueA = configValueA->value.get(); - Value* valueB = configValueB->value.get(); - if (!valueA->equals(valueB)) { - std::stringstream strStream; - strStream << "value " << pkgA->name << ":" << typeA->type << "/" << entryA->name - << " config=" << configValueA->config << " does not match:\n"; - valueA->print(&strStream); - strStream << "\n vs \n"; - valueB->print(&strStream); - emitDiffLine(apkB->getSource(), strStream.str()); - return true; - } - return false; +static bool EmitResourceConfigValueDiff( + IAaptContext* context, LoadedApk* apk_a, ResourceTablePackage* pkg_a, + ResourceTableType* type_a, ResourceEntry* entry_a, + ResourceConfigValue* config_value_a, LoadedApk* apk_b, + ResourceTablePackage* pkg_b, ResourceTableType* type_b, + ResourceEntry* entry_b, ResourceConfigValue* config_value_b) { + Value* value_a = config_value_a->value.get(); + Value* value_b = config_value_b->value.get(); + if (!value_a->Equals(value_b)) { + std::stringstream str_stream; + str_stream << "value " << pkg_a->name << ":" << type_a->type << "/" + << entry_a->name << " config=" << config_value_a->config + << " does not match:\n"; + value_a->Print(&str_stream); + str_stream << "\n vs \n"; + value_b->Print(&str_stream); + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + return true; + } + return false; } -static bool emitResourceEntryDiff(IAaptContext* context, - LoadedApk* apkA, - ResourceTablePackage* pkgA, - ResourceTableType* typeA, - ResourceEntry* entryA, - LoadedApk* apkB, - ResourceTablePackage* pkgB, - ResourceTableType* typeB, - ResourceEntry* entryB) { - bool diff = false; - for (std::unique_ptr<ResourceConfigValue>& configValueA : entryA->values) { - ResourceConfigValue* configValueB = entryB->findValue(configValueA->config); - if (!configValueB) { - std::stringstream strStream; - strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name - << " config=" << configValueA->config; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; - } else { - diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA, - configValueA.get(), apkB, pkgB, typeB, entryB, - configValueB); - } +static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a, + ResourceTablePackage* pkg_a, + ResourceTableType* type_a, + ResourceEntry* entry_a, LoadedApk* apk_b, + ResourceTablePackage* pkg_b, + ResourceTableType* type_b, + ResourceEntry* entry_b) { + bool diff = false; + for (std::unique_ptr<ResourceConfigValue>& config_value_a : entry_a->values) { + ResourceConfigValue* config_value_b = + entry_b->FindValue(config_value_a->config); + if (!config_value_b) { + std::stringstream str_stream; + str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/" + << entry_a->name << " config=" << config_value_a->config; + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + diff = true; + } else { + diff |= EmitResourceConfigValueDiff( + context, apk_a, pkg_a, type_a, entry_a, config_value_a.get(), apk_b, + pkg_b, type_b, entry_b, config_value_b); } - - // Check for any newly added config values. - for (std::unique_ptr<ResourceConfigValue>& configValueB : entryB->values) { - ResourceConfigValue* configValueA = entryA->findValue(configValueB->config); - if (!configValueA) { - std::stringstream strStream; - strStream << "new config " << pkgB->name << ":" << typeB->type << "/" << entryB->name - << " config=" << configValueB->config; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; - } + } + + // Check for any newly added config values. + for (std::unique_ptr<ResourceConfigValue>& config_value_b : entry_b->values) { + ResourceConfigValue* config_value_a = + entry_a->FindValue(config_value_b->config); + if (!config_value_a) { + std::stringstream str_stream; + str_stream << "new config " << pkg_b->name << ":" << type_b->type << "/" + << entry_b->name << " config=" << config_value_b->config; + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + diff = true; } - return false; + } + return false; } -static bool emitResourceTypeDiff(IAaptContext* context, - LoadedApk* apkA, - ResourceTablePackage* pkgA, - ResourceTableType* typeA, - LoadedApk* apkB, - ResourceTablePackage* pkgB, - ResourceTableType* typeB) { - bool diff = false; - for (std::unique_ptr<ResourceEntry>& entryA : typeA->entries) { - ResourceEntry* entryB = typeB->findEntry(entryA->name); - if (!entryB) { - std::stringstream strStream; - strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; +static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a, + ResourceTablePackage* pkg_a, + ResourceTableType* type_a, LoadedApk* apk_b, + ResourceTablePackage* pkg_b, + ResourceTableType* type_b) { + bool diff = false; + for (std::unique_ptr<ResourceEntry>& entry_a : type_a->entries) { + ResourceEntry* entry_b = type_b->FindEntry(entry_a->name); + if (!entry_b) { + std::stringstream str_stream; + str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/" + << entry_a->name; + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + diff = true; + } else { + if (IsSymbolVisibilityDifferent(entry_a->symbol_status, + entry_b->symbol_status)) { + std::stringstream str_stream; + str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name + << " has different visibility ("; + if (entry_b->symbol_status.state == SymbolState::kPublic) { + str_stream << "PUBLIC"; } else { - if (isSymbolVisibilityDifferent(entryA->symbolStatus, entryB->symbolStatus)) { - std::stringstream strStream; - strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name - << " has different visibility ("; - if (entryB->symbolStatus.state == SymbolState::kPublic) { - strStream << "PUBLIC"; - } else { - strStream << "PRIVATE"; - } - strStream << " vs "; - if (entryA->symbolStatus.state == SymbolState::kPublic) { - strStream << "PUBLIC"; - } else { - strStream << "PRIVATE"; - } - strStream << ")"; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; - } else if (isIdDiff(entryA->symbolStatus, entryA->id, - entryB->symbolStatus, entryB->id)) { - std::stringstream strStream; - strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name - << " has different public ID ("; - if (entryB->id) { - strStream << "0x" << std::hex << entryB->id.value(); - } else { - strStream << "none"; - } - strStream << " vs "; - if (entryA->id) { - strStream << "0x " << std::hex << entryA->id.value(); - } else { - strStream << "none"; - } - strStream << ")"; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; - } - diff |= emitResourceEntryDiff(context, apkA, pkgA, typeA, entryA.get(), - apkB, pkgB, typeB, entryB); + str_stream << "PRIVATE"; } - } - - // Check for any newly added entries. - for (std::unique_ptr<ResourceEntry>& entryB : typeB->entries) { - ResourceEntry* entryA = typeA->findEntry(entryB->name); - if (!entryA) { - std::stringstream strStream; - strStream << "new entry " << pkgB->name << ":" << typeB->type << "/" << entryB->name; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; + str_stream << " vs "; + if (entry_a->symbol_status.state == SymbolState::kPublic) { + str_stream << "PUBLIC"; + } else { + str_stream << "PRIVATE"; + } + str_stream << ")"; + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + diff = true; + } else if (IsIdDiff(entry_a->symbol_status, entry_a->id, + entry_b->symbol_status, entry_b->id)) { + std::stringstream str_stream; + str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name + << " has different public ID ("; + if (entry_b->id) { + str_stream << "0x" << std::hex << entry_b->id.value(); + } else { + str_stream << "none"; + } + str_stream << " vs "; + if (entry_a->id) { + str_stream << "0x " << std::hex << entry_a->id.value(); + } else { + str_stream << "none"; } + str_stream << ")"; + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + diff = true; + } + diff |= + EmitResourceEntryDiff(context, apk_a, pkg_a, type_a, entry_a.get(), + apk_b, pkg_b, type_b, entry_b); } - return diff; + } + + // Check for any newly added entries. + for (std::unique_ptr<ResourceEntry>& entry_b : type_b->entries) { + ResourceEntry* entry_a = type_a->FindEntry(entry_b->name); + if (!entry_a) { + std::stringstream str_stream; + str_stream << "new entry " << pkg_b->name << ":" << type_b->type << "/" + << entry_b->name; + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + diff = true; + } + } + return diff; } -static bool emitResourcePackageDiff(IAaptContext* context, LoadedApk* apkA, - ResourceTablePackage* pkgA, - LoadedApk* apkB, ResourceTablePackage* pkgB) { - bool diff = false; - for (std::unique_ptr<ResourceTableType>& typeA : pkgA->types) { - ResourceTableType* typeB = pkgB->findType(typeA->type); - if (!typeB) { - std::stringstream strStream; - strStream << "missing " << pkgA->name << ":" << typeA->type; - emitDiffLine(apkA->getSource(), strStream.str()); - diff = true; +static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a, + ResourceTablePackage* pkg_a, + LoadedApk* apk_b, + ResourceTablePackage* pkg_b) { + bool diff = false; + for (std::unique_ptr<ResourceTableType>& type_a : pkg_a->types) { + ResourceTableType* type_b = pkg_b->FindType(type_a->type); + if (!type_b) { + std::stringstream str_stream; + str_stream << "missing " << pkg_a->name << ":" << type_a->type; + EmitDiffLine(apk_a->GetSource(), str_stream.str()); + diff = true; + } else { + if (IsSymbolVisibilityDifferent(type_a->symbol_status, + type_b->symbol_status)) { + std::stringstream str_stream; + str_stream << pkg_a->name << ":" << type_a->type + << " has different visibility ("; + if (type_b->symbol_status.state == SymbolState::kPublic) { + str_stream << "PUBLIC"; } else { - if (isSymbolVisibilityDifferent(typeA->symbolStatus, typeB->symbolStatus)) { - std::stringstream strStream; - strStream << pkgA->name << ":" << typeA->type << " has different visibility ("; - if (typeB->symbolStatus.state == SymbolState::kPublic) { - strStream << "PUBLIC"; - } else { - strStream << "PRIVATE"; - } - strStream << " vs "; - if (typeA->symbolStatus.state == SymbolState::kPublic) { - strStream << "PUBLIC"; - } else { - strStream << "PRIVATE"; - } - strStream << ")"; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; - } else if (isIdDiff(typeA->symbolStatus, typeA->id, typeB->symbolStatus, typeB->id)) { - std::stringstream strStream; - strStream << pkgA->name << ":" << typeA->type << " has different public ID ("; - if (typeB->id) { - strStream << "0x" << std::hex << typeB->id.value(); - } else { - strStream << "none"; - } - strStream << " vs "; - if (typeA->id) { - strStream << "0x " << std::hex << typeA->id.value(); - } else { - strStream << "none"; - } - strStream << ")"; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; - } - diff |= emitResourceTypeDiff(context, apkA, pkgA, typeA.get(), apkB, pkgB, typeB); + str_stream << "PRIVATE"; } - } - - // Check for any newly added types. - for (std::unique_ptr<ResourceTableType>& typeB : pkgB->types) { - ResourceTableType* typeA = pkgA->findType(typeB->type); - if (!typeA) { - std::stringstream strStream; - strStream << "new type " << pkgB->name << ":" << typeB->type; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; + str_stream << " vs "; + if (type_a->symbol_status.state == SymbolState::kPublic) { + str_stream << "PUBLIC"; + } else { + str_stream << "PRIVATE"; + } + str_stream << ")"; + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + diff = true; + } else if (IsIdDiff(type_a->symbol_status, type_a->id, + type_b->symbol_status, type_b->id)) { + std::stringstream str_stream; + str_stream << pkg_a->name << ":" << type_a->type + << " has different public ID ("; + if (type_b->id) { + str_stream << "0x" << std::hex << type_b->id.value(); + } else { + str_stream << "none"; } + str_stream << " vs "; + if (type_a->id) { + str_stream << "0x " << std::hex << type_a->id.value(); + } else { + str_stream << "none"; + } + str_stream << ")"; + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + diff = true; + } + diff |= EmitResourceTypeDiff(context, apk_a, pkg_a, type_a.get(), apk_b, + pkg_b, type_b); } - return diff; + } + + // Check for any newly added types. + for (std::unique_ptr<ResourceTableType>& type_b : pkg_b->types) { + ResourceTableType* type_a = pkg_a->FindType(type_b->type); + if (!type_a) { + std::stringstream str_stream; + str_stream << "new type " << pkg_b->name << ":" << type_b->type; + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + diff = true; + } + } + return diff; } -static bool emitResourceTableDiff(IAaptContext* context, LoadedApk* apkA, LoadedApk* apkB) { - ResourceTable* tableA = apkA->getResourceTable(); - ResourceTable* tableB = apkB->getResourceTable(); - - bool diff = false; - for (std::unique_ptr<ResourceTablePackage>& pkgA : tableA->packages) { - ResourceTablePackage* pkgB = tableB->findPackage(pkgA->name); - if (!pkgB) { - std::stringstream strStream; - strStream << "missing package " << pkgA->name; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; +static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a, + LoadedApk* apk_b) { + ResourceTable* table_a = apk_a->GetResourceTable(); + ResourceTable* table_b = apk_b->GetResourceTable(); + + bool diff = false; + for (std::unique_ptr<ResourceTablePackage>& pkg_a : table_a->packages) { + ResourceTablePackage* pkg_b = table_b->FindPackage(pkg_a->name); + if (!pkg_b) { + std::stringstream str_stream; + str_stream << "missing package " << pkg_a->name; + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + diff = true; + } else { + if (pkg_a->id != pkg_b->id) { + std::stringstream str_stream; + str_stream << "package '" << pkg_a->name << "' has different id ("; + if (pkg_b->id) { + str_stream << "0x" << std::hex << pkg_b->id.value(); } else { - if (pkgA->id != pkgB->id) { - std::stringstream strStream; - strStream << "package '" << pkgA->name << "' has different id ("; - if (pkgB->id) { - strStream << "0x" << std::hex << pkgB->id.value(); - } else { - strStream << "none"; - } - strStream << " vs "; - if (pkgA->id) { - strStream << "0x" << std::hex << pkgA->id.value(); - } else { - strStream << "none"; - } - strStream << ")"; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; - } - diff |= emitResourcePackageDiff(context, apkA, pkgA.get(), apkB, pkgB); + str_stream << "none"; } - } - - // Check for any newly added packages. - for (std::unique_ptr<ResourceTablePackage>& pkgB : tableB->packages) { - ResourceTablePackage* pkgA = tableA->findPackage(pkgB->name); - if (!pkgA) { - std::stringstream strStream; - strStream << "new package " << pkgB->name; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; + str_stream << " vs "; + if (pkg_a->id) { + str_stream << "0x" << std::hex << pkg_a->id.value(); + } else { + str_stream << "none"; } + str_stream << ")"; + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + diff = true; + } + diff |= + EmitResourcePackageDiff(context, apk_a, pkg_a.get(), apk_b, pkg_b); + } + } + + // Check for any newly added packages. + for (std::unique_ptr<ResourceTablePackage>& pkg_b : table_b->packages) { + ResourceTablePackage* pkg_a = table_a->FindPackage(pkg_b->name); + if (!pkg_a) { + std::stringstream str_stream; + str_stream << "new package " << pkg_b->name; + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + diff = true; } - return diff; + } + return diff; } -int diff(const std::vector<StringPiece>& args) { - DiffContext context; +class ZeroingReferenceVisitor : public ValueVisitor { + public: + using ValueVisitor::Visit; - Flags flags; - if (!flags.parse("aapt2 diff", args, &std::cerr)) { - return 1; - } - - if (flags.getArgs().size() != 2u) { - std::cerr << "must have two apks as arguments.\n\n"; - flags.usage("aapt2 diff", &std::cerr); - return 1; + void Visit(Reference* ref) override { + if (ref->name && ref->id) { + if (ref->id.value().package_id() == 0x7f) { + ref->id = {}; + } } + } +}; - std::unique_ptr<LoadedApk> apkA = loadApkFromPath(&context, flags.getArgs()[0]); - std::unique_ptr<LoadedApk> apkB = loadApkFromPath(&context, flags.getArgs()[1]); - if (!apkA || !apkB) { - return 1; - } +static void ZeroOutAppReferences(ResourceTable* table) { + ZeroingReferenceVisitor visitor; + VisitAllValuesInTable(table, &visitor); +} - if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) { - // We emitted a diff, so return 1 (failure). - return 1; - } - return 0; +int Diff(const std::vector<StringPiece>& args) { + DiffContext context; + + Flags flags; + if (!flags.Parse("aapt2 diff", args, &std::cerr)) { + return 1; + } + + if (flags.GetArgs().size() != 2u) { + std::cerr << "must have two apks as arguments.\n\n"; + flags.Usage("aapt2 diff", &std::cerr); + return 1; + } + + std::unique_ptr<LoadedApk> apk_a = + LoadApkFromPath(&context, flags.GetArgs()[0]); + std::unique_ptr<LoadedApk> apk_b = + LoadApkFromPath(&context, flags.GetArgs()[1]); + if (!apk_a || !apk_b) { + return 1; + } + + // Zero out Application IDs in references. + ZeroOutAppReferences(apk_a->GetResourceTable()); + ZeroOutAppReferences(apk_b->GetResourceTable()); + + if (EmitResourceTableDiff(&context, apk_a.get(), apk_b.get())) { + // We emitted a diff, so return 1 (failure). + return 1; + } + return 0; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/dump/Dump.cpp b/tools/aapt2/dump/Dump.cpp index 56b9f9a3e081..2920c2abb57a 100644 --- a/tools/aapt2/dump/Dump.cpp +++ b/tools/aapt2/dump/Dump.cpp @@ -14,157 +14,191 @@ * limitations under the License. */ +#include <vector> + #include "Debug.h" #include "Diagnostics.h" #include "Flags.h" #include "io/ZipArchive.h" #include "process/IResourceTableConsumer.h" #include "proto/ProtoSerialize.h" +#include "unflatten/BinaryResourceParser.h" #include "util/Files.h" #include "util/StringPiece.h" -#include <vector> - namespace aapt { -//struct DumpOptions { -// -//}; +void DumpCompiledFile(const pb::CompiledFile& pb_file, const void* data, + size_t len, const Source& source, IAaptContext* context) { + std::unique_ptr<ResourceFile> file = + DeserializeCompiledFileFromPb(pb_file, source, context->GetDiagnostics()); + if (!file) { + context->GetDiagnostics()->Warn(DiagMessage() + << "failed to read compiled file"); + return; + } + + std::cout << "Resource: " << file->name << "\n" + << "Config: " << file->config << "\n" + << "Source: " << file->source << "\n"; +} -void dumpCompiledFile(const pb::CompiledFile& pbFile, const void* data, size_t len, - const Source& source, IAaptContext* context) { - std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(pbFile, source, - context->getDiagnostics()); - if (!file) { +void TryDumpFile(IAaptContext* context, const std::string& file_path) { + std::unique_ptr<ResourceTable> table; + + std::string err; + std::unique_ptr<io::ZipFileCollection> zip = + io::ZipFileCollection::Create(file_path, &err); + if (zip) { + io::IFile* file = zip->FindFile("resources.arsc.flat"); + if (file) { + std::unique_ptr<io::IData> data = file->OpenAsData(); + if (!data) { + context->GetDiagnostics()->Error( + DiagMessage(file_path) << "failed to open resources.arsc.flat"); return; - } + } - std::cout << "Resource: " << file->name << "\n" - << "Config: " << file->config << "\n" - << "Source: " << file->source << "\n"; -} + pb::ResourceTable pb_table; + if (!pb_table.ParseFromArray(data->data(), data->size())) { + context->GetDiagnostics()->Error(DiagMessage(file_path) + << "invalid resources.arsc.flat"); + return; + } -void dumpCompiledTable(const pb::ResourceTable& pbTable, const Source& source, - IAaptContext* context) { - std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, - context->getDiagnostics()); - if (!table) { + table = DeserializeTableFromPb(pb_table, Source(file_path), + context->GetDiagnostics()); + if (!table) { return; + } } - Debug::printTable(table.get()); -} + if (!table) { + file = zip->FindFile("resources.arsc"); + if (file) { + std::unique_ptr<io::IData> data = file->OpenAsData(); + if (!data) { + context->GetDiagnostics()->Error(DiagMessage(file_path) + << "failed to open resources.arsc"); + return; + } -void tryDumpFile(IAaptContext* context, const std::string& filePath) { - std::string err; - std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::create(filePath, &err); - if (zip) { - io::IFile* file = zip->findFile("resources.arsc.flat"); - if (file) { - std::unique_ptr<io::IData> data = file->openAsData(); - if (!data) { - context->getDiagnostics()->error(DiagMessage(filePath) - << "failed to open resources.arsc.flat"); - return; - } - - pb::ResourceTable pbTable; - if (!pbTable.ParseFromArray(data->data(), data->size())) { - context->getDiagnostics()->error(DiagMessage(filePath) - << "invalid resources.arsc.flat"); - return; - } - - std::unique_ptr<ResourceTable> table = deserializeTableFromPb( - pbTable, Source(filePath), context->getDiagnostics()); - if (table) { - DebugPrintTableOptions debugPrintTableOptions; - debugPrintTableOptions.showSources = true; - Debug::printTable(table.get(), debugPrintTableOptions); - } + table = util::make_unique<ResourceTable>(); + BinaryResourceParser parser(context, table.get(), Source(file_path), + data->data(), data->size()); + if (!parser.Parse()) { + return; } - return; + } } + } - Maybe<android::FileMap> file = file::mmapPath(filePath, &err); + if (!table) { + Maybe<android::FileMap> file = file::MmapPath(file_path, &err); if (!file) { - context->getDiagnostics()->error(DiagMessage(filePath) << err); - return; + context->GetDiagnostics()->Error(DiagMessage(file_path) << err); + return; } - android::FileMap* fileMap = &file.value(); + android::FileMap* file_map = &file.value(); // Try as a compiled table. - pb::ResourceTable pbTable; - if (pbTable.ParseFromArray(fileMap->getDataPtr(), fileMap->getDataLength())) { - dumpCompiledTable(pbTable, Source(filePath), context); - return; + pb::ResourceTable pb_table; + if (pb_table.ParseFromArray(file_map->getDataPtr(), + file_map->getDataLength())) { + table = DeserializeTableFromPb(pb_table, Source(file_path), + context->GetDiagnostics()); } - // Try as a compiled file. - CompiledFileInputStream input(fileMap->getDataPtr(), fileMap->getDataLength()); - if (const pb::CompiledFile* pbFile = input.CompiledFile()) { - dumpCompiledFile(*pbFile, input.data(), input.size(), Source(filePath), context); - return; + if (!table) { + // Try as a compiled file. + CompiledFileInputStream input(file_map->getDataPtr(), + file_map->getDataLength()); + + uint32_t num_files = 0; + if (!input.ReadLittleEndian32(&num_files)) { + return; + } + + for (uint32_t i = 0; i < num_files; i++) { + pb::CompiledFile compiled_file; + if (!input.ReadCompiledFile(&compiled_file)) { + context->GetDiagnostics()->Warn(DiagMessage() + << "failed to read compiled file"); + return; + } + + uint64_t offset, len; + if (!input.ReadDataMetaData(&offset, &len)) { + context->GetDiagnostics()->Warn(DiagMessage() + << "failed to read meta data"); + return; + } + + const void* data = + static_cast<const uint8_t*>(file_map->getDataPtr()) + offset; + DumpCompiledFile(compiled_file, data, len, Source(file_path), context); + } } + } + + if (table) { + DebugPrintTableOptions options; + options.show_sources = true; + Debug::PrintTable(table.get(), options); + } } class DumpContext : public IAaptContext { -public: - IDiagnostics* getDiagnostics() override { - return &mDiagnostics; - } + public: + IDiagnostics* GetDiagnostics() override { return &diagnostics_; } - NameMangler* getNameMangler() override { - abort(); - return nullptr; - } + NameMangler* GetNameMangler() override { + abort(); + return nullptr; + } - const std::u16string& getCompilationPackage() override { - static std::u16string empty; - return empty; - } + const std::string& GetCompilationPackage() override { + static std::string empty; + return empty; + } - uint8_t getPackageId() override { - return 0; - } + uint8_t GetPackageId() override { return 0; } - SymbolTable* getExternalSymbols() override { - abort(); - return nullptr; - } + SymbolTable* GetExternalSymbols() override { + abort(); + return nullptr; + } - bool verbose() override { - return mVerbose; - } + bool IsVerbose() override { return verbose_; } - void setVerbose(bool val) { - mVerbose = val; - } + void SetVerbose(bool val) { verbose_ = val; } + + int GetMinSdkVersion() override { return 0; } -private: - StdErrDiagnostics mDiagnostics; - bool mVerbose = false; + private: + StdErrDiagnostics diagnostics_; + bool verbose_ = false; }; /** * Entry point for dump command. */ -int dump(const std::vector<StringPiece>& args) { - bool verbose = false; - Flags flags = Flags() - .optionalSwitch("-v", "increase verbosity of output", &verbose); - if (!flags.parse("aapt2 dump", args, &std::cerr)) { - return 1; - } - - DumpContext context; - context.setVerbose(verbose); - - for (const std::string& arg : flags.getArgs()) { - tryDumpFile(&context, arg); - } - return 0; +int Dump(const std::vector<StringPiece>& args) { + bool verbose = false; + Flags flags = + Flags().OptionalSwitch("-v", "increase verbosity of output", &verbose); + if (!flags.Parse("aapt2 dump", args, &std::cerr)) { + return 1; + } + + DumpContext context; + context.SetVerbose(verbose); + + for (const std::string& arg : flags.GetArgs()) { + TryDumpFile(&context, arg); + } + return 0; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/filter/ConfigFilter.cpp b/tools/aapt2/filter/ConfigFilter.cpp index 68a017d247f6..66aff829d71c 100644 --- a/tools/aapt2/filter/ConfigFilter.cpp +++ b/tools/aapt2/filter/ConfigFilter.cpp @@ -14,64 +14,69 @@ * limitations under the License. */ -#include "ConfigDescription.h" #include "filter/ConfigFilter.h" -#include <androidfw/ResourceTypes.h> +#include "androidfw/ResourceTypes.h" + +#include "ConfigDescription.h" namespace aapt { -void AxisConfigFilter::addConfig(ConfigDescription config) { - uint32_t diffMask = ConfigDescription::defaultConfig().diff(config); +void AxisConfigFilter::AddConfig(ConfigDescription config) { + uint32_t diff_mask = ConfigDescription::DefaultConfig().diff(config); - // Ignore the version - diffMask &= ~android::ResTable_config::CONFIG_VERSION; + // Ignore the version + diff_mask &= ~android::ResTable_config::CONFIG_VERSION; - // Ignore any densities. Those are best handled in --preferred-density - if ((diffMask & android::ResTable_config::CONFIG_DENSITY) != 0) { - config.density = 0; - diffMask &= ~android::ResTable_config::CONFIG_DENSITY; - } + // Ignore any densities. Those are best handled in --preferred-density + if ((diff_mask & android::ResTable_config::CONFIG_DENSITY) != 0) { + config.density = 0; + diff_mask &= ~android::ResTable_config::CONFIG_DENSITY; + } - mConfigs.insert(std::make_pair(config, diffMask)); - mConfigMask |= diffMask; + configs_.insert(std::make_pair(config, diff_mask)); + config_mask_ |= diff_mask; } -bool AxisConfigFilter::match(const ConfigDescription& config) const { - const uint32_t mask = ConfigDescription::defaultConfig().diff(config); - if ((mConfigMask & mask) == 0) { - // The two configurations don't have any common axis. - return true; - } +bool AxisConfigFilter::Match(const ConfigDescription& config) const { + const uint32_t mask = ConfigDescription::DefaultConfig().diff(config); + if ((config_mask_ & mask) == 0) { + // The two configurations don't have any common axis. + return true; + } - uint32_t matchedAxis = 0; - for (const auto& entry : mConfigs) { - const ConfigDescription& target = entry.first; - const uint32_t diffMask = entry.second; - uint32_t diff = target.diff(config); - if ((diff & diffMask) == 0) { - // Mark the axis that was matched. - matchedAxis |= diffMask; - } else if ((diff & diffMask) == android::ResTable_config::CONFIG_LOCALE) { - // If the locales differ, but the languages are the same and - // the locale we are matching only has a language specified, - // we match. - if (config.language[0] && - memcmp(config.language, target.language, sizeof(config.language)) == 0) { - if (config.country[0] == 0) { - matchedAxis |= android::ResTable_config::CONFIG_LOCALE; - } - } - } else if ((diff & diffMask) == android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) { - // Special case if the smallest screen width doesn't match. We check that the - // config being matched has a smaller screen width than the filter specified. - if (config.smallestScreenWidthDp != 0 && - config.smallestScreenWidthDp < target.smallestScreenWidthDp) { - matchedAxis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE; - } + uint32_t matched_axis = 0; + for (const auto& entry : configs_) { + const ConfigDescription& target = entry.first; + const uint32_t diff_mask = entry.second; + uint32_t diff = target.diff(config); + if ((diff & diff_mask) == 0) { + // Mark the axis that was matched. + matched_axis |= diff_mask; + } else if ((diff & diff_mask) == android::ResTable_config::CONFIG_LOCALE) { + // If the locales differ, but the languages are the same and + // the locale we are matching only has a language specified, + // we match. + if (config.language[0] && + memcmp(config.language, target.language, sizeof(config.language)) == + 0) { + if (config.country[0] == 0) { + matched_axis |= android::ResTable_config::CONFIG_LOCALE; } + } + } else if ((diff & diff_mask) == + android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) { + // Special case if the smallest screen width doesn't match. We check that + // the + // config being matched has a smaller screen width than the filter + // specified. + if (config.smallestScreenWidthDp != 0 && + config.smallestScreenWidthDp < target.smallestScreenWidthDp) { + matched_axis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE; + } } - return matchedAxis == (mConfigMask & mask); + } + return matched_axis == (config_mask_ & mask); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/filter/ConfigFilter.h b/tools/aapt2/filter/ConfigFilter.h index 36e9c44255e4..3f1341684912 100644 --- a/tools/aapt2/filter/ConfigFilter.h +++ b/tools/aapt2/filter/ConfigFilter.h @@ -17,45 +17,48 @@ #ifndef AAPT_FILTER_CONFIGFILTER_H #define AAPT_FILTER_CONFIGFILTER_H -#include "ConfigDescription.h" - #include <set> #include <utility> +#include "ConfigDescription.h" + namespace aapt { /** * Matches ConfigDescriptions based on some pattern. */ class IConfigFilter { -public: - virtual ~IConfigFilter() = default; + public: + virtual ~IConfigFilter() = default; - /** - * Returns true if the filter matches the configuration, false otherwise. - */ - virtual bool match(const ConfigDescription& config) const = 0; + /** + * Returns true if the filter matches the configuration, false otherwise. + */ + virtual bool Match(const ConfigDescription& config) const = 0; }; /** - * Implements config axis matching. An axis is one component of a configuration, like screen - * density or locale. If an axis is specified in the filter, and the axis is specified in - * the configuration to match, they must be compatible. Otherwise the configuration to match is + * Implements config axis matching. An axis is one component of a configuration, + * like screen + * density or locale. If an axis is specified in the filter, and the axis is + * specified in + * the configuration to match, they must be compatible. Otherwise the + * configuration to match is * accepted. * * Used when handling "-c" options. */ class AxisConfigFilter : public IConfigFilter { -public: - void addConfig(ConfigDescription config); + public: + void AddConfig(ConfigDescription config); - bool match(const ConfigDescription& config) const override; + bool Match(const ConfigDescription& config) const override; -private: - std::set<std::pair<ConfigDescription, uint32_t>> mConfigs; - uint32_t mConfigMask = 0; + private: + std::set<std::pair<ConfigDescription, uint32_t>> configs_; + uint32_t config_mask_ = 0; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_FILTER_CONFIGFILTER_H */ diff --git a/tools/aapt2/filter/ConfigFilter_test.cpp b/tools/aapt2/filter/ConfigFilter_test.cpp index f6b49557306d..586dd5f68f1b 100644 --- a/tools/aapt2/filter/ConfigFilter_test.cpp +++ b/tools/aapt2/filter/ConfigFilter_test.cpp @@ -15,98 +15,98 @@ */ #include "filter/ConfigFilter.h" -#include "test/Common.h" -#include <gtest/gtest.h> +#include "test/Test.h" namespace aapt { TEST(ConfigFilterTest, EmptyFilterMatchesAnything) { - AxisConfigFilter filter; + AxisConfigFilter filter; - EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi"))); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr"))); + EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("320dpi"))); + EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("fr"))); } TEST(ConfigFilterTest, MatchesConfigWithUnrelatedAxis) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("fr")); + AxisConfigFilter filter; + filter.AddConfig(test::ParseConfigOrDie("fr")); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi"))); + EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("320dpi"))); } TEST(ConfigFilterTest, MatchesConfigWithSameValueAxis) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("fr")); + AxisConfigFilter filter; + filter.AddConfig(test::ParseConfigOrDie("fr")); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr"))); + EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("fr"))); } TEST(ConfigFilterTest, MatchesConfigWithSameValueAxisAndOtherUnrelatedAxis) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("fr")); + AxisConfigFilter filter; + filter.AddConfig(test::ParseConfigOrDie("fr")); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-320dpi"))); + EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("fr-320dpi"))); } TEST(ConfigFilterTest, MatchesConfigWithOneMatchingAxis) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("fr-rFR")); - filter.addConfig(test::parseConfigOrDie("sw360dp")); - filter.addConfig(test::parseConfigOrDie("normal")); - filter.addConfig(test::parseConfigOrDie("en-rUS")); + AxisConfigFilter filter; + filter.AddConfig(test::ParseConfigOrDie("fr-rFR")); + filter.AddConfig(test::ParseConfigOrDie("sw360dp")); + filter.AddConfig(test::ParseConfigOrDie("normal")); + filter.AddConfig(test::ParseConfigOrDie("en-rUS")); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("en"))); + EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("en"))); } TEST(ConfigFilterTest, DoesNotMatchConfigWithDifferentValueAxis) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("fr")); + AxisConfigFilter filter; + filter.AddConfig(test::ParseConfigOrDie("fr")); - EXPECT_FALSE(filter.match(test::parseConfigOrDie("de"))); + EXPECT_FALSE(filter.Match(test::ParseConfigOrDie("de"))); } TEST(ConfigFilterTest, DoesNotMatchWhenOneQualifierIsExplicitlyNotMatched) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("fr-rFR")); - filter.addConfig(test::parseConfigOrDie("en-rUS")); - filter.addConfig(test::parseConfigOrDie("normal")); - filter.addConfig(test::parseConfigOrDie("large")); - filter.addConfig(test::parseConfigOrDie("xxhdpi")); - filter.addConfig(test::parseConfigOrDie("sw320dp")); - - EXPECT_FALSE(filter.match(test::parseConfigOrDie("fr-sw600dp-v13"))); + AxisConfigFilter filter; + filter.AddConfig(test::ParseConfigOrDie("fr-rFR")); + filter.AddConfig(test::ParseConfigOrDie("en-rUS")); + filter.AddConfig(test::ParseConfigOrDie("normal")); + filter.AddConfig(test::ParseConfigOrDie("large")); + filter.AddConfig(test::ParseConfigOrDie("xxhdpi")); + filter.AddConfig(test::ParseConfigOrDie("sw320dp")); + + EXPECT_FALSE(filter.Match(test::ParseConfigOrDie("fr-sw600dp-v13"))); } TEST(ConfigFilterTest, MatchesSmallestWidthWhenSmaller) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("sw600dp")); + AxisConfigFilter filter; + filter.AddConfig(test::ParseConfigOrDie("sw600dp")); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-sw320dp-v13"))); + EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("fr-sw320dp-v13"))); } TEST(ConfigFilterTest, MatchesConfigWithSameLanguageButNoRegionSpecified) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("de-rDE")); + AxisConfigFilter filter; + filter.AddConfig(test::ParseConfigOrDie("de-rDE")); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("de"))); + EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("de"))); } TEST(ConfigFilterTest, IgnoresVersion) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("normal-v4")); + AxisConfigFilter filter; + filter.AddConfig(test::ParseConfigOrDie("normal-v4")); - // The configs don't match on any axis besides version, which should be ignored. - EXPECT_TRUE(filter.match(test::parseConfigOrDie("sw600dp-v13"))); + // The configs don't match on any axis besides version, which should be + // ignored. + EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("sw600dp-v13"))); } TEST(ConfigFilterTest, MatchesConfigWithRegion) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("kok")); - filter.addConfig(test::parseConfigOrDie("kok-rIN")); - filter.addConfig(test::parseConfigOrDie("kok-v419")); + AxisConfigFilter filter; + filter.AddConfig(test::ParseConfigOrDie("kok")); + filter.AddConfig(test::ParseConfigOrDie("kok-rIN")); + filter.AddConfig(test::ParseConfigOrDie("kok-v419")); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("kok-rIN"))); + EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("kok-rIN"))); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp index 3a244c05efec..47de0a3b8d6c 100644 --- a/tools/aapt2/flatten/Archive.cpp +++ b/tools/aapt2/flatten/Archive.cpp @@ -15,170 +15,186 @@ */ #include "flatten/Archive.h" -#include "util/Files.h" -#include "util/StringPiece.h" #include <cstdio> #include <memory> #include <string> #include <vector> -#include <ziparchive/zip_writer.h> + +#include "android-base/macros.h" +#include "ziparchive/zip_writer.h" + +#include "util/Files.h" +#include "util/StringPiece.h" namespace aapt { namespace { -struct DirectoryWriter : public IArchiveWriter { - std::string mOutDir; - std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose }; - - bool open(IDiagnostics* diag, const StringPiece& outDir) { - mOutDir = outDir.toString(); - file::FileType type = file::getFileType(mOutDir); - if (type == file::FileType::kNonexistant) { - diag->error(DiagMessage() << "directory " << mOutDir << " does not exist"); - return false; - } else if (type != file::FileType::kDirectory) { - diag->error(DiagMessage() << mOutDir << " is not a directory"); - return false; - } - return true; - } - - bool startEntry(const StringPiece& path, uint32_t flags) override { - if (mFile) { - return false; - } - - std::string fullPath = mOutDir; - file::appendPath(&fullPath, path); - file::mkdirs(file::getStem(fullPath)); - - mFile = { fopen(fullPath.data(), "wb"), fclose }; - if (!mFile) { - return false; - } - return true; - } - - bool writeEntry(const BigBuffer& buffer) override { - if (!mFile) { - return false; - } - - for (const BigBuffer::Block& b : buffer) { - if (fwrite(b.buffer.get(), 1, b.size, mFile.get()) != b.size) { - mFile.reset(nullptr); - return false; - } - } - return true; - } - - bool writeEntry(const void* data, size_t len) override { - if (fwrite(data, 1, len, mFile.get()) != len) { - mFile.reset(nullptr); - return false; - } - return true; - } - - bool finishEntry() override { - if (!mFile) { - return false; - } - mFile.reset(nullptr); - return true; +class DirectoryWriter : public IArchiveWriter { + public: + DirectoryWriter() = default; + + bool Open(IDiagnostics* diag, const StringPiece& out_dir) { + dir_ = out_dir.ToString(); + file::FileType type = file::GetFileType(dir_); + if (type == file::FileType::kNonexistant) { + diag->Error(DiagMessage() << "directory " << dir_ << " does not exist"); + return false; + } else if (type != file::FileType::kDirectory) { + diag->Error(DiagMessage() << dir_ << " is not a directory"); + return false; + } + return true; + } + + bool StartEntry(const StringPiece& path, uint32_t flags) override { + if (file_) { + return false; + } + + std::string full_path = dir_; + file::AppendPath(&full_path, path); + file::mkdirs(file::GetStem(full_path)); + + file_ = {fopen(full_path.data(), "wb"), fclose}; + if (!file_) { + return false; + } + return true; + } + + bool WriteEntry(const BigBuffer& buffer) override { + if (!file_) { + return false; } -}; -struct ZipFileWriter : public IArchiveWriter { - std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose }; - std::unique_ptr<ZipWriter> mWriter; + for (const BigBuffer::Block& b : buffer) { + if (fwrite(b.buffer.get(), 1, b.size, file_.get()) != b.size) { + file_.reset(nullptr); + return false; + } + } + return true; + } + + bool WriteEntry(const void* data, size_t len) override { + if (fwrite(data, 1, len, file_.get()) != len) { + file_.reset(nullptr); + return false; + } + return true; + } - bool open(IDiagnostics* diag, const StringPiece& path) { - mFile = { fopen(path.data(), "w+b"), fclose }; - if (!mFile) { - diag->error(DiagMessage() << "failed to open " << path << ": " << strerror(errno)); - return false; - } - mWriter = util::make_unique<ZipWriter>(mFile.get()); - return true; + bool FinishEntry() override { + if (!file_) { + return false; } + file_.reset(nullptr); + return true; + } - bool startEntry(const StringPiece& path, uint32_t flags) override { - if (!mWriter) { - return false; - } + private: + DISALLOW_COPY_AND_ASSIGN(DirectoryWriter); - size_t zipFlags = 0; - if (flags & ArchiveEntry::kCompress) { - zipFlags |= ZipWriter::kCompress; - } + std::string dir_; + std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose}; +}; - if (flags & ArchiveEntry::kAlign) { - zipFlags |= ZipWriter::kAlign32; - } +class ZipFileWriter : public IArchiveWriter { + public: + ZipFileWriter() = default; - int32_t result = mWriter->StartEntry(path.data(), zipFlags); - if (result != 0) { - return false; - } - return true; + bool Open(IDiagnostics* diag, const StringPiece& path) { + file_ = {fopen(path.data(), "w+b"), fclose}; + if (!file_) { + diag->Error(DiagMessage() << "failed to Open " << path << ": " + << strerror(errno)); + return false; } + writer_ = util::make_unique<ZipWriter>(file_.get()); + return true; + } - bool writeEntry(const void* data, size_t len) override { - int32_t result = mWriter->WriteBytes(data, len); - if (result != 0) { - return false; - } - return true; + bool StartEntry(const StringPiece& path, uint32_t flags) override { + if (!writer_) { + return false; } - bool writeEntry(const BigBuffer& buffer) override { - for (const BigBuffer::Block& b : buffer) { - int32_t result = mWriter->WriteBytes(b.buffer.get(), b.size); - if (result != 0) { - return false; - } - } - return true; + size_t zip_flags = 0; + if (flags & ArchiveEntry::kCompress) { + zip_flags |= ZipWriter::kCompress; } - bool finishEntry() override { - int32_t result = mWriter->FinishEntry(); - if (result != 0) { - return false; - } - return true; + if (flags & ArchiveEntry::kAlign) { + zip_flags |= ZipWriter::kAlign32; } - virtual ~ZipFileWriter() { - if (mWriter) { - mWriter->Finish(); - } + int32_t result = writer_->StartEntry(path.data(), zip_flags); + if (result != 0) { + return false; } -}; + return true; + } -} // namespace + bool WriteEntry(const void* data, size_t len) override { + int32_t result = writer_->WriteBytes(data, len); + if (result != 0) { + return false; + } + return true; + } + + bool WriteEntry(const BigBuffer& buffer) override { + for (const BigBuffer::Block& b : buffer) { + int32_t result = writer_->WriteBytes(b.buffer.get(), b.size); + if (result != 0) { + return false; + } + } + return true; + } -std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag, - const StringPiece& path) { + bool FinishEntry() override { + int32_t result = writer_->FinishEntry(); + if (result != 0) { + return false; + } + return true; + } - std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>(); - if (!writer->open(diag, path)) { - return {}; + virtual ~ZipFileWriter() { + if (writer_) { + writer_->Finish(); } - return std::move(writer); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ZipFileWriter); + + std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose}; + std::unique_ptr<ZipWriter> writer_; +}; + +} // namespace + +std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter( + IDiagnostics* diag, const StringPiece& path) { + std::unique_ptr<DirectoryWriter> writer = + util::make_unique<DirectoryWriter>(); + if (!writer->Open(diag, path)) { + return {}; + } + return std::move(writer); } -std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag, - const StringPiece& path) { - std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>(); - if (!writer->open(diag, path)) { - return {}; - } - return std::move(writer); +std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter( + IDiagnostics* diag, const StringPiece& path) { + std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>(); + if (!writer->Open(diag, path)) { + return {}; + } + return std::move(writer); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h index 34c10ad40365..4fcb3ffa2b78 100644 --- a/tools/aapt2/flatten/Archive.h +++ b/tools/aapt2/flatten/Archive.h @@ -17,50 +17,52 @@ #ifndef AAPT_FLATTEN_ARCHIVE_H #define AAPT_FLATTEN_ARCHIVE_H -#include "Diagnostics.h" -#include "util/BigBuffer.h" -#include "util/Files.h" -#include "util/StringPiece.h" - -#include <google/protobuf/io/zero_copy_stream_impl_lite.h> #include <fstream> #include <memory> #include <string> #include <vector> +#include "google/protobuf/io/zero_copy_stream_impl_lite.h" + +#include "Diagnostics.h" +#include "util/BigBuffer.h" +#include "util/Files.h" +#include "util/StringPiece.h" + namespace aapt { struct ArchiveEntry { - enum : uint32_t { - kCompress = 0x01, - kAlign = 0x02, - }; + enum : uint32_t { + kCompress = 0x01, + kAlign = 0x02, + }; - std::string path; - uint32_t flags; - size_t uncompressedSize; + std::string path; + uint32_t flags; + size_t uncompressed_size; }; -struct IArchiveWriter : public google::protobuf::io::CopyingOutputStream { - virtual ~IArchiveWriter() = default; +class IArchiveWriter : public google::protobuf::io::CopyingOutputStream { + public: + virtual ~IArchiveWriter() = default; - virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0; - virtual bool writeEntry(const BigBuffer& buffer) = 0; - virtual bool writeEntry(const void* data, size_t len) = 0; - virtual bool finishEntry() = 0; + virtual bool StartEntry(const StringPiece& path, uint32_t flags) = 0; + virtual bool WriteEntry(const BigBuffer& buffer) = 0; + virtual bool WriteEntry(const void* data, size_t len) = 0; + virtual bool FinishEntry() = 0; - // CopyingOutputStream implementations. - bool Write(const void* buffer, int size) override { - return writeEntry(buffer, size); - } + // CopyingOutputStream implementations. + bool Write(const void* buffer, int size) override { + return WriteEntry(buffer, size); + } }; -std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag, - const StringPiece& path); +std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter( + IDiagnostics* diag, const StringPiece& path); -std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag, - const StringPiece& path); +std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter( + IDiagnostics* diag, const StringPiece& path); -} // namespace aapt +} // namespace aapt #endif /* AAPT_FLATTEN_ARCHIVE_H */ diff --git a/tools/aapt2/flatten/ChunkWriter.h b/tools/aapt2/flatten/ChunkWriter.h index de1d87a57e6d..968d3eef48ec 100644 --- a/tools/aapt2/flatten/ChunkWriter.h +++ b/tools/aapt2/flatten/ChunkWriter.h @@ -17,71 +17,64 @@ #ifndef AAPT_FLATTEN_CHUNKWRITER_H #define AAPT_FLATTEN_CHUNKWRITER_H +#include "android-base/macros.h" +#include "androidfw/ResourceTypes.h" + #include "util/BigBuffer.h" #include "util/Util.h" -#include <androidfw/ResourceTypes.h> - namespace aapt { class ChunkWriter { -private: - BigBuffer* mBuffer; - size_t mStartSize = 0; - android::ResChunk_header* mHeader = nullptr; - -public: - explicit inline ChunkWriter(BigBuffer* buffer) : mBuffer(buffer) { - } - - ChunkWriter(const ChunkWriter&) = delete; - ChunkWriter& operator=(const ChunkWriter&) = delete; - ChunkWriter(ChunkWriter&&) = default; - ChunkWriter& operator=(ChunkWriter&&) = default; - - template <typename T> - inline T* startChunk(uint16_t type) { - mStartSize = mBuffer->size(); - T* chunk = mBuffer->nextBlock<T>(); - mHeader = &chunk->header; - mHeader->type = util::hostToDevice16(type); - mHeader->headerSize = util::hostToDevice16(sizeof(T)); - return chunk; - } - - template <typename T> - inline T* nextBlock(size_t count = 1) { - return mBuffer->nextBlock<T>(count); - } - - inline BigBuffer* getBuffer() { - return mBuffer; - } - - inline android::ResChunk_header* getChunkHeader() { - return mHeader; - } - - inline size_t size() { - return mBuffer->size() - mStartSize; - } - - inline android::ResChunk_header* finish() { - mBuffer->align4(); - mHeader->size = util::hostToDevice32(mBuffer->size() - mStartSize); - return mHeader; - } + public: + explicit inline ChunkWriter(BigBuffer* buffer) : buffer_(buffer) {} + ChunkWriter(ChunkWriter&&) = default; + ChunkWriter& operator=(ChunkWriter&&) = default; + + template <typename T> + inline T* StartChunk(uint16_t type) { + start_size_ = buffer_->size(); + T* chunk = buffer_->NextBlock<T>(); + header_ = &chunk->header; + header_->type = util::HostToDevice16(type); + header_->headerSize = util::HostToDevice16(sizeof(T)); + return chunk; + } + + template <typename T> + inline T* NextBlock(size_t count = 1) { + return buffer_->NextBlock<T>(count); + } + + inline BigBuffer* buffer() { return buffer_; } + + inline android::ResChunk_header* chunk_header() { return header_; } + + inline size_t size() { return buffer_->size() - start_size_; } + + inline android::ResChunk_header* Finish() { + buffer_->Align4(); + header_->size = util::HostToDevice32(buffer_->size() - start_size_); + return header_; + } + + private: + DISALLOW_COPY_AND_ASSIGN(ChunkWriter); + + BigBuffer* buffer_; + size_t start_size_ = 0; + android::ResChunk_header* header_ = nullptr; }; template <> -inline android::ResChunk_header* ChunkWriter::startChunk(uint16_t type) { - mStartSize = mBuffer->size(); - mHeader = mBuffer->nextBlock<android::ResChunk_header>(); - mHeader->type = util::hostToDevice16(type); - mHeader->headerSize = util::hostToDevice16(sizeof(android::ResChunk_header)); - return mHeader; +inline android::ResChunk_header* ChunkWriter::StartChunk(uint16_t type) { + start_size_ = buffer_->size(); + header_ = buffer_->NextBlock<android::ResChunk_header>(); + header_->type = util::HostToDevice16(type); + header_->headerSize = util::HostToDevice16(sizeof(android::ResChunk_header)); + return header_; } -} // namespace aapt +} // namespace aapt #endif /* AAPT_FLATTEN_CHUNKWRITER_H */ diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h index 3e20ad643eb6..6359b4143f1e 100644 --- a/tools/aapt2/flatten/ResourceTypeExtensions.h +++ b/tools/aapt2/flatten/ResourceTypeExtensions.h @@ -17,20 +17,21 @@ #ifndef AAPT_RESOURCE_TYPE_EXTENSIONS_H #define AAPT_RESOURCE_TYPE_EXTENSIONS_H -#include <androidfw/ResourceTypes.h> +#include "androidfw/ResourceTypes.h" namespace aapt { /** - * An alternative struct to use instead of ResTable_map_entry. This one is a standard_layout + * An alternative struct to use instead of ResTable_map_entry. This one is a + * standard_layout * struct. */ struct ResTable_entry_ext { - android::ResTable_entry entry; - android::ResTable_ref parent; - uint32_t count; + android::ResTable_entry entry; + android::ResTable_ref parent; + uint32_t count; }; -} // namespace aapt +} // namespace aapt -#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H +#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp index 28a792820de3..19d030ec4a25 100644 --- a/tools/aapt2/flatten/TableFlattener.cpp +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -14,20 +14,23 @@ * limitations under the License. */ +#include "flatten/TableFlattener.h" + +#include <algorithm> +#include <numeric> +#include <sstream> +#include <type_traits> + +#include "android-base/logging.h" +#include "android-base/macros.h" + #include "ResourceTable.h" #include "ResourceValues.h" #include "ValueVisitor.h" - #include "flatten/ChunkWriter.h" #include "flatten/ResourceTypeExtensions.h" -#include "flatten/TableFlattener.h" #include "util/BigBuffer.h" -#include <android-base/macros.h> -#include <algorithm> -#include <type_traits> -#include <numeric> - using namespace android; namespace aapt { @@ -35,438 +38,458 @@ namespace aapt { namespace { template <typename T> -static bool cmpIds(const T* a, const T* b) { - return a->id.value() < b->id.value(); +static bool cmp_ids(const T* a, const T* b) { + return a->id.value() < b->id.value(); } static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) { - if (len == 0) { - return; - } - - size_t i; - const char16_t* srcData = src.data(); - for (i = 0; i < len - 1 && i < src.size(); i++) { - dst[i] = util::hostToDevice16((uint16_t) srcData[i]); - } - dst[i] = 0; + if (len == 0) { + return; + } + + size_t i; + const char16_t* src_data = src.data(); + for (i = 0; i < len - 1 && i < src.size(); i++) { + dst[i] = util::HostToDevice16((uint16_t)src_data[i]); + } + dst[i] = 0; } -static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) { - if (a.key.id) { - if (b.key.id) { - return a.key.id.value() < b.key.id.value(); - } - return true; - } else if (!b.key.id) { - return a.key.name.value() < b.key.name.value(); - } - return false; +static bool cmp_style_entries(const Style::Entry& a, const Style::Entry& b) { + if (a.key.id) { + if (b.key.id) { + return a.key.id.value() < b.key.id.value(); + } + return true; + } else if (!b.key.id) { + return a.key.name.value() < b.key.name.value(); + } + return false; } struct FlatEntry { - ResourceEntry* entry; - Value* value; + ResourceEntry* entry; + Value* value; - // The entry string pool index to the entry's name. - uint32_t entryKey; + // The entry string pool index to the entry's name. + uint32_t entry_key; }; class MapFlattenVisitor : public RawValueVisitor { -public: - using RawValueVisitor::visit; + public: + using RawValueVisitor::Visit; - MapFlattenVisitor(ResTable_entry_ext* outEntry, BigBuffer* buffer) : - mOutEntry(outEntry), mBuffer(buffer) { - } + MapFlattenVisitor(ResTable_entry_ext* out_entry, BigBuffer* buffer) + : out_entry_(out_entry), buffer_(buffer) {} - void visit(Attribute* attr) override { - { - Reference key = Reference(ResTable_map::ATTR_TYPE); - BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask); - flattenEntry(&key, &val); - } - - if (attr->minInt != std::numeric_limits<int32_t>::min()) { - Reference key = Reference(ResTable_map::ATTR_MIN); - BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->minInt)); - flattenEntry(&key, &val); - } - - if (attr->maxInt != std::numeric_limits<int32_t>::max()) { - Reference key = Reference(ResTable_map::ATTR_MAX); - BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->maxInt)); - flattenEntry(&key, &val); - } - - for (Attribute::Symbol& s : attr->symbols) { - BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value); - flattenEntry(&s.symbol, &val); - } + void Visit(Attribute* attr) override { + { + Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE)); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->type_mask); + FlattenEntry(&key, &val); } - void visit(Style* style) override { - if (style->parent) { - const Reference& parentRef = style->parent.value(); - assert(parentRef.id && "parent has no ID"); - mOutEntry->parent.ident = util::hostToDevice32(parentRef.id.value().id); - } - - // Sort the style. - std::sort(style->entries.begin(), style->entries.end(), cmpStyleEntries); - - for (Style::Entry& entry : style->entries) { - flattenEntry(&entry.key, entry.value.get()); - } + if (attr->min_int != std::numeric_limits<int32_t>::min()) { + Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN)); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, + static_cast<uint32_t>(attr->min_int)); + FlattenEntry(&key, &val); } - void visit(Styleable* styleable) override { - for (auto& attrRef : styleable->entries) { - BinaryPrimitive val(Res_value{}); - flattenEntry(&attrRef, &val); - } - + if (attr->max_int != std::numeric_limits<int32_t>::max()) { + Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX)); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, + static_cast<uint32_t>(attr->max_int)); + FlattenEntry(&key, &val); } - void visit(Array* array) override { - for (auto& item : array->items) { - ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>(); - flattenValue(item.get(), outEntry); - outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value)); - mEntryCount++; - } + for (Attribute::Symbol& s : attr->symbols) { + BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value); + FlattenEntry(&s.symbol, &val); } + } - void visit(Plural* plural) override { - const size_t count = plural->values.size(); - for (size_t i = 0; i < count; i++) { - if (!plural->values[i]) { - continue; - } - - ResourceId q; - switch (i) { - case Plural::Zero: - q.id = android::ResTable_map::ATTR_ZERO; - break; - - case Plural::One: - q.id = android::ResTable_map::ATTR_ONE; - break; - - case Plural::Two: - q.id = android::ResTable_map::ATTR_TWO; - break; - - case Plural::Few: - q.id = android::ResTable_map::ATTR_FEW; - break; - - case Plural::Many: - q.id = android::ResTable_map::ATTR_MANY; - break; - - case Plural::Other: - q.id = android::ResTable_map::ATTR_OTHER; - break; - - default: - assert(false); - break; - } - - Reference key(q); - flattenEntry(&key, plural->values[i].get()); - } + void Visit(Style* style) override { + if (style->parent) { + const Reference& parent_ref = style->parent.value(); + CHECK(bool(parent_ref.id)) << "parent has no ID"; + out_entry_->parent.ident = util::HostToDevice32(parent_ref.id.value().id); } - /** - * Call this after visiting a Value. This will finish any work that - * needs to be done to prepare the entry. - */ - void finish() { - mOutEntry->count = util::hostToDevice32(mEntryCount); - } + // Sort the style. + std::sort(style->entries.begin(), style->entries.end(), cmp_style_entries); -private: - void flattenKey(Reference* key, ResTable_map* outEntry) { - assert(key->id && "key has no ID"); - outEntry->name.ident = util::hostToDevice32(key->id.value().id); + for (Style::Entry& entry : style->entries) { + FlattenEntry(&entry.key, entry.value.get()); } + } - void flattenValue(Item* value, ResTable_map* outEntry) { - bool result = value->flatten(&outEntry->value); - assert(result && "flatten failed"); + void Visit(Styleable* styleable) override { + for (auto& attr_ref : styleable->entries) { + BinaryPrimitive val(Res_value{}); + FlattenEntry(&attr_ref, &val); } - - void flattenEntry(Reference* key, Item* value) { - ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>(); - flattenKey(key, outEntry); - flattenValue(value, outEntry); - outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value)); - mEntryCount++; + } + + void Visit(Array* array) override { + for (auto& item : array->items) { + ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>(); + FlattenValue(item.get(), out_entry); + out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value)); + entry_count_++; } - - ResTable_entry_ext* mOutEntry; - BigBuffer* mBuffer; - size_t mEntryCount = 0; + } + + void Visit(Plural* plural) override { + const size_t count = plural->values.size(); + for (size_t i = 0; i < count; i++) { + if (!plural->values[i]) { + continue; + } + + ResourceId q; + switch (i) { + case Plural::Zero: + q.id = android::ResTable_map::ATTR_ZERO; + break; + + case Plural::One: + q.id = android::ResTable_map::ATTR_ONE; + break; + + case Plural::Two: + q.id = android::ResTable_map::ATTR_TWO; + break; + + case Plural::Few: + q.id = android::ResTable_map::ATTR_FEW; + break; + + case Plural::Many: + q.id = android::ResTable_map::ATTR_MANY; + break; + + case Plural::Other: + q.id = android::ResTable_map::ATTR_OTHER; + break; + + default: + LOG(FATAL) << "unhandled plural type"; + break; + } + + Reference key(q); + FlattenEntry(&key, plural->values[i].get()); + } + } + + /** + * Call this after visiting a Value. This will finish any work that + * needs to be done to prepare the entry. + */ + void Finish() { out_entry_->count = util::HostToDevice32(entry_count_); } + + private: + DISALLOW_COPY_AND_ASSIGN(MapFlattenVisitor); + + void FlattenKey(Reference* key, ResTable_map* out_entry) { + CHECK(bool(key->id)) << "key has no ID"; + out_entry->name.ident = util::HostToDevice32(key->id.value().id); + } + + void FlattenValue(Item* value, ResTable_map* out_entry) { + CHECK(value->Flatten(&out_entry->value)) << "flatten failed"; + } + + void FlattenEntry(Reference* key, Item* value) { + ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>(); + FlattenKey(key, out_entry); + FlattenValue(value, out_entry); + out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value)); + entry_count_++; + } + + ResTable_entry_ext* out_entry_; + BigBuffer* buffer_; + size_t entry_count_ = 0; }; class PackageFlattener { -public: - PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package) : - mDiag(diag), mPackage(package) { + public: + PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package) + : diag_(diag), package_(package) {} + + bool FlattenPackage(BigBuffer* buffer) { + ChunkWriter pkg_writer(buffer); + ResTable_package* pkg_header = + pkg_writer.StartChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE); + pkg_header->id = util::HostToDevice32(package_->id.value()); + + if (package_->name.size() >= arraysize(pkg_header->name)) { + diag_->Error(DiagMessage() << "package name '" << package_->name + << "' is too long"); + return false; } - bool flattenPackage(BigBuffer* buffer) { - ChunkWriter pkgWriter(buffer); - ResTable_package* pkgHeader = pkgWriter.startChunk<ResTable_package>( - RES_TABLE_PACKAGE_TYPE); - pkgHeader->id = util::hostToDevice32(mPackage->id.value()); + // Copy the package name in device endianness. + strcpy16_htod(pkg_header->name, arraysize(pkg_header->name), + util::Utf8ToUtf16(package_->name)); - if (mPackage->name.size() >= arraysize(pkgHeader->name)) { - mDiag->error(DiagMessage() << - "package name '" << mPackage->name << "' is too long"); - return false; - } + // Serialize the types. We do this now so that our type and key strings + // are populated. We write those first. + BigBuffer type_buffer(1024); + FlattenTypes(&type_buffer); - // Copy the package name in device endianness. - strcpy16_htod(pkgHeader->name, arraysize(pkgHeader->name), mPackage->name); + pkg_header->typeStrings = util::HostToDevice32(pkg_writer.size()); + StringPool::FlattenUtf16(pkg_writer.buffer(), type_pool_); - // Serialize the types. We do this now so that our type and key strings - // are populated. We write those first. - BigBuffer typeBuffer(1024); - flattenTypes(&typeBuffer); + pkg_header->keyStrings = util::HostToDevice32(pkg_writer.size()); + StringPool::FlattenUtf8(pkg_writer.buffer(), key_pool_); - pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size()); - StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool); + // Append the types. + buffer->AppendBuffer(std::move(type_buffer)); - pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size()); - StringPool::flattenUtf16(pkgWriter.getBuffer(), mKeyPool); - - // Append the types. - buffer->appendBuffer(std::move(typeBuffer)); - - pkgWriter.finish(); - return true; - } + pkg_writer.Finish(); + return true; + } -private: - IDiagnostics* mDiag; - ResourceTablePackage* mPackage; - StringPool mTypePool; - StringPool mKeyPool; + private: + DISALLOW_COPY_AND_ASSIGN(PackageFlattener); - template <typename T, bool IsItem> - T* writeEntry(FlatEntry* entry, BigBuffer* buffer) { - static_assert(std::is_same<ResTable_entry, T>::value || + template <typename T, bool IsItem> + T* WriteEntry(FlatEntry* entry, BigBuffer* buffer) { + static_assert(std::is_same<ResTable_entry, T>::value || std::is_same<ResTable_entry_ext, T>::value, - "T must be ResTable_entry or ResTable_entry_ext"); + "T must be ResTable_entry or ResTable_entry_ext"); - T* result = buffer->nextBlock<T>(); - ResTable_entry* outEntry = (ResTable_entry*)(result); - if (entry->entry->symbolStatus.state == SymbolState::kPublic) { - outEntry->flags |= ResTable_entry::FLAG_PUBLIC; - } - - if (entry->value->isWeak()) { - outEntry->flags |= ResTable_entry::FLAG_WEAK; - } - - if (!IsItem) { - outEntry->flags |= ResTable_entry::FLAG_COMPLEX; - } + T* result = buffer->NextBlock<T>(); + ResTable_entry* out_entry = (ResTable_entry*)result; + if (entry->entry->symbol_status.state == SymbolState::kPublic) { + out_entry->flags |= ResTable_entry::FLAG_PUBLIC; + } - outEntry->flags = util::hostToDevice16(outEntry->flags); - outEntry->key.index = util::hostToDevice32(entry->entryKey); - outEntry->size = util::hostToDevice16(sizeof(T)); - return result; + if (entry->value->IsWeak()) { + out_entry->flags |= ResTable_entry::FLAG_WEAK; } - bool flattenValue(FlatEntry* entry, BigBuffer* buffer) { - if (Item* item = valueCast<Item>(entry->value)) { - writeEntry<ResTable_entry, true>(entry, buffer); - Res_value* outValue = buffer->nextBlock<Res_value>(); - bool result = item->flatten(outValue); - assert(result && "flatten failed"); - outValue->size = util::hostToDevice16(sizeof(*outValue)); - } else { - ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer); - MapFlattenVisitor visitor(outEntry, buffer); - entry->value->accept(&visitor); - visitor.finish(); - } - return true; + if (!IsItem) { + out_entry->flags |= ResTable_entry::FLAG_COMPLEX; } - bool flattenConfig(const ResourceTableType* type, const ConfigDescription& config, - std::vector<FlatEntry>* entries, BigBuffer* buffer) { - ChunkWriter typeWriter(buffer); - ResTable_type* typeHeader = typeWriter.startChunk<ResTable_type>(RES_TABLE_TYPE_TYPE); - typeHeader->id = type->id.value(); - typeHeader->config = config; - typeHeader->config.swapHtoD(); - - auto maxAccum = [](uint32_t max, const std::unique_ptr<ResourceEntry>& a) -> uint32_t { - return std::max(max, (uint32_t) a->id.value()); - }; - - // Find the largest entry ID. That is how many entries we will have. - const uint32_t entryCount = - std::accumulate(type->entries.begin(), type->entries.end(), 0, maxAccum) + 1; - - typeHeader->entryCount = util::hostToDevice32(entryCount); - uint32_t* indices = typeWriter.nextBlock<uint32_t>(entryCount); - - assert((size_t) entryCount <= std::numeric_limits<uint16_t>::max() + 1); - memset(indices, 0xff, entryCount * sizeof(uint32_t)); - - typeHeader->entriesStart = util::hostToDevice32(typeWriter.size()); - - const size_t entryStart = typeWriter.getBuffer()->size(); - for (FlatEntry& flatEntry : *entries) { - assert(flatEntry.entry->id.value() < entryCount); - indices[flatEntry.entry->id.value()] = util::hostToDevice32( - typeWriter.getBuffer()->size() - entryStart); - if (!flattenValue(&flatEntry, typeWriter.getBuffer())) { - mDiag->error(DiagMessage() - << "failed to flatten resource '" - << ResourceNameRef(mPackage->name, type->type, flatEntry.entry->name) - << "' for configuration '" << config << "'"); - return false; - } - } - typeWriter.finish(); - return true; + out_entry->flags = util::HostToDevice16(out_entry->flags); + out_entry->key.index = util::HostToDevice32(entry->entry_key); + out_entry->size = util::HostToDevice16(sizeof(T)); + return result; + } + + bool FlattenValue(FlatEntry* entry, BigBuffer* buffer) { + if (Item* item = ValueCast<Item>(entry->value)) { + WriteEntry<ResTable_entry, true>(entry, buffer); + Res_value* outValue = buffer->NextBlock<Res_value>(); + CHECK(item->Flatten(outValue)) << "flatten failed"; + outValue->size = util::HostToDevice16(sizeof(*outValue)); + } else { + ResTable_entry_ext* out_entry = + WriteEntry<ResTable_entry_ext, false>(entry, buffer); + MapFlattenVisitor visitor(out_entry, buffer); + entry->value->Accept(&visitor); + visitor.Finish(); } + return true; + } + + bool FlattenConfig(const ResourceTableType* type, + const ConfigDescription& config, + std::vector<FlatEntry>* entries, BigBuffer* buffer) { + ChunkWriter type_writer(buffer); + ResTable_type* type_header = + type_writer.StartChunk<ResTable_type>(RES_TABLE_TYPE_TYPE); + type_header->id = type->id.value(); + type_header->config = config; + type_header->config.swapHtoD(); + + auto max_accum = [](uint32_t max, + const std::unique_ptr<ResourceEntry>& a) -> uint32_t { + return std::max(max, (uint32_t)a->id.value()); + }; + + // Find the largest entry ID. That is how many entries we will have. + const uint32_t entry_count = + std::accumulate(type->entries.begin(), type->entries.end(), 0, + max_accum) + + 1; + + type_header->entryCount = util::HostToDevice32(entry_count); + uint32_t* indices = type_writer.NextBlock<uint32_t>(entry_count); + + CHECK((size_t)entry_count <= std::numeric_limits<uint16_t>::max()); + memset(indices, 0xff, entry_count * sizeof(uint32_t)); + + type_header->entriesStart = util::HostToDevice32(type_writer.size()); + + const size_t entry_start = type_writer.buffer()->size(); + for (FlatEntry& flat_entry : *entries) { + CHECK(flat_entry.entry->id.value() < entry_count); + indices[flat_entry.entry->id.value()] = + util::HostToDevice32(type_writer.buffer()->size() - entry_start); + if (!FlattenValue(&flat_entry, type_writer.buffer())) { + diag_->Error(DiagMessage() + << "failed to flatten resource '" + << ResourceNameRef(package_->name, type->type, + flat_entry.entry->name) + << "' for configuration '" << config << "'"); + return false; + } + } + type_writer.Finish(); + return true; + } - std::vector<ResourceTableType*> collectAndSortTypes() { - std::vector<ResourceTableType*> sortedTypes; - for (auto& type : mPackage->types) { - if (type->type == ResourceType::kStyleable) { - // Styleables aren't real Resource Types, they are represented in the R.java - // file. - continue; - } + std::vector<ResourceTableType*> CollectAndSortTypes() { + std::vector<ResourceTableType*> sorted_types; + for (auto& type : package_->types) { + if (type->type == ResourceType::kStyleable) { + // Styleables aren't real Resource Types, they are represented in the + // R.java file. + continue; + } - assert(type->id && "type must have an ID set"); + CHECK(bool(type->id)) << "type must have an ID set"; - sortedTypes.push_back(type.get()); - } - std::sort(sortedTypes.begin(), sortedTypes.end(), cmpIds<ResourceTableType>); - return sortedTypes; + sorted_types.push_back(type.get()); } - - std::vector<ResourceEntry*> collectAndSortEntries(ResourceTableType* type) { - // Sort the entries by entry ID. - std::vector<ResourceEntry*> sortedEntries; - for (auto& entry : type->entries) { - assert(entry->id && "entry must have an ID set"); - sortedEntries.push_back(entry.get()); - } - std::sort(sortedEntries.begin(), sortedEntries.end(), cmpIds<ResourceEntry>); - return sortedEntries; + std::sort(sorted_types.begin(), sorted_types.end(), + cmp_ids<ResourceTableType>); + return sorted_types; + } + + std::vector<ResourceEntry*> CollectAndSortEntries(ResourceTableType* type) { + // Sort the entries by entry ID. + std::vector<ResourceEntry*> sorted_entries; + for (auto& entry : type->entries) { + CHECK(bool(entry->id)) << "entry must have an ID set"; + sorted_entries.push_back(entry.get()); + } + std::sort(sorted_entries.begin(), sorted_entries.end(), + cmp_ids<ResourceEntry>); + return sorted_entries; + } + + bool FlattenTypeSpec(ResourceTableType* type, + std::vector<ResourceEntry*>* sorted_entries, + BigBuffer* buffer) { + ChunkWriter type_spec_writer(buffer); + ResTable_typeSpec* spec_header = + type_spec_writer.StartChunk<ResTable_typeSpec>( + RES_TABLE_TYPE_SPEC_TYPE); + spec_header->id = type->id.value(); + + if (sorted_entries->empty()) { + type_spec_writer.Finish(); + return true; } - bool flattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries, - BigBuffer* buffer) { - ChunkWriter typeSpecWriter(buffer); - ResTable_typeSpec* specHeader = typeSpecWriter.startChunk<ResTable_typeSpec>( - RES_TABLE_TYPE_SPEC_TYPE); - specHeader->id = type->id.value(); - - if (sortedEntries->empty()) { - typeSpecWriter.finish(); - return true; - } - - // We can't just take the size of the vector. There may be holes in the entry ID space. - // Since the entries are sorted by ID, the last one will be the biggest. - const size_t numEntries = sortedEntries->back()->id.value() + 1; + // We can't just take the size of the vector. There may be holes in the + // entry ID space. + // Since the entries are sorted by ID, the last one will be the biggest. + const size_t num_entries = sorted_entries->back()->id.value() + 1; - specHeader->entryCount = util::hostToDevice32(numEntries); + spec_header->entryCount = util::HostToDevice32(num_entries); - // Reserve space for the masks of each resource in this type. These - // show for which configuration axis the resource changes. - uint32_t* configMasks = typeSpecWriter.nextBlock<uint32_t>(numEntries); + // Reserve space for the masks of each resource in this type. These + // show for which configuration axis the resource changes. + uint32_t* config_masks = type_spec_writer.NextBlock<uint32_t>(num_entries); - const size_t actualNumEntries = sortedEntries->size(); - for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) { - ResourceEntry* entry = sortedEntries->at(entryIndex); + const size_t actual_num_entries = sorted_entries->size(); + for (size_t entryIndex = 0; entryIndex < actual_num_entries; entryIndex++) { + ResourceEntry* entry = sorted_entries->at(entryIndex); - // Populate the config masks for this entry. + // Populate the config masks for this entry. - if (entry->symbolStatus.state == SymbolState::kPublic) { - configMasks[entry->id.value()] |= - util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); - } + if (entry->symbol_status.state == SymbolState::kPublic) { + config_masks[entry->id.value()] |= + util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); + } - const size_t configCount = entry->values.size(); - for (size_t i = 0; i < configCount; i++) { - const ConfigDescription& config = entry->values[i]->config; - for (size_t j = i + 1; j < configCount; j++) { - configMasks[entry->id.value()] |= util::hostToDevice32( - config.diff(entry->values[j]->config)); - } - } + const size_t config_count = entry->values.size(); + for (size_t i = 0; i < config_count; i++) { + const ConfigDescription& config = entry->values[i]->config; + for (size_t j = i + 1; j < config_count; j++) { + config_masks[entry->id.value()] |= + util::HostToDevice32(config.diff(entry->values[j]->config)); } - typeSpecWriter.finish(); - return true; + } } + type_spec_writer.Finish(); + return true; + } + + bool FlattenTypes(BigBuffer* buffer) { + // Sort the types by their IDs. They will be inserted into the StringPool in + // this order. + std::vector<ResourceTableType*> sorted_types = CollectAndSortTypes(); + + size_t expected_type_id = 1; + for (ResourceTableType* type : sorted_types) { + // If there is a gap in the type IDs, fill in the StringPool + // with empty values until we reach the ID we expect. + while (type->id.value() > expected_type_id) { + std::stringstream type_name; + type_name << "?" << expected_type_id; + type_pool_.MakeRef(type_name.str()); + expected_type_id++; + } + expected_type_id++; + type_pool_.MakeRef(ToString(type->type)); + + std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type); + + if (!FlattenTypeSpec(type, &sorted_entries, buffer)) { + return false; + } + + // The binary resource table lists resource entries for each + // configuration. + // We store them inverted, where a resource entry lists the values for + // each + // configuration available. Here we reverse this to match the binary + // table. + std::map<ConfigDescription, std::vector<FlatEntry>> + config_to_entry_list_map; + for (ResourceEntry* entry : sorted_entries) { + const uint32_t key_index = + (uint32_t)key_pool_.MakeRef(entry->name).index(); + + // Group values by configuration. + for (auto& config_value : entry->values) { + config_to_entry_list_map[config_value->config].push_back( + FlatEntry{entry, config_value->value.get(), key_index}); + } + } - bool flattenTypes(BigBuffer* buffer) { - // Sort the types by their IDs. They will be inserted into the StringPool in this order. - std::vector<ResourceTableType*> sortedTypes = collectAndSortTypes(); - - size_t expectedTypeId = 1; - for (ResourceTableType* type : sortedTypes) { - // If there is a gap in the type IDs, fill in the StringPool - // with empty values until we reach the ID we expect. - while (type->id.value() > expectedTypeId) { - std::u16string typeName(u"?"); - typeName += expectedTypeId; - mTypePool.makeRef(typeName); - expectedTypeId++; - } - expectedTypeId++; - mTypePool.makeRef(toString(type->type)); - - std::vector<ResourceEntry*> sortedEntries = collectAndSortEntries(type); - - if (!flattenTypeSpec(type, &sortedEntries, buffer)) { - return false; - } - - // The binary resource table lists resource entries for each configuration. - // We store them inverted, where a resource entry lists the values for each - // configuration available. Here we reverse this to match the binary table. - std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap; - for (ResourceEntry* entry : sortedEntries) { - const uint32_t keyIndex = (uint32_t) mKeyPool.makeRef(entry->name).getIndex(); - - // Group values by configuration. - for (auto& configValue : entry->values) { - configToEntryListMap[configValue->config].push_back(FlatEntry{ - entry, configValue->value.get(), keyIndex }); - } - } - - // Flatten a configuration value. - for (auto& entry : configToEntryListMap) { - if (!flattenConfig(type, entry.first, &entry.second, buffer)) { - return false; - } - } + // Flatten a configuration value. + for (auto& entry : config_to_entry_list_map) { + if (!FlattenConfig(type, entry.first, &entry.second, buffer)) { + return false; } - return true; + } } + return true; + } + + IDiagnostics* diag_; + ResourceTablePackage* package_; + StringPool type_pool_; + StringPool key_pool_; }; -} // namespace +} // namespace -bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) { - // We must do this before writing the resources, since the string pool IDs may change. - table->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { +bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { + // We must do this before writing the resources, since the string pool IDs may + // change. + table->string_pool.Sort( + [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { int diff = a.context.priority - b.context.priority; if (diff < 0) return true; if (diff > 0) return false; @@ -474,31 +497,32 @@ bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) { if (diff < 0) return true; if (diff > 0) return false; return a.value < b.value; - }); - table->stringPool.prune(); + }); + table->string_pool.Prune(); - // Write the ResTable header. - ChunkWriter tableWriter(mBuffer); - ResTable_header* tableHeader = tableWriter.startChunk<ResTable_header>(RES_TABLE_TYPE); - tableHeader->packageCount = util::hostToDevice32(table->packages.size()); + // Write the ResTable header. + ChunkWriter table_writer(buffer_); + ResTable_header* table_header = + table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE); + table_header->packageCount = util::HostToDevice32(table->packages.size()); - // Flatten the values string pool. - StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool); + // Flatten the values string pool. + StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool); - BigBuffer packageBuffer(1024); + BigBuffer package_buffer(1024); - // Flatten each package. - for (auto& package : table->packages) { - PackageFlattener flattener(context->getDiagnostics(), package.get()); - if (!flattener.flattenPackage(&packageBuffer)) { - return false; - } + // Flatten each package. + for (auto& package : table->packages) { + PackageFlattener flattener(context->GetDiagnostics(), package.get()); + if (!flattener.FlattenPackage(&package_buffer)) { + return false; } + } - // Finally merge all the packages into the main buffer. - tableWriter.getBuffer()->appendBuffer(std::move(packageBuffer)); - tableWriter.finish(); - return true; + // Finally merge all the packages into the main buffer. + table_writer.buffer()->AppendBuffer(std::move(package_buffer)); + table_writer.Finish(); + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/flatten/TableFlattener.h b/tools/aapt2/flatten/TableFlattener.h index 0ab01974044b..53f52c29a6a3 100644 --- a/tools/aapt2/flatten/TableFlattener.h +++ b/tools/aapt2/flatten/TableFlattener.h @@ -17,24 +17,26 @@ #ifndef AAPT_FLATTEN_TABLEFLATTENER_H #define AAPT_FLATTEN_TABLEFLATTENER_H +#include "android-base/macros.h" + +#include "ResourceTable.h" #include "process/IResourceTableConsumer.h" +#include "util/BigBuffer.h" namespace aapt { -class BigBuffer; -class ResourceTable; - class TableFlattener : public IResourceTableConsumer { -public: - TableFlattener(BigBuffer* buffer) : mBuffer(buffer) { - } + public: + explicit TableFlattener(BigBuffer* buffer) : buffer_(buffer) {} + + bool Consume(IAaptContext* context, ResourceTable* table) override; - bool consume(IAaptContext* context, ResourceTable* table) override; + private: + DISALLOW_COPY_AND_ASSIGN(TableFlattener); -private: - BigBuffer* mBuffer; + BigBuffer* buffer_; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_FLATTEN_TABLEFLATTENER_H */ diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp index 39c4fd318508..c72624066fb8 100644 --- a/tools/aapt2/flatten/TableFlattener_test.cpp +++ b/tools/aapt2/flatten/TableFlattener_test.cpp @@ -15,217 +15,222 @@ */ #include "flatten/TableFlattener.h" -#include "test/Builders.h" -#include "test/Context.h" + +#include "ResourceUtils.h" +#include "test/Test.h" #include "unflatten/BinaryResourceParser.h" #include "util/Util.h" - -#include <gtest/gtest.h> - using namespace android; namespace aapt { class TableFlattenerTest : public ::testing::Test { -public: - void SetUp() override { - mContext = test::ContextBuilder() - .setCompilationPackage(u"com.app.test") - .setPackageId(0x7f) - .build(); + public: + void SetUp() override { + context_ = test::ContextBuilder() + .SetCompilationPackage("com.app.test") + .SetPackageId(0x7f) + .Build(); + } + + ::testing::AssertionResult Flatten(ResourceTable* table, + ResTable* out_table) { + BigBuffer buffer(1024); + TableFlattener flattener(&buffer); + if (!flattener.Consume(context_.get(), table)) { + return ::testing::AssertionFailure() << "failed to flatten ResourceTable"; + } + + std::unique_ptr<uint8_t[]> data = util::Copy(buffer); + if (out_table->add(data.get(), buffer.size(), -1, true) != NO_ERROR) { + return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; + } + return ::testing::AssertionSuccess(); + } + + ::testing::AssertionResult Flatten(ResourceTable* table, + ResourceTable* out_table) { + BigBuffer buffer(1024); + TableFlattener flattener(&buffer); + if (!flattener.Consume(context_.get(), table)) { + return ::testing::AssertionFailure() << "failed to flatten ResourceTable"; + } + + std::unique_ptr<uint8_t[]> data = util::Copy(buffer); + BinaryResourceParser parser(context_.get(), out_table, {}, data.get(), + buffer.size()); + if (!parser.Parse()) { + return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; + } + return ::testing::AssertionSuccess(); + } + + ::testing::AssertionResult Exists(ResTable* table, + const StringPiece& expected_name, + const ResourceId& expected_id, + const ConfigDescription& expected_config, + const uint8_t expected_data_type, + const uint32_t expected_data, + const uint32_t expected_spec_flags) { + const ResourceName expected_res_name = test::ParseNameOrDie(expected_name); + + table->setParameters(&expected_config); + + ResTable_config config; + Res_value val; + uint32_t spec_flags; + if (table->getResource(expected_id.id, &val, false, 0, &spec_flags, + &config) < 0) { + return ::testing::AssertionFailure() << "could not find resource with"; + } + + if (expected_data_type != val.dataType) { + return ::testing::AssertionFailure() + << "expected data type " << std::hex << (int)expected_data_type + << " but got data type " << (int)val.dataType << std::dec + << " instead"; + } + + if (expected_data != val.data) { + return ::testing::AssertionFailure() + << "expected data " << std::hex << expected_data + << " but got data " << val.data << std::dec << " instead"; + } + + if (expected_spec_flags != spec_flags) { + return ::testing::AssertionFailure() + << "expected specFlags " << std::hex << expected_spec_flags + << " but got specFlags " << spec_flags << std::dec << " instead"; } - ::testing::AssertionResult flatten(ResourceTable* table, ResTable* outTable) { - BigBuffer buffer(1024); - TableFlattener flattener(&buffer); - if (!flattener.consume(mContext.get(), table)) { - return ::testing::AssertionFailure() << "failed to flatten ResourceTable"; - } - - std::unique_ptr<uint8_t[]> data = util::copy(buffer); - if (outTable->add(data.get(), buffer.size(), -1, true) != NO_ERROR) { - return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; - } - return ::testing::AssertionSuccess(); + ResTable::resource_name actual_name; + if (!table->getResourceName(expected_id.id, false, &actual_name)) { + return ::testing::AssertionFailure() << "failed to find resource name"; } - ::testing::AssertionResult flatten(ResourceTable* table, ResourceTable* outTable) { - BigBuffer buffer(1024); - TableFlattener flattener(&buffer); - if (!flattener.consume(mContext.get(), table)) { - return ::testing::AssertionFailure() << "failed to flatten ResourceTable"; - } - - std::unique_ptr<uint8_t[]> data = util::copy(buffer); - BinaryResourceParser parser(mContext.get(), outTable, {}, data.get(), buffer.size()); - if (!parser.parse()) { - return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; - } - return ::testing::AssertionSuccess(); + Maybe<ResourceName> resName = ResourceUtils::ToResourceName(actual_name); + if (!resName) { + return ::testing::AssertionFailure() + << "expected name '" << expected_res_name << "' but got '" + << StringPiece16(actual_name.package, actual_name.packageLen) + << ":" << StringPiece16(actual_name.type, actual_name.typeLen) + << "/" << StringPiece16(actual_name.name, actual_name.nameLen) + << "'"; } - ::testing::AssertionResult exists(ResTable* table, - const StringPiece16& expectedName, - const ResourceId expectedId, - const ConfigDescription& expectedConfig, - const uint8_t expectedDataType, const uint32_t expectedData, - const uint32_t expectedSpecFlags) { - const ResourceName expectedResName = test::parseNameOrDie(expectedName); - - table->setParameters(&expectedConfig); - - ResTable_config config; - Res_value val; - uint32_t specFlags; - if (table->getResource(expectedId.id, &val, false, 0, &specFlags, &config) < 0) { - return ::testing::AssertionFailure() << "could not find resource with"; - } - - if (expectedDataType != val.dataType) { - return ::testing::AssertionFailure() - << "expected data type " - << std::hex << (int) expectedDataType << " but got data type " - << (int) val.dataType << std::dec << " instead"; - } - - if (expectedData != val.data) { - return ::testing::AssertionFailure() - << "expected data " - << std::hex << expectedData << " but got data " - << val.data << std::dec << " instead"; - } - - if (expectedSpecFlags != specFlags) { - return ::testing::AssertionFailure() - << "expected specFlags " - << std::hex << expectedSpecFlags << " but got specFlags " - << specFlags << std::dec << " instead"; - } - - ResTable::resource_name actualName; - if (!table->getResourceName(expectedId.id, false, &actualName)) { - return ::testing::AssertionFailure() << "failed to find resource name"; - } - - StringPiece16 package16(actualName.package, actualName.packageLen); - if (package16 != expectedResName.package) { - return ::testing::AssertionFailure() - << "expected package '" << expectedResName.package << "' but got '" - << package16 << "'"; - } - - StringPiece16 type16(actualName.type, actualName.typeLen); - if (type16 != toString(expectedResName.type)) { - return ::testing::AssertionFailure() - << "expected type '" << expectedResName.type - << "' but got '" << type16 << "'"; - } - - StringPiece16 name16(actualName.name, actualName.nameLen); - if (name16 != expectedResName.entry) { - return ::testing::AssertionFailure() - << "expected name '" << expectedResName.entry - << "' but got '" << name16 << "'"; - } - - if (expectedConfig != config) { - return ::testing::AssertionFailure() - << "expected config '" << expectedConfig << "' but got '" - << ConfigDescription(config) << "'"; - } - return ::testing::AssertionSuccess(); + if (expected_config != config) { + return ::testing::AssertionFailure() << "expected config '" + << expected_config << "' but got '" + << ConfigDescription(config) << "'"; } + return ::testing::AssertionSuccess(); + } -private: - std::unique_ptr<IAaptContext> mContext; + private: + std::unique_ptr<IAaptContext> context_; }; TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"com.app.test", 0x7f) - .addSimple(u"@com.app.test:id/one", ResourceId(0x7f020000)) - .addSimple(u"@com.app.test:id/two", ResourceId(0x7f020001)) - .addValue(u"@com.app.test:id/three", ResourceId(0x7f020002), - test::buildReference(u"@com.app.test:id/one", ResourceId(0x7f020000))) - .addValue(u"@com.app.test:integer/one", ResourceId(0x7f030000), - util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)) - .addValue(u"@com.app.test:integer/one", ResourceId(0x7f030000), - test::parseConfigOrDie("v1"), - util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)) - .addString(u"@com.app.test:string/test", ResourceId(0x7f040000), u"foo") - .addString(u"@com.app.test:layout/bar", ResourceId(0x7f050000), u"res/layout/bar.xml") - .build(); - - ResTable resTable; - ASSERT_TRUE(flatten(table.get(), &resTable)); - - EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/one", ResourceId(0x7f020000), {}, - Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); - - EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/two", ResourceId(0x7f020001), {}, - Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); - - EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/three", ResourceId(0x7f020002), {}, - Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); - - EXPECT_TRUE(exists(&resTable, u"@com.app.test:integer/one", ResourceId(0x7f030000), - {}, Res_value::TYPE_INT_DEC, 1u, - ResTable_config::CONFIG_VERSION)); - - EXPECT_TRUE(exists(&resTable, u"@com.app.test:integer/one", ResourceId(0x7f030000), - test::parseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u, - ResTable_config::CONFIG_VERSION)); - - StringPiece16 fooStr = u"foo"; - ssize_t idx = resTable.getTableStringBlock(0)->indexOfString(fooStr.data(), fooStr.size()); - ASSERT_GE(idx, 0); - EXPECT_TRUE(exists(&resTable, u"@com.app.test:string/test", ResourceId(0x7f040000), - {}, Res_value::TYPE_STRING, (uint32_t) idx, 0u)); - - StringPiece16 barPath = u"res/layout/bar.xml"; - idx = resTable.getTableStringBlock(0)->indexOfString(barPath.data(), barPath.size()); - ASSERT_GE(idx, 0); - EXPECT_TRUE(exists(&resTable, u"@com.app.test:layout/bar", ResourceId(0x7f050000), {}, - Res_value::TYPE_STRING, (uint32_t) idx, 0u)); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddSimple("com.app.test:id/one", ResourceId(0x7f020000)) + .AddSimple("com.app.test:id/two", ResourceId(0x7f020001)) + .AddValue("com.app.test:id/three", ResourceId(0x7f020002), + test::BuildReference("com.app.test:id/one", + ResourceId(0x7f020000))) + .AddValue("com.app.test:integer/one", ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>( + uint8_t(Res_value::TYPE_INT_DEC), 1u)) + .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"), + ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>( + uint8_t(Res_value::TYPE_INT_DEC), 2u)) + .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo") + .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), + "res/layout/bar.xml") + .Build(); + + ResTable res_table; + ASSERT_TRUE(Flatten(table.get(), &res_table)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020000), + {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/two", ResourceId(0x7f020001), + {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", + ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, + 0x7f020000u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", + ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u, + ResTable_config::CONFIG_VERSION)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", + ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), + Res_value::TYPE_INT_DEC, 2u, + ResTable_config::CONFIG_VERSION)); + + std::u16string foo_str = u"foo"; + ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), + foo_str.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", + ResourceId(0x7f040000), {}, Res_value::TYPE_STRING, + (uint32_t)idx, 0u)); + + std::u16string bar_path = u"res/layout/bar.xml"; + idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), + bar_path.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/bar", + ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, + (uint32_t)idx, 0u)); } TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"com.app.test", 0x7f) - .addSimple(u"@com.app.test:id/one", ResourceId(0x7f020001)) - .addSimple(u"@com.app.test:id/three", ResourceId(0x7f020003)) - .build(); - - ResTable resTable; - ASSERT_TRUE(flatten(table.get(), &resTable)); - - EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/one", ResourceId(0x7f020001), {}, - Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); - EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/three", ResourceId(0x7f020003), {}, - Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddSimple("com.app.test:id/one", ResourceId(0x7f020001)) + .AddSimple("com.app.test:id/three", ResourceId(0x7f020003)) + .Build(); + + ResTable res_table; + ASSERT_TRUE(Flatten(table.get(), &res_table)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020001), + {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", + ResourceId(0x7f020003), {}, Res_value::TYPE_INT_BOOLEAN, + 0u, 0u)); } TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) { - Attribute attr(false); - attr.typeMask = android::ResTable_map::TYPE_INTEGER; - attr.minInt = 10; - attr.maxInt = 23; - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"android", 0x01) - .addValue(u"@android:attr/foo", ResourceId(0x01010000), - util::make_unique<Attribute>(attr)) - .build(); - - ResourceTable result; - ASSERT_TRUE(flatten(table.get(), &result)); - - Attribute* actualAttr = test::getValue<Attribute>(&result, u"@android:attr/foo"); - ASSERT_NE(nullptr, actualAttr); - EXPECT_EQ(attr.isWeak(), actualAttr->isWeak()); - EXPECT_EQ(attr.typeMask, actualAttr->typeMask); - EXPECT_EQ(attr.minInt, actualAttr->minInt); - EXPECT_EQ(attr.maxInt, actualAttr->maxInt); + Attribute attr(false); + attr.type_mask = android::ResTable_map::TYPE_INTEGER; + attr.min_int = 10; + attr.max_int = 23; + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("android", 0x01) + .AddValue("android:attr/foo", ResourceId(0x01010000), + util::make_unique<Attribute>(attr)) + .Build(); + + ResourceTable result; + ASSERT_TRUE(Flatten(table.get(), &result)); + + Attribute* actualAttr = + test::GetValue<Attribute>(&result, "android:attr/foo"); + ASSERT_NE(nullptr, actualAttr); + EXPECT_EQ(attr.IsWeak(), actualAttr->IsWeak()); + EXPECT_EQ(attr.type_mask, actualAttr->type_mask); + EXPECT_EQ(attr.min_int, actualAttr->min_int); + EXPECT_EQ(attr.max_int, actualAttr->max_int); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp index 570cd9635de3..366c373223dc 100644 --- a/tools/aapt2/flatten/XmlFlattener.cpp +++ b/tools/aapt2/flatten/XmlFlattener.cpp @@ -14,17 +14,22 @@ * limitations under the License. */ -#include "SdkConstants.h" -#include "flatten/ChunkWriter.h" -#include "flatten/ResourceTypeExtensions.h" #include "flatten/XmlFlattener.h" -#include "xml/XmlDom.h" -#include <androidfw/ResourceTypes.h> #include <algorithm> -#include <utils/misc.h> +#include <map> #include <vector> +#include "android-base/logging.h" +#include "android-base/macros.h" +#include "androidfw/ResourceTypes.h" +#include "utils/misc.h" + +#include "SdkConstants.h" +#include "flatten/ChunkWriter.h" +#include "flatten/ResourceTypeExtensions.h" +#include "xml/XmlDom.h" + using namespace android; namespace aapt { @@ -33,283 +38,323 @@ namespace { constexpr uint32_t kLowPriority = 0xffffffffu; -struct XmlFlattenerVisitor : public xml::Visitor { - using xml::Visitor::visit; - - BigBuffer* mBuffer; - XmlFlattenerOptions mOptions; - StringPool mPool; - std::map<uint8_t, StringPool> mPackagePools; - - struct StringFlattenDest { - StringPool::Ref ref; - ResStringPool_ref* dest; - }; - std::vector<StringFlattenDest> mStringRefs; - - // Scratch vector to filter attributes. We avoid allocations - // making this a member. - std::vector<xml::Attribute*> mFilteredAttrs; - - - XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) : - mBuffer(buffer), mOptions(options) { +static bool cmp_xml_attribute_by_id(const xml::Attribute* a, + const xml::Attribute* b) { + if (a->compiled_attribute && a->compiled_attribute.value().id) { + if (b->compiled_attribute && b->compiled_attribute.value().id) { + return a->compiled_attribute.value().id.value() < + b->compiled_attribute.value().id.value(); } - - void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) { - if (!str.empty()) { - mStringRefs.push_back(StringFlattenDest{ - mPool.makeRef(str, StringPool::Context{ priority }), - dest }); - } else { - // The device doesn't think a string of size 0 is the same as null. - dest->index = util::deviceToHost32(-1); - } + return true; + } else if (!b->compiled_attribute) { + int diff = a->namespace_uri.compare(b->namespace_uri); + if (diff < 0) { + return true; + } else if (diff > 0) { + return false; } + return a->name < b->name; + } + return false; +} - void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) { - mStringRefs.push_back(StringFlattenDest{ ref, dest }); - } +class XmlFlattenerVisitor : public xml::Visitor { + public: + using xml::Visitor::Visit; + + StringPool pool; + std::map<uint8_t, StringPool> package_pools; - void writeNamespace(xml::Namespace* node, uint16_t type) { - ChunkWriter writer(mBuffer); + struct StringFlattenDest { + StringPool::Ref ref; + ResStringPool_ref* dest; + }; - ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type); - flatNode->lineNumber = util::hostToDevice32(node->lineNumber); - flatNode->comment.index = util::hostToDevice32(-1); + std::vector<StringFlattenDest> string_refs; - ResXMLTree_namespaceExt* flatNs = writer.nextBlock<ResXMLTree_namespaceExt>(); - addString(node->namespacePrefix, kLowPriority, &flatNs->prefix); - addString(node->namespaceUri, kLowPriority, &flatNs->uri); + XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) + : buffer_(buffer), options_(options) {} - writer.finish(); + void Visit(xml::Namespace* node) override { + if (node->namespace_uri == xml::kSchemaTools) { + // Skip dedicated tools namespace. + xml::Visitor::Visit(node); + } else { + WriteNamespace(node, android::RES_XML_START_NAMESPACE_TYPE); + xml::Visitor::Visit(node); + WriteNamespace(node, android::RES_XML_END_NAMESPACE_TYPE); } + } - void visit(xml::Namespace* node) override { - writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE); - xml::Visitor::visit(node); - writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE); + void Visit(xml::Text* node) override { + if (util::TrimWhitespace(node->text).empty()) { + // Skip whitespace only text nodes. + return; } - void visit(xml::Text* node) override { - if (util::trimWhitespace(node->text).empty()) { - // Skip whitespace only text nodes. - return; - } + ChunkWriter writer(buffer_); + ResXMLTree_node* flat_node = + writer.StartChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE); + flat_node->lineNumber = util::HostToDevice32(node->line_number); + flat_node->comment.index = util::HostToDevice32(-1); - ChunkWriter writer(mBuffer); - ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE); - flatNode->lineNumber = util::hostToDevice32(node->lineNumber); - flatNode->comment.index = util::hostToDevice32(-1); + ResXMLTree_cdataExt* flat_text = writer.NextBlock<ResXMLTree_cdataExt>(); + AddString(node->text, kLowPriority, &flat_text->data); - ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>(); - addString(node->text, kLowPriority, &flatText->data); + writer.Finish(); + } - writer.finish(); - } - - void visit(xml::Element* node) override { - { - ChunkWriter startWriter(mBuffer); - ResXMLTree_node* flatNode = startWriter.startChunk<ResXMLTree_node>( - RES_XML_START_ELEMENT_TYPE); - flatNode->lineNumber = util::hostToDevice32(node->lineNumber); - flatNode->comment.index = util::hostToDevice32(-1); + void Visit(xml::Element* node) override { + { + ChunkWriter start_writer(buffer_); + ResXMLTree_node* flat_node = + start_writer.StartChunk<ResXMLTree_node>(RES_XML_START_ELEMENT_TYPE); + flat_node->lineNumber = util::HostToDevice32(node->line_number); + flat_node->comment.index = util::HostToDevice32(-1); - ResXMLTree_attrExt* flatElem = startWriter.nextBlock<ResXMLTree_attrExt>(); - addString(node->namespaceUri, kLowPriority, &flatElem->ns); - addString(node->name, kLowPriority, &flatElem->name); - flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem)); - flatElem->attributeSize = util::hostToDevice16(sizeof(ResXMLTree_attribute)); + ResXMLTree_attrExt* flat_elem = + start_writer.NextBlock<ResXMLTree_attrExt>(); - writeAttributes(node, flatElem, &startWriter); + // A missing namespace must be null, not an empty string. Otherwise the + // runtime complains. + AddString(node->namespace_uri, kLowPriority, &flat_elem->ns, + true /* treat_empty_string_as_null */); + AddString(node->name, kLowPriority, &flat_elem->name, + true /* treat_empty_string_as_null */); - startWriter.finish(); - } + flat_elem->attributeStart = util::HostToDevice16(sizeof(*flat_elem)); + flat_elem->attributeSize = + util::HostToDevice16(sizeof(ResXMLTree_attribute)); - xml::Visitor::visit(node); + WriteAttributes(node, flat_elem, &start_writer); - { - ChunkWriter endWriter(mBuffer); - ResXMLTree_node* flatEndNode = endWriter.startChunk<ResXMLTree_node>( - RES_XML_END_ELEMENT_TYPE); - flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber); - flatEndNode->comment.index = util::hostToDevice32(-1); + start_writer.Finish(); + } - ResXMLTree_endElementExt* flatEndElem = endWriter.nextBlock<ResXMLTree_endElementExt>(); - addString(node->namespaceUri, kLowPriority, &flatEndElem->ns); - addString(node->name, kLowPriority, &flatEndElem->name); + xml::Visitor::Visit(node); - endWriter.finish(); + { + ChunkWriter end_writer(buffer_); + ResXMLTree_node* flat_end_node = + end_writer.StartChunk<ResXMLTree_node>(RES_XML_END_ELEMENT_TYPE); + flat_end_node->lineNumber = util::HostToDevice32(node->line_number); + flat_end_node->comment.index = util::HostToDevice32(-1); + + ResXMLTree_endElementExt* flat_end_elem = + end_writer.NextBlock<ResXMLTree_endElementExt>(); + AddString(node->namespace_uri, kLowPriority, &flat_end_elem->ns, + true /* treat_empty_string_as_null */); + AddString(node->name, kLowPriority, &flat_end_elem->name); + + end_writer.Finish(); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(XmlFlattenerVisitor); + + void AddString(const StringPiece& str, uint32_t priority, + android::ResStringPool_ref* dest, + bool treat_empty_string_as_null = false) { + if (str.empty() && treat_empty_string_as_null) { + // Some parts of the runtime treat null differently than empty string. + dest->index = util::DeviceToHost32(-1); + } else { + string_refs.push_back(StringFlattenDest{ + pool.MakeRef(str, StringPool::Context(priority)), dest}); + } + } + + void AddString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) { + string_refs.push_back(StringFlattenDest{ref, dest}); + } + + void WriteNamespace(xml::Namespace* node, uint16_t type) { + ChunkWriter writer(buffer_); + + ResXMLTree_node* flatNode = writer.StartChunk<ResXMLTree_node>(type); + flatNode->lineNumber = util::HostToDevice32(node->line_number); + flatNode->comment.index = util::HostToDevice32(-1); + + ResXMLTree_namespaceExt* flat_ns = + writer.NextBlock<ResXMLTree_namespaceExt>(); + AddString(node->namespace_prefix, kLowPriority, &flat_ns->prefix); + AddString(node->namespace_uri, kLowPriority, &flat_ns->uri); + + writer.Finish(); + } + + void WriteAttributes(xml::Element* node, ResXMLTree_attrExt* flat_elem, + ChunkWriter* writer) { + filtered_attrs_.clear(); + filtered_attrs_.reserve(node->attributes.size()); + + // Filter the attributes. + for (xml::Attribute& attr : node->attributes) { + if (options_.max_sdk_level && attr.compiled_attribute && + attr.compiled_attribute.value().id) { + size_t sdk_level = + FindAttributeSdkLevel(attr.compiled_attribute.value().id.value()); + if (sdk_level > options_.max_sdk_level.value()) { + continue; } + } + if (attr.namespace_uri == xml::kSchemaTools) { + continue; + } + filtered_attrs_.push_back(&attr); } - static bool cmpXmlAttributeById(const xml::Attribute* a, const xml::Attribute* b) { - if (a->compiledAttribute && a->compiledAttribute.value().id) { - if (b->compiledAttribute && b->compiledAttribute.value().id) { - return a->compiledAttribute.value().id.value() < b->compiledAttribute.value().id.value(); - } - return true; - } else if (!b->compiledAttribute) { - int diff = a->namespaceUri.compare(b->namespaceUri); - if (diff < 0) { - return true; - } else if (diff > 0) { - return false; - } - return a->name < b->name; - } - return false; + if (filtered_attrs_.empty()) { + return; } - void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem, ChunkWriter* writer) { - mFilteredAttrs.clear(); - mFilteredAttrs.reserve(node->attributes.size()); - - // Filter the attributes. - for (xml::Attribute& attr : node->attributes) { - if (mOptions.maxSdkLevel && attr.compiledAttribute && attr.compiledAttribute.value().id) { - size_t sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id.value()); - if (sdkLevel > mOptions.maxSdkLevel.value()) { - continue; - } - } - mFilteredAttrs.push_back(&attr); + const ResourceId kIdAttr(0x010100d0); + + std::sort(filtered_attrs_.begin(), filtered_attrs_.end(), + cmp_xml_attribute_by_id); + + flat_elem->attributeCount = util::HostToDevice16(filtered_attrs_.size()); + + ResXMLTree_attribute* flat_attr = + writer->NextBlock<ResXMLTree_attribute>(filtered_attrs_.size()); + uint16_t attribute_index = 1; + for (const xml::Attribute* xml_attr : filtered_attrs_) { + // Assign the indices for specific attributes. + if (xml_attr->compiled_attribute && + xml_attr->compiled_attribute.value().id && + xml_attr->compiled_attribute.value().id.value() == kIdAttr) { + flat_elem->idIndex = util::HostToDevice16(attribute_index); + } else if (xml_attr->namespace_uri.empty()) { + if (xml_attr->name == "class") { + flat_elem->classIndex = util::HostToDevice16(attribute_index); + } else if (xml_attr->name == "style") { + flat_elem->styleIndex = util::HostToDevice16(attribute_index); } + } + attribute_index++; + + // Add the namespaceUri to the list of StringRefs to encode. Use null if + // the namespace + // is empty (doesn't exist). + AddString(xml_attr->namespace_uri, kLowPriority, &flat_attr->ns, + true /* treat_empty_string_as_null */); + + flat_attr->rawValue.index = util::HostToDevice32(-1); + + if (!xml_attr->compiled_attribute || + !xml_attr->compiled_attribute.value().id) { + // The attribute has no associated ResourceID, so the string order + // doesn't matter. + AddString(xml_attr->name, kLowPriority, &flat_attr->name); + } else { + // Attribute names are stored without packages, but we use + // their StringPool index to lookup their resource IDs. + // This will cause collisions, so we can't dedupe + // attribute names from different packages. We use separate + // pools that we later combine. + // + // Lookup the StringPool for this package and make the reference there. + const xml::AaptAttribute& aapt_attr = + xml_attr->compiled_attribute.value(); + + StringPool::Ref name_ref = + package_pools[aapt_attr.id.value().package_id()].MakeRef( + xml_attr->name, StringPool::Context(aapt_attr.id.value().id)); + + // Add it to the list of strings to flatten. + AddString(name_ref, &flat_attr->name); + } + + if (options_.keep_raw_values || !xml_attr->compiled_value) { + // Keep raw values if the value is not compiled or + // if we're building a static library (need symbols). + AddString(xml_attr->value, kLowPriority, &flat_attr->rawValue); + } + + if (xml_attr->compiled_value) { + CHECK(xml_attr->compiled_value->Flatten(&flat_attr->typedValue)); + } else { + // Flatten as a regular string type. + flat_attr->typedValue.dataType = android::Res_value::TYPE_STRING; + AddString(xml_attr->value, kLowPriority, + (ResStringPool_ref*)&flat_attr->typedValue.data); + } + + flat_attr->typedValue.size = + util::HostToDevice16(sizeof(flat_attr->typedValue)); + flat_attr++; + } + } - if (mFilteredAttrs.empty()) { - return; - } + BigBuffer* buffer_; + XmlFlattenerOptions options_; - const ResourceId kIdAttr(0x010100d0); - - std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(), cmpXmlAttributeById); - - flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size()); - - ResXMLTree_attribute* flatAttr = writer->nextBlock<ResXMLTree_attribute>( - mFilteredAttrs.size()); - uint16_t attributeIndex = 1; - for (const xml::Attribute* xmlAttr : mFilteredAttrs) { - // Assign the indices for specific attributes. - if (xmlAttr->compiledAttribute && xmlAttr->compiledAttribute.value().id && - xmlAttr->compiledAttribute.value().id.value() == kIdAttr) { - flatElem->idIndex = util::hostToDevice16(attributeIndex); - } else if (xmlAttr->namespaceUri.empty()) { - if (xmlAttr->name == u"class") { - flatElem->classIndex = util::hostToDevice16(attributeIndex); - } else if (xmlAttr->name == u"style") { - flatElem->styleIndex = util::hostToDevice16(attributeIndex); - } - } - attributeIndex++; - - // Add the namespaceUri to the list of StringRefs to encode. - addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns); - - flatAttr->rawValue.index = util::hostToDevice32(-1); - - if (!xmlAttr->compiledAttribute || !xmlAttr->compiledAttribute.value().id) { - // The attribute has no associated ResourceID, so the string order doesn't matter. - addString(xmlAttr->name, kLowPriority, &flatAttr->name); - } else { - // Attribute names are stored without packages, but we use - // their StringPool index to lookup their resource IDs. - // This will cause collisions, so we can't dedupe - // attribute names from different packages. We use separate - // pools that we later combine. - // - // Lookup the StringPool for this package and make the reference there. - const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value(); - - StringPool::Ref nameRef = mPackagePools[aaptAttr.id.value().packageId()].makeRef( - xmlAttr->name, StringPool::Context{ aaptAttr.id.value().id }); - - // Add it to the list of strings to flatten. - addString(nameRef, &flatAttr->name); - } - - if (mOptions.keepRawValues || !xmlAttr->compiledValue) { - // Keep raw values if the value is not compiled or - // if we're building a static library (need symbols). - addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue); - } - - if (xmlAttr->compiledValue) { - bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue); - assert(result); - } else { - // Flatten as a regular string type. - flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING; - addString(xmlAttr->value, kLowPriority, - (ResStringPool_ref*) &flatAttr->typedValue.data); - } - - flatAttr->typedValue.size = util::hostToDevice16(sizeof(flatAttr->typedValue)); - flatAttr++; - } - } + // Scratch vector to filter attributes. We avoid allocations + // making this a member. + std::vector<xml::Attribute*> filtered_attrs_; }; -} // namespace +} // namespace -bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) { - BigBuffer nodeBuffer(1024); - XmlFlattenerVisitor visitor(&nodeBuffer, mOptions); - node->accept(&visitor); +bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) { + BigBuffer node_buffer(1024); + XmlFlattenerVisitor visitor(&node_buffer, options_); + node->Accept(&visitor); - // Merge the package pools into the main pool. - for (auto& packagePoolEntry : visitor.mPackagePools) { - visitor.mPool.merge(std::move(packagePoolEntry.second)); - } + // Merge the package pools into the main pool. + for (auto& package_pool_entry : visitor.package_pools) { + visitor.pool.Merge(std::move(package_pool_entry.second)); + } - // Sort the string pool so that attribute resource IDs show up first. - visitor.mPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { + // Sort the string pool so that attribute resource IDs show up first. + visitor.pool.Sort( + [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { return a.context.priority < b.context.priority; - }); - - // Now we flatten the string pool references into the correct places. - for (const auto& refEntry : visitor.mStringRefs) { - refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex()); + }); + + // Now we flatten the string pool references into the correct places. + for (const auto& ref_entry : visitor.string_refs) { + ref_entry.dest->index = util::HostToDevice32(ref_entry.ref.index()); + } + + // Write the XML header. + ChunkWriter xml_header_writer(buffer_); + xml_header_writer.StartChunk<ResXMLTree_header>(RES_XML_TYPE); + + // Flatten the StringPool. + StringPool::FlattenUtf8(buffer_, visitor.pool); + + { + // Write the array of resource IDs, indexed by StringPool order. + ChunkWriter res_id_map_writer(buffer_); + res_id_map_writer.StartChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE); + for (const auto& str : visitor.pool) { + ResourceId id = {str->context.priority}; + if (id.id == kLowPriority || !id.is_valid()) { + // When we see the first non-resource ID, + // we're done. + break; + } + + *res_id_map_writer.NextBlock<uint32_t>() = id.id; } + res_id_map_writer.Finish(); + } - // Write the XML header. - ChunkWriter xmlHeaderWriter(mBuffer); - xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE); + // Move the nodeBuffer and append it to the out buffer. + buffer_->AppendBuffer(std::move(node_buffer)); - // Flatten the StringPool. - StringPool::flattenUtf16(mBuffer, visitor.mPool); - - { - // Write the array of resource IDs, indexed by StringPool order. - ChunkWriter resIdMapWriter(mBuffer); - resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE); - for (const auto& str : visitor.mPool) { - ResourceId id = { str->context.priority }; - if (id.id == kLowPriority || !id.isValid()) { - // When we see the first non-resource ID, - // we're done. - break; - } - - *resIdMapWriter.nextBlock<uint32_t>() = id.id; - } - resIdMapWriter.finish(); - } - - // Move the nodeBuffer and append it to the out buffer. - mBuffer->appendBuffer(std::move(nodeBuffer)); - - // Finish the xml header. - xmlHeaderWriter.finish(); - return true; + // Finish the xml header. + xml_header_writer.Finish(); + return true; } -bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) { - if (!resource->root) { - return false; - } - return flatten(context, resource->root.get()); +bool XmlFlattener::Consume(IAaptContext* context, xml::XmlResource* resource) { + if (!resource->root) { + return false; + } + return Flatten(context, resource->root.get()); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h index a688ac965b0d..f5129fd40e99 100644 --- a/tools/aapt2/flatten/XmlFlattener.h +++ b/tools/aapt2/flatten/XmlFlattener.h @@ -17,6 +17,8 @@ #ifndef AAPT_FLATTEN_XMLFLATTENER_H #define AAPT_FLATTEN_XMLFLATTENER_H +#include "android-base/macros.h" + #include "process/IResourceTableConsumer.h" #include "util/BigBuffer.h" #include "xml/XmlDom.h" @@ -24,32 +26,33 @@ namespace aapt { struct XmlFlattenerOptions { - /** - * Keep attribute raw string values along with typed values. - */ - bool keepRawValues = false; - - /** - * If set, the max SDK level of attribute to flatten. All others are ignored. - */ - Maybe<size_t> maxSdkLevel; + /** + * Keep attribute raw string values along with typed values. + */ + bool keep_raw_values = false; + + /** + * If set, the max SDK level of attribute to flatten. All others are ignored. + */ + Maybe<size_t> max_sdk_level; }; class XmlFlattener : public IXmlResourceConsumer { -public: - XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) : - mBuffer(buffer), mOptions(options) { - } + public: + XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) + : buffer_(buffer), options_(options) {} + + bool Consume(IAaptContext* context, xml::XmlResource* resource) override; - bool consume(IAaptContext* context, xml::XmlResource* resource) override; + private: + DISALLOW_COPY_AND_ASSIGN(XmlFlattener); -private: - BigBuffer* mBuffer; - XmlFlattenerOptions mOptions; + bool Flatten(IAaptContext* context, xml::Node* node); - bool flatten(IAaptContext* context, xml::Node* node); + BigBuffer* buffer_; + XmlFlattenerOptions options_; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_FLATTEN_XMLFLATTENER_H */ diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp index 4e6eb811e572..2c83bb384cc5 100644 --- a/tools/aapt2/flatten/XmlFlattener_test.cpp +++ b/tools/aapt2/flatten/XmlFlattener_test.cpp @@ -15,196 +15,251 @@ */ #include "flatten/XmlFlattener.h" + +#include "androidfw/ResourceTypes.h" + #include "link/Linkers.h" -#include "test/Builders.h" -#include "test/Context.h" +#include "test/Test.h" #include "util/BigBuffer.h" #include "util/Util.h" -#include <androidfw/ResourceTypes.h> -#include <gtest/gtest.h> - namespace aapt { class XmlFlattenerTest : public ::testing::Test { -public: - void SetUp() override { - mContext = test::ContextBuilder() - .setCompilationPackage(u"com.app.test") - .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" }) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addSymbol(u"@android:attr/id", ResourceId(0x010100d0), - test::AttributeBuilder().build()) - .addSymbol(u"@com.app.test:id/id", ResourceId(0x7f020000)) - .addSymbol(u"@android:attr/paddingStart", ResourceId(0x010103b3), - test::AttributeBuilder().build()) - .addSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435), - test::AttributeBuilder().build()) - .build()) - .build(); + public: + void SetUp() override { + context_ = + test::ContextBuilder() + .SetCompilationPackage("com.app.test") + .SetNameManglerPolicy(NameManglerPolicy{"com.app.test"}) + .AddSymbolSource( + test::StaticSymbolSourceBuilder() + .AddSymbol("android:attr/id", ResourceId(0x010100d0), + test::AttributeBuilder().Build()) + .AddSymbol("com.app.test:id/id", ResourceId(0x7f020000)) + .AddSymbol("android:attr/paddingStart", + ResourceId(0x010103b3), + test::AttributeBuilder().Build()) + .AddSymbol("android:attr/colorAccent", + ResourceId(0x01010435), + test::AttributeBuilder().Build()) + .Build()) + .Build(); + } + + ::testing::AssertionResult Flatten(xml::XmlResource* doc, + android::ResXMLTree* out_tree, + const XmlFlattenerOptions& options = {}) { + using namespace android; // For NO_ERROR on windows because it is a macro. + + BigBuffer buffer(1024); + XmlFlattener flattener(&buffer, options); + if (!flattener.Consume(context_.get(), doc)) { + return ::testing::AssertionFailure() << "failed to flatten XML Tree"; } - ::testing::AssertionResult flatten(xml::XmlResource* doc, android::ResXMLTree* outTree, - XmlFlattenerOptions options = {}) { - using namespace android; // For NO_ERROR on windows because it is a macro. - - BigBuffer buffer(1024); - XmlFlattener flattener(&buffer, options); - if (!flattener.consume(mContext.get(), doc)) { - return ::testing::AssertionFailure() << "failed to flatten XML Tree"; - } - - std::unique_ptr<uint8_t[]> data = util::copy(buffer); - if (outTree->setTo(data.get(), buffer.size(), true) != NO_ERROR) { - return ::testing::AssertionFailure() << "flattened XML is corrupt"; - } - return ::testing::AssertionSuccess(); + std::unique_ptr<uint8_t[]> data = util::Copy(buffer); + if (out_tree->setTo(data.get(), buffer.size(), true) != NO_ERROR) { + return ::testing::AssertionFailure() << "flattened XML is corrupt"; } + return ::testing::AssertionSuccess(); + } -protected: - std::unique_ptr<IAaptContext> mContext; + protected: + std::unique_ptr<IAaptContext> context_; }; TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( <View xmlns:test="http://com.test" attr="hey"> <Layout test:hello="hi" /> <Layout>Some text</Layout> </View>)EOF"); + android::ResXMLTree tree; + ASSERT_TRUE(Flatten(doc.get(), &tree)); - android::ResXMLTree tree; - ASSERT_TRUE(flatten(doc.get(), &tree)); + ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE); - ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE); + size_t len; + const char16_t* namespace_prefix = tree.getNamespacePrefix(&len); + EXPECT_EQ(StringPiece16(namespace_prefix, len), u"test"); - size_t len; - const char16_t* namespacePrefix = tree.getNamespacePrefix(&len); - EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test"); + const char16_t* namespace_uri = tree.getNamespaceUri(&len); + ASSERT_EQ(StringPiece16(namespace_uri, len), u"http://com.test"); - const char16_t* namespaceUri = tree.getNamespaceUri(&len); - ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test"); + ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); - ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); + ASSERT_EQ(tree.getElementNamespace(&len), nullptr); + const char16_t* tag_name = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tag_name, len), u"View"); - ASSERT_EQ(tree.getElementNamespace(&len), nullptr); - const char16_t* tagName = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(tagName, len), u"View"); + ASSERT_EQ(1u, tree.getAttributeCount()); + ASSERT_EQ(tree.getAttributeNamespace(0, &len), nullptr); + const char16_t* attr_name = tree.getAttributeName(0, &len); + EXPECT_EQ(StringPiece16(attr_name, len), u"attr"); - ASSERT_EQ(1u, tree.getAttributeCount()); - ASSERT_EQ(tree.getAttributeNamespace(0, &len), nullptr); - const char16_t* attrName = tree.getAttributeName(0, &len); - EXPECT_EQ(StringPiece16(attrName, len), u"attr"); + EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", + StringPiece16(u"attr").size())); - EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size())); + ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); - ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); + ASSERT_EQ(tree.getElementNamespace(&len), nullptr); + tag_name = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tag_name, len), u"Layout"); - ASSERT_EQ(tree.getElementNamespace(&len), nullptr); - tagName = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(tagName, len), u"Layout"); + ASSERT_EQ(1u, tree.getAttributeCount()); + const char16_t* attr_namespace = tree.getAttributeNamespace(0, &len); + EXPECT_EQ(StringPiece16(attr_namespace, len), u"http://com.test"); - ASSERT_EQ(1u, tree.getAttributeCount()); - const char16_t* attrNamespace = tree.getAttributeNamespace(0, &len); - EXPECT_EQ(StringPiece16(attrNamespace, len), u"http://com.test"); + attr_name = tree.getAttributeName(0, &len); + EXPECT_EQ(StringPiece16(attr_name, len), u"hello"); - attrName = tree.getAttributeName(0, &len); - EXPECT_EQ(StringPiece16(attrName, len), u"hello"); + ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG); + ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); - ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG); - ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); + ASSERT_EQ(tree.getElementNamespace(&len), nullptr); + tag_name = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tag_name, len), u"Layout"); + ASSERT_EQ(0u, tree.getAttributeCount()); - ASSERT_EQ(tree.getElementNamespace(&len), nullptr); - tagName = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(tagName, len), u"Layout"); - ASSERT_EQ(0u, tree.getAttributeCount()); + ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT); + const char16_t* text = tree.getText(&len); + EXPECT_EQ(StringPiece16(text, len), u"Some text"); - ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT); - const char16_t* text = tree.getText(&len); - EXPECT_EQ(StringPiece16(text, len), u"Some text"); + ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG); + ASSERT_EQ(tree.getElementNamespace(&len), nullptr); + tag_name = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tag_name, len), u"Layout"); - ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG); - ASSERT_EQ(tree.getElementNamespace(&len), nullptr); - tagName = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(tagName, len), u"Layout"); + ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG); + ASSERT_EQ(tree.getElementNamespace(&len), nullptr); + tag_name = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tag_name, len), u"View"); - ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG); - ASSERT_EQ(tree.getElementNamespace(&len), nullptr); - tagName = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(tagName, len), u"View"); + ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE); + namespace_prefix = tree.getNamespacePrefix(&len); + EXPECT_EQ(StringPiece16(namespace_prefix, len), u"test"); - ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE); - namespacePrefix = tree.getNamespacePrefix(&len); - EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test"); + namespace_uri = tree.getNamespaceUri(&len); + ASSERT_EQ(StringPiece16(namespace_uri, len), u"http://com.test"); - namespaceUri = tree.getNamespaceUri(&len); - ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test"); - - ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT); + ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT); } TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:paddingStart="1dp" android:colorAccent="#ffffff"/>)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - ASSERT_TRUE(linker.getSdkLevels().count(17) == 1); - ASSERT_TRUE(linker.getSdkLevels().count(21) == 1); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); + ASSERT_TRUE(linker.sdk_levels().count(17) == 1); + ASSERT_TRUE(linker.sdk_levels().count(21) == 1); - android::ResXMLTree tree; - XmlFlattenerOptions options; - options.maxSdkLevel = 17; - ASSERT_TRUE(flatten(doc.get(), &tree, options)); + android::ResXMLTree tree; + XmlFlattenerOptions options; + options.max_sdk_level = 17; + ASSERT_TRUE(Flatten(doc.get(), &tree, options)); - while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); - } + while (tree.next() != android::ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); + ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + } - ASSERT_EQ(1u, tree.getAttributeCount()); - EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0)); + ASSERT_EQ(1u, tree.getAttributeCount()); + EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0)); +} + +TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) { + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( + <View xmlns:tools="http://schemas.android.com/tools" + xmlns:foo="http://schemas.android.com/foo" + foo:bar="Foo" + tools:ignore="MissingTranslation"/>)EOF"); + + android::ResXMLTree tree; + ASSERT_TRUE(Flatten(doc.get(), &tree)); + + ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE); + + size_t len; + const char16_t* namespace_prefix = tree.getNamespacePrefix(&len); + EXPECT_EQ(StringPiece16(namespace_prefix, len), u"foo"); + + const char16_t* namespace_uri = tree.getNamespaceUri(&len); + ASSERT_EQ(StringPiece16(namespace_uri, len), + u"http://schemas.android.com/foo"); + + ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); + + EXPECT_EQ(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"), + android::NAME_NOT_FOUND); + EXPECT_GE(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), 0); } TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:id="@id/id" class="str" style="@id/id"/>)EOF"); - android::ResXMLTree tree; - ASSERT_TRUE(flatten(doc.get(), &tree)); + android::ResXMLTree tree; + ASSERT_TRUE(Flatten(doc.get(), &tree)); - while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); - } + while (tree.next() != android::ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); + ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + } - EXPECT_EQ(tree.indexOfClass(), 0); - EXPECT_EQ(tree.indexOfStyle(), 1); + EXPECT_EQ(tree.indexOfClass(), 0); + EXPECT_EQ(tree.indexOfStyle(), 1); } /* - * The device ResXMLParser in libandroidfw differentiates between empty namespace and null + * The device ResXMLParser in libandroidfw differentiates between empty + * namespace and null * namespace. */ TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>"); + std::unique_ptr<xml::XmlResource> doc = + test::BuildXmlDom("<View package=\"android\"/>"); - android::ResXMLTree tree; - ASSERT_TRUE(flatten(doc.get(), &tree)); + android::ResXMLTree tree; + ASSERT_TRUE(Flatten(doc.get(), &tree)); - while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); - } + while (tree.next() != android::ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); + ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + } + + const StringPiece16 kPackage = u"package"; + EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), + 0); +} + +TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) { + std::unique_ptr<xml::XmlResource> doc = + test::BuildXmlDom("<View package=\"\"/>"); + + android::ResXMLTree tree; + ASSERT_TRUE(Flatten(doc.get(), &tree)); + + while (tree.next() != android::ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); + ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + } + + const StringPiece16 kPackage = u"package"; + ssize_t idx = + tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()); + ASSERT_GE(idx, 0); - const StringPiece16 kPackage = u"package"; - EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); + size_t len; + EXPECT_NE(nullptr, tree.getAttributeStringValue(idx, &len)); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml b/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml index b6d8f2d1b748..1156b01fe70d 100644 --- a/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml +++ b/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml @@ -14,4 +14,7 @@ limitations under the License. --> -<manifest package="com.android.aapt.app.one" /> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.aapt.app.one" coreApp="true"> + <uses-sdk android:minSdkVersion="21" /> +</manifest> diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/cheap_transparency.png b/tools/aapt2/integration-tests/AppOne/res/drawable/cheap_transparency.png Binary files differnew file mode 100644 index 000000000000..0522a9979db9 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/drawable/cheap_transparency.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/complex.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/complex.9.png Binary files differnew file mode 100644 index 000000000000..baf9fff13ab5 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/drawable/complex.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/outline_8x8.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/outline_8x8.9.png Binary files differnew file mode 100644 index 000000000000..7b331e16fcbd --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/drawable/outline_8x8.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_off_center_outline_32x16.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_off_center_outline_32x16.9.png Binary files differnew file mode 100644 index 000000000000..0ec6c70a2b9f --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_off_center_outline_32x16.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_outline_32x16.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_outline_32x16.9.png Binary files differnew file mode 100644 index 000000000000..e05708a089a3 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_outline_32x16.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_3x3.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_3x3.9.png Binary files differnew file mode 100644 index 000000000000..a11377a0d670 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_3x3.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_optical_bounds_3x3.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_optical_bounds_3x3.9.png Binary files differnew file mode 100644 index 000000000000..6803e4243484 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_optical_bounds_3x3.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/white_3x3.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/white_3x3.9.png Binary files differnew file mode 100644 index 000000000000..1a3731bbc8b8 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/drawable/white_3x3.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/white_optical_bounds_3x3.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/white_optical_bounds_3x3.9.png Binary files differnew file mode 100644 index 000000000000..489ace292e1f --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/drawable/white_optical_bounds_3x3.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/font/myfont-italic.ttf b/tools/aapt2/integration-tests/AppOne/res/font/myfont-italic.ttf new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/font/myfont-italic.ttf diff --git a/tools/aapt2/integration-tests/AppOne/res/font/myfont-normal.ttf b/tools/aapt2/integration-tests/AppOne/res/font/myfont-normal.ttf new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/font/myfont-normal.ttf diff --git a/tools/aapt2/integration-tests/AppOne/res/font/myfont.xml b/tools/aapt2/integration-tests/AppOne/res/font/myfont.xml new file mode 100644 index 000000000000..1fb67914894e --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/font/myfont.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<font-family xmlns:android="http://schemas.android.com/apk/res/android"> + <font android:fontStyle="normal" android:fontWeight="400" android:font="@font/myfont-normal" /> + <font android:fontStyle="italic" android:fontWeight="400" android:font="@font/myfont-italic" /> +</font-family> diff --git a/tools/aapt2/integration-tests/AppOne/res/layout/special.xml b/tools/aapt2/integration-tests/AppOne/res/layout/special.xml new file mode 100644 index 000000000000..28c85ca92019 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/layout/special.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<View xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + <include> + <aapt:attr name="layout" xmlns:aapt="http://schemas.android.com/aapt"> + <RelativeLayout android:id="@+id/hello" /> + </aapt:attr> + </include> +</View> diff --git a/tools/aapt2/integration-tests/AppOne/res/values/test.xml b/tools/aapt2/integration-tests/AppOne/res/values/test.xml index f4b7471aefae..91f8bfd0dd14 100644 --- a/tools/aapt2/integration-tests/AppOne/res/values/test.xml +++ b/tools/aapt2/integration-tests/AppOne/res/values/test.xml @@ -29,4 +29,11 @@ <flag name="pub" value="2" /> <flag name="weak" value="4" /> </attr> + + <!-- Override the Widget styleable declared in StaticLibOne. + This should merge the two when built in overlay mode. --> + <declare-styleable name="Widget"> + <attr name="android:text" /> + <attr name="layout_width" /> + </declare-styleable> </resources> diff --git a/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml b/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml index d09a4851f7b4..b4dc90b3e640 100644 --- a/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml +++ b/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml @@ -23,5 +23,6 @@ <declare-styleable name="Widget"> <attr name="StaticLibOne_attr" /> + <attr name="android:text" /> </declare-styleable> </resources> diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h index 467e60464a68..fdc044d86e5a 100644 --- a/tools/aapt2/io/Data.h +++ b/tools/aapt2/io/Data.h @@ -17,84 +17,97 @@ #ifndef AAPT_IO_DATA_H #define AAPT_IO_DATA_H -#include <utils/FileMap.h> - #include <memory> +#include "android-base/macros.h" +#include "utils/FileMap.h" + namespace aapt { namespace io { /** - * Interface for a block of contiguous memory. An instance of this interface owns the data. + * Interface for a block of contiguous memory. An instance of this interface + * owns the data. */ class IData { -public: - virtual ~IData() = default; + public: + virtual ~IData() = default; + + virtual const void* data() const = 0; + virtual size_t size() const = 0; +}; + +class DataSegment : public IData { + public: + explicit DataSegment(std::unique_ptr<IData> data, size_t offset, size_t len) + : data_(std::move(data)), offset_(offset), len_(len) {} + + const void* data() const override { + return static_cast<const uint8_t*>(data_->data()) + offset_; + } - virtual const void* data() const = 0; - virtual size_t size() const = 0; + size_t size() const override { return len_; } + + private: + DISALLOW_COPY_AND_ASSIGN(DataSegment); + + std::unique_ptr<IData> data_; + size_t offset_; + size_t len_; }; /** - * Implementation of IData that exposes a memory mapped file. The mmapped file is owned by this + * Implementation of IData that exposes a memory mapped file. The mmapped file + * is owned by this * object. */ class MmappedData : public IData { -public: - explicit MmappedData(android::FileMap&& map) : mMap(std::forward<android::FileMap>(map)) { - } + public: + explicit MmappedData(android::FileMap&& map) + : map_(std::forward<android::FileMap>(map)) {} - const void* data() const override { - return mMap.getDataPtr(); - } + const void* data() const override { return map_.getDataPtr(); } - size_t size() const override { - return mMap.getDataLength(); - } + size_t size() const override { return map_.getDataLength(); } -private: - android::FileMap mMap; + private: + android::FileMap map_; }; /** - * Implementation of IData that exposes a block of memory that was malloc'ed (new'ed). The + * Implementation of IData that exposes a block of memory that was malloc'ed + * (new'ed). The * memory is owned by this object. */ class MallocData : public IData { -public: - MallocData(std::unique_ptr<const uint8_t[]> data, size_t size) : - mData(std::move(data)), mSize(size) { - } - - const void* data() const override { - return mData.get(); - } - - size_t size() const override { - return mSize; - } - -private: - std::unique_ptr<const uint8_t[]> mData; - size_t mSize; + public: + MallocData(std::unique_ptr<const uint8_t[]> data, size_t size) + : data_(std::move(data)), size_(size) {} + + const void* data() const override { return data_.get(); } + + size_t size() const override { return size_; } + + private: + std::unique_ptr<const uint8_t[]> data_; + size_t size_; }; /** - * When mmap fails because the file has length 0, we use the EmptyData to simulate data of length 0. + * When mmap fails because the file has length 0, we use the EmptyData to + * simulate data of length 0. */ class EmptyData : public IData { -public: - const void* data() const override { - static const uint8_t d = 0; - return &d; - } - - size_t size() const override { - return 0u; - } + public: + const void* data() const override { + static const uint8_t d = 0; + return &d; + } + + size_t size() const override { return 0u; } }; -} // namespace io -} // namespace aapt +} // namespace io +} // namespace aapt #endif /* AAPT_IO_DATA_H */ diff --git a/tools/aapt2/io/File.cpp b/tools/aapt2/io/File.cpp new file mode 100644 index 000000000000..ee737280ec81 --- /dev/null +++ b/tools/aapt2/io/File.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "io/File.h" + +#include <memory> + +namespace aapt { +namespace io { + +IFile* IFile::CreateFileSegment(size_t offset, size_t len) { + FileSegment* file_segment = new FileSegment(this, offset, len); + segments_.push_back(std::unique_ptr<IFile>(file_segment)); + return file_segment; +} + +std::unique_ptr<IData> FileSegment::OpenAsData() { + std::unique_ptr<IData> data = file_->OpenAsData(); + if (!data) { + return {}; + } + + if (offset_ <= data->size() - len_) { + return util::make_unique<DataSegment>(std::move(data), offset_, len_); + } + return {}; +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h index b4d49719aa3e..644f59f8f3ba 100644 --- a/tools/aapt2/io/File.h +++ b/tools/aapt2/io/File.h @@ -17,62 +17,104 @@ #ifndef AAPT_IO_FILE_H #define AAPT_IO_FILE_H -#include "Source.h" -#include "io/Data.h" - +#include <list> #include <memory> #include <vector> +#include "android-base/macros.h" + +#include "Source.h" +#include "io/Data.h" +#include "util/Util.h" + namespace aapt { namespace io { /** - * Interface for a file, which could be a real file on the file system, or a file inside + * Interface for a file, which could be a real file on the file system, or a + * file inside * a ZIP archive. */ class IFile { -public: - virtual ~IFile() = default; - - /** - * Open the file and return it as a block of contiguous memory. How this occurs is - * implementation dependent. For example, if this is a file on the file system, it may - * simply mmap the contents. If this file represents a compressed file in a ZIP archive, - * it may need to inflate it to memory, incurring a copy. - * - * Returns nullptr on failure. - */ - virtual std::unique_ptr<IData> openAsData() = 0; - - /** - * Returns the source of this file. This is for presentation to the user and may not be a - * valid file system path (for example, it may contain a '@' sign to separate the files within - * a ZIP archive from the path to the containing ZIP archive. - */ - virtual const Source& getSource() const = 0; + public: + virtual ~IFile() = default; + + /** + * Open the file and return it as a block of contiguous memory. How this + * occurs is + * implementation dependent. For example, if this is a file on the file + * system, it may + * simply mmap the contents. If this file represents a compressed file in a + * ZIP archive, + * it may need to inflate it to memory, incurring a copy. + * + * Returns nullptr on failure. + */ + virtual std::unique_ptr<IData> OpenAsData() = 0; + + /** + * Returns the source of this file. This is for presentation to the user and + * may not be a + * valid file system path (for example, it may contain a '@' sign to separate + * the files within + * a ZIP archive from the path to the containing ZIP archive. + */ + virtual const Source& GetSource() const = 0; + + IFile* CreateFileSegment(size_t offset, size_t len); + + private: + // Any segments created from this IFile need to be owned by this IFile, so + // keep them + // in a list. This will never be read, so we prefer better insertion + // performance + // than cache locality, hence the list. + std::list<std::unique_ptr<IFile>> segments_; +}; + +/** + * An IFile that wraps an underlying IFile but limits it to a subsection of that + * file. + */ +class FileSegment : public IFile { + public: + explicit FileSegment(IFile* file, size_t offset, size_t len) + : file_(file), offset_(offset), len_(len) {} + + std::unique_ptr<IData> OpenAsData() override; + + const Source& GetSource() const override { return file_->GetSource(); } + + private: + DISALLOW_COPY_AND_ASSIGN(FileSegment); + + IFile* file_; + size_t offset_; + size_t len_; }; class IFileCollectionIterator { -public: - virtual ~IFileCollectionIterator() = default; + public: + virtual ~IFileCollectionIterator() = default; - virtual bool hasNext() = 0; - virtual IFile* next() = 0; + virtual bool HasNext() = 0; + virtual IFile* Next() = 0; }; /** - * Interface for a collection of files, all of which share a common source. That source may + * Interface for a collection of files, all of which share a common source. That + * source may * simply be the filesystem, or a ZIP archive. */ class IFileCollection { -public: - virtual ~IFileCollection() = default; + public: + virtual ~IFileCollection() = default; - virtual IFile* findFile(const StringPiece& path) = 0; - virtual std::unique_ptr<IFileCollectionIterator> iterator() = 0; + virtual IFile* FindFile(const StringPiece& path) = 0; + virtual std::unique_ptr<IFileCollectionIterator> Iterator() = 0; }; -} // namespace io -} // namespace aapt +} // namespace io +} // namespace aapt #endif /* AAPT_IO_FILE_H */ diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp index e758d8a421e1..828f34e9c883 100644 --- a/tools/aapt2/io/FileSystem.cpp +++ b/tools/aapt2/io/FileSystem.cpp @@ -14,65 +14,62 @@ * limitations under the License. */ -#include "Source.h" #include "io/FileSystem.h" + +#include "utils/FileMap.h" + +#include "Source.h" #include "util/Files.h" #include "util/Maybe.h" #include "util/StringPiece.h" #include "util/Util.h" -#include <utils/FileMap.h> - namespace aapt { namespace io { -RegularFile::RegularFile(const Source& source) : mSource(source) { -} +RegularFile::RegularFile(const Source& source) : source_(source) {} -std::unique_ptr<IData> RegularFile::openAsData() { - android::FileMap map; - if (Maybe<android::FileMap> map = file::mmapPath(mSource.path, nullptr)) { - if (map.value().getDataPtr() && map.value().getDataLength() > 0) { - return util::make_unique<MmappedData>(std::move(map.value())); - } - return util::make_unique<EmptyData>(); +std::unique_ptr<IData> RegularFile::OpenAsData() { + android::FileMap map; + if (Maybe<android::FileMap> map = file::MmapPath(source_.path, nullptr)) { + if (map.value().getDataPtr() && map.value().getDataLength() > 0) { + return util::make_unique<MmappedData>(std::move(map.value())); } - return {}; + return util::make_unique<EmptyData>(); + } + return {}; } -const Source& RegularFile::getSource() const { - return mSource; -} +const Source& RegularFile::GetSource() const { return source_; } -FileCollectionIterator::FileCollectionIterator(FileCollection* collection) : - mCurrent(collection->mFiles.begin()), mEnd(collection->mFiles.end()) { -} +FileCollectionIterator::FileCollectionIterator(FileCollection* collection) + : current_(collection->files_.begin()), end_(collection->files_.end()) {} -bool FileCollectionIterator::hasNext() { - return mCurrent != mEnd; -} +bool FileCollectionIterator::HasNext() { return current_ != end_; } -IFile* FileCollectionIterator::next() { - IFile* result = mCurrent->second.get(); - ++mCurrent; - return result; +IFile* FileCollectionIterator::Next() { + IFile* result = current_->second.get(); + ++current_; + return result; } -IFile* FileCollection::insertFile(const StringPiece& path) { - return (mFiles[path.toString()] = util::make_unique<RegularFile>(Source(path))).get(); +IFile* FileCollection::InsertFile(const StringPiece& path) { + return (files_[path.ToString()] = + util::make_unique<RegularFile>(Source(path))) + .get(); } -IFile* FileCollection::findFile(const StringPiece& path) { - auto iter = mFiles.find(path.toString()); - if (iter != mFiles.end()) { - return iter->second.get(); - } - return nullptr; +IFile* FileCollection::FindFile(const StringPiece& path) { + auto iter = files_.find(path.ToString()); + if (iter != files_.end()) { + return iter->second.get(); + } + return nullptr; } -std::unique_ptr<IFileCollectionIterator> FileCollection::iterator() { - return util::make_unique<FileCollectionIterator>(this); +std::unique_ptr<IFileCollectionIterator> FileCollection::Iterator() { + return util::make_unique<FileCollectionIterator>(this); } -} // namespace io -} // namespace aapt +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h index f0559c03a8b8..84f851ff694b 100644 --- a/tools/aapt2/io/FileSystem.h +++ b/tools/aapt2/io/FileSystem.h @@ -17,10 +17,10 @@ #ifndef AAPT_IO_FILESYSTEM_H #define AAPT_IO_FILESYSTEM_H -#include "io/File.h" - #include <map> +#include "io/File.h" + namespace aapt { namespace io { @@ -28,47 +28,47 @@ namespace io { * A regular file from the file system. Uses mmap to open the data. */ class RegularFile : public IFile { -public: - RegularFile(const Source& source); + public: + explicit RegularFile(const Source& source); - std::unique_ptr<IData> openAsData() override; - const Source& getSource() const override; + std::unique_ptr<IData> OpenAsData() override; + const Source& GetSource() const override; -private: - Source mSource; + private: + Source source_; }; class FileCollection; class FileCollectionIterator : public IFileCollectionIterator { -public: - FileCollectionIterator(FileCollection* collection); + public: + explicit FileCollectionIterator(FileCollection* collection); - bool hasNext() override; - io::IFile* next() override; + bool HasNext() override; + io::IFile* Next() override; -private: - std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd; + private: + std::map<std::string, std::unique_ptr<IFile>>::const_iterator current_, end_; }; /** * An IFileCollection representing the file system. */ class FileCollection : public IFileCollection { -public: - /** - * Adds a file located at path. Returns the IFile representation of that file. - */ - IFile* insertFile(const StringPiece& path); - IFile* findFile(const StringPiece& path) override; - std::unique_ptr<IFileCollectionIterator> iterator() override; + public: + /** + * Adds a file located at path. Returns the IFile representation of that file. + */ + IFile* InsertFile(const StringPiece& path); + IFile* FindFile(const StringPiece& path) override; + std::unique_ptr<IFileCollectionIterator> Iterator() override; -private: - friend class FileCollectionIterator; - std::map<std::string, std::unique_ptr<IFile>> mFiles; + private: + friend class FileCollectionIterator; + std::map<std::string, std::unique_ptr<IFile>> files_; }; -} // namespace io -} // namespace aapt +} // namespace io +} // namespace aapt -#endif // AAPT_IO_FILESYSTEM_H +#endif // AAPT_IO_FILESYSTEM_H diff --git a/tools/aapt2/io/Io.cpp b/tools/aapt2/io/Io.cpp new file mode 100644 index 000000000000..cab4b65f2f5a --- /dev/null +++ b/tools/aapt2/io/Io.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "io/Io.h" + +#include <algorithm> +#include <cstring> + +namespace aapt { +namespace io { + +bool Copy(OutputStream* out, InputStream* in) { + const void* in_buffer; + int in_len; + while (in->Next(&in_buffer, &in_len)) { + void* out_buffer; + int out_len; + if (!out->Next(&out_buffer, &out_len)) { + return !out->HadError(); + } + + const int bytes_to_copy = std::min(in_len, out_len); + memcpy(out_buffer, in_buffer, bytes_to_copy); + out->BackUp(out_len - bytes_to_copy); + in->BackUp(in_len - bytes_to_copy); + } + return !in->HadError(); +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/Io.h b/tools/aapt2/io/Io.h new file mode 100644 index 000000000000..33cdc7bbe498 --- /dev/null +++ b/tools/aapt2/io/Io.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef AAPT_IO_IO_H +#define AAPT_IO_IO_H + +#include <string> + +#include "google/protobuf/io/zero_copy_stream_impl_lite.h" + +namespace aapt { +namespace io { + +/** + * InputStream interface that inherits from protobuf's ZeroCopyInputStream, + * but adds error handling methods to better report issues. + * + * The code style here matches the protobuf style. + */ +class InputStream : public ::google::protobuf::io::ZeroCopyInputStream { + public: + virtual std::string GetError() const { return {}; } + + virtual bool HadError() const = 0; +}; + +/** + * OutputStream interface that inherits from protobuf's ZeroCopyOutputStream, + * but adds error handling methods to better report issues. + * + * The code style here matches the protobuf style. + */ +class OutputStream : public ::google::protobuf::io::ZeroCopyOutputStream { + public: + virtual std::string GetError() const { return {}; } + + virtual bool HadError() const = 0; +}; + +/** + * Copies the data from in to out. Returns true if there was no error. + * If there was an error, check the individual streams' HadError/GetError + * methods. + */ +bool Copy(OutputStream* out, InputStream* in); + +} // namespace io +} // namespace aapt + +#endif /* AAPT_IO_IO_H */ diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp index b3e7a02102e4..f4a128eca9d1 100644 --- a/tools/aapt2/io/ZipArchive.cpp +++ b/tools/aapt2/io/ZipArchive.cpp @@ -14,129 +14,128 @@ * limitations under the License. */ -#include "Source.h" #include "io/ZipArchive.h" -#include "util/Util.h" -#include <utils/FileMap.h> -#include <ziparchive/zip_archive.h> +#include "utils/FileMap.h" +#include "ziparchive/zip_archive.h" + +#include "Source.h" +#include "util/Util.h" namespace aapt { namespace io { -ZipFile::ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source) : - mZipHandle(handle), mZipEntry(entry), mSource(source) { -} +ZipFile::ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, + const Source& source) + : zip_handle_(handle), zip_entry_(entry), source_(source) {} -std::unique_ptr<IData> ZipFile::openAsData() { - if (mZipEntry.method == kCompressStored) { - int fd = GetFileDescriptor(mZipHandle); - - android::FileMap fileMap; - bool result = fileMap.create(nullptr, fd, mZipEntry.offset, - mZipEntry.uncompressed_length, true); - if (!result) { - return {}; - } - return util::make_unique<MmappedData>(std::move(fileMap)); - - } else { - std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>( - new uint8_t[mZipEntry.uncompressed_length]); - int32_t result = ExtractToMemory(mZipHandle, &mZipEntry, data.get(), - static_cast<uint32_t>(mZipEntry.uncompressed_length)); - if (result != 0) { - return {}; - } - return util::make_unique<MallocData>(std::move(data), mZipEntry.uncompressed_length); - } -} +std::unique_ptr<IData> ZipFile::OpenAsData() { + if (zip_entry_.method == kCompressStored) { + int fd = GetFileDescriptor(zip_handle_); -const Source& ZipFile::getSource() const { - return mSource; + android::FileMap file_map; + bool result = file_map.create(nullptr, fd, zip_entry_.offset, + zip_entry_.uncompressed_length, true); + if (!result) { + return {}; + } + return util::make_unique<MmappedData>(std::move(file_map)); + + } else { + std::unique_ptr<uint8_t[]> data = + std::unique_ptr<uint8_t[]>(new uint8_t[zip_entry_.uncompressed_length]); + int32_t result = + ExtractToMemory(zip_handle_, &zip_entry_, data.get(), + static_cast<uint32_t>(zip_entry_.uncompressed_length)); + if (result != 0) { + return {}; + } + return util::make_unique<MallocData>(std::move(data), + zip_entry_.uncompressed_length); + } } -ZipFileCollectionIterator::ZipFileCollectionIterator(ZipFileCollection* collection) : - mCurrent(collection->mFiles.begin()), mEnd(collection->mFiles.end()) { -} +const Source& ZipFile::GetSource() const { return source_; } -bool ZipFileCollectionIterator::hasNext() { - return mCurrent != mEnd; -} +ZipFileCollectionIterator::ZipFileCollectionIterator( + ZipFileCollection* collection) + : current_(collection->files_.begin()), end_(collection->files_.end()) {} -IFile* ZipFileCollectionIterator::next() { - IFile* result = mCurrent->second.get(); - ++mCurrent; - return result; -} +bool ZipFileCollectionIterator::HasNext() { return current_ != end_; } -ZipFileCollection::ZipFileCollection() : mHandle(nullptr) { +IFile* ZipFileCollectionIterator::Next() { + IFile* result = current_->second.get(); + ++current_; + return result; } -std::unique_ptr<ZipFileCollection> ZipFileCollection::create(const StringPiece& path, - std::string* outError) { - constexpr static const int32_t kEmptyArchive = -6; +ZipFileCollection::ZipFileCollection() : handle_(nullptr) {} - std::unique_ptr<ZipFileCollection> collection = std::unique_ptr<ZipFileCollection>( - new ZipFileCollection()); +std::unique_ptr<ZipFileCollection> ZipFileCollection::Create( + const StringPiece& path, std::string* out_error) { + constexpr static const int32_t kEmptyArchive = -6; - int32_t result = OpenArchive(path.data(), &collection->mHandle); - if (result != 0) { - // If a zip is empty, result will be an error code. This is fine and we should - // return an empty ZipFileCollection. - if (result == kEmptyArchive) { - return collection; - } - - if (outError) *outError = ErrorCodeString(result); - return {}; - } + std::unique_ptr<ZipFileCollection> collection = + std::unique_ptr<ZipFileCollection>(new ZipFileCollection()); - void* cookie = nullptr; - result = StartIteration(collection->mHandle, &cookie, nullptr, nullptr); - if (result != 0) { - if (outError) *outError = ErrorCodeString(result); - return {}; + int32_t result = OpenArchive(path.data(), &collection->handle_); + if (result != 0) { + // If a zip is empty, result will be an error code. This is fine and we + // should + // return an empty ZipFileCollection. + if (result == kEmptyArchive) { + return collection; } - using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>; - IterationEnder iterationEnder(cookie, EndIteration); - - ZipString zipEntryName; - ZipEntry zipData; - while ((result = Next(cookie, &zipData, &zipEntryName)) == 0) { - std::string zipEntryPath = std::string(reinterpret_cast<const char*>(zipEntryName.name), - zipEntryName.name_length); - std::string nestedPath = path.toString() + "@" + zipEntryPath; - collection->mFiles[zipEntryPath] = util::make_unique<ZipFile>(collection->mHandle, - zipData, - Source(nestedPath)); - } - - if (result != -1) { - if (outError) *outError = ErrorCodeString(result); - return {}; - } - return collection; + if (out_error) *out_error = ErrorCodeString(result); + return {}; + } + + void* cookie = nullptr; + result = StartIteration(collection->handle_, &cookie, nullptr, nullptr); + if (result != 0) { + if (out_error) *out_error = ErrorCodeString(result); + return {}; + } + + using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>; + IterationEnder iteration_ender(cookie, EndIteration); + + ZipString zip_entry_name; + ZipEntry zip_data; + while ((result = Next(cookie, &zip_data, &zip_entry_name)) == 0) { + std::string zip_entry_path = + std::string(reinterpret_cast<const char*>(zip_entry_name.name), + zip_entry_name.name_length); + std::string nested_path = path.ToString() + "@" + zip_entry_path; + collection->files_[zip_entry_path] = util::make_unique<ZipFile>( + collection->handle_, zip_data, Source(nested_path)); + } + + if (result != -1) { + if (out_error) *out_error = ErrorCodeString(result); + return {}; + } + return collection; } -IFile* ZipFileCollection::findFile(const StringPiece& path) { - auto iter = mFiles.find(path.toString()); - if (iter != mFiles.end()) { - return iter->second.get(); - } - return nullptr; +IFile* ZipFileCollection::FindFile(const StringPiece& path) { + auto iter = files_.find(path.ToString()); + if (iter != files_.end()) { + return iter->second.get(); + } + return nullptr; } -std::unique_ptr<IFileCollectionIterator> ZipFileCollection::iterator() { - return util::make_unique<ZipFileCollectionIterator>(this); +std::unique_ptr<IFileCollectionIterator> ZipFileCollection::Iterator() { + return util::make_unique<ZipFileCollectionIterator>(this); } ZipFileCollection::~ZipFileCollection() { - if (mHandle) { - CloseArchive(mHandle); - } + if (handle_) { + CloseArchive(handle_); + } } -} // namespace io -} // namespace aapt +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h index 5ad119d1d6d4..85ca1aed4edc 100644 --- a/tools/aapt2/io/ZipArchive.h +++ b/tools/aapt2/io/ZipArchive.h @@ -17,67 +17,70 @@ #ifndef AAPT_IO_ZIPARCHIVE_H #define AAPT_IO_ZIPARCHIVE_H -#include "io/File.h" -#include "util/StringPiece.h" +#include "ziparchive/zip_archive.h" #include <map> -#include <ziparchive/zip_archive.h> + +#include "io/File.h" +#include "util/StringPiece.h" namespace aapt { namespace io { /** - * An IFile representing a file within a ZIP archive. If the file is compressed, it is uncompressed - * and copied into memory when opened. Otherwise it is mmapped from the ZIP archive. + * An IFile representing a file within a ZIP archive. If the file is compressed, + * it is uncompressed + * and copied into memory when opened. Otherwise it is mmapped from the ZIP + * archive. */ class ZipFile : public IFile { -public: - ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source); + public: + ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source); - std::unique_ptr<IData> openAsData() override; - const Source& getSource() const override; + std::unique_ptr<IData> OpenAsData() override; + const Source& GetSource() const override; -private: - ZipArchiveHandle mZipHandle; - ZipEntry mZipEntry; - Source mSource; + private: + ZipArchiveHandle zip_handle_; + ZipEntry zip_entry_; + Source source_; }; class ZipFileCollection; class ZipFileCollectionIterator : public IFileCollectionIterator { -public: - ZipFileCollectionIterator(ZipFileCollection* collection); + public: + explicit ZipFileCollectionIterator(ZipFileCollection* collection); - bool hasNext() override; - io::IFile* next() override; + bool HasNext() override; + io::IFile* Next() override; -private: - std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd; + private: + std::map<std::string, std::unique_ptr<IFile>>::const_iterator current_, end_; }; /** * An IFileCollection that represents a ZIP archive and the entries within it. */ class ZipFileCollection : public IFileCollection { -public: - static std::unique_ptr<ZipFileCollection> create(const StringPiece& path, - std::string* outError); + public: + static std::unique_ptr<ZipFileCollection> Create(const StringPiece& path, + std::string* outError); - io::IFile* findFile(const StringPiece& path) override; - std::unique_ptr<IFileCollectionIterator> iterator() override; + io::IFile* FindFile(const StringPiece& path) override; + std::unique_ptr<IFileCollectionIterator> Iterator() override; - ~ZipFileCollection() override; + ~ZipFileCollection() override; -private: - friend class ZipFileCollectionIterator; - ZipFileCollection(); + private: + friend class ZipFileCollectionIterator; + ZipFileCollection(); - ZipArchiveHandle mHandle; - std::map<std::string, std::unique_ptr<IFile>> mFiles; + ZipArchiveHandle handle_; + std::map<std::string, std::unique_ptr<IFile>> files_; }; -} // namespace io -} // namespace aapt +} // namespace io +} // namespace aapt #endif /* AAPT_IO_ZIPARCHIVE_H */ diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp index b7e7f903a2b1..2951e5cff6d7 100644 --- a/tools/aapt2/java/AnnotationProcessor.cpp +++ b/tools/aapt2/java/AnnotationProcessor.cpp @@ -15,79 +15,71 @@ */ #include "java/AnnotationProcessor.h" -#include "util/Util.h" #include <algorithm> +#include "util/Util.h" + namespace aapt { -void AnnotationProcessor::appendCommentLine(std::string& comment) { - static const std::string sDeprecated = "@deprecated"; - static const std::string sSystemApi = "@SystemApi"; +void AnnotationProcessor::AppendCommentLine(std::string& comment) { + static const std::string sDeprecated = "@deprecated"; + static const std::string sSystemApi = "@SystemApi"; - if (comment.find(sDeprecated) != std::string::npos) { - mAnnotationBitMask |= kDeprecated; - } + if (comment.find(sDeprecated) != std::string::npos) { + annotation_bit_mask_ |= kDeprecated; + } - std::string::size_type idx = comment.find(sSystemApi); - if (idx != std::string::npos) { - mAnnotationBitMask |= kSystemApi; - comment.erase(comment.begin() + idx, comment.begin() + idx + sSystemApi.size()); - } + std::string::size_type idx = comment.find(sSystemApi); + if (idx != std::string::npos) { + annotation_bit_mask_ |= kSystemApi; + comment.erase(comment.begin() + idx, + comment.begin() + idx + sSystemApi.size()); + } - if (util::trimWhitespace(comment).empty()) { - return; - } + if (util::TrimWhitespace(comment).empty()) { + return; + } - if (!mHasComments) { - mHasComments = true; - mComment << "/**"; - } + if (!has_comments_) { + has_comments_ = true; + comment_ << "/**"; + } - mComment << "\n * " << std::move(comment); + comment_ << "\n * " << std::move(comment); } -void AnnotationProcessor::appendComment(const StringPiece16& comment) { - // We need to process line by line to clean-up whitespace and append prefixes. - for (StringPiece16 line : util::tokenize(comment, u'\n')) { - line = util::trimWhitespace(line); - if (!line.empty()) { - std::string utf8Line = util::utf16ToUtf8(line); - appendCommentLine(utf8Line); - } +void AnnotationProcessor::AppendComment(const StringPiece& comment) { + // We need to process line by line to clean-up whitespace and append prefixes. + for (StringPiece line : util::Tokenize(comment, '\n')) { + line = util::TrimWhitespace(line); + if (!line.empty()) { + std::string lineCopy = line.ToString(); + AppendCommentLine(lineCopy); } + } } -void AnnotationProcessor::appendComment(const StringPiece& comment) { - for (StringPiece line : util::tokenize(comment, '\n')) { - line = util::trimWhitespace(line); - if (!line.empty()) { - std::string utf8Line = line.toString(); - appendCommentLine(utf8Line); - } - } -} - -void AnnotationProcessor::appendNewLine() { - mComment << "\n *"; -} +void AnnotationProcessor::AppendNewLine() { comment_ << "\n *"; } -void AnnotationProcessor::writeToStream(std::ostream* out, const StringPiece& prefix) const { - if (mHasComments) { - std::string result = mComment.str(); - for (StringPiece line : util::tokenize<char>(result, '\n')) { - *out << prefix << line << "\n"; - } - *out << prefix << " */" << "\n"; +void AnnotationProcessor::WriteToStream(std::ostream* out, + const StringPiece& prefix) const { + if (has_comments_) { + std::string result = comment_.str(); + for (StringPiece line : util::Tokenize(result, '\n')) { + *out << prefix << line << "\n"; } + *out << prefix << " */" + << "\n"; + } - if (mAnnotationBitMask & kDeprecated) { - *out << prefix << "@Deprecated\n"; - } + if (annotation_bit_mask_ & kDeprecated) { + *out << prefix << "@Deprecated\n"; + } - if (mAnnotationBitMask & kSystemApi) { - *out << prefix << "@android.annotation.SystemApi\n"; - } + if (annotation_bit_mask_ & kSystemApi) { + *out << prefix << "@android.annotation.SystemApi\n"; + } } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h index 8309dd978175..666a7f356768 100644 --- a/tools/aapt2/java/AnnotationProcessor.h +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -17,11 +17,11 @@ #ifndef AAPT_JAVA_ANNOTATIONPROCESSOR_H #define AAPT_JAVA_ANNOTATIONPROCESSOR_H -#include "util/StringPiece.h" - #include <sstream> #include <string> +#include "util/StringPiece.h" + namespace aapt { /** @@ -52,35 +52,36 @@ namespace aapt { * */ class AnnotationProcessor { -public: - /** - * Adds more comments. Since resources can have various values with different configurations, - * we need to collect all the comments. - */ - void appendComment(const StringPiece16& comment); - void appendComment(const StringPiece& comment); + public: + /** + * Adds more comments. Since resources can have various values with different + * configurations, + * we need to collect all the comments. + */ + void AppendComment(const StringPiece& comment); - void appendNewLine(); + void AppendNewLine(); - /** - * Writes the comments and annotations to the stream, with the given prefix before each line. - */ - void writeToStream(std::ostream* out, const StringPiece& prefix) const; + /** + * Writes the comments and annotations to the stream, with the given prefix + * before each line. + */ + void WriteToStream(std::ostream* out, const StringPiece& prefix) const; -private: - enum : uint32_t { - kDeprecated = 0x01, - kSystemApi = 0x02, - }; + private: + enum : uint32_t { + kDeprecated = 0x01, + kSystemApi = 0x02, + }; - std::stringstream mComment; - std::stringstream mAnnotations; - bool mHasComments = false; - uint32_t mAnnotationBitMask = 0; + std::stringstream comment_; + std::stringstream mAnnotations; + bool has_comments_ = false; + uint32_t annotation_bit_mask_ = 0; - void appendCommentLine(std::string& line); + void AppendCommentLine(std::string& line); }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_JAVA_ANNOTATIONPROCESSOR_H */ diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp index 5a39add48fbd..3e43c4295c07 100644 --- a/tools/aapt2/java/AnnotationProcessor_test.cpp +++ b/tools/aapt2/java/AnnotationProcessor_test.cpp @@ -15,38 +15,39 @@ */ #include "java/AnnotationProcessor.h" + #include "test/Test.h" namespace aapt { TEST(AnnotationProcessorTest, EmitsDeprecated) { - const char* comment = "Some comment, and it should contain a marker word, " - "something that marks this resource as nor needed. " - "{@deprecated That's the marker! }"; + const char* comment = + "Some comment, and it should contain a marker word, " + "something that marks this resource as nor needed. " + "{@deprecated That's the marker! }"; - AnnotationProcessor processor; - processor.appendComment(comment); + AnnotationProcessor processor; + processor.AppendComment(comment); - std::stringstream result; - processor.writeToStream(&result, ""); - std::string annotations = result.str(); + std::stringstream result; + processor.WriteToStream(&result, ""); + std::string annotations = result.str(); - EXPECT_NE(std::string::npos, annotations.find("@Deprecated")); + EXPECT_NE(std::string::npos, annotations.find("@Deprecated")); } TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) { - AnnotationProcessor processor; - processor.appendComment("@SystemApi This is a system API"); + AnnotationProcessor processor; + processor.AppendComment("@SystemApi This is a system API"); - std::stringstream result; - processor.writeToStream(&result, ""); - std::string annotations = result.str(); + std::stringstream result; + processor.WriteToStream(&result, ""); + std::string annotations = result.str(); - EXPECT_NE(std::string::npos, annotations.find("@android.annotation.SystemApi")); - EXPECT_EQ(std::string::npos, annotations.find("@SystemApi")); - EXPECT_NE(std::string::npos, annotations.find("This is a system API")); + EXPECT_NE(std::string::npos, + annotations.find("@android.annotation.SystemApi")); + EXPECT_EQ(std::string::npos, annotations.find("@SystemApi")); + EXPECT_NE(std::string::npos, annotations.find("This is a system API")); } -} // namespace aapt - - +} // namespace aapt diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp index 08f2c8b9805c..f1f1f925480c 100644 --- a/tools/aapt2/java/ClassDefinition.cpp +++ b/tools/aapt2/java/ClassDefinition.cpp @@ -15,61 +15,59 @@ */ #include "java/ClassDefinition.h" -#include "util/StringPiece.h" -#include <ostream> +#include "util/StringPiece.h" namespace aapt { bool ClassDefinition::empty() const { - for (const std::unique_ptr<ClassMember>& member : mMembers) { - if (!member->empty()) { - return false; - } + for (const std::unique_ptr<ClassMember>& member : members_) { + if (!member->empty()) { + return false; } - return true; + } + return true; } -void ClassDefinition::writeToStream(const StringPiece& prefix, bool final, +void ClassDefinition::WriteToStream(const StringPiece& prefix, bool final, std::ostream* out) const { - if (mMembers.empty() && !mCreateIfEmpty) { - return; - } + if (members_.empty() && !create_if_empty_) { + return; + } - ClassMember::writeToStream(prefix, final, out); + ClassMember::WriteToStream(prefix, final, out); - *out << prefix << "public "; - if (mQualifier == ClassQualifier::Static) { - *out << "static "; - } - *out << "final class " << mName << " {\n"; + *out << prefix << "public "; + if (qualifier_ == ClassQualifier::Static) { + *out << "static "; + } + *out << "final class " << name_ << " {\n"; - std::string newPrefix = prefix.toString(); - newPrefix.append(kIndent); + std::string new_prefix = prefix.ToString(); + new_prefix.append(kIndent); - for (const std::unique_ptr<ClassMember>& member : mMembers) { - member->writeToStream(newPrefix, final, out); - *out << "\n"; - } + for (const std::unique_ptr<ClassMember>& member : members_) { + member->WriteToStream(new_prefix, final, out); + *out << "\n"; + } - *out << prefix << "}"; + *out << prefix << "}"; } constexpr static const char* sWarningHeader = - "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n" - " *\n" - " * This class was automatically generated by the\n" - " * aapt tool from the resource data it found. It\n" - " * should not be modified by hand.\n" - " */\n\n"; + "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n" + " *\n" + " * This class was automatically generated by the\n" + " * aapt tool from the resource data it found. It\n" + " * should not be modified by hand.\n" + " */\n\n"; -bool ClassDefinition::writeJavaFile(const ClassDefinition* def, - const StringPiece& package, - bool final, +bool ClassDefinition::WriteJavaFile(const ClassDefinition* def, + const StringPiece& package, bool final, std::ostream* out) { - *out << sWarningHeader << "package " << package << ";\n\n"; - def->writeToStream("", final, out); - return bool(*out); + *out << sWarningHeader << "package " << package << ";\n\n"; + def->WriteToStream("", final, out); + return bool(*out); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h index d45328fedba2..d8b61d919fd8 100644 --- a/tools/aapt2/java/ClassDefinition.h +++ b/tools/aapt2/java/ClassDefinition.h @@ -17,15 +17,16 @@ #ifndef AAPT_JAVA_CLASSDEFINITION_H #define AAPT_JAVA_CLASSDEFINITION_H +#include <ostream> +#include <string> + +#include "android-base/macros.h" + #include "Resource.h" #include "java/AnnotationProcessor.h" #include "util/StringPiece.h" #include "util/Util.h" -#include <android-base/macros.h> -#include <sstream> -#include <string> - namespace aapt { // The number of attributes to emit per line in a Styleable array. @@ -33,46 +34,43 @@ constexpr static size_t kAttribsPerLine = 4; constexpr static const char* kIndent = " "; class ClassMember { -public: - virtual ~ClassMember() = default; + public: + virtual ~ClassMember() = default; - AnnotationProcessor* getCommentBuilder() { - return &mProcessor; - } + AnnotationProcessor* GetCommentBuilder() { return &processor_; } - virtual bool empty() const = 0; + virtual bool empty() const = 0; - virtual void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const { - mProcessor.writeToStream(out, prefix); - } + virtual void WriteToStream(const StringPiece& prefix, bool final, + std::ostream* out) const { + processor_.WriteToStream(out, prefix); + } -private: - AnnotationProcessor mProcessor; + private: + AnnotationProcessor processor_; }; template <typename T> class PrimitiveMember : public ClassMember { -public: - PrimitiveMember(const StringPiece& name, const T& val) : - mName(name.toString()), mVal(val) { - } + public: + PrimitiveMember(const StringPiece& name, const T& val) + : name_(name.ToString()), val_(val) {} - bool empty() const override { - return false; - } + bool empty() const override { return false; } - void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override { - ClassMember::writeToStream(prefix, final, out); + void WriteToStream(const StringPiece& prefix, bool final, + std::ostream* out) const override { + ClassMember::WriteToStream(prefix, final, out); - *out << prefix << "public static " << (final ? "final " : "") - << "int " << mName << "=" << mVal << ";"; - } + *out << prefix << "public static " << (final ? "final " : "") << "int " + << name_ << "=" << val_ << ";"; + } -private: - std::string mName; - T mVal; + private: + std::string name_; + T val_; - DISALLOW_COPY_AND_ASSIGN(PrimitiveMember); + DISALLOW_COPY_AND_ASSIGN(PrimitiveMember); }; /** @@ -80,27 +78,25 @@ private: */ template <> class PrimitiveMember<std::string> : public ClassMember { -public: - PrimitiveMember(const StringPiece& name, const std::string& val) : - mName(name.toString()), mVal(val) { - } + public: + PrimitiveMember(const StringPiece& name, const std::string& val) + : name_(name.ToString()), val_(val) {} - bool empty() const override { - return false; - } + bool empty() const override { return false; } - void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override { - ClassMember::writeToStream(prefix, final, out); + void WriteToStream(const StringPiece& prefix, bool final, + std::ostream* out) const override { + ClassMember::WriteToStream(prefix, final, out); - *out << prefix << "public static " << (final ? "final " : "") - << "String " << mName << "=\"" << mVal << "\";"; - } + *out << prefix << "public static " << (final ? "final " : "") << "String " + << name_ << "=\"" << val_ << "\";"; + } -private: - std::string mName; - std::string mVal; + private: + std::string name_; + std::string val_; - DISALLOW_COPY_AND_ASSIGN(PrimitiveMember); + DISALLOW_COPY_AND_ASSIGN(PrimitiveMember); }; using IntMember = PrimitiveMember<uint32_t>; @@ -109,80 +105,75 @@ using StringMember = PrimitiveMember<std::string>; template <typename T> class PrimitiveArrayMember : public ClassMember { -public: - PrimitiveArrayMember(const StringPiece& name) : - mName(name.toString()) { - } + public: + explicit PrimitiveArrayMember(const StringPiece& name) + : name_(name.ToString()) {} - void addElement(const T& val) { - mElements.push_back(val); - } + void AddElement(const T& val) { elements_.push_back(val); } - bool empty() const override { - return false; - } + bool empty() const override { return false; } - void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override { - ClassMember::writeToStream(prefix, final, out); + void WriteToStream(const StringPiece& prefix, bool final, + std::ostream* out) const override { + ClassMember::WriteToStream(prefix, final, out); - *out << prefix << "public static final int[] " << mName << "={"; + *out << prefix << "public static final int[] " << name_ << "={"; - const auto begin = mElements.begin(); - const auto end = mElements.end(); - for (auto current = begin; current != end; ++current) { - if (std::distance(begin, current) % kAttribsPerLine == 0) { - *out << "\n" << prefix << kIndent << kIndent; - } + const auto begin = elements_.begin(); + const auto end = elements_.end(); + for (auto current = begin; current != end; ++current) { + if (std::distance(begin, current) % kAttribsPerLine == 0) { + *out << "\n" << prefix << kIndent << kIndent; + } - *out << *current; - if (std::distance(current, end) > 1) { - *out << ", "; - } - } - *out << "\n" << prefix << kIndent <<"};"; + *out << *current; + if (std::distance(current, end) > 1) { + *out << ", "; + } } + *out << "\n" << prefix << kIndent << "};"; + } -private: - std::string mName; - std::vector<T> mElements; + private: + std::string name_; + std::vector<T> elements_; - DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember); + DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember); }; using ResourceArrayMember = PrimitiveArrayMember<ResourceId>; -enum class ClassQualifier { - None, - Static -}; +enum class ClassQualifier { None, Static }; class ClassDefinition : public ClassMember { -public: - static bool writeJavaFile(const ClassDefinition* def, - const StringPiece& package, - bool final, - std::ostream* out); - - ClassDefinition(const StringPiece& name, ClassQualifier qualifier, bool createIfEmpty) : - mName(name.toString()), mQualifier(qualifier), mCreateIfEmpty(createIfEmpty) { - } - - void addMember(std::unique_ptr<ClassMember> member) { - mMembers.push_back(std::move(member)); - } - - bool empty() const override; - void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override; - -private: - std::string mName; - ClassQualifier mQualifier; - bool mCreateIfEmpty; - std::vector<std::unique_ptr<ClassMember>> mMembers; - - DISALLOW_COPY_AND_ASSIGN(ClassDefinition); + public: + static bool WriteJavaFile(const ClassDefinition* def, + const StringPiece& package, bool final, + std::ostream* out); + + ClassDefinition(const StringPiece& name, ClassQualifier qualifier, + bool createIfEmpty) + : name_(name.ToString()), + qualifier_(qualifier), + create_if_empty_(createIfEmpty) {} + + void AddMember(std::unique_ptr<ClassMember> member) { + members_.push_back(std::move(member)); + } + + bool empty() const override; + void WriteToStream(const StringPiece& prefix, bool final, + std::ostream* out) const override; + + private: + std::string name_; + ClassQualifier qualifier_; + bool create_if_empty_; + std::vector<std::unique_ptr<ClassMember>> members_; + + DISALLOW_COPY_AND_ASSIGN(ClassDefinition); }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_JAVA_CLASSDEFINITION_H */ diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index 84df0b429fc5..6e7c707847b9 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -14,60 +14,57 @@ * limitations under the License. */ +#include "java/JavaClassGenerator.h" + +#include <algorithm> +#include <ostream> +#include <set> +#include <sstream> +#include <tuple> + +#include "android-base/logging.h" + #include "NameMangler.h" #include "Resource.h" #include "ResourceTable.h" #include "ResourceValues.h" #include "ValueVisitor.h" - #include "java/AnnotationProcessor.h" #include "java/ClassDefinition.h" -#include "java/JavaClassGenerator.h" #include "process/SymbolTable.h" #include "util/StringPiece.h" -#include <algorithm> -#include <ostream> -#include <set> -#include <sstream> -#include <tuple> - namespace aapt { -JavaClassGenerator::JavaClassGenerator(IAaptContext* context, ResourceTable* table, - const JavaClassGeneratorOptions& options) : - mContext(context), mTable(table), mOptions(options) { -} - -static const std::set<StringPiece16> sJavaIdentifiers = { - u"abstract", u"assert", u"boolean", u"break", u"byte", - u"case", u"catch", u"char", u"class", u"const", u"continue", - u"default", u"do", u"double", u"else", u"enum", u"extends", - u"final", u"finally", u"float", u"for", u"goto", u"if", - u"implements", u"import", u"instanceof", u"int", u"interface", - u"long", u"native", u"new", u"package", u"private", u"protected", - u"public", u"return", u"short", u"static", u"strictfp", u"super", - u"switch", u"synchronized", u"this", u"throw", u"throws", - u"transient", u"try", u"void", u"volatile", u"while", u"true", - u"false", u"null" -}; - -static bool isValidSymbol(const StringPiece16& symbol) { - return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end(); +static const std::set<StringPiece> sJavaIdentifiers = { + "abstract", "assert", "boolean", "break", "byte", + "case", "catch", "char", "class", "const", + "continue", "default", "do", "double", "else", + "enum", "extends", "final", "finally", "float", + "for", "goto", "if", "implements", "import", + "instanceof", "int", "interface", "long", "native", + "new", "package", "private", "protected", "public", + "return", "short", "static", "strictfp", "super", + "switch", "synchronized", "this", "throw", "throws", + "transient", "try", "void", "volatile", "while", + "true", "false", "null"}; + +static bool IsValidSymbol(const StringPiece& symbol) { + return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end(); } /* * Java symbols can not contain . or -, but those are valid in a resource name. * Replace those with '_'. */ -static std::string transform(const StringPiece16& symbol) { - std::string output = util::utf16ToUtf8(symbol); - for (char& c : output) { - if (c == '.' || c == '-') { - c = '_'; - } +static std::string Transform(const StringPiece& symbol) { + std::string output = symbol.ToString(); + for (char& c : output) { + if (c == '.' || c == '-') { + c = '_'; } - return output; + } + return output; } /** @@ -81,477 +78,519 @@ static std::string transform(const StringPiece16& symbol) { * Foo_android_bar * Foo_bar */ -static std::string transformNestedAttr(const ResourceNameRef& attrName, - const std::string& styleableClassName, - const StringPiece16& packageNameToGenerate) { - std::string output = styleableClassName; - - // We may reference IDs from other packages, so prefix the entry name with - // the package. - if (!attrName.package.empty() && packageNameToGenerate != attrName.package) { - output += "_" + transform(attrName.package); - } - output += "_" + transform(attrName.entry); - return output; +static std::string TransformNestedAttr( + const ResourceNameRef& attr_name, const std::string& styleable_class_name, + const StringPiece& package_name_to_generate) { + std::string output = styleable_class_name; + + // We may reference IDs from other packages, so prefix the entry name with + // the package. + if (!attr_name.package.empty() && + package_name_to_generate != attr_name.package) { + output += "_" + Transform(attr_name.package); + } + output += "_" + Transform(attr_name.entry); + return output; } -static void addAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) { - const uint32_t typeMask = attr->typeMask; - if (typeMask & android::ResTable_map::TYPE_REFERENCE) { - processor->appendComment( - "<p>May be a reference to another resource, in the form\n" - "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a theme\n" - "attribute in the form\n" - "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\"."); +static void AddAttributeFormatDoc(AnnotationProcessor* processor, + Attribute* attr) { + const uint32_t type_mask = attr->type_mask; + if (type_mask & android::ResTable_map::TYPE_REFERENCE) { + processor->AppendComment( + "<p>May be a reference to another resource, in the form\n" + "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a " + "theme\n" + "attribute in the form\n" + "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\"."); + } + + if (type_mask & android::ResTable_map::TYPE_STRING) { + processor->AppendComment( + "<p>May be a string value, using '\\\\;' to escape characters such as\n" + "'\\\\n' or '\\\\uxxxx' for a unicode character;"); + } + + if (type_mask & android::ResTable_map::TYPE_INTEGER) { + processor->AppendComment( + "<p>May be an integer value, such as \"<code>100</code>\"."); + } + + if (type_mask & android::ResTable_map::TYPE_BOOLEAN) { + processor->AppendComment( + "<p>May be a boolean value, such as \"<code>true</code>\" or\n" + "\"<code>false</code>\"."); + } + + if (type_mask & android::ResTable_map::TYPE_COLOR) { + processor->AppendComment( + "<p>May be a color value, in the form of " + "\"<code>#<i>rgb</i></code>\",\n" + "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code\", or \n" + "\"<code>#<i>aarrggbb</i></code>\"."); + } + + if (type_mask & android::ResTable_map::TYPE_FLOAT) { + processor->AppendComment( + "<p>May be a floating point value, such as \"<code>1.2</code>\"."); + } + + if (type_mask & android::ResTable_map::TYPE_DIMENSION) { + processor->AppendComment( + "<p>May be a dimension value, which is a floating point number " + "appended with a\n" + "unit such as \"<code>14.5sp</code>\".\n" + "Available units are: px (pixels), dp (density-independent pixels),\n" + "sp (scaled pixels based on preferred font size), in (inches), and\n" + "mm (millimeters)."); + } + + if (type_mask & android::ResTable_map::TYPE_FRACTION) { + processor->AppendComment( + "<p>May be a fractional value, which is a floating point number " + "appended with\n" + "either % or %p, such as \"<code>14.5%</code>\".\n" + "The % suffix always means a percentage of the base size;\n" + "the optional %p suffix provides a size relative to some parent " + "container."); + } + + if (type_mask & + (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) { + if (type_mask & android::ResTable_map::TYPE_FLAGS) { + processor->AppendComment( + "<p>Must be one or more (separated by '|') of the following " + "constant values.</p>"); + } else { + processor->AppendComment( + "<p>Must be one of the following constant values.</p>"); } - if (typeMask & android::ResTable_map::TYPE_STRING) { - processor->appendComment( - "<p>May be a string value, using '\\\\;' to escape characters such as\n" - "'\\\\n' or '\\\\uxxxx' for a unicode character;"); + processor->AppendComment( + "<table>\n<colgroup align=\"left\" />\n" + "<colgroup align=\"left\" />\n" + "<colgroup align=\"left\" />\n" + "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n"); + for (const Attribute::Symbol& symbol : attr->symbols) { + std::stringstream line; + line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>" + << "<td>" << std::hex << symbol.value << std::dec << "</td>" + << "<td>" << util::TrimWhitespace(symbol.symbol.GetComment()) + << "</td></tr>"; + processor->AppendComment(line.str()); } + processor->AppendComment("</table>"); + } +} - if (typeMask & android::ResTable_map::TYPE_INTEGER) { - processor->appendComment("<p>May be an integer value, such as \"<code>100</code>\"."); - } +JavaClassGenerator::JavaClassGenerator(IAaptContext* context, + ResourceTable* table, + const JavaClassGeneratorOptions& options) + : context_(context), table_(table), options_(options) {} - if (typeMask & android::ResTable_map::TYPE_BOOLEAN) { - processor->appendComment( - "<p>May be a boolean value, such as \"<code>true</code>\" or\n" - "\"<code>false</code>\"."); - } +bool JavaClassGenerator::SkipSymbol(SymbolState state) { + switch (options_.types) { + case JavaClassGeneratorOptions::SymbolTypes::kAll: + return false; + case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate: + return state == SymbolState::kUndefined; + case JavaClassGeneratorOptions::SymbolTypes::kPublic: + return state != SymbolState::kPublic; + } + return true; +} - if (typeMask & android::ResTable_map::TYPE_COLOR) { - processor->appendComment( - "<p>May be a color value, in the form of \"<code>#<i>rgb</i></code>\",\n" - "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code\", or \n" - "\"<code>#<i>aarrggbb</i></code>\"."); - } +struct StyleableAttr { + const Reference* attr_ref; + std::string field_name; + std::unique_ptr<SymbolTable::Symbol> symbol; +}; - if (typeMask & android::ResTable_map::TYPE_FLOAT) { - processor->appendComment( - "<p>May be a floating point value, such as \"<code>1.2</code>\"."); - } +static bool less_styleable_attr(const StyleableAttr& lhs, + const StyleableAttr& rhs) { + const ResourceId lhs_id = + lhs.attr_ref->id ? lhs.attr_ref->id.value() : ResourceId(0); + const ResourceId rhs_id = + rhs.attr_ref->id ? rhs.attr_ref->id.value() : ResourceId(0); + if (lhs_id < rhs_id) { + return true; + } else if (lhs_id > rhs_id) { + return false; + } else { + return lhs.attr_ref->name.value() < rhs.attr_ref->name.value(); + } +} - if (typeMask & android::ResTable_map::TYPE_DIMENSION) { - processor->appendComment( - "<p>May be a dimension value, which is a floating point number appended with a\n" - "unit such as \"<code>14.5sp</code>\".\n" - "Available units are: px (pixels), dp (density-independent pixels),\n" - "sp (scaled pixels based on preferred font size), in (inches), and\n" - "mm (millimeters)."); +void JavaClassGenerator::AddMembersToStyleableClass( + const StringPiece& package_name_to_generate, const std::string& entry_name, + const Styleable* styleable, ClassDefinition* out_styleable_class_def) { + const std::string class_name = Transform(entry_name); + + std::unique_ptr<ResourceArrayMember> styleable_array_def = + util::make_unique<ResourceArrayMember>(class_name); + + // This must be sorted by resource ID. + std::vector<StyleableAttr> sorted_attributes; + sorted_attributes.reserve(styleable->entries.size()); + for (const auto& attr : styleable->entries) { + // If we are not encoding final attributes, the styleable entry may have no + // ID if we are building a static library. + CHECK(!options_.use_final || attr.id) << "no ID set for Styleable entry"; + CHECK(bool(attr.name)) << "no name set for Styleable entry"; + + // We will need the unmangled, transformed name in the comments and the + // field, + // so create it once and cache it in this StyleableAttr data structure. + StyleableAttr styleable_attr = {}; + styleable_attr.attr_ref = &attr; + styleable_attr.field_name = TransformNestedAttr( + attr.name.value(), class_name, package_name_to_generate); + + Reference mangled_reference; + mangled_reference.id = attr.id; + mangled_reference.name = attr.name; + if (mangled_reference.name.value().package.empty()) { + mangled_reference.name.value().package = + context_->GetCompilationPackage(); } - if (typeMask & android::ResTable_map::TYPE_FRACTION) { - processor->appendComment( - "<p>May be a fractional value, which is a floating point number appended with\n" - "either % or %p, such as \"<code>14.5%</code>\".\n" - "The % suffix always means a percentage of the base size;\n" - "the optional %p suffix provides a size relative to some parent container."); + if (Maybe<ResourceName> mangled_name = + context_->GetNameMangler()->MangleName( + mangled_reference.name.value())) { + mangled_reference.name = mangled_name; } - if (typeMask & (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) { - if (typeMask & android::ResTable_map::TYPE_FLAGS) { - processor->appendComment( - "<p>Must be one or more (separated by '|') of the following " - "constant values.</p>"); - } else { - processor->appendComment("<p>Must be one of the following constant values.</p>"); - } - - processor->appendComment("<table>\n<colgroup align=\"left\" />\n" - "<colgroup align=\"left\" />\n" - "<colgroup align=\"left\" />\n" - "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n"); - for (const Attribute::Symbol& symbol : attr->symbols) { - std::stringstream line; - line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>" - << "<td>" << std::hex << symbol.value << std::dec << "</td>" - << "<td>" << util::trimWhitespace(symbol.symbol.getComment()) << "</td></tr>"; - processor->appendComment(line.str()); - } - processor->appendComment("</table>"); + // Look up the symbol so that we can write out in the comments what are + // possible + // legal values for this attribute. + const SymbolTable::Symbol* symbol = + context_->GetExternalSymbols()->FindByReference(mangled_reference); + if (symbol && symbol->attribute) { + // Copy the symbol data structure because the returned instance can be + // destroyed. + styleable_attr.symbol = util::make_unique<SymbolTable::Symbol>(*symbol); } -} - -bool JavaClassGenerator::skipSymbol(SymbolState state) { - switch (mOptions.types) { - case JavaClassGeneratorOptions::SymbolTypes::kAll: - return false; - case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate: - return state == SymbolState::kUndefined; - case JavaClassGeneratorOptions::SymbolTypes::kPublic: - return state != SymbolState::kPublic; + sorted_attributes.push_back(std::move(styleable_attr)); + } + + // Sort the attributes by ID. + std::sort(sorted_attributes.begin(), sorted_attributes.end(), + less_styleable_attr); + + const size_t attr_count = sorted_attributes.size(); + if (attr_count > 0) { + // Build the comment string for the Styleable. It includes details about the + // child attributes. + std::stringstream styleable_comment; + if (!styleable->GetComment().empty()) { + styleable_comment << styleable->GetComment() << "\n"; + } else { + styleable_comment << "Attributes that can be used with a " << class_name + << ".\n"; } - return true; -} -struct StyleableAttr { - const Reference* attrRef; - std::string fieldName; - std::unique_ptr<SymbolTable::Symbol> symbol; -}; - -static bool lessStyleableAttr(const StyleableAttr& lhs, const StyleableAttr& rhs) { - const ResourceId lhsId = lhs.attrRef->id ? lhs.attrRef->id.value() : ResourceId(0); - const ResourceId rhsId = rhs.attrRef->id ? rhs.attrRef->id.value() : ResourceId(0); - if (lhsId < rhsId) { - return true; - } else if (lhsId > rhsId) { - return false; - } else { - return lhs.attrRef->name.value() < rhs.attrRef->name.value(); + styleable_comment << "<p>Includes the following attributes:</p>\n" + "<table>\n" + "<colgroup align=\"left\" />\n" + "<colgroup align=\"left\" />\n" + "<tr><th>Attribute</th><th>Description</th></tr>\n"; + + for (const StyleableAttr& entry : sorted_attributes) { + if (!entry.symbol) { + continue; + } + + if (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic && + !entry.symbol->is_public) { + // Don't write entries for non-public attributes. + continue; + } + + StringPiece attr_comment_line = entry.symbol->attribute->GetComment(); + if (attr_comment_line.contains("@removed")) { + // Removed attributes are public but hidden from the documentation, so + // don't emit + // them as part of the class documentation. + continue; + } + + const ResourceName& attr_name = entry.attr_ref->name.value(); + styleable_comment << "<tr><td>"; + styleable_comment << "<code>{@link #" << entry.field_name << " " + << (!attr_name.package.empty() + ? attr_name.package + : context_->GetCompilationPackage()) + << ":" << attr_name.entry << "}</code>"; + styleable_comment << "</td>"; + + styleable_comment << "<td>"; + + // Only use the comment up until the first '.'. This is to stay compatible + // with + // the way old AAPT did it (presumably to keep it short and to avoid + // including + // annotations like @hide which would affect this Styleable). + auto iter = + std::find(attr_comment_line.begin(), attr_comment_line.end(), u'.'); + if (iter != attr_comment_line.end()) { + attr_comment_line = + attr_comment_line.substr(0, (iter - attr_comment_line.begin()) + 1); + } + styleable_comment << attr_comment_line << "</td></tr>\n"; + } + styleable_comment << "</table>\n"; + + for (const StyleableAttr& entry : sorted_attributes) { + if (!entry.symbol) { + continue; + } + + if (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic && + !entry.symbol->is_public) { + // Don't write entries for non-public attributes. + continue; + } + styleable_comment << "@see #" << entry.field_name << "\n"; } -} -void JavaClassGenerator::addMembersToStyleableClass(const StringPiece16& packageNameToGenerate, - const std::u16string& entryName, - const Styleable* styleable, - ClassDefinition* outStyleableClassDef) { - const std::string className = transform(entryName); - - std::unique_ptr<ResourceArrayMember> styleableArrayDef = - util::make_unique<ResourceArrayMember>(className); - - // This must be sorted by resource ID. - std::vector<StyleableAttr> sortedAttributes; - sortedAttributes.reserve(styleable->entries.size()); - for (const auto& attr : styleable->entries) { - // If we are not encoding final attributes, the styleable entry may have no ID - // if we are building a static library. - assert((!mOptions.useFinal || attr.id) && "no ID set for Styleable entry"); - assert(attr.name && "no name set for Styleable entry"); - - // We will need the unmangled, transformed name in the comments and the field, - // so create it once and cache it in this StyleableAttr data structure. - StyleableAttr styleableAttr = {}; - styleableAttr.attrRef = &attr; - styleableAttr.fieldName = transformNestedAttr(attr.name.value(), className, - packageNameToGenerate); - - Reference mangledReference; - mangledReference.id = attr.id; - mangledReference.name = attr.name; - if (mangledReference.name.value().package.empty()) { - mangledReference.name.value().package = mContext->getCompilationPackage(); - } + styleable_array_def->GetCommentBuilder()->AppendComment( + styleable_comment.str()); + } - if (Maybe<ResourceName> mangledName = - mContext->getNameMangler()->mangleName(mangledReference.name.value())) { - mangledReference.name = mangledName; - } + // Add the ResourceIds to the array member. + for (const StyleableAttr& styleable_attr : sorted_attributes) { + styleable_array_def->AddElement(styleable_attr.attr_ref->id + ? styleable_attr.attr_ref->id.value() + : ResourceId(0)); + } - // Look up the symbol so that we can write out in the comments what are possible - // legal values for this attribute. - const SymbolTable::Symbol* symbol = mContext->getExternalSymbols()->findByReference( - mangledReference); - if (symbol && symbol->attribute) { - // Copy the symbol data structure because the returned instance can be destroyed. - styleableAttr.symbol = util::make_unique<SymbolTable::Symbol>(*symbol); - } - sortedAttributes.push_back(std::move(styleableAttr)); - } + // Add the Styleable array to the Styleable class. + out_styleable_class_def->AddMember(std::move(styleable_array_def)); - // Sort the attributes by ID. - std::sort(sortedAttributes.begin(), sortedAttributes.end(), lessStyleableAttr); - - const size_t attrCount = sortedAttributes.size(); - if (attrCount > 0) { - // Build the comment string for the Styleable. It includes details about the - // child attributes. - std::stringstream styleableComment; - if (!styleable->getComment().empty()) { - styleableComment << styleable->getComment() << "\n"; - } else { - styleableComment << "Attributes that can be used with a " << className << ".\n"; - } + // Now we emit the indices into the array. + for (size_t i = 0; i < attr_count; i++) { + const StyleableAttr& styleable_attr = sorted_attributes[i]; - styleableComment << - "<p>Includes the following attributes:</p>\n" - "<table>\n" - "<colgroup align=\"left\" />\n" - "<colgroup align=\"left\" />\n" - "<tr><th>Attribute</th><th>Description</th></tr>\n"; - - for (const StyleableAttr& entry : sortedAttributes) { - if (!entry.symbol) { - continue; - } - - if (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic && - !entry.symbol->isPublic) { - // Don't write entries for non-public attributes. - continue; - } - - StringPiece16 attrCommentLine = entry.symbol->attribute->getComment(); - if (attrCommentLine.contains(StringPiece16(u"@removed"))) { - // Removed attributes are public but hidden from the documentation, so don't emit - // them as part of the class documentation. - continue; - } - - const ResourceName& attrName = entry.attrRef->name.value(); - styleableComment << "<tr><td>"; - styleableComment << "<code>{@link #" - << entry.fieldName << " " - << (!attrName.package.empty() - ? attrName.package : mContext->getCompilationPackage()) - << ":" << attrName.entry - << "}</code>"; - styleableComment << "</td>"; - - styleableComment << "<td>"; - - // Only use the comment up until the first '.'. This is to stay compatible with - // the way old AAPT did it (presumably to keep it short and to avoid including - // annotations like @hide which would affect this Styleable). - auto iter = std::find(attrCommentLine.begin(), attrCommentLine.end(), u'.'); - if (iter != attrCommentLine.end()) { - attrCommentLine = attrCommentLine.substr( - 0, (iter - attrCommentLine.begin()) + 1); - } - styleableComment << attrCommentLine << "</td></tr>\n"; - } - styleableComment << "</table>\n"; - - for (const StyleableAttr& entry : sortedAttributes) { - if (!entry.symbol) { - continue; - } - - if (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic && - !entry.symbol->isPublic) { - // Don't write entries for non-public attributes. - continue; - } - styleableComment << "@see #" << entry.fieldName << "\n"; - } + if (!styleable_attr.symbol) { + continue; + } - styleableArrayDef->getCommentBuilder()->appendComment(styleableComment.str()); + if (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic && + !styleable_attr.symbol->is_public) { + // Don't write entries for non-public attributes. + continue; } - // Add the ResourceIds to the array member. - for (const StyleableAttr& styleableAttr : sortedAttributes) { - styleableArrayDef->addElement( - styleableAttr.attrRef->id ? styleableAttr.attrRef->id.value() : ResourceId(0)); + StringPiece comment = styleable_attr.attr_ref->GetComment(); + if (styleable_attr.symbol->attribute && comment.empty()) { + comment = styleable_attr.symbol->attribute->GetComment(); } - // Add the Styleable array to the Styleable class. - outStyleableClassDef->addMember(std::move(styleableArrayDef)); + if (comment.contains("@removed")) { + // Removed attributes are public but hidden from the documentation, so + // don't emit them + // as part of the class documentation. + continue; + } - // Now we emit the indices into the array. - for (size_t i = 0; i < attrCount; i++) { - const StyleableAttr& styleableAttr = sortedAttributes[i]; + const ResourceName& attr_name = styleable_attr.attr_ref->name.value(); - if (!styleableAttr.symbol) { - continue; - } + StringPiece package_name = attr_name.package; + if (package_name.empty()) { + package_name = context_->GetCompilationPackage(); + } - if (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic && - !styleableAttr.symbol->isPublic) { - // Don't write entries for non-public attributes. - continue; - } + std::unique_ptr<IntMember> index_member = util::make_unique<IntMember>( + sorted_attributes[i].field_name, static_cast<uint32_t>(i)); - StringPiece16 comment = styleableAttr.attrRef->getComment(); - if (styleableAttr.symbol->attribute && comment.empty()) { - comment = styleableAttr.symbol->attribute->getComment(); - } + AnnotationProcessor* attr_processor = index_member->GetCommentBuilder(); - if (comment.contains(StringPiece16(u"@removed"))) { - // Removed attributes are public but hidden from the documentation, so don't emit them - // as part of the class documentation. - continue; - } + if (!comment.empty()) { + attr_processor->AppendComment("<p>\n@attr description"); + attr_processor->AppendComment(comment); + } else { + std::stringstream default_comment; + default_comment << "<p>This symbol is the offset where the " + << "{@link " << package_name << ".R.attr#" + << Transform(attr_name.entry) << "}\n" + << "attribute's value can be found in the " + << "{@link #" << class_name << "} array."; + attr_processor->AppendComment(default_comment.str()); + } - const ResourceName& attrName = styleableAttr.attrRef->name.value(); + attr_processor->AppendNewLine(); - StringPiece16 packageName = attrName.package; - if (packageName.empty()) { - packageName = mContext->getCompilationPackage(); - } + AddAttributeFormatDoc(attr_processor, + styleable_attr.symbol->attribute.get()); + attr_processor->AppendNewLine(); - std::unique_ptr<IntMember> indexMember = util::make_unique<IntMember>( - sortedAttributes[i].fieldName, static_cast<uint32_t>(i)); - - AnnotationProcessor* attrProcessor = indexMember->getCommentBuilder(); - - if (!comment.empty()) { - attrProcessor->appendComment("<p>\n@attr description"); - attrProcessor->appendComment(comment); - } else { - std::stringstream defaultComment; - defaultComment - << "<p>This symbol is the offset where the " - << "{@link " << packageName << ".R.attr#" << transform(attrName.entry) << "}\n" - << "attribute's value can be found in the " - << "{@link #" << className << "} array."; - attrProcessor->appendComment(defaultComment.str()); - } + std::stringstream doclava_name; + doclava_name << "@attr name " << package_name << ":" << attr_name.entry; - attrProcessor->appendNewLine(); + attr_processor->AppendComment(doclava_name.str()); - addAttributeFormatDoc(attrProcessor, styleableAttr.symbol->attribute.get()); - attrProcessor->appendNewLine(); + out_styleable_class_def->AddMember(std::move(index_member)); + } +} - std::stringstream doclavaName; - doclavaName << "@attr name " << packageName << ":" << attrName.entry;; - attrProcessor->appendComment(doclavaName.str()); +bool JavaClassGenerator::AddMembersToTypeClass( + const StringPiece& package_name_to_generate, + const ResourceTablePackage* package, const ResourceTableType* type, + ClassDefinition* out_type_class_def) { + for (const auto& entry : type->entries) { + if (SkipSymbol(entry->symbol_status.state)) { + continue; + } - outStyleableClassDef->addMember(std::move(indexMember)); + ResourceId id; + if (package->id && type->id && entry->id) { + id = ResourceId(package->id.value(), type->id.value(), entry->id.value()); } -} -bool JavaClassGenerator::addMembersToTypeClass(const StringPiece16& packageNameToGenerate, - const ResourceTablePackage* package, - const ResourceTableType* type, - ClassDefinition* outTypeClassDef) { + std::string unmangled_package; + std::string unmangled_name = entry->name; + if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) { + // The entry name was mangled, and we successfully unmangled it. + // Check that we want to emit this symbol. + if (package->name != unmangled_package) { + // Skip the entry if it doesn't belong to the package we're writing. + continue; + } + } else if (package_name_to_generate != package->name) { + // We are processing a mangled package name, + // but this is a non-mangled resource. + continue; + } - for (const auto& entry : type->entries) { - if (skipSymbol(entry->symbolStatus.state)) { - continue; - } + if (!IsValidSymbol(unmangled_name)) { + ResourceNameRef resource_name(package_name_to_generate, type->type, + unmangled_name); + std::stringstream err; + err << "invalid symbol name '" << resource_name << "'"; + error_ = err.str(); + return false; + } - ResourceId id; - if (package->id && type->id && entry->id) { - id = ResourceId(package->id.value(), type->id.value(), entry->id.value()); - } + if (type->type == ResourceType::kStyleable) { + CHECK(!entry->values.empty()); - std::u16string unmangledPackage; - std::u16string unmangledName = entry->name; - if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) { - // The entry name was mangled, and we successfully unmangled it. - // Check that we want to emit this symbol. - if (package->name != unmangledPackage) { - // Skip the entry if it doesn't belong to the package we're writing. - continue; - } - } else if (packageNameToGenerate != package->name) { - // We are processing a mangled package name, - // but this is a non-mangled resource. - continue; - } + const Styleable* styleable = + static_cast<const Styleable*>(entry->values.front()->value.get()); - if (!isValidSymbol(unmangledName)) { - ResourceNameRef resourceName(packageNameToGenerate, type->type, unmangledName); - std::stringstream err; - err << "invalid symbol name '" << resourceName << "'"; - mError = err.str(); - return false; + // Comments are handled within this method. + AddMembersToStyleableClass(package_name_to_generate, unmangled_name, + styleable, out_type_class_def); + } else { + std::unique_ptr<ResourceMember> resource_member = + util::make_unique<ResourceMember>(Transform(unmangled_name), id); + + // Build the comments and annotations for this entry. + AnnotationProcessor* processor = resource_member->GetCommentBuilder(); + + // Add the comments from any <public> tags. + if (entry->symbol_status.state != SymbolState::kUndefined) { + processor->AppendComment(entry->symbol_status.comment); + } + + // Add the comments from all configurations of this entry. + for (const auto& config_value : entry->values) { + processor->AppendComment(config_value->value->GetComment()); + } + + // If this is an Attribute, append the format Javadoc. + if (!entry->values.empty()) { + if (Attribute* attr = + ValueCast<Attribute>(entry->values.front()->value.get())) { + // We list out the available values for the given attribute. + AddAttributeFormatDoc(processor, attr); } + } - if (type->type == ResourceType::kStyleable) { - assert(!entry->values.empty()); - - const Styleable* styleable = static_cast<const Styleable*>( - entry->values.front()->value.get()); - - // Comments are handled within this method. - addMembersToStyleableClass(packageNameToGenerate, unmangledName, styleable, - outTypeClassDef); - } else { - std::unique_ptr<ResourceMember> resourceMember = - util::make_unique<ResourceMember>(transform(unmangledName), id); - - // Build the comments and annotations for this entry. - AnnotationProcessor* processor = resourceMember->getCommentBuilder(); - - // Add the comments from any <public> tags. - if (entry->symbolStatus.state != SymbolState::kUndefined) { - processor->appendComment(entry->symbolStatus.comment); - } - - // Add the comments from all configurations of this entry. - for (const auto& configValue : entry->values) { - processor->appendComment(configValue->value->getComment()); - } - - // If this is an Attribute, append the format Javadoc. - if (!entry->values.empty()) { - if (Attribute* attr = valueCast<Attribute>(entry->values.front()->value.get())) { - // We list out the available values for the given attribute. - addAttributeFormatDoc(processor, attr); - } - } - - outTypeClassDef->addMember(std::move(resourceMember)); - } + out_type_class_def->AddMember(std::move(resource_member)); } - return true; + } + return true; } -bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, std::ostream* out) { - return generate(packageNameToGenerate, packageNameToGenerate, out); +bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, + std::ostream* out) { + return Generate(package_name_to_generate, package_name_to_generate, out); } -static void appendJavaDocAnnotations(const std::vector<std::string>& annotations, - AnnotationProcessor* processor) { - for (const std::string& annotation : annotations) { - std::string properAnnotation = "@"; - properAnnotation += annotation; - processor->appendComment(properAnnotation); - } +static void AppendJavaDocAnnotations( + const std::vector<std::string>& annotations, + AnnotationProcessor* processor) { + for (const std::string& annotation : annotations) { + std::string proper_annotation = "@"; + proper_annotation += annotation; + processor->AppendComment(proper_annotation); + } } -bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, - const StringPiece16& outPackageName, std::ostream* out) { - - ClassDefinition rClass("R", ClassQualifier::None, true); - - for (const auto& package : mTable->packages) { - for (const auto& type : package->types) { - if (type->type == ResourceType::kAttrPrivate) { - continue; - } - - const bool forceCreationIfEmpty = - (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic); - - std::unique_ptr<ClassDefinition> classDef = util::make_unique<ClassDefinition>( - util::utf16ToUtf8(toString(type->type)), ClassQualifier::Static, - forceCreationIfEmpty); - - bool result = addMembersToTypeClass(packageNameToGenerate, package.get(), type.get(), - classDef.get()); - if (!result) { - return false; - } - - if (type->type == ResourceType::kAttr) { - // Also include private attributes in this same class. - ResourceTableType* privType = package->findType(ResourceType::kAttrPrivate); - if (privType) { - result = addMembersToTypeClass(packageNameToGenerate, package.get(), privType, - classDef.get()); - if (!result) { - return false; - } - } - } - - if (type->type == ResourceType::kStyleable && - mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) { - // When generating a public R class, we don't want Styleable to be part of the API. - // It is only emitted for documentation purposes. - classDef->getCommentBuilder()->appendComment("@doconly"); - } - - appendJavaDocAnnotations(mOptions.javadocAnnotations, classDef->getCommentBuilder()); - - rClass.addMember(std::move(classDef)); - } - } +bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, + const StringPiece& out_package_name, + std::ostream* out) { + ClassDefinition r_class("R", ClassQualifier::None, true); + + for (const auto& package : table_->packages) { + for (const auto& type : package->types) { + if (type->type == ResourceType::kAttrPrivate) { + continue; + } - appendJavaDocAnnotations(mOptions.javadocAnnotations, rClass.getCommentBuilder()); + const bool force_creation_if_empty = + (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic); - if (!ClassDefinition::writeJavaFile(&rClass, util::utf16ToUtf8(outPackageName), - mOptions.useFinal, out)) { + std::unique_ptr<ClassDefinition> class_def = + util::make_unique<ClassDefinition>(ToString(type->type), + ClassQualifier::Static, + force_creation_if_empty); + + bool result = AddMembersToTypeClass( + package_name_to_generate, package.get(), type.get(), class_def.get()); + if (!result) { return false; + } + + if (type->type == ResourceType::kAttr) { + // Also include private attributes in this same class. + ResourceTableType* priv_type = + package->FindType(ResourceType::kAttrPrivate); + if (priv_type) { + result = + AddMembersToTypeClass(package_name_to_generate, package.get(), + priv_type, class_def.get()); + if (!result) { + return false; + } + } + } + + if (type->type == ResourceType::kStyleable && + options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) { + // When generating a public R class, we don't want Styleable to be part + // of the API. + // It is only emitted for documentation purposes. + class_def->GetCommentBuilder()->AppendComment("@doconly"); + } + + AppendJavaDocAnnotations(options_.javadoc_annotations, + class_def->GetCommentBuilder()); + + r_class.AddMember(std::move(class_def)); } + } - out->flush(); - return true; + AppendJavaDocAnnotations(options_.javadoc_annotations, + r_class.GetCommentBuilder()); + + if (!ClassDefinition::WriteJavaFile(&r_class, out_package_name, + options_.use_final, out)) { + return false; + } + + out->flush(); + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h index 77e0ed76143a..190e73b66b9e 100644 --- a/tools/aapt2/java/JavaClassGenerator.h +++ b/tools/aapt2/java/JavaClassGenerator.h @@ -17,86 +17,88 @@ #ifndef AAPT_JAVA_CLASS_GENERATOR_H #define AAPT_JAVA_CLASS_GENERATOR_H +#include <ostream> +#include <string> + #include "ResourceTable.h" #include "ResourceValues.h" #include "process/IResourceTableConsumer.h" #include "util/StringPiece.h" -#include <ostream> -#include <string> - namespace aapt { class AnnotationProcessor; class ClassDefinition; struct JavaClassGeneratorOptions { - /* - * Specifies whether to use the 'final' modifier - * on resource entries. Default is true. - */ - bool useFinal = true; - - enum class SymbolTypes { - kAll, - kPublicPrivate, - kPublic, - }; - - SymbolTypes types = SymbolTypes::kAll; - - /** - * A list of JavaDoc annotations to add to the comments of all generated classes. - */ - std::vector<std::string> javadocAnnotations; + /* + * Specifies whether to use the 'final' modifier + * on resource entries. Default is true. + */ + bool use_final = true; + + enum class SymbolTypes { + kAll, + kPublicPrivate, + kPublic, + }; + + SymbolTypes types = SymbolTypes::kAll; + + /** + * A list of JavaDoc annotations to add to the comments of all generated + * classes. + */ + std::vector<std::string> javadoc_annotations; }; /* * Generates the R.java file for a resource table. */ class JavaClassGenerator { -public: - JavaClassGenerator(IAaptContext* context, ResourceTable* table, - const JavaClassGeneratorOptions& options); - - /* - * Writes the R.java file to `out`. Only symbols belonging to `package` are written. - * All symbols technically belong to a single package, but linked libraries will - * have their names mangled, denoting that they came from a different package. - * We need to generate these symbols in a separate file. - * Returns true on success. - */ - bool generate(const StringPiece16& packageNameToGenerate, std::ostream* out); - - bool generate(const StringPiece16& packageNameToGenerate, - const StringPiece16& outputPackageName, - std::ostream* out); - - const std::string& getError() const; - -private: - bool addMembersToTypeClass(const StringPiece16& packageNameToGenerate, - const ResourceTablePackage* package, - const ResourceTableType* type, - ClassDefinition* outTypeClassDef); - - void addMembersToStyleableClass(const StringPiece16& packageNameToGenerate, - const std::u16string& entryName, - const Styleable* styleable, - ClassDefinition* outStyleableClassDef); - - bool skipSymbol(SymbolState state); - - IAaptContext* mContext; - ResourceTable* mTable; - JavaClassGeneratorOptions mOptions; - std::string mError; + public: + JavaClassGenerator(IAaptContext* context, ResourceTable* table, + const JavaClassGeneratorOptions& options); + + /* + * Writes the R.java file to `out`. Only symbols belonging to `package` are + * written. + * All symbols technically belong to a single package, but linked libraries + * will + * have their names mangled, denoting that they came from a different package. + * We need to generate these symbols in a separate file. + * Returns true on success. + */ + bool Generate(const StringPiece& packageNameToGenerate, std::ostream* out); + + bool Generate(const StringPiece& packageNameToGenerate, + const StringPiece& outputPackageName, std::ostream* out); + + const std::string& getError() const; + + private: + bool AddMembersToTypeClass(const StringPiece& packageNameToGenerate, + const ResourceTablePackage* package, + const ResourceTableType* type, + ClassDefinition* outTypeClassDef); + + void AddMembersToStyleableClass(const StringPiece& packageNameToGenerate, + const std::string& entryName, + const Styleable* styleable, + ClassDefinition* outStyleableClassDef); + + bool SkipSymbol(SymbolState state); + + IAaptContext* context_; + ResourceTable* table_; + JavaClassGeneratorOptions options_; + std::string error_; }; inline const std::string& JavaClassGenerator::getError() const { - return mError; + return error_; } -} // namespace aapt +} // namespace aapt -#endif // AAPT_JAVA_CLASS_GENERATOR_H +#endif // AAPT_JAVA_CLASS_GENERATOR_H diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp index 46266b3f3e89..3d3d24e6aab5 100644 --- a/tools/aapt2/java/JavaClassGenerator_test.cpp +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -15,154 +15,181 @@ */ #include "java/JavaClassGenerator.h" -#include "test/Test.h" -#include "util/Util.h" #include <sstream> #include <string> +#include "test/Test.h" +#include "util/Util.h" + namespace aapt { TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"android", 0x01) - .addSimple(u"@android:id/class", ResourceId(0x01020000)) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .setNameManglerPolicy(NameManglerPolicy{ u"android" }) - .build(); - JavaClassGenerator generator(context.get(), table.get(), {}); - - std::stringstream out; - EXPECT_FALSE(generator.generate(u"android", &out)); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("android", 0x01) + .AddSimple("android:id/class", ResourceId(0x01020000)) + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .AddSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .SetNameManglerPolicy(NameManglerPolicy{"android"}) + .Build(); + JavaClassGenerator generator(context.get(), table.get(), {}); + + std::stringstream out; + EXPECT_FALSE(generator.Generate("android", &out)); } TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"android", 0x01) - .addSimple(u"@android:id/hey-man", ResourceId(0x01020000)) - .addValue(u"@android:attr/cool.attr", ResourceId(0x01010000), - test::AttributeBuilder(false).build()) - .addValue(u"@android:styleable/hey.dude", ResourceId(0x01030000), - test::StyleableBuilder() - .addItem(u"@android:attr/cool.attr", ResourceId(0x01010000)) - .build()) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .setNameManglerPolicy(NameManglerPolicy{ u"android" }) - .build(); - JavaClassGenerator generator(context.get(), table.get(), {}); - - std::stringstream out; - EXPECT_TRUE(generator.generate(u"android", &out)); - - std::string output = out.str(); - - EXPECT_NE(std::string::npos, - output.find("public static final int hey_man=0x01020000;")); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("android", 0x01) + .AddSimple("android:id/hey-man", ResourceId(0x01020000)) + .AddValue("android:attr/cool.attr", ResourceId(0x01010000), + test::AttributeBuilder(false).Build()) + .AddValue( + "android:styleable/hey.dude", ResourceId(0x01030000), + test::StyleableBuilder() + .AddItem("android:attr/cool.attr", ResourceId(0x01010000)) + .Build()) + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .AddSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .SetNameManglerPolicy(NameManglerPolicy{"android"}) + .Build(); + JavaClassGenerator generator(context.get(), table.get(), {}); + + std::stringstream out; + EXPECT_TRUE(generator.Generate("android", &out)); + + std::string output = out.str(); + + EXPECT_NE(std::string::npos, + output.find("public static final int hey_man=0x01020000;")); + + EXPECT_NE(std::string::npos, + output.find("public static final int[] hey_dude={")); + + EXPECT_NE(std::string::npos, + output.find("public static final int hey_dude_cool_attr=0;")); +} - EXPECT_NE(std::string::npos, - output.find("public static final int[] hey_dude={")); +TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("android", 0x01) + .AddSimple("android:id/one", ResourceId(0x01020000)) + .AddSimple("android:id/com.foo$two", ResourceId(0x01020001)) + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .AddSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .SetNameManglerPolicy(NameManglerPolicy{"android"}) + .Build(); + JavaClassGenerator generator(context.get(), table.get(), {}); + std::stringstream out; + ASSERT_TRUE(generator.Generate("android", "com.android.internal", &out)); + + std::string output = out.str(); + EXPECT_NE(std::string::npos, output.find("package com.android.internal;")); + EXPECT_NE(std::string::npos, + output.find("public static final int one=0x01020000;")); + EXPECT_EQ(std::string::npos, output.find("two")); + EXPECT_EQ(std::string::npos, output.find("com_foo$two")); +} - EXPECT_NE(std::string::npos, - output.find("public static final int hey_dude_cool_attr=0;")); +TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("android", 0x01) + .AddSimple("android:attr/two", ResourceId(0x01010001)) + .AddSimple("android:^attr-private/one", ResourceId(0x01010000)) + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .AddSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .SetNameManglerPolicy(NameManglerPolicy{"android"}) + .Build(); + JavaClassGenerator generator(context.get(), table.get(), {}); + std::stringstream out; + ASSERT_TRUE(generator.Generate("android", &out)); + + std::string output = out.str(); + EXPECT_NE(std::string::npos, output.find("public static final class attr")); + EXPECT_EQ(std::string::npos, + output.find("public static final class ^attr-private")); } -TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"android", 0x01) - .addSimple(u"@android:id/one", ResourceId(0x01020000)) - .addSimple(u"@android:id/com.foo$two", ResourceId(0x01020001)) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .setNameManglerPolicy(NameManglerPolicy{ u"android" }) - .build(); - JavaClassGenerator generator(context.get(), table.get(), {}); +TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { + StdErrDiagnostics diag; + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("android", 0x01) + .AddSimple("android:id/one", ResourceId(0x01020000)) + .AddSimple("android:id/two", ResourceId(0x01020001)) + .AddSimple("android:id/three", ResourceId(0x01020002)) + .SetSymbolState("android:id/one", ResourceId(0x01020000), + SymbolState::kPublic) + .SetSymbolState("android:id/two", ResourceId(0x01020001), + SymbolState::kPrivate) + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .AddSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .SetNameManglerPolicy(NameManglerPolicy{"android"}) + .Build(); + + JavaClassGeneratorOptions options; + options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; + { + JavaClassGenerator generator(context.get(), table.get(), options); std::stringstream out; - ASSERT_TRUE(generator.generate(u"android", u"com.android.internal", &out)); - + ASSERT_TRUE(generator.Generate("android", &out)); std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("package com.android.internal;")); - EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;")); + EXPECT_NE(std::string::npos, + output.find("public static final int one=0x01020000;")); EXPECT_EQ(std::string::npos, output.find("two")); - EXPECT_EQ(std::string::npos, output.find("com_foo$two")); -} + EXPECT_EQ(std::string::npos, output.find("three")); + } -TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"android", 0x01) - .addSimple(u"@android:attr/two", ResourceId(0x01010001)) - .addSimple(u"@android:^attr-private/one", ResourceId(0x01010000)) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .setNameManglerPolicy(NameManglerPolicy{ u"android" }) - .build(); - JavaClassGenerator generator(context.get(), table.get(), {}); + options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; + { + JavaClassGenerator generator(context.get(), table.get(), options); std::stringstream out; - ASSERT_TRUE(generator.generate(u"android", &out)); - + ASSERT_TRUE(generator.Generate("android", &out)); std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("public static final class attr")); - EXPECT_EQ(std::string::npos, output.find("public static final class ^attr-private")); -} + EXPECT_NE(std::string::npos, + output.find("public static final int one=0x01020000;")); + EXPECT_NE(std::string::npos, + output.find("public static final int two=0x01020001;")); + EXPECT_EQ(std::string::npos, output.find("three")); + } -TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { - StdErrDiagnostics diag; - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"android", 0x01) - .addSimple(u"@android:id/one", ResourceId(0x01020000)) - .addSimple(u"@android:id/two", ResourceId(0x01020001)) - .addSimple(u"@android:id/three", ResourceId(0x01020002)) - .setSymbolState(u"@android:id/one", ResourceId(0x01020000), SymbolState::kPublic) - .setSymbolState(u"@android:id/two", ResourceId(0x01020001), SymbolState::kPrivate) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .setNameManglerPolicy(NameManglerPolicy{ u"android" }) - .build(); - - JavaClassGeneratorOptions options; - options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; - { - JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; - ASSERT_TRUE(generator.generate(u"android", &out)); - std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;")); - EXPECT_EQ(std::string::npos, output.find("two")); - EXPECT_EQ(std::string::npos, output.find("three")); - } - - options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; - { - JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; - ASSERT_TRUE(generator.generate(u"android", &out)); - std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;")); - EXPECT_NE(std::string::npos, output.find("public static final int two=0x01020001;")); - EXPECT_EQ(std::string::npos, output.find("three")); - } - - options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; - { - JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; - ASSERT_TRUE(generator.generate(u"android", &out)); - std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;")); - EXPECT_NE(std::string::npos, output.find("public static final int two=0x01020001;")); - EXPECT_NE(std::string::npos, output.find("public static final int three=0x01020002;")); - } + options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; + { + JavaClassGenerator generator(context.get(), table.get(), options); + std::stringstream out; + ASSERT_TRUE(generator.Generate("android", &out)); + std::string output = out.str(); + EXPECT_NE(std::string::npos, + output.find("public static final int one=0x01020000;")); + EXPECT_NE(std::string::npos, + output.find("public static final int two=0x01020001;")); + EXPECT_NE(std::string::npos, + output.find("public static final int three=0x01020002;")); + } } /* @@ -172,12 +199,15 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { ResourceId{ 0x01, 0x02, 0x0000 })); ResourceTable table; table.setPackage(u"com.lib"); - ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" }, {}, - Source{ "lib.xml", 33 }, util::make_unique<Id>())); + ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" +}, {}, + Source{ "lib.xml", 33 }, +util::make_unique<Id>())); ASSERT_TRUE(mTable->merge(std::move(table))); Linker linker(mTable, - std::make_shared<MockResolver>(mTable, std::map<ResourceName, ResourceId>()), + std::make_shared<MockResolver>(mTable, std::map<ResourceName, +ResourceId>()), {}); ASSERT_TRUE(linker.linkAndValidate()); @@ -197,129 +227,139 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { }*/ TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"android", 0x01) - .setPackageId(u"com.lib", 0x02) - .addValue(u"@android:attr/bar", ResourceId(0x01010000), - test::AttributeBuilder(false).build()) - .addValue(u"@com.lib:attr/bar", ResourceId(0x02010000), - test::AttributeBuilder(false).build()) - .addValue(u"@android:styleable/foo", ResourceId(0x01030000), - test::StyleableBuilder() - .addItem(u"@android:attr/bar", ResourceId(0x01010000)) - .addItem(u"@com.lib:attr/bar", ResourceId(0x02010000)) - .build()) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .setNameManglerPolicy(NameManglerPolicy{ u"android" }) - .build(); - JavaClassGenerator generator(context.get(), table.get(), {}); - - std::stringstream out; - EXPECT_TRUE(generator.generate(u"android", &out)); - - std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("int foo_bar=")); - EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar=")); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("android", 0x01) + .SetPackageId("com.lib", 0x02) + .AddValue("android:attr/bar", ResourceId(0x01010000), + test::AttributeBuilder(false).Build()) + .AddValue("com.lib:attr/bar", ResourceId(0x02010000), + test::AttributeBuilder(false).Build()) + .AddValue("android:styleable/foo", ResourceId(0x01030000), + test::StyleableBuilder() + .AddItem("android:attr/bar", ResourceId(0x01010000)) + .AddItem("com.lib:attr/bar", ResourceId(0x02010000)) + .Build()) + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .AddSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .SetNameManglerPolicy(NameManglerPolicy{"android"}) + .Build(); + JavaClassGenerator generator(context.get(), table.get(), {}); + + std::stringstream out; + EXPECT_TRUE(generator.Generate("android", &out)); + + std::string output = out.str(); + EXPECT_NE(std::string::npos, output.find("int foo_bar=")); + EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar=")); } TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"android", 0x01) - .addSimple(u"@android:id/foo", ResourceId(0x01010000)) - .build(); - test::getValue<Id>(table.get(), u"@android:id/foo") - ->setComment(std::u16string(u"This is a comment\n@deprecated")); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .setNameManglerPolicy(NameManglerPolicy{ u"android" }) - .build(); - JavaClassGenerator generator(context.get(), table.get(), {}); - std::stringstream out; - ASSERT_TRUE(generator.generate(u"android", &out)); - std::string actual = out.str(); - - const char* expectedText = -R"EOF(/** + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("android", 0x01) + .AddSimple("android:id/foo", ResourceId(0x01010000)) + .Build(); + test::GetValue<Id>(table.get(), "android:id/foo") + ->SetComment(std::string("This is a comment\n@deprecated")); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .AddSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .SetNameManglerPolicy(NameManglerPolicy{"android"}) + .Build(); + JavaClassGenerator generator(context.get(), table.get(), {}); + std::stringstream out; + ASSERT_TRUE(generator.Generate("android", &out)); + std::string actual = out.str(); + + const char* expectedText = + R"EOF(/** * This is a comment * @deprecated */ @Deprecated public static final int foo=0x01010000;)EOF"; - EXPECT_NE(std::string::npos, actual.find(expectedText)); + EXPECT_NE(std::string::npos, actual.find(expectedText)); } -TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) { - -} - -TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) { - Attribute attr(false); - attr.setComment(StringPiece16(u"This is an attribute")); - - Styleable styleable; - styleable.entries.push_back(Reference(test::parseNameOrDie(u"@android:attr/one"))); - styleable.setComment(StringPiece16(u"This is a styleable")); - - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"android", 0x01) - .addValue(u"@android:attr/one", util::make_unique<Attribute>(attr)) - .addValue(u"@android:styleable/Container", - std::unique_ptr<Styleable>(styleable.clone(nullptr))) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .setNameManglerPolicy(NameManglerPolicy{ u"android" }) - .build(); - JavaClassGeneratorOptions options; - options.useFinal = false; - JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; - ASSERT_TRUE(generator.generate(u"android", &out)); - std::string actual = out.str(); - - EXPECT_NE(std::string::npos, actual.find("@attr name android:one")); - EXPECT_NE(std::string::npos, actual.find("@attr description")); - EXPECT_NE(std::string::npos, actual.find(util::utf16ToUtf8(attr.getComment()))); - EXPECT_NE(std::string::npos, actual.find(util::utf16ToUtf8(styleable.getComment()))); +TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {} + +TEST(JavaClassGeneratorTest, + CommentsForStyleablesAndNestedAttributesArePresent) { + Attribute attr(false); + attr.SetComment(StringPiece("This is an attribute")); + + Styleable styleable; + styleable.entries.push_back( + Reference(test::ParseNameOrDie("android:attr/one"))); + styleable.SetComment(StringPiece("This is a styleable")); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("android", 0x01) + .AddValue("android:attr/one", util::make_unique<Attribute>(attr)) + .AddValue("android:styleable/Container", + std::unique_ptr<Styleable>(styleable.Clone(nullptr))) + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .AddSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .SetNameManglerPolicy(NameManglerPolicy{"android"}) + .Build(); + JavaClassGeneratorOptions options; + options.use_final = false; + JavaClassGenerator generator(context.get(), table.get(), options); + std::stringstream out; + ASSERT_TRUE(generator.Generate("android", &out)); + std::string actual = out.str(); + + EXPECT_NE(std::string::npos, actual.find("attr name android:one")); + EXPECT_NE(std::string::npos, actual.find("attr description")); + EXPECT_NE(std::string::npos, actual.find(attr.GetComment().data())); + EXPECT_NE(std::string::npos, actual.find(styleable.GetComment().data())); } TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) { - Attribute attr(false); - attr.setComment(StringPiece16(u"@removed")); - - - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"android", 0x01) - .addValue(u"@android:attr/one", util::make_unique<Attribute>(attr)) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .setNameManglerPolicy(NameManglerPolicy{ u"android" }) - .build(); - JavaClassGeneratorOptions options; - options.useFinal = false; - JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; - ASSERT_TRUE(generator.generate(u"android", &out)); - std::string actual = out.str(); - - std::cout << actual << std::endl; - - EXPECT_EQ(std::string::npos, actual.find("@attr name android:one")); - EXPECT_EQ(std::string::npos, actual.find("@attr description")); - - // We should find @removed only in the attribute javadoc and not anywhere else (i.e. the class - // javadoc). - const size_t pos = actual.find("@removed"); - EXPECT_NE(std::string::npos, pos); - EXPECT_EQ(std::string::npos, actual.find("@removed", pos + 1)); + Attribute attr(false); + attr.SetComment(StringPiece("removed")); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("android", 0x01) + .AddValue("android:attr/one", util::make_unique<Attribute>(attr)) + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .AddSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .SetNameManglerPolicy(NameManglerPolicy{"android"}) + .Build(); + JavaClassGeneratorOptions options; + options.use_final = false; + JavaClassGenerator generator(context.get(), table.get(), options); + std::stringstream out; + ASSERT_TRUE(generator.Generate("android", &out)); + std::string actual = out.str(); + + EXPECT_EQ(std::string::npos, actual.find("@attr name android:one")); + EXPECT_EQ(std::string::npos, actual.find("@attr description")); + + // We should find @removed only in the attribute javadoc and not anywhere else + // (i.e. the class + // javadoc). + const size_t pos = actual.find("removed"); + EXPECT_NE(std::string::npos, pos); + EXPECT_EQ(std::string::npos, actual.find("removed", pos + 1)); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp index be8955ecdf83..db84f295db2a 100644 --- a/tools/aapt2/java/ManifestClassGenerator.cpp +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -14,111 +14,120 @@ * limitations under the License. */ +#include "java/ManifestClassGenerator.h" + +#include <algorithm> + #include "Source.h" #include "java/AnnotationProcessor.h" #include "java/ClassDefinition.h" -#include "java/ManifestClassGenerator.h" #include "util/Maybe.h" #include "xml/XmlDom.h" -#include <algorithm> - namespace aapt { -static Maybe<StringPiece16> extractJavaIdentifier(IDiagnostics* diag, const Source& source, - const StringPiece16& value) { - const StringPiece16 sep = u"."; - auto iter = std::find_end(value.begin(), value.end(), sep.begin(), sep.end()); - - StringPiece16 result; - if (iter != value.end()) { - result.assign(iter + sep.size(), value.end() - (iter + sep.size())); - } else { - result = value; - } - - if (result.empty()) { - diag->error(DiagMessage(source) << "empty symbol"); - return {}; - } - - iter = util::findNonAlphaNumericAndNotInSet(result, u"_"); - if (iter != result.end()) { - diag->error(DiagMessage(source) - << "invalid character '" << StringPiece16(iter, 1) - << "' in '" << result << "'"); - return {}; - } - - if (*result.begin() >= u'0' && *result.begin() <= u'9') { - diag->error(DiagMessage(source) << "symbol can not start with a digit"); - return {}; - } - - return result; +static Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag, + const Source& source, + const StringPiece& value) { + const StringPiece sep = "."; + auto iter = std::find_end(value.begin(), value.end(), sep.begin(), sep.end()); + + StringPiece result; + if (iter != value.end()) { + result.assign(iter + sep.size(), value.end() - (iter + sep.size())); + } else { + result = value; + } + + if (result.empty()) { + diag->Error(DiagMessage(source) << "empty symbol"); + return {}; + } + + iter = util::FindNonAlphaNumericAndNotInSet(result, "_"); + if (iter != result.end()) { + diag->Error(DiagMessage(source) << "invalid character '" + << StringPiece(iter, 1) << "' in '" + << result << "'"); + return {}; + } + + if (*result.begin() >= '0' && *result.begin() <= '9') { + diag->Error(DiagMessage(source) << "symbol can not start with a digit"); + return {}; + } + + return result; } -static bool writeSymbol(const Source& source, IDiagnostics* diag, xml::Element* el, - ClassDefinition* classDef) { - xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name"); - if (!attr) { - diag->error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'"); - return false; - } - - Maybe<StringPiece16> result = extractJavaIdentifier(diag, source.withLine(el->lineNumber), - attr->value); - if (!result) { - return false; - } - - std::unique_ptr<StringMember> stringMember = util::make_unique<StringMember>( - util::utf16ToUtf8(result.value()), util::utf16ToUtf8(attr->value)); - stringMember->getCommentBuilder()->appendComment(el->comment); - - classDef->addMember(std::move(stringMember)); - return true; +static bool WriteSymbol(const Source& source, IDiagnostics* diag, + xml::Element* el, ClassDefinition* class_def) { + xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name"); + if (!attr) { + diag->Error(DiagMessage(source) << "<" << el->name + << "> must define 'android:name'"); + return false; + } + + Maybe<StringPiece> result = ExtractJavaIdentifier( + diag, source.WithLine(el->line_number), attr->value); + if (!result) { + return false; + } + + std::unique_ptr<StringMember> string_member = + util::make_unique<StringMember>(result.value(), attr->value); + string_member->GetCommentBuilder()->AppendComment(el->comment); + + class_def->AddMember(std::move(string_member)); + return true; } -std::unique_ptr<ClassDefinition> generateManifestClass(IDiagnostics* diag, xml::XmlResource* res) { - xml::Element* el = xml::findRootElement(res->root.get()); - if (!el) { - diag->error(DiagMessage(res->file.source) << "no root tag defined"); - return {}; +std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, + xml::XmlResource* res) { + xml::Element* el = xml::FindRootElement(res->root.get()); + if (!el) { + diag->Error(DiagMessage(res->file.source) << "no root tag defined"); + return {}; + } + + if (el->name != "manifest" && !el->namespace_uri.empty()) { + diag->Error(DiagMessage(res->file.source) + << "no <manifest> root tag defined"); + return {}; + } + + std::unique_ptr<ClassDefinition> permission_class = + util::make_unique<ClassDefinition>("permission", ClassQualifier::Static, + false); + std::unique_ptr<ClassDefinition> permission_group_class = + util::make_unique<ClassDefinition>("permission_group", + ClassQualifier::Static, false); + + bool error = false; + std::vector<xml::Element*> children = el->GetChildElements(); + for (xml::Element* child_el : children) { + if (child_el->namespace_uri.empty()) { + if (child_el->name == "permission") { + error |= !WriteSymbol(res->file.source, diag, child_el, + permission_class.get()); + } else if (child_el->name == "permission-group") { + error |= !WriteSymbol(res->file.source, diag, child_el, + permission_group_class.get()); + } } - - if (el->name != u"manifest" && !el->namespaceUri.empty()) { - diag->error(DiagMessage(res->file.source) << "no <manifest> root tag defined"); - return {}; - } - - std::unique_ptr<ClassDefinition> permissionClass = - util::make_unique<ClassDefinition>("permission", ClassQualifier::Static, false); - std::unique_ptr<ClassDefinition> permissionGroupClass = - util::make_unique<ClassDefinition>("permission_group", ClassQualifier::Static, false); - - bool error = false; - - std::vector<xml::Element*> children = el->getChildElements(); - for (xml::Element* childEl : children) { - if (childEl->namespaceUri.empty()) { - if (childEl->name == u"permission") { - error |= !writeSymbol(res->file.source, diag, childEl, permissionClass.get()); - } else if (childEl->name == u"permission-group") { - error |= !writeSymbol(res->file.source, diag, childEl, permissionGroupClass.get()); - } - } - } - - if (error) { - return {}; - } - - std::unique_ptr<ClassDefinition> manifestClass = - util::make_unique<ClassDefinition>("Manifest", ClassQualifier::None, false); - manifestClass->addMember(std::move(permissionClass)); - manifestClass->addMember(std::move(permissionGroupClass)); - return manifestClass; + } + + if (error) { + return {}; + } + + std::unique_ptr<ClassDefinition> manifest_class = + util::make_unique<ClassDefinition>("Manifest", ClassQualifier::None, + false); + manifest_class->AddMember(std::move(permission_class)); + manifest_class->AddMember(std::move(permission_group_class)); + return manifest_class; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h index f565289393fb..b12202a8d137 100644 --- a/tools/aapt2/java/ManifestClassGenerator.h +++ b/tools/aapt2/java/ManifestClassGenerator.h @@ -19,15 +19,13 @@ #include "Diagnostics.h" #include "java/ClassDefinition.h" -#include "util/StringPiece.h" #include "xml/XmlDom.h" -#include <iostream> - namespace aapt { -std::unique_ptr<ClassDefinition> generateManifestClass(IDiagnostics* diag, xml::XmlResource* res); +std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, + xml::XmlResource* res); -} // namespace aapt +} // namespace aapt #endif /* AAPT_JAVA_MANIFESTCLASSGENERATOR_H */ diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp index d3bca7068cb2..5ebf508807e8 100644 --- a/tools/aapt2/java/ManifestClassGenerator_test.cpp +++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp @@ -15,33 +15,33 @@ */ #include "java/ManifestClassGenerator.h" -#include "test/Builders.h" -#include "test/Context.h" -#include <gtest/gtest.h> +#include "test/Test.h" namespace aapt { -static ::testing::AssertionResult getManifestClassText(IAaptContext* context, xml::XmlResource* res, - std::string* outStr) { - std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass( - context->getDiagnostics(), res); - if (!manifestClass) { - return ::testing::AssertionFailure() << "manifestClass == nullptr"; - } - - std::stringstream out; - if (!manifestClass->writeJavaFile(manifestClass.get(), "android", true, &out)) { - return ::testing::AssertionFailure() << "failed to write java file"; - } - - *outStr = out.str(); - return ::testing::AssertionSuccess(); +static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, + xml::XmlResource* res, + std::string* out_str) { + std::unique_ptr<ClassDefinition> manifest_class = + GenerateManifestClass(context->GetDiagnostics(), res); + if (!manifest_class) { + return ::testing::AssertionFailure() << "manifest_class == nullptr"; + } + + std::stringstream out; + if (!manifest_class->WriteJavaFile(manifest_class.get(), "android", true, + &out)) { + return ::testing::AssertionFailure() << "failed to write java file"; + } + + *out_str = out.str(); + return ::testing::AssertionSuccess(); } TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF( + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <permission android:name="android.permission.ACCESS_INTERNET" /> <permission android:name="android.DO_DANGEROUS_THINGS" /> @@ -49,46 +49,51 @@ TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) { <permission-group android:name="foo.bar.PERMISSION" /> </manifest>)EOF"); - std::string actual; - ASSERT_TRUE(getManifestClassText(context.get(), manifest.get(), &actual)); - - const size_t permissionClassPos = actual.find("public static final class permission {"); - const size_t permissionGroupClassPos = - actual.find("public static final class permission_group {"); - ASSERT_NE(std::string::npos, permissionClassPos); - ASSERT_NE(std::string::npos, permissionGroupClassPos); - - // - // Make sure these permissions are in the permission class. - // - - size_t pos = actual.find("public static final String ACCESS_INTERNET=" - "\"android.permission.ACCESS_INTERNET\";"); - EXPECT_GT(pos, permissionClassPos); - EXPECT_LT(pos, permissionGroupClassPos); - - pos = actual.find("public static final String DO_DANGEROUS_THINGS=" - "\"android.DO_DANGEROUS_THINGS\";"); - EXPECT_GT(pos, permissionClassPos); - EXPECT_LT(pos, permissionGroupClassPos); - - pos = actual.find("public static final String HUH=\"com.test.sample.permission.HUH\";"); - EXPECT_GT(pos, permissionClassPos); - EXPECT_LT(pos, permissionGroupClassPos); - - // - // Make sure these permissions are in the permission_group class - // - - pos = actual.find("public static final String PERMISSION=" - "\"foo.bar.PERMISSION\";"); - EXPECT_GT(pos, permissionGroupClassPos); - EXPECT_LT(pos, std::string::npos); + std::string actual; + ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual)); + + const size_t permission_class_pos = + actual.find("public static final class permission {"); + const size_t permission_croup_class_pos = + actual.find("public static final class permission_group {"); + ASSERT_NE(std::string::npos, permission_class_pos); + ASSERT_NE(std::string::npos, permission_croup_class_pos); + + // + // Make sure these permissions are in the permission class. + // + + size_t pos = actual.find( + "public static final String ACCESS_INTERNET=" + "\"android.permission.ACCESS_INTERNET\";"); + EXPECT_GT(pos, permission_class_pos); + EXPECT_LT(pos, permission_croup_class_pos); + + pos = actual.find( + "public static final String DO_DANGEROUS_THINGS=" + "\"android.DO_DANGEROUS_THINGS\";"); + EXPECT_GT(pos, permission_class_pos); + EXPECT_LT(pos, permission_croup_class_pos); + + pos = actual.find( + "public static final String HUH=\"com.test.sample.permission.HUH\";"); + EXPECT_GT(pos, permission_class_pos); + EXPECT_LT(pos, permission_croup_class_pos); + + // + // Make sure these permissions are in the permission_group class + // + + pos = actual.find( + "public static final String PERMISSION=" + "\"foo.bar.PERMISSION\";"); + EXPECT_GT(pos, permission_croup_class_pos); + EXPECT_LT(pos, std::string::npos); } TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF( + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Required to access the internet. Added in API 1. --> @@ -101,36 +106,36 @@ TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { <permission android:name="android.permission.SECRET" /> </manifest>)EOF"); - std::string actual; - ASSERT_TRUE(getManifestClassText(context.get(), manifest.get(), &actual)); + std::string actual; + ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual)); - const char* expectedAccessInternet = -R"EOF( /** + const char* expected_access_internet = + R"EOF( /** * Required to access the internet. * Added in API 1. */ public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)EOF"; - EXPECT_NE(std::string::npos, actual.find(expectedAccessInternet)); + EXPECT_NE(std::string::npos, actual.find(expected_access_internet)); - const char* expectedPlayOutside = -R"EOF( /** + const char* expected_play_outside = + R"EOF( /** * @deprecated This permission is for playing outside. */ @Deprecated public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)EOF"; - EXPECT_NE(std::string::npos, actual.find(expectedPlayOutside)); + EXPECT_NE(std::string::npos, actual.find(expected_play_outside)); - const char* expectedSecret = -R"EOF( /** + const char* expected_secret = + R"EOF( /** * This is a private permission for system only! * @hide */ @android.annotation.SystemApi public static final String SECRET="android.permission.SECRET";)EOF"; - EXPECT_NE(std::string::npos, actual.find(expectedSecret)); + EXPECT_NE(std::string::npos, actual.find(expected_secret)); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index c610bb0f2ff2..624a559c4dae 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -15,231 +15,281 @@ */ #include "java/ProguardRules.h" -#include "util/Util.h" -#include "xml/XmlDom.h" #include <memory> #include <string> +#include "android-base/macros.h" + +#include "util/Util.h" +#include "xml/XmlDom.h" + namespace aapt { namespace proguard { class BaseVisitor : public xml::Visitor { -public: - BaseVisitor(const Source& source, KeepSet* keepSet) : mSource(source), mKeepSet(keepSet) { - } + public: + BaseVisitor(const Source& source, KeepSet* keep_set) + : source_(source), keep_set_(keep_set) {} - virtual void visit(xml::Text*) override {}; + virtual void Visit(xml::Text*) override{}; - virtual void visit(xml::Namespace* node) override { - for (const auto& child : node->children) { - child->accept(this); - } + virtual void Visit(xml::Namespace* node) override { + for (const auto& child : node->children) { + child->Accept(this); } - - virtual void visit(xml::Element* node) override { - if (!node->namespaceUri.empty()) { - Maybe<xml::ExtractedPackage> maybePackage = xml::extractPackageFromNamespace( - node->namespaceUri); - if (maybePackage) { - // This is a custom view, let's figure out the class name from this. - std::u16string package = maybePackage.value().package + u"." + node->name; - if (util::isJavaClassName(package)) { - addClass(node->lineNumber, package); - } - } - } else if (util::isJavaClassName(node->name)) { - addClass(node->lineNumber, node->name); - } - - for (const auto& child: node->children) { - child->accept(this); + } + + virtual void Visit(xml::Element* node) override { + if (!node->namespace_uri.empty()) { + Maybe<xml::ExtractedPackage> maybe_package = + xml::ExtractPackageFromNamespace(node->namespace_uri); + if (maybe_package) { + // This is a custom view, let's figure out the class name from this. + std::string package = maybe_package.value().package + "." + node->name; + if (util::IsJavaClassName(package)) { + AddClass(node->line_number, package); } + } + } else if (util::IsJavaClassName(node->name)) { + AddClass(node->line_number, node->name); } -protected: - void addClass(size_t lineNumber, const std::u16string& className) { - mKeepSet->addClass(Source(mSource.path, lineNumber), className); + for (const auto& child : node->children) { + child->Accept(this); } + } - void addMethod(size_t lineNumber, const std::u16string& methodName) { - mKeepSet->addMethod(Source(mSource.path, lineNumber), methodName); - } + protected: + void AddClass(size_t line_number, const std::string& class_name) { + keep_set_->AddClass(Source(source_.path, line_number), class_name); + } -private: - Source mSource; - KeepSet* mKeepSet; + void AddMethod(size_t line_number, const std::string& method_name) { + keep_set_->AddMethod(Source(source_.path, line_number), method_name); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BaseVisitor); + + Source source_; + KeepSet* keep_set_; }; -struct LayoutVisitor : public BaseVisitor { - LayoutVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) { +class LayoutVisitor : public BaseVisitor { + public: + LayoutVisitor(const Source& source, KeepSet* keep_set) + : BaseVisitor(source, keep_set) {} + + virtual void Visit(xml::Element* node) override { + bool check_class = false; + bool check_name = false; + if (node->namespace_uri.empty()) { + check_class = node->name == "view" || node->name == "fragment"; + } else if (node->namespace_uri == xml::kSchemaAndroid) { + check_name = node->name == "fragment"; } - virtual void visit(xml::Element* node) override { - bool checkClass = false; - bool checkName = false; - if (node->namespaceUri.empty()) { - checkClass = node->name == u"view" || node->name == u"fragment"; - } else if (node->namespaceUri == xml::kSchemaAndroid) { - checkName = node->name == u"fragment"; - } + for (const auto& attr : node->attributes) { + if (check_class && attr.namespace_uri.empty() && attr.name == "class" && + util::IsJavaClassName(attr.value)) { + AddClass(node->line_number, attr.value); + } else if (check_name && attr.namespace_uri == xml::kSchemaAndroid && + attr.name == "name" && util::IsJavaClassName(attr.value)) { + AddClass(node->line_number, attr.value); + } else if (attr.namespace_uri == xml::kSchemaAndroid && + attr.name == "onClick") { + AddMethod(node->line_number, attr.value); + } + } - for (const auto& attr : node->attributes) { - if (checkClass && attr.namespaceUri.empty() && attr.name == u"class" && - util::isJavaClassName(attr.value)) { - addClass(node->lineNumber, attr.value); - } else if (checkName && attr.namespaceUri == xml::kSchemaAndroid && - attr.name == u"name" && util::isJavaClassName(attr.value)) { - addClass(node->lineNumber, attr.value); - } else if (attr.namespaceUri == xml::kSchemaAndroid && attr.name == u"onClick") { - addMethod(node->lineNumber, attr.value); - } - } + BaseVisitor::Visit(node); + } - BaseVisitor::visit(node); - } + private: + DISALLOW_COPY_AND_ASSIGN(LayoutVisitor); }; -struct XmlResourceVisitor : public BaseVisitor { - XmlResourceVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) { +class XmlResourceVisitor : public BaseVisitor { + public: + XmlResourceVisitor(const Source& source, KeepSet* keep_set) + : BaseVisitor(source, keep_set) {} + + virtual void Visit(xml::Element* node) override { + bool check_fragment = false; + if (node->namespace_uri.empty()) { + check_fragment = + node->name == "PreferenceScreen" || node->name == "header"; } - virtual void visit(xml::Element* node) override { - bool checkFragment = false; - if (node->namespaceUri.empty()) { - checkFragment = node->name == u"PreferenceScreen" || node->name == u"header"; - } + if (check_fragment) { + xml::Attribute* attr = + node->FindAttribute(xml::kSchemaAndroid, "fragment"); + if (attr && util::IsJavaClassName(attr->value)) { + AddClass(node->line_number, attr->value); + } + } - if (checkFragment) { - xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"fragment"); - if (attr && util::isJavaClassName(attr->value)) { - addClass(node->lineNumber, attr->value); - } - } + BaseVisitor::Visit(node); + } - BaseVisitor::visit(node); - } + private: + DISALLOW_COPY_AND_ASSIGN(XmlResourceVisitor); }; -struct TransitionVisitor : public BaseVisitor { - TransitionVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) { +class TransitionVisitor : public BaseVisitor { + public: + TransitionVisitor(const Source& source, KeepSet* keep_set) + : BaseVisitor(source, keep_set) {} + + virtual void Visit(xml::Element* node) override { + bool check_class = + node->namespace_uri.empty() && + (node->name == "transition" || node->name == "pathMotion"); + if (check_class) { + xml::Attribute* attr = node->FindAttribute({}, "class"); + if (attr && util::IsJavaClassName(attr->value)) { + AddClass(node->line_number, attr->value); + } } - virtual void visit(xml::Element* node) override { - bool checkClass = node->namespaceUri.empty() && - (node->name == u"transition" || node->name == u"pathMotion"); - if (checkClass) { - xml::Attribute* attr = node->findAttribute({}, u"class"); - if (attr && util::isJavaClassName(attr->value)) { - addClass(node->lineNumber, attr->value); - } - } + BaseVisitor::Visit(node); + } - BaseVisitor::visit(node); - } + private: + DISALLOW_COPY_AND_ASSIGN(TransitionVisitor); }; -struct ManifestVisitor : public BaseVisitor { - ManifestVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) { - } +class ManifestVisitor : public BaseVisitor { + public: + ManifestVisitor(const Source& source, KeepSet* keep_set, bool main_dex_only) + : BaseVisitor(source, keep_set), main_dex_only_(main_dex_only) {} + + virtual void Visit(xml::Element* node) override { + if (node->namespace_uri.empty()) { + bool get_name = false; + if (node->name == "manifest") { + xml::Attribute* attr = node->FindAttribute({}, "package"); + if (attr) { + package_ = attr->value; + } + } else if (node->name == "application") { + get_name = true; + xml::Attribute* attr = + node->FindAttribute(xml::kSchemaAndroid, "backupAgent"); + if (attr) { + Maybe<std::string> result = + util::GetFullyQualifiedClassName(package_, attr->value); + if (result) { + AddClass(node->line_number, result.value()); + } + } + if (main_dex_only_) { + xml::Attribute* default_process = + node->FindAttribute(xml::kSchemaAndroid, "process"); + if (default_process) { + default_process_ = default_process->value; + } + } + } else if (node->name == "activity" || node->name == "service" || + node->name == "receiver" || node->name == "provider") { + get_name = true; + + if (main_dex_only_) { + xml::Attribute* component_process = + node->FindAttribute(xml::kSchemaAndroid, "process"); - virtual void visit(xml::Element* node) override { - if (node->namespaceUri.empty()) { - bool getName = false; - if (node->name == u"manifest") { - xml::Attribute* attr = node->findAttribute({}, u"package"); - if (attr) { - mPackage = attr->value; - } - } else if (node->name == u"application") { - getName = true; - xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"backupAgent"); - if (attr) { - Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage, - attr->value); - if (result) { - addClass(node->lineNumber, result.value()); - } - } - } else if (node->name == u"activity" || node->name == u"service" || - node->name == u"receiver" || node->name == u"provider" || - node->name == u"instrumentation") { - getName = true; - } - - if (getName) { - xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"name"); - if (attr) { - Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage, - attr->value); - if (result) { - addClass(node->lineNumber, result.value()); - } - } - } + const std::string& process = + component_process ? component_process->value : default_process_; + get_name = !process.empty() && process[0] != ':'; } - BaseVisitor::visit(node); + } else if (node->name == "instrumentation") { + get_name = true; + } + + if (get_name) { + xml::Attribute* attr = node->FindAttribute(xml::kSchemaAndroid, "name"); + get_name = attr != nullptr; + + if (get_name) { + Maybe<std::string> result = + util::GetFullyQualifiedClassName(package_, attr->value); + if (result) { + AddClass(node->line_number, result.value()); + } + } + } } + BaseVisitor::Visit(node); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ManifestVisitor); - std::u16string mPackage; + std::string package_; + const bool main_dex_only_; + std::string default_process_; }; -bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, - KeepSet* keepSet) { - ManifestVisitor visitor(source, keepSet); - if (res->root) { - res->root->accept(&visitor); - return true; - } - return false; +bool CollectProguardRulesForManifest(const Source& source, + xml::XmlResource* res, KeepSet* keep_set, + bool main_dex_only) { + ManifestVisitor visitor(source, keep_set, main_dex_only); + if (res->root) { + res->root->Accept(&visitor); + return true; + } + return false; } -bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet) { - if (!res->root) { - return false; - } - - switch (res->file.name.type) { - case ResourceType::kLayout: { - LayoutVisitor visitor(source, keepSet); - res->root->accept(&visitor); - break; - } +bool CollectProguardRules(const Source& source, xml::XmlResource* res, + KeepSet* keep_set) { + if (!res->root) { + return false; + } - case ResourceType::kXml: { - XmlResourceVisitor visitor(source, keepSet); - res->root->accept(&visitor); - break; - } + switch (res->file.name.type) { + case ResourceType::kLayout: { + LayoutVisitor visitor(source, keep_set); + res->root->Accept(&visitor); + break; + } - case ResourceType::kTransition: { - TransitionVisitor visitor(source, keepSet); - res->root->accept(&visitor); - break; - } + case ResourceType::kXml: { + XmlResourceVisitor visitor(source, keep_set); + res->root->Accept(&visitor); + break; + } - default: - break; + case ResourceType::kTransition: { + TransitionVisitor visitor(source, keep_set); + res->root->Accept(&visitor); + break; } - return true; + + default: + break; + } + return true; } -bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) { - for (const auto& entry : keepSet.mKeepSet) { - for (const Source& source : entry.second) { - *out << "# Referenced at " << source << "\n"; - } - *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl; +bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set) { + for (const auto& entry : keep_set.keep_set_) { + for (const Source& source : entry.second) { + *out << "# Referenced at " << source << "\n"; } + *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl; + } - for (const auto& entry : keepSet.mKeepMethodSet) { - for (const Source& source : entry.second) { - *out << "# Referenced at " << source << "\n"; - } - *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl; + for (const auto& entry : keep_set.keep_method_set_) { + for (const Source& source : entry.second) { + *out << "# Referenced at " << source << "\n"; } - return true; + *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" + << std::endl; + } + return true; } -} // namespace proguard -} // namespace aapt +} // namespace proguard +} // namespace aapt diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h index aafffd39d84e..3c349bab1217 100644 --- a/tools/aapt2/java/ProguardRules.h +++ b/tools/aapt2/java/ProguardRules.h @@ -17,41 +17,44 @@ #ifndef AAPT_PROGUARD_RULES_H #define AAPT_PROGUARD_RULES_H -#include "Resource.h" -#include "Source.h" -#include "xml/XmlDom.h" - #include <map> #include <ostream> #include <set> #include <string> +#include "Resource.h" +#include "Source.h" +#include "xml/XmlDom.h" + namespace aapt { namespace proguard { class KeepSet { -public: - inline void addClass(const Source& source, const std::u16string& className) { - mKeepSet[className].insert(source); - } + public: + inline void AddClass(const Source& source, const std::string& class_name) { + keep_set_[class_name].insert(source); + } - inline void addMethod(const Source& source, const std::u16string& methodName) { - mKeepMethodSet[methodName].insert(source); - } + inline void AddMethod(const Source& source, const std::string& method_name) { + keep_method_set_[method_name].insert(source); + } -private: - friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet); + private: + friend bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set); - std::map<std::u16string, std::set<Source>> mKeepSet; - std::map<std::u16string, std::set<Source>> mKeepMethodSet; + std::map<std::string, std::set<Source>> keep_set_; + std::map<std::string, std::set<Source>> keep_method_set_; }; -bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keepSet); -bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet); +bool CollectProguardRulesForManifest(const Source& source, + xml::XmlResource* res, KeepSet* keep_set, + bool main_dex_only = false); +bool CollectProguardRules(const Source& source, xml::XmlResource* res, + KeepSet* keep_set); -bool writeKeepSet(std::ostream* out, const KeepSet& keepSet); +bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set); -} // namespace proguard -} // namespace aapt +} // namespace proguard +} // namespace aapt -#endif // AAPT_PROGUARD_RULES_H +#endif // AAPT_PROGUARD_RULES_H diff --git a/tools/aapt2/jni/ScopedUtfChars.h b/tools/aapt2/jni/ScopedUtfChars.h new file mode 100644 index 000000000000..a8c4b136dcf6 --- /dev/null +++ b/tools/aapt2/jni/ScopedUtfChars.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef SCOPED_UTF_CHARS_H_included +#define SCOPED_UTF_CHARS_H_included + +#include <string.h> +#include <jni.h> + +#include "android-base/logging.h" + +// This file was copied with some minor modifications from libnativehelper. +// As soon as libnativehelper can be compiled for Windows, this file should be +// replaced with libnativehelper's implementation. +class ScopedUtfChars { + public: + ScopedUtfChars(JNIEnv* env, jstring s) : env_(env), string_(s) { + CHECK(s != nullptr); + utf_chars_ = env->GetStringUTFChars(s, nullptr); + } + + ScopedUtfChars(ScopedUtfChars&& rhs) : + env_(rhs.env_), string_(rhs.string_), utf_chars_(rhs.utf_chars_) { + rhs.env_ = nullptr; + rhs.string_ = nullptr; + rhs.utf_chars_ = nullptr; + } + + ~ScopedUtfChars() { + if (utf_chars_) { + env_->ReleaseStringUTFChars(string_, utf_chars_); + } + } + + ScopedUtfChars& operator=(ScopedUtfChars&& rhs) { + if (this != &rhs) { + // Delete the currently owned UTF chars. + this->~ScopedUtfChars(); + + // Move the rhs ScopedUtfChars and zero it out. + env_ = rhs.env_; + string_ = rhs.string_; + utf_chars_ = rhs.utf_chars_; + rhs.env_ = nullptr; + rhs.string_ = nullptr; + rhs.utf_chars_ = nullptr; + } + return *this; + } + + const char* c_str() const { + return utf_chars_; + } + + size_t size() const { + return strlen(utf_chars_); + } + + const char& operator[](size_t n) const { + return utf_chars_[n]; + } + + private: + JNIEnv* env_; + jstring string_; + const char* utf_chars_; + + DISALLOW_COPY_AND_ASSIGN(ScopedUtfChars); +}; + +#endif // SCOPED_UTF_CHARS_H_included diff --git a/tools/aapt2/jni/aapt2_jni.cpp b/tools/aapt2/jni/aapt2_jni.cpp new file mode 100644 index 000000000000..5518fe2cae37 --- /dev/null +++ b/tools/aapt2/jni/aapt2_jni.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2015 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. + */ + +#include "com_android_tools_aapt2_Aapt2Jni.h" + +#include <algorithm> +#include <memory> +#include <utility> +#include <vector> + +#include "android-base/logging.h" +#include "ScopedUtfChars.h" + +#include "util/Util.h" + +namespace aapt { +extern int Compile(const std::vector<StringPiece> &args); +extern int Link(const std::vector<StringPiece> &args); +} + +/* + * Converts a java List<String> into C++ vector<ScopedUtfChars>. + */ +static std::vector<ScopedUtfChars> list_to_utfchars(JNIEnv *env, jobject obj) { + std::vector<ScopedUtfChars> converted; + + // Call size() method on the list to know how many elements there are. + jclass list_cls = env->GetObjectClass(obj); + jmethodID size_method_id = env->GetMethodID(list_cls, "size", "()I"); + CHECK(size_method_id != 0); + jint size = env->CallIntMethod(obj, size_method_id); + CHECK(size >= 0); + + // Now, iterate all strings in the list + // (note: generic erasure means get() return an Object) + jmethodID get_method_id = env->GetMethodID(list_cls, "get", "(I)Ljava/lang/Object;"); + CHECK(get_method_id != 0); + for (jint i = 0; i < size; i++) { + // Call get(i) to get the string in the ith position. + jobject string_obj_uncast = env->CallObjectMethod(obj, get_method_id, i); + CHECK(string_obj_uncast != nullptr); + jstring string_obj = static_cast<jstring>(string_obj_uncast); + converted.push_back(ScopedUtfChars(env, string_obj)); + } + + return converted; +} + +/* + * Extracts all StringPiece from the ScopedUtfChars instances. + * + * The returned pieces can only be used while the original ones have not been + * destroyed. + */ +static std::vector<aapt::StringPiece> extract_pieces( + const std::vector<ScopedUtfChars> &strings) { + std::vector<aapt::StringPiece> pieces; + + std::for_each( + strings.begin(), strings.end(), + [&pieces](const ScopedUtfChars &p) { pieces.push_back(p.c_str()); }); + + return pieces; +} + +JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeCompile( + JNIEnv *env, jclass aapt_obj, jobject arguments_obj) { + std::vector<ScopedUtfChars> compile_args_jni = + list_to_utfchars(env, arguments_obj); + std::vector<aapt::StringPiece> compile_args = + extract_pieces(compile_args_jni); + aapt::Compile(compile_args); +} + +JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeLink( + JNIEnv *env, jclass aapt_obj, jobject arguments_obj) { + std::vector<ScopedUtfChars> link_args_jni = + list_to_utfchars(env, arguments_obj); + std::vector<aapt::StringPiece> link_args = extract_pieces(link_args_jni); + aapt::Link(link_args); +} + +JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_ping( + JNIEnv *env, jclass aapt_obj) { + // This is just a dummy method to see if the library has been loaded. +} diff --git a/tools/aapt2/jni/com_android_tools_aapt2_Aapt2Jni.h b/tools/aapt2/jni/com_android_tools_aapt2_Aapt2Jni.h new file mode 100644 index 000000000000..56c3c18e3a1e --- /dev/null +++ b/tools/aapt2/jni/com_android_tools_aapt2_Aapt2Jni.h @@ -0,0 +1,37 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include <jni.h> +/* Header for class com_android_tools_aapt2_Aapt2Jni */ + +#ifndef _Included_com_android_tools_aapt2_Aapt2Jni +#define _Included_com_android_tools_aapt2_Aapt2Jni +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_android_tools_aapt2_Aapt2Jni + * Method: ping + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_ping + (JNIEnv *, jclass); + +/* + * Class: com_android_tools_aapt2_Aapt2Jni + * Method: nativeCompile + * Signature: (Ljava/util/List;)V + */ +JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeCompile + (JNIEnv *, jclass, jobject); + +/* + * Class: com_android_tools_aapt2_Aapt2Jni + * Method: nativeLink + * Signature: (Ljava/util/List;)V + */ +JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeLink + (JNIEnv *, jclass, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp index 459c330cfbdc..77471ea5d0da 100644 --- a/tools/aapt2/link/AutoVersioner.cpp +++ b/tools/aapt2/link/AutoVersioner.cpp @@ -14,126 +14,142 @@ * limitations under the License. */ +#include "link/Linkers.h" + +#include <algorithm> + +#include "android-base/logging.h" + #include "ConfigDescription.h" #include "ResourceTable.h" #include "SdkConstants.h" #include "ValueVisitor.h" -#include "link/Linkers.h" - -#include <algorithm> -#include <cassert> namespace aapt { -bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config, - const int sdkVersionToGenerate) { - assert(sdkVersionToGenerate > config.sdkVersion); - const auto endIter = entry->values.end(); - auto iter = entry->values.begin(); - for (; iter != endIter; ++iter) { - if ((*iter)->config == config) { - break; - } +bool ShouldGenerateVersionedResource(const ResourceEntry* entry, + const ConfigDescription& config, + const int sdk_version_to_generate) { + // We assume the caller is trying to generate a version greater than the + // current configuration. + CHECK(sdk_version_to_generate > config.sdkVersion); + + const auto end_iter = entry->values.end(); + auto iter = entry->values.begin(); + for (; iter != end_iter; ++iter) { + if ((*iter)->config == config) { + break; } - - // The source config came from this list, so it should be here. - assert(iter != entry->values.end()); - ++iter; - - // The next configuration either only varies in sdkVersion, or it is completely different - // and therefore incompatible. If it is incompatible, we must generate the versioned resource. - - // NOTE: The ordering of configurations takes sdkVersion as higher precedence than other - // qualifiers, so we need to iterate through the entire list to be sure there - // are no higher sdk level versions of this resource. - ConfigDescription tempConfig(config); - for (; iter != endIter; ++iter) { - tempConfig.sdkVersion = (*iter)->config.sdkVersion; - if (tempConfig == (*iter)->config) { - // The two configs are the same, check the sdk version. - return sdkVersionToGenerate < (*iter)->config.sdkVersion; - } + } + + // The source config came from this list, so it should be here. + CHECK(iter != entry->values.end()); + ++iter; + + // The next configuration either only varies in sdkVersion, or it is + // completely different + // and therefore incompatible. If it is incompatible, we must generate the + // versioned resource. + + // NOTE: The ordering of configurations takes sdkVersion as higher precedence + // than other + // qualifiers, so we need to iterate through the entire list to be sure there + // are no higher sdk level versions of this resource. + ConfigDescription temp_config(config); + for (; iter != end_iter; ++iter) { + temp_config.sdkVersion = (*iter)->config.sdkVersion; + if (temp_config == (*iter)->config) { + // The two configs are the same, check the sdk version. + return sdk_version_to_generate < (*iter)->config.sdkVersion; } + } - // No match was found, so we should generate the versioned resource. - return true; + // No match was found, so we should generate the versioned resource. + return true; } -bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) { - for (auto& package : table->packages) { - for (auto& type : package->types) { - if (type->type != ResourceType::kStyle) { +bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) { + for (auto& package : table->packages) { + for (auto& type : package->types) { + if (type->type != ResourceType::kStyle) { + continue; + } + + for (auto& entry : type->entries) { + for (size_t i = 0; i < entry->values.size(); i++) { + ResourceConfigValue* config_value = entry->values[i].get(); + if (config_value->config.sdkVersion >= SDK_LOLLIPOP_MR1) { + // If this configuration is only used on L-MR1 then we don't need + // to do anything since we use private attributes since that + // version. + continue; + } + + if (Style* style = ValueCast<Style>(config_value->value.get())) { + Maybe<size_t> min_sdk_stripped; + std::vector<Style::Entry> stripped; + + auto iter = style->entries.begin(); + while (iter != style->entries.end()) { + CHECK(bool(iter->key.id)) << "IDs must be assigned and linked"; + + // Find the SDK level that is higher than the configuration + // allows. + const size_t sdk_level = + FindAttributeSdkLevel(iter->key.id.value()); + if (sdk_level > + std::max<size_t>(config_value->config.sdkVersion, 1)) { + // Record that we are about to strip this. + stripped.emplace_back(std::move(*iter)); + + // We use the smallest SDK level to generate the new style. + if (min_sdk_stripped) { + min_sdk_stripped = + std::min(min_sdk_stripped.value(), sdk_level); + } else { + min_sdk_stripped = sdk_level; + } + + // Erase this from this style. + iter = style->entries.erase(iter); continue; + } + ++iter; } - for (auto& entry : type->entries) { - for (size_t i = 0; i < entry->values.size(); i++) { - ResourceConfigValue* configValue = entry->values[i].get(); - if (configValue->config.sdkVersion >= SDK_LOLLIPOP_MR1) { - // If this configuration is only used on L-MR1 then we don't need - // to do anything since we use private attributes since that version. - continue; - } - - if (Style* style = valueCast<Style>(configValue->value.get())) { - Maybe<size_t> minSdkStripped; - std::vector<Style::Entry> stripped; - - auto iter = style->entries.begin(); - while (iter != style->entries.end()) { - assert(iter->key.id && "IDs must be assigned and linked"); - - // Find the SDK level that is higher than the configuration allows. - const size_t sdkLevel = findAttributeSdkLevel(iter->key.id.value()); - if (sdkLevel > std::max<size_t>(configValue->config.sdkVersion, 1)) { - // Record that we are about to strip this. - stripped.emplace_back(std::move(*iter)); - - // We use the smallest SDK level to generate the new style. - if (minSdkStripped) { - minSdkStripped = std::min(minSdkStripped.value(), sdkLevel); - } else { - minSdkStripped = sdkLevel; - } - - // Erase this from this style. - iter = style->entries.erase(iter); - continue; - } - ++iter; - } - - if (minSdkStripped && !stripped.empty()) { - // We found attributes from a higher SDK level. Check that - // there is no other defined resource for the version we want to - // generate. - if (shouldGenerateVersionedResource(entry.get(), - configValue->config, - minSdkStripped.value())) { - // Let's create a new Style for this versioned resource. - ConfigDescription newConfig(configValue->config); - newConfig.sdkVersion = minSdkStripped.value(); - - std::unique_ptr<Style> newStyle(style->clone(&table->stringPool)); - newStyle->setComment(style->getComment()); - newStyle->setSource(style->getSource()); - - // Move the previously stripped attributes into this style. - newStyle->entries.insert(newStyle->entries.end(), - std::make_move_iterator(stripped.begin()), - std::make_move_iterator(stripped.end())); - - // Insert the new Resource into the correct place. - entry->findOrCreateValue(newConfig, {})->value = - std::move(newStyle); - } - } - } - } + if (min_sdk_stripped && !stripped.empty()) { + // We found attributes from a higher SDK level. Check that + // there is no other defined resource for the version we want to + // generate. + if (ShouldGenerateVersionedResource(entry.get(), + config_value->config, + min_sdk_stripped.value())) { + // Let's create a new Style for this versioned resource. + ConfigDescription new_config(config_value->config); + new_config.sdkVersion = min_sdk_stripped.value(); + + std::unique_ptr<Style> new_style( + style->Clone(&table->string_pool)); + new_style->SetComment(style->GetComment()); + new_style->SetSource(style->GetSource()); + + // Move the previously stripped attributes into this style. + new_style->entries.insert( + new_style->entries.end(), + std::make_move_iterator(stripped.begin()), + std::make_move_iterator(stripped.end())); + + // Insert the new Resource into the correct place. + entry->FindOrCreateValue(new_config, {})->value = + std::move(new_style); + } } + } } + } } - return true; + } + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp index 9b3a87c4eed0..755af0a1c6cc 100644 --- a/tools/aapt2/link/AutoVersioner_test.cpp +++ b/tools/aapt2/link/AutoVersioner_test.cpp @@ -14,112 +14,124 @@ * limitations under the License. */ -#include "ConfigDescription.h" #include "link/Linkers.h" -#include "test/Builders.h" -#include "test/Context.h" -#include <gtest/gtest.h> +#include "ConfigDescription.h" +#include "test/Test.h" namespace aapt { TEST(AutoVersionerTest, GenerateVersionedResources) { - const ConfigDescription defaultConfig = {}; - const ConfigDescription landConfig = test::parseConfigOrDie("land"); - const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land"); - - ResourceEntry entry(u"foo"); - entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, "")); - entry.values.push_back(util::make_unique<ResourceConfigValue>(landConfig, "")); - entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpLandConfig, "")); - - EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17)); - EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17)); + const ConfigDescription land_config = test::ParseConfigOrDie("land"); + const ConfigDescription sw600dp_land_config = + test::ParseConfigOrDie("sw600dp-land"); + + ResourceEntry entry("foo"); + entry.values.push_back(util::make_unique<ResourceConfigValue>( + ConfigDescription::DefaultConfig(), "")); + entry.values.push_back( + util::make_unique<ResourceConfigValue>(land_config, "")); + entry.values.push_back( + util::make_unique<ResourceConfigValue>(sw600dp_land_config, "")); + + EXPECT_TRUE(ShouldGenerateVersionedResource( + &entry, ConfigDescription::DefaultConfig(), 17)); + EXPECT_TRUE(ShouldGenerateVersionedResource(&entry, land_config, 17)); } TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) { - const ConfigDescription defaultConfig = {}; - const ConfigDescription sw600dpV13Config = test::parseConfigOrDie("sw600dp-v13"); - const ConfigDescription v21Config = test::parseConfigOrDie("v21"); - - ResourceEntry entry(u"foo"); - entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, "")); - entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpV13Config, "")); - entry.values.push_back(util::make_unique<ResourceConfigValue>(v21Config, "")); - - EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17)); - EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22)); + const ConfigDescription sw600dp_v13_config = + test::ParseConfigOrDie("sw600dp-v13"); + const ConfigDescription v21_config = test::ParseConfigOrDie("v21"); + + ResourceEntry entry("foo"); + entry.values.push_back(util::make_unique<ResourceConfigValue>( + ConfigDescription::DefaultConfig(), "")); + entry.values.push_back( + util::make_unique<ResourceConfigValue>(sw600dp_v13_config, "")); + entry.values.push_back( + util::make_unique<ResourceConfigValue>(v21_config, "")); + + EXPECT_TRUE(ShouldGenerateVersionedResource( + &entry, ConfigDescription::DefaultConfig(), 17)); + EXPECT_FALSE(ShouldGenerateVersionedResource( + &entry, ConfigDescription::DefaultConfig(), 22)); } TEST(AutoVersionerTest, VersionStylesForTable) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"app", 0x7f) - .addValue(u"@app:style/Foo", ResourceId(0x7f020000), test::parseConfigOrDie("v4"), - test::StyleBuilder() - .addItem(u"@android:attr/onClick", ResourceId(0x0101026f), - util::make_unique<Id>()) - .addItem(u"@android:attr/paddingStart", ResourceId(0x010103b3), - util::make_unique<Id>()) - .addItem(u"@android:attr/requiresSmallestWidthDp", - ResourceId(0x01010364), util::make_unique<Id>()) - .addItem(u"@android:attr/colorAccent", ResourceId(0x01010435), - util::make_unique<Id>()) - .build()) - .addValue(u"@app:style/Foo", ResourceId(0x7f020000), test::parseConfigOrDie("v21"), - test::StyleBuilder() - .addItem(u"@android:attr/paddingEnd", ResourceId(0x010103b4), - util::make_unique<Id>()) - .build()) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .setCompilationPackage(u"app") - .setPackageId(0x7f) - .build(); - - AutoVersioner versioner; - ASSERT_TRUE(versioner.consume(context.get(), table.get())); - - Style* style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo", - test::parseConfigOrDie("v4")); - ASSERT_NE(style, nullptr); - ASSERT_EQ(style->entries.size(), 1u); - AAPT_ASSERT_TRUE(style->entries.front().key.name); - EXPECT_EQ(style->entries.front().key.name.value(), - test::parseNameOrDie(u"@android:attr/onClick")); - - style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo", - test::parseConfigOrDie("v13")); - ASSERT_NE(style, nullptr); - ASSERT_EQ(style->entries.size(), 2u); - AAPT_ASSERT_TRUE(style->entries[0].key.name); - EXPECT_EQ(style->entries[0].key.name.value(), - test::parseNameOrDie(u"@android:attr/onClick")); - AAPT_ASSERT_TRUE(style->entries[1].key.name); - EXPECT_EQ(style->entries[1].key.name.value(), - test::parseNameOrDie(u"@android:attr/requiresSmallestWidthDp")); - - style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo", - test::parseConfigOrDie("v17")); - ASSERT_NE(style, nullptr); - ASSERT_EQ(style->entries.size(), 3u); - AAPT_ASSERT_TRUE(style->entries[0].key.name); - EXPECT_EQ(style->entries[0].key.name.value(), - test::parseNameOrDie(u"@android:attr/onClick")); - AAPT_ASSERT_TRUE(style->entries[1].key.name); - EXPECT_EQ(style->entries[1].key.name.value(), - test::parseNameOrDie(u"@android:attr/requiresSmallestWidthDp")); - AAPT_ASSERT_TRUE(style->entries[2].key.name); - EXPECT_EQ(style->entries[2].key.name.value(), - test::parseNameOrDie(u"@android:attr/paddingStart")); - - style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo", - test::parseConfigOrDie("v21")); - ASSERT_NE(style, nullptr); - ASSERT_EQ(style->entries.size(), 1u); - AAPT_ASSERT_TRUE(style->entries.front().key.name); - EXPECT_EQ(style->entries.front().key.name.value(), - test::parseNameOrDie(u"@android:attr/paddingEnd")); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("app", 0x7f) + .AddValue( + "app:style/Foo", test::ParseConfigOrDie("v4"), + ResourceId(0x7f020000), + test::StyleBuilder() + .AddItem("android:attr/onClick", ResourceId(0x0101026f), + util::make_unique<Id>()) + .AddItem("android:attr/paddingStart", ResourceId(0x010103b3), + util::make_unique<Id>()) + .AddItem("android:attr/requiresSmallestWidthDp", + ResourceId(0x01010364), util::make_unique<Id>()) + .AddItem("android:attr/colorAccent", ResourceId(0x01010435), + util::make_unique<Id>()) + .Build()) + .AddValue( + "app:style/Foo", test::ParseConfigOrDie("v21"), + ResourceId(0x7f020000), + test::StyleBuilder() + .AddItem("android:attr/paddingEnd", ResourceId(0x010103b4), + util::make_unique<Id>()) + .Build()) + .Build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder() + .SetCompilationPackage("app") + .SetPackageId(0x7f) + .Build(); + + AutoVersioner versioner; + ASSERT_TRUE(versioner.Consume(context.get(), table.get())); + + Style* style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", + test::ParseConfigOrDie("v4")); + ASSERT_NE(style, nullptr); + ASSERT_EQ(style->entries.size(), 1u); + AAPT_ASSERT_TRUE(style->entries.front().key.name); + EXPECT_EQ(style->entries.front().key.name.value(), + test::ParseNameOrDie("android:attr/onClick")); + + style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", + test::ParseConfigOrDie("v13")); + ASSERT_NE(style, nullptr); + ASSERT_EQ(style->entries.size(), 2u); + AAPT_ASSERT_TRUE(style->entries[0].key.name); + EXPECT_EQ(style->entries[0].key.name.value(), + test::ParseNameOrDie("android:attr/onClick")); + AAPT_ASSERT_TRUE(style->entries[1].key.name); + EXPECT_EQ(style->entries[1].key.name.value(), + test::ParseNameOrDie("android:attr/requiresSmallestWidthDp")); + + style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", + test::ParseConfigOrDie("v17")); + ASSERT_NE(style, nullptr); + ASSERT_EQ(style->entries.size(), 3u); + AAPT_ASSERT_TRUE(style->entries[0].key.name); + EXPECT_EQ(style->entries[0].key.name.value(), + test::ParseNameOrDie("android:attr/onClick")); + AAPT_ASSERT_TRUE(style->entries[1].key.name); + EXPECT_EQ(style->entries[1].key.name.value(), + test::ParseNameOrDie("android:attr/requiresSmallestWidthDp")); + AAPT_ASSERT_TRUE(style->entries[2].key.name); + EXPECT_EQ(style->entries[2].key.name.value(), + test::ParseNameOrDie("android:attr/paddingStart")); + + style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", + test::ParseConfigOrDie("v21")); + ASSERT_NE(style, nullptr); + ASSERT_EQ(style->entries.size(), 1u); + AAPT_ASSERT_TRUE(style->entries.front().key.name); + EXPECT_EQ(style->entries.front().key.name.value(), + test::ParseNameOrDie("android:attr/paddingEnd")); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index 49971201fb3c..b525758004f0 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -14,6 +14,17 @@ * limitations under the License. */ +#include <sys/stat.h> + +#include <fstream> +#include <queue> +#include <unordered_map> +#include <vector> + +#include "android-base/errors.h" +#include "android-base/file.h" +#include "google/protobuf/io/coded_stream.h" + #include "AppInfo.h" #include "Debug.h" #include "Flags.h" @@ -31,9 +42,8 @@ #include "java/ManifestClassGenerator.h" #include "java/ProguardRules.h" #include "link/Linkers.h" -#include "link/ProductFilter.h" -#include "link/ReferenceLinker.h" #include "link/ManifestFixer.h" +#include "link/ReferenceLinker.h" #include "link/TableMerger.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" @@ -44,1546 +54,2059 @@ #include "util/StringPiece.h" #include "xml/XmlDom.h" -#include <google/protobuf/io/coded_stream.h> - -#include <fstream> -#include <sys/stat.h> -#include <vector> +using ::google::protobuf::io::CopyingOutputStreamAdaptor; namespace aapt { struct LinkOptions { - std::string outputPath; - std::string manifestPath; - std::vector<std::string> includePaths; - std::vector<std::string> overlayFiles; - Maybe<std::string> generateJavaClassPath; - Maybe<std::u16string> customJavaPackage; - std::set<std::u16string> extraJavaPackages; - Maybe<std::string> generateProguardRulesPath; - bool noAutoVersion = false; - bool noVersionVectors = false; - bool staticLib = false; - bool noStaticLibPackages = false; - bool generateNonFinalIds = false; - std::vector<std::string> javadocAnnotations; - bool outputToDirectory = false; - bool autoAddOverlay = false; - bool doNotCompressAnything = false; - std::vector<std::string> extensionsToNotCompress; - Maybe<std::u16string> privateSymbols; - ManifestFixerOptions manifestFixerOptions; - std::unordered_set<std::string> products; - TableSplitterOptions tableSplitterOptions; + std::string output_path; + std::string manifest_path; + std::vector<std::string> include_paths; + std::vector<std::string> overlay_files; + bool output_to_directory = false; + bool auto_add_overlay = false; + + // Java/Proguard options. + Maybe<std::string> generate_java_class_path; + Maybe<std::string> custom_java_package; + std::set<std::string> extra_java_packages; + Maybe<std::string> generate_proguard_rules_path; + Maybe<std::string> generate_main_dex_proguard_rules_path; + bool generate_non_final_ids = false; + std::vector<std::string> javadoc_annotations; + Maybe<std::string> private_symbols; + + // Optimizations/features. + bool no_auto_version = false; + bool no_version_vectors = false; + bool no_resource_deduping = false; + bool no_xml_namespaces = false; + bool do_not_compress_anything = false; + std::unordered_set<std::string> extensions_to_not_compress; + + // Static lib options. + bool static_lib = false; + bool no_static_lib_packages = false; + + // AndroidManifest.xml massaging options. + ManifestFixerOptions manifest_fixer_options; + + // Products to use/filter on. + std::unordered_set<std::string> products; + + // Split APK options. + TableSplitterOptions table_splitter_options; + std::vector<SplitConstraints> split_constraints; + std::vector<std::string> split_paths; + + // Stable ID options. + std::unordered_map<ResourceName, ResourceId> stable_id_map; + Maybe<std::string> resource_id_map_path; }; class LinkContext : public IAaptContext { -public: - LinkContext() : mNameMangler({}) { - } + public: + LinkContext() : name_mangler_({}) {} - IDiagnostics* getDiagnostics() override { - return &mDiagnostics; - } + IDiagnostics* GetDiagnostics() override { return &diagnostics_; } - NameMangler* getNameMangler() override { - return &mNameMangler; - } + NameMangler* GetNameMangler() override { return &name_mangler_; } - void setNameManglerPolicy(const NameManglerPolicy& policy) { - mNameMangler = NameMangler(policy); - } + void SetNameManglerPolicy(const NameManglerPolicy& policy) { + name_mangler_ = NameMangler(policy); + } - const std::u16string& getCompilationPackage() override { - return mCompilationPackage; - } + const std::string& GetCompilationPackage() override { + return compilation_package_; + } - void setCompilationPackage(const StringPiece16& packageName) { - mCompilationPackage = packageName.toString(); - } + void SetCompilationPackage(const StringPiece& package_name) { + compilation_package_ = package_name.ToString(); + } - uint8_t getPackageId() override { - return mPackageId; - } + uint8_t GetPackageId() override { return package_id_; } - void setPackageId(uint8_t id) { - mPackageId = id; - } + void SetPackageId(uint8_t id) { package_id_ = id; } - SymbolTable* getExternalSymbols() override { - return &mSymbols; - } + SymbolTable* GetExternalSymbols() override { return &symbols_; } - bool verbose() override { - return mVerbose; - } + bool IsVerbose() override { return verbose_; } - void setVerbose(bool val) { - mVerbose = val; - } + void SetVerbose(bool val) { verbose_ = val; } + + int GetMinSdkVersion() override { return min_sdk_version_; } + + void SetMinSdkVersion(int minSdk) { min_sdk_version_ = minSdk; } -private: - StdErrDiagnostics mDiagnostics; - NameMangler mNameMangler; - std::u16string mCompilationPackage; - uint8_t mPackageId = 0x0; - SymbolTable mSymbols; - bool mVerbose = false; + private: + DISALLOW_COPY_AND_ASSIGN(LinkContext); + + StdErrDiagnostics diagnostics_; + NameMangler name_mangler_; + std::string compilation_package_; + uint8_t package_id_ = 0x0; + SymbolTable symbols_; + bool verbose_ = false; + int min_sdk_version_ = 0; }; -static bool copyFileToArchive(io::IFile* file, const std::string& outPath, - uint32_t compressionFlags, +static bool CopyFileToArchive(io::IFile* file, const std::string& out_path, + uint32_t compression_flags, IArchiveWriter* writer, IAaptContext* context) { - std::unique_ptr<io::IData> data = file->openAsData(); - if (!data) { - context->getDiagnostics()->error(DiagMessage(file->getSource()) - << "failed to open file"); - return false; - } + std::unique_ptr<io::IData> data = file->OpenAsData(); + if (!data) { + context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + << "failed to open file"); + return false; + } - const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data()); - size_t bufferSize = data->size(); + const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data()); + const size_t buffer_size = data->size(); - // If the file ends with .flat, we must strip off the CompiledFileHeader from it. - if (util::stringEndsWith<char>(file->getSource().path, ".flat")) { - CompiledFileInputStream inputStream(data->data(), data->size()); - if (!inputStream.CompiledFile()) { - context->getDiagnostics()->error(DiagMessage(file->getSource()) - << "invalid compiled file header"); - return false; - } - buffer = reinterpret_cast<const uint8_t*>(inputStream.data()); - bufferSize = inputStream.size(); - } - - if (context->verbose()) { - context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive"); - } + if (context->IsVerbose()) { + context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path + << " to archive"); + } - if (writer->startEntry(outPath, compressionFlags)) { - if (writer->writeEntry(buffer, bufferSize)) { - if (writer->finishEntry()) { - return true; - } - } + if (writer->StartEntry(out_path, compression_flags)) { + if (writer->WriteEntry(buffer, buffer_size)) { + if (writer->FinishEntry()) { + return true; + } } + } - context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath); - return false; + context->GetDiagnostics()->Error(DiagMessage() << "failed to write file " + << out_path); + return false; } -static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel, - bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) { - BigBuffer buffer(1024); - XmlFlattenerOptions options = {}; - options.keepRawValues = keepRawValues; - options.maxSdkLevel = maxSdkLevel; - XmlFlattener flattener(&buffer, options); - if (!flattener.consume(context, xmlRes)) { - return false; - } +static bool FlattenXml(xml::XmlResource* xml_res, const StringPiece& path, + Maybe<size_t> max_sdk_level, bool keep_raw_values, + IArchiveWriter* writer, IAaptContext* context) { + BigBuffer buffer(1024); + XmlFlattenerOptions options = {}; + options.keep_raw_values = keep_raw_values; + options.max_sdk_level = max_sdk_level; + XmlFlattener flattener(&buffer, options); + if (!flattener.Consume(context, xml_res)) { + return false; + } - if (context->verbose()) { - DiagMessage msg; - msg << "writing " << path << " to archive"; - if (maxSdkLevel) { - msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues; - } - context->getDiagnostics()->note(msg); + if (context->IsVerbose()) { + DiagMessage msg; + msg << "writing " << path << " to archive"; + if (max_sdk_level) { + msg << " maxSdkLevel=" << max_sdk_level.value() + << " keepRawValues=" << keep_raw_values; } + context->GetDiagnostics()->Note(msg); + } - if (writer->startEntry(path, ArchiveEntry::kCompress)) { - if (writer->writeEntry(buffer)) { - if (writer->finishEntry()) { - return true; - } - } + if (writer->StartEntry(path, ArchiveEntry::kCompress)) { + if (writer->WriteEntry(buffer)) { + if (writer->FinishEntry()) { + return true; + } } - context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive"); - return false; + } + context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << path + << " to archive"); + return false; } -/*static std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len, - IDiagnostics* diag) { - std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); - BinaryResourceParser parser(diag, table.get(), source, data, len); - if (!parser.parse()) { - return {}; - } - return table; -}*/ - -static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source, - const void* data, size_t len, +static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, + const void* data, + size_t len, IDiagnostics* diag) { - pb::ResourceTable pbTable; - if (!pbTable.ParseFromArray(data, len)) { - diag->error(DiagMessage(source) << "invalid compiled table"); - return {}; - } - - std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag); - if (!table) { - return {}; - } - return table; + pb::ResourceTable pb_table; + if (!pb_table.ParseFromArray(data, len)) { + diag->Error(DiagMessage(source) << "invalid compiled table"); + return {}; + } + + std::unique_ptr<ResourceTable> table = + DeserializeTableFromPb(pb_table, source, diag); + if (!table) { + return {}; + } + return table; } /** * Inflates an XML file from the source path. */ -static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) { - std::ifstream fin(path, std::ifstream::binary); - if (!fin) { - diag->error(DiagMessage(path) << strerror(errno)); - return {}; - } - return xml::inflate(&fin, diag, Source(path)); +static std::unique_ptr<xml::XmlResource> LoadXml(const std::string& path, + IDiagnostics* diag) { + std::ifstream fin(path, std::ifstream::binary); + if (!fin) { + diag->Error(DiagMessage(path) << strerror(errno)); + return {}; + } + return xml::Inflate(&fin, diag, Source(path)); } -static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source, - const void* data, size_t len, - IDiagnostics* diag) { - CompiledFileInputStream inputStream(data, len); - if (!inputStream.CompiledFile()) { - diag->error(DiagMessage(source) << "invalid compiled file header"); - return {}; - } - - const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data()); - const size_t xmlDataLen = inputStream.size(); +struct ResourceFileFlattenerOptions { + bool no_auto_version = false; + bool no_version_vectors = false; + bool no_xml_namespaces = false; + bool keep_raw_values = false; + bool do_not_compress_anything = false; + bool update_proguard_spec = false; + std::unordered_set<std::string> extensions_to_not_compress; +}; - std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source); - if (!xmlRes) { - return {}; - } - return xmlRes; -} +class ResourceFileFlattener { + public: + ResourceFileFlattener(const ResourceFileFlattenerOptions& options, + IAaptContext* context, proguard::KeepSet* keep_set) + : options_(options), context_(context), keep_set_(keep_set) {} -static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source, - const void* data, size_t len, - IDiagnostics* diag) { - CompiledFileInputStream inputStream(data, len); - const pb::CompiledFile* pbFile = inputStream.CompiledFile(); - if (!pbFile) { - diag->error(DiagMessage(source) << "invalid compiled file header"); - return {}; - } + bool Flatten(ResourceTable* table, IArchiveWriter* archive_writer); - std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag); - if (!resFile) { - return {}; - } - return resFile; -} + private: + struct FileOperation { + ConfigDescription config; -struct ResourceFileFlattenerOptions { - bool noAutoVersion = false; - bool noVersionVectors = false; - bool keepRawValues = false; - bool doNotCompressAnything = false; - std::vector<std::string> extensionsToNotCompress; -}; + // The entry this file came from. + const ResourceEntry* entry; -class ResourceFileFlattener { -public: - ResourceFileFlattener(const ResourceFileFlattenerOptions& options, - IAaptContext* context, proguard::KeepSet* keepSet) : - mOptions(options), mContext(context), mKeepSet(keepSet) { - } + // The file to copy as-is. + io::IFile* file_to_copy; - bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter); + // The XML to process and flatten. + std::unique_ptr<xml::XmlResource> xml_to_flatten; -private: - struct FileOperation { - io::IFile* fileToCopy; - std::unique_ptr<xml::XmlResource> xmlToFlatten; - std::string dstPath; - bool skipVersion = false; - }; + // The destination to write this file to. + std::string dst_path; + bool skip_version = false; + }; - uint32_t getCompressionFlags(const StringPiece& str); + uint32_t GetCompressionFlags(const StringPiece& str); - bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc, - io::IFile* file, ResourceTable* table, FileOperation* outFileOp); + bool LinkAndVersionXmlFile(ResourceTable* table, FileOperation* file_op, + std::queue<FileOperation>* out_file_op_queue); - ResourceFileFlattenerOptions mOptions; - IAaptContext* mContext; - proguard::KeepSet* mKeepSet; + ResourceFileFlattenerOptions options_; + IAaptContext* context_; + proguard::KeepSet* keep_set_; }; -uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) { - if (mOptions.doNotCompressAnything) { - return 0; - } +uint32_t ResourceFileFlattener::GetCompressionFlags(const StringPiece& str) { + if (options_.do_not_compress_anything) { + return 0; + } - for (const std::string& extension : mOptions.extensionsToNotCompress) { - if (util::stringEndsWith<char>(str, extension)) { - return 0; - } + for (const std::string& extension : options_.extensions_to_not_compress) { + if (util::EndsWith(str, extension)) { + return 0; } - return ArchiveEntry::kCompress; + } + return ArchiveEntry::kCompress; } -bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry, - const ResourceFile& fileDesc, - io::IFile* file, - ResourceTable* table, - FileOperation* outFileOp) { - const StringPiece srcPath = file->getSource().path; - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath); - } +bool ResourceFileFlattener::LinkAndVersionXmlFile( + ResourceTable* table, FileOperation* file_op, + std::queue<FileOperation>* out_file_op_queue) { + xml::XmlResource* doc = file_op->xml_to_flatten.get(); + const Source& src = doc->file.source; - std::unique_ptr<io::IData> data = file->openAsData(); - if (!data) { - mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); - return false; - } + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note(DiagMessage() << "linking " << src.path); + } - if (util::stringEndsWith<char>(srcPath, ".flat")) { - outFileOp->xmlToFlatten = loadBinaryXmlSkipFileExport(file->getSource(), - data->data(), data->size(), - mContext->getDiagnostics()); - } else { - outFileOp->xmlToFlatten = xml::inflate(data->data(), data->size(), - mContext->getDiagnostics(), - file->getSource()); + XmlReferenceLinker xml_linker; + if (!xml_linker.Consume(context_, doc)) { + return false; + } + + if (options_.update_proguard_spec && + !proguard::CollectProguardRules(src, doc, keep_set_)) { + return false; + } + + if (options_.no_xml_namespaces) { + XmlNamespaceRemover namespace_remover; + if (!namespace_remover.Consume(context_, doc)) { + return false; } + } - if (!outFileOp->xmlToFlatten) { - return false; + if (!options_.no_auto_version) { + if (options_.no_version_vectors) { + // Skip this if it is a vector or animated-vector. + xml::Element* el = xml::FindRootElement(doc); + if (el && el->namespace_uri.empty()) { + if (el->name == "vector" || el->name == "animated-vector") { + // We are NOT going to version this file. + file_op->skip_version = true; + return true; + } + } } - // Copy the the file description header. - outFileOp->xmlToFlatten->file = fileDesc; + const ConfigDescription& config = file_op->config; - XmlReferenceLinker xmlLinker; - if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) { - return false; - } + // Find the first SDK level used that is higher than this defined config and + // not superseded by a lower or equal SDK level resource. + const int min_sdk_version = context_->GetMinSdkVersion(); + for (int sdk_level : xml_linker.sdk_levels()) { + if (sdk_level > min_sdk_version && sdk_level > config.sdkVersion) { + if (!ShouldGenerateVersionedResource(file_op->entry, config, + sdk_level)) { + // If we shouldn't generate a versioned resource, stop checking. + break; + } - if (!proguard::collectProguardRules(outFileOp->xmlToFlatten->file.source, - outFileOp->xmlToFlatten.get(), mKeepSet)) { - return false; - } + ResourceFile versioned_file_desc = doc->file; + versioned_file_desc.config.sdkVersion = (uint16_t)sdk_level; - if (!mOptions.noAutoVersion) { - if (mOptions.noVersionVectors) { - // Skip this if it is a vector or animated-vector. - xml::Element* el = xml::findRootElement(outFileOp->xmlToFlatten.get()); - if (el && el->namespaceUri.empty()) { - if (el->name == u"vector" || el->name == u"animated-vector") { - // We are NOT going to version this file. - outFileOp->skipVersion = true; - return true; - } - } + FileOperation new_file_op; + new_file_op.xml_to_flatten = util::make_unique<xml::XmlResource>( + versioned_file_desc, doc->root->Clone()); + new_file_op.config = versioned_file_desc.config; + new_file_op.entry = file_op->entry; + new_file_op.dst_path = ResourceUtils::BuildResourceFileName( + versioned_file_desc, context_->GetNameMangler()); + + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note( + DiagMessage(versioned_file_desc.source) + << "auto-versioning resource from config '" << config << "' -> '" + << versioned_file_desc.config << "'"); } - // Find the first SDK level used that is higher than this defined config and - // not superseded by a lower or equal SDK level resource. - for (int sdkLevel : xmlLinker.getSdkLevels()) { - if (sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) { - if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config, - sdkLevel)) { - // If we shouldn't generate a versioned resource, stop checking. - break; - } - - ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file; - versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel; - - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source) - << "auto-versioning resource from config '" - << outFileOp->xmlToFlatten->file.config - << "' -> '" - << versionedFileDesc.config << "'"); - } - - std::u16string genPath = util::utf8ToUtf16(ResourceUtils::buildResourceFileName( - versionedFileDesc, mContext->getNameMangler())); - - bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name, - versionedFileDesc.config, - versionedFileDesc.source, - genPath, - file, - mContext->getDiagnostics()); - if (!added) { - return false; - } - break; - } + bool added = table->AddFileReferenceAllowMangled( + versioned_file_desc.name, versioned_file_desc.config, + versioned_file_desc.source, new_file_op.dst_path, nullptr, + context_->GetDiagnostics()); + if (!added) { + return false; } + + out_file_op_queue->push(std::move(new_file_op)); + break; + } } - return true; + } + return true; } /** - * Do not insert or remove any resources while executing in this function. It will + * Do not insert or remove any resources while executing in this function. It + * will * corrupt the iteration order. */ -bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) { - bool error = false; - std::map<std::pair<ConfigDescription, StringPiece16>, FileOperation> configSortedFiles; - - for (auto& pkg : table->packages) { - for (auto& type : pkg->types) { - // Sort by config and name, so that we get better locality in the zip file. - configSortedFiles.clear(); - for (auto& entry : type->entries) { - // Iterate via indices because auto generated values can be inserted ahead of - // the value being processed. - for (size_t i = 0; i < entry->values.size(); i++) { - ResourceConfigValue* configValue = entry->values[i].get(); - - FileReference* fileRef = valueCast<FileReference>(configValue->value.get()); - if (!fileRef) { - continue; - } - - io::IFile* file = fileRef->file; - if (!file) { - mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource()) - << "file not found"); - return false; - } - - FileOperation fileOp; - fileOp.dstPath = util::utf16ToUtf8(*fileRef->path); - - const StringPiece srcPath = file->getSource().path; - if (type->type != ResourceType::kRaw && - (util::stringEndsWith<char>(srcPath, ".xml.flat") || - util::stringEndsWith<char>(srcPath, ".xml"))) { - ResourceFile fileDesc; - fileDesc.config = configValue->config; - fileDesc.name = ResourceName(pkg->name, type->type, entry->name); - fileDesc.source = fileRef->getSource(); - if (!linkAndVersionXmlFile(entry.get(), fileDesc, file, table, &fileOp)) { - error = true; - continue; - } - - } else { - fileOp.fileToCopy = file; - } - - // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else - // we end up copying the string in the std::make_pair() method, then creating - // a StringPiece16 from the copy, which would cause us to end up referencing - // garbage in the map. - const StringPiece16 entryName(entry->name); - configSortedFiles[std::make_pair(configValue->config, entryName)] = - std::move(fileOp); - } +bool ResourceFileFlattener::Flatten(ResourceTable* table, + IArchiveWriter* archive_writer) { + bool error = false; + std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> + config_sorted_files; + + for (auto& pkg : table->packages) { + for (auto& type : pkg->types) { + // Sort by config and name, so that we get better locality in the zip + // file. + config_sorted_files.clear(); + std::queue<FileOperation> file_operations; + + // Populate the queue with all files in the ResourceTable. + for (auto& entry : type->entries) { + for (auto& config_value : entry->values) { + FileReference* file_ref = + ValueCast<FileReference>(config_value->value.get()); + if (!file_ref) { + continue; + } + + io::IFile* file = file_ref->file; + if (!file) { + context_->GetDiagnostics()->Error(DiagMessage(file_ref->GetSource()) + << "file not found"); + return false; + } + + FileOperation file_op; + file_op.entry = entry.get(); + file_op.dst_path = *file_ref->path; + file_op.config = config_value->config; + + const StringPiece src_path = file->GetSource().path; + if (type->type != ResourceType::kRaw && + (util::EndsWith(src_path, ".xml.flat") || + util::EndsWith(src_path, ".xml"))) { + std::unique_ptr<io::IData> data = file->OpenAsData(); + if (!data) { + context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + << "failed to open file"); + return false; } - if (error) { - return false; - } + file_op.xml_to_flatten = + xml::Inflate(data->data(), data->size(), + context_->GetDiagnostics(), file->GetSource()); - // Now flatten the sorted values. - for (auto& mapEntry : configSortedFiles) { - const ConfigDescription& config = mapEntry.first.first; - const FileOperation& fileOp = mapEntry.second; - - if (fileOp.xmlToFlatten) { - Maybe<size_t> maxSdkLevel; - if (!mOptions.noAutoVersion && !fileOp.skipVersion) { - maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u); - } - - bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel, - mOptions.keepRawValues, - archiveWriter, mContext); - if (!result) { - error = true; - } - } else { - bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath, - getCompressionFlags(fileOp.dstPath), - archiveWriter, mContext); - if (!result) { - error = true; - } - } + if (!file_op.xml_to_flatten) { + return false; } + + file_op.xml_to_flatten->file.config = config_value->config; + file_op.xml_to_flatten->file.source = file_ref->GetSource(); + file_op.xml_to_flatten->file.name = + ResourceName(pkg->name, type->type, entry->name); + + // Enqueue the XML files to be processed. + file_operations.push(std::move(file_op)); + } else { + file_op.file_to_copy = file; + + // NOTE(adamlesinski): Explicitly construct a StringPiece here, or + // else we end up copying the string in the std::make_pair() method, + // then creating a StringPiece from the copy, which would cause us + // to end up referencing garbage in the map. + const StringPiece entry_name(entry->name); + config_sorted_files[std::make_pair( + config_value->config, entry_name)] = std::move(file_op); + } + } + } + + // Now process the XML queue + for (; !file_operations.empty(); file_operations.pop()) { + FileOperation& file_op = file_operations.front(); + + if (!LinkAndVersionXmlFile(table, &file_op, &file_operations)) { + error = true; + continue; + } + + // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else + // we end up copying the string in the std::make_pair() method, then + // creating a StringPiece from the copy, which would cause us to end up + // referencing garbage in the map. + const StringPiece entry_name(file_op.entry->name); + config_sorted_files[std::make_pair(file_op.config, entry_name)] = + std::move(file_op); + } + + if (error) { + return false; + } + + // Now flatten the sorted values. + for (auto& map_entry : config_sorted_files) { + const ConfigDescription& config = map_entry.first.first; + const FileOperation& file_op = map_entry.second; + + if (file_op.xml_to_flatten) { + Maybe<size_t> max_sdk_level; + if (!options_.no_auto_version && !file_op.skip_version) { + max_sdk_level = + std::max<size_t>(std::max<size_t>(config.sdkVersion, 1u), + context_->GetMinSdkVersion()); + } + + bool result = FlattenXml( + file_op.xml_to_flatten.get(), file_op.dst_path, max_sdk_level, + options_.keep_raw_values, archive_writer, context_); + if (!result) { + error = true; + } + } else { + bool result = CopyFileToArchive( + file_op.file_to_copy, file_op.dst_path, + GetCompressionFlags(file_op.dst_path), archive_writer, context_); + if (!result) { + error = true; + } } + } } - return !error; + } + return !error; } -class LinkCommand { -public: - LinkCommand(LinkContext* context, const LinkOptions& options) : - mOptions(options), mContext(context), mFinalTable(), - mFileCollection(util::make_unique<io::FileCollection>()) { - } - - /** - * Creates a SymbolTable that loads symbols from the various APKs and caches the - * results for faster lookup. - */ - bool loadSymbolsFromIncludePaths() { - std::unique_ptr<AssetManagerSymbolSource> assetSource = - util::make_unique<AssetManagerSymbolSource>(); - for (const std::string& path : mOptions.includePaths) { - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path"); - } +static bool WriteStableIdMapToPath( + IDiagnostics* diag, + const std::unordered_map<ResourceName, ResourceId>& id_map, + const std::string& id_map_path) { + std::ofstream fout(id_map_path, std::ofstream::binary); + if (!fout) { + diag->Error(DiagMessage(id_map_path) << strerror(errno)); + return false; + } + + for (const auto& entry : id_map) { + const ResourceName& name = entry.first; + const ResourceId& id = entry.second; + fout << name << " = " << id << "\n"; + } + + if (!fout) { + diag->Error(DiagMessage(id_map_path) + << "failed writing to file: " + << android::base::SystemErrorCodeToString(errno)); + return false; + } - // First try to load the file as a static lib. - std::string errorStr; - std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr); - if (staticInclude) { - if (!mOptions.staticLib) { - // Can't include static libraries when not building a static library. - mContext->getDiagnostics()->error( - DiagMessage(path) << "can't include static library when building app"); - return false; - } - - // If we are using --no-static-lib-packages, we need to rename the package of this - // table to our compilation package. - if (mOptions.noStaticLibPackages) { - if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) { - pkg->name = mContext->getCompilationPackage(); - } - } - - mContext->getExternalSymbols()->appendSource( - util::make_unique<ResourceTableSymbolSource>(staticInclude.get())); - - mStaticTableIncludes.push_back(std::move(staticInclude)); - - } else if (!errorStr.empty()) { - // We had an error with reading, so fail. - mContext->getDiagnostics()->error(DiagMessage(path) << errorStr); - return false; - } + return true; +} - if (!assetSource->addAssetPath(path)) { - mContext->getDiagnostics()->error( - DiagMessage(path) << "failed to load include path"); - return false; - } - } +static bool LoadStableIdMap( + IDiagnostics* diag, const std::string& path, + std::unordered_map<ResourceName, ResourceId>* out_id_map) { + std::string content; + if (!android::base::ReadFileToString(path, &content)) { + diag->Error(DiagMessage(path) << "failed reading stable ID file"); + return false; + } - mContext->getExternalSymbols()->appendSource(std::move(assetSource)); - return true; + out_id_map->clear(); + size_t line_no = 0; + for (StringPiece line : util::Tokenize(content, '\n')) { + line_no++; + line = util::TrimWhitespace(line); + if (line.empty()) { + continue; } - Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) { - // Make sure the first element is <manifest> with package attribute. - if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) { - if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") { - if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) { - return AppInfo{ packageAttr->value }; - } - } - } - return {}; + auto iter = std::find(line.begin(), line.end(), '='); + if (iter == line.end()) { + diag->Error(DiagMessage(Source(path, line_no)) << "missing '='"); + return false; } - /** - * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked. - * Postcondition: ResourceTable has only one package left. All others are stripped, or there - * is an error and false is returned. - */ - bool verifyNoExternalPackages() { - auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool { - return mContext->getCompilationPackage() != pkg->name || - !pkg->id || - pkg->id.value() != mContext->getPackageId(); - }; - - bool error = false; - for (const auto& package : mFinalTable.packages) { - if (isExtPackageFunc(package)) { - // We have a package that is not related to the one we're building! - for (const auto& type : package->types) { - for (const auto& entry : type->entries) { - ResourceNameRef resName(package->name, type->type, entry->name); - - for (const auto& configValue : entry->values) { - // Special case the occurrence of an ID that is being generated for the - // 'android' package. This is due to legacy reasons. - if (valueCast<Id>(configValue->value.get()) && - package->name == u"android") { - mContext->getDiagnostics()->warn( - DiagMessage(configValue->value->getSource()) - << "generated id '" << resName - << "' for external package '" << package->name - << "'"); - } else { - mContext->getDiagnostics()->error( - DiagMessage(configValue->value->getSource()) - << "defined resource '" << resName - << "' for external package '" << package->name - << "'"); - error = true; - } - } - } - } - } - } - - auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(), - isExtPackageFunc); - mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end()); - return !error; - } - - /** - * Returns true if no IDs have been set, false otherwise. - */ - bool verifyNoIdsSet() { - for (const auto& package : mFinalTable.packages) { - for (const auto& type : package->types) { - if (type->id) { - mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type - << " has ID " << std::hex - << (int) type->id.value() - << std::dec << " assigned"); - return false; - } - - for (const auto& entry : type->entries) { - if (entry->id) { - ResourceNameRef resName(package->name, type->type, entry->name); - mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName - << " has ID " << std::hex - << (int) entry->id.value() - << std::dec << " assigned"); - return false; - } - } - } - } - return true; + ResourceNameRef name; + StringPiece res_name_str = + util::TrimWhitespace(line.substr(0, std::distance(line.begin(), iter))); + if (!ResourceUtils::ParseResourceName(res_name_str, &name)) { + diag->Error(DiagMessage(Source(path, line_no)) + << "invalid resource name '" << res_name_str << "'"); + return false; } - std::unique_ptr<IArchiveWriter> makeArchiveWriter() { - if (mOptions.outputToDirectory) { - return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); - } else { - return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); - } + const size_t res_id_start_idx = std::distance(line.begin(), iter) + 1; + const size_t res_id_str_len = line.size() - res_id_start_idx; + StringPiece res_id_str = + util::TrimWhitespace(line.substr(res_id_start_idx, res_id_str_len)); + + Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(res_id_str); + if (!maybe_id) { + diag->Error(DiagMessage(Source(path, line_no)) << "invalid resource ID '" + << res_id_str << "'"); + return false; } - bool flattenTable(ResourceTable* table, IArchiveWriter* writer) { - BigBuffer buffer(1024); - TableFlattener flattener(&buffer); - if (!flattener.consume(mContext, table)) { - return false; - } + (*out_id_map)[name.ToResourceName()] = maybe_id.value(); + } + return true; +} - if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) { - if (writer->writeEntry(buffer)) { - if (writer->finishEntry()) { - return true; - } - } - } +static bool ParseSplitParameter(const StringPiece& arg, IDiagnostics* diag, + std::string* out_path, + SplitConstraints* out_split) { + std::vector<std::string> parts = util::Split(arg, ':'); + if (parts.size() != 2) { + diag->Error(DiagMessage() << "invalid split parameter '" << arg << "'"); + diag->Note( + DiagMessage() + << "should be --split path/to/output.apk:<config>[,<config>...]"); + return false; + } + *out_path = parts[0]; + std::vector<ConfigDescription> configs; + for (const StringPiece& config_str : util::Tokenize(parts[1], ',')) { + configs.push_back({}); + if (!ConfigDescription::Parse(config_str, &configs.back())) { + diag->Error(DiagMessage() << "invalid config '" << config_str + << "' in split parameter '" << arg << "'"); + return false; + } + } + out_split->configs.insert(configs.begin(), configs.end()); + return true; +} - mContext->getDiagnostics()->error( - DiagMessage() << "failed to write resources.arsc to archive"); +class LinkCommand { + public: + LinkCommand(LinkContext* context, const LinkOptions& options) + : options_(options), + context_(context), + final_table_(), + file_collection_(util::make_unique<io::FileCollection>()) {} + + /** + * Creates a SymbolTable that loads symbols from the various APKs and caches + * the results for faster lookup. + */ + bool LoadSymbolsFromIncludePaths() { + std::unique_ptr<AssetManagerSymbolSource> asset_source = + util::make_unique<AssetManagerSymbolSource>(); + for (const std::string& path : options_.include_paths) { + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note(DiagMessage(path) + << "loading include path"); + } + + // First try to load the file as a static lib. + std::string error_str; + std::unique_ptr<ResourceTable> static_include = + LoadStaticLibrary(path, &error_str); + if (static_include) { + if (!options_.static_lib) { + // Can't include static libraries when not building a static library. + context_->GetDiagnostics()->Error( + DiagMessage(path) + << "can't include static library when building app"); + return false; + } + + // If we are using --no-static-lib-packages, we need to rename the + // package of this + // table to our compilation package. + if (options_.no_static_lib_packages) { + if (ResourceTablePackage* pkg = + static_include->FindPackageById(0x7f)) { + pkg->name = context_->GetCompilationPackage(); + } + } + + context_->GetExternalSymbols()->AppendSource( + util::make_unique<ResourceTableSymbolSource>(static_include.get())); + + static_table_includes_.push_back(std::move(static_include)); + + } else if (!error_str.empty()) { + // We had an error with reading, so fail. + context_->GetDiagnostics()->Error(DiagMessage(path) << error_str); return false; - } + } - bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) { - // Create the file/zip entry. - if (!writer->startEntry("resources.arsc.flat", 0)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed to open"); - return false; - } + if (!asset_source->AddAssetPath(path)) { + context_->GetDiagnostics()->Error(DiagMessage(path) + << "failed to load include path"); + return false; + } + } - std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table); + context_->GetExternalSymbols()->AppendSource(std::move(asset_source)); + return true; + } + + Maybe<AppInfo> ExtractAppInfoFromManifest(xml::XmlResource* xml_res, + IDiagnostics* diag) { + // Make sure the first element is <manifest> with package attribute. + if (xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get())) { + AppInfo app_info; + + if (!manifest_el->namespace_uri.empty() || + manifest_el->name != "manifest") { + diag->Error(DiagMessage(xml_res->file.source) + << "root tag must be <manifest>"); + return {}; + } - // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream - // interface. - { - google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); + xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package"); + if (!package_attr) { + diag->Error(DiagMessage(xml_res->file.source) + << "<manifest> must have a 'package' attribute"); + return {}; + } + + app_info.package = package_attr->value; + + if (xml::Attribute* version_code_attr = + manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) { + Maybe<uint32_t> maybe_code = + ResourceUtils::ParseInt(version_code_attr->value); + if (!maybe_code) { + diag->Error(DiagMessage(xml_res->file.source.WithLine( + manifest_el->line_number)) + << "invalid android:versionCode '" + << version_code_attr->value << "'"); + return {}; + } + app_info.version_code = maybe_code.value(); + } + + if (xml::Attribute* revision_code_attr = + manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) { + Maybe<uint32_t> maybe_code = + ResourceUtils::ParseInt(revision_code_attr->value); + if (!maybe_code) { + diag->Error(DiagMessage(xml_res->file.source.WithLine( + manifest_el->line_number)) + << "invalid android:revisionCode '" + << revision_code_attr->value << "'"); + return {}; + } + app_info.revision_code = maybe_code.value(); + } + + if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) { + if (xml::Attribute* min_sdk = uses_sdk_el->FindAttribute( + xml::kSchemaAndroid, "minSdkVersion")) { + app_info.min_sdk_version = min_sdk->value; + } + } + return app_info; + } + return {}; + } + + /** + * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it + * linked. + * Postcondition: ResourceTable has only one package left. All others are + * stripped, or there is an error and false is returned. + */ + bool VerifyNoExternalPackages() { + auto is_ext_package_func = + [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool { + return context_->GetCompilationPackage() != pkg->name || !pkg->id || + pkg->id.value() != context_->GetPackageId(); + }; - if (!pbTable->SerializeToZeroCopyStream(&adaptor)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed to write"); - return false; + bool error = false; + for (const auto& package : final_table_.packages) { + if (is_ext_package_func(package)) { + // We have a package that is not related to the one we're building! + for (const auto& type : package->types) { + for (const auto& entry : type->entries) { + ResourceNameRef res_name(package->name, type->type, entry->name); + + for (const auto& config_value : entry->values) { + // Special case the occurrence of an ID that is being generated + // for the 'android' package. This is due to legacy reasons. + if (ValueCast<Id>(config_value->value.get()) && + package->name == "android") { + context_->GetDiagnostics()->Warn( + DiagMessage(config_value->value->GetSource()) + << "generated id '" << res_name + << "' for external package '" << package->name << "'"); + } else { + context_->GetDiagnostics()->Error( + DiagMessage(config_value->value->GetSource()) + << "defined resource '" << res_name + << "' for external package '" << package->name << "'"); + error = true; + } } + } } + } + } - if (!writer->finishEntry()) { - mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry"); + auto new_end_iter = + std::remove_if(final_table_.packages.begin(), + final_table_.packages.end(), is_ext_package_func); + final_table_.packages.erase(new_end_iter, final_table_.packages.end()); + return !error; + } + + /** + * Returns true if no IDs have been set, false otherwise. + */ + bool VerifyNoIdsSet() { + for (const auto& package : final_table_.packages) { + for (const auto& type : package->types) { + if (type->id) { + context_->GetDiagnostics()->Error( + DiagMessage() << "type " << type->type << " has ID " << std::hex + << (int)type->id.value() << std::dec + << " assigned"); + return false; + } + + for (const auto& entry : type->entries) { + if (entry->id) { + ResourceNameRef res_name(package->name, type->type, entry->name); + context_->GetDiagnostics()->Error( + DiagMessage() << "entry " << res_name << " has ID " << std::hex + << (int)entry->id.value() << std::dec + << " assigned"); return false; + } } - return true; + } } + return true; + } - bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate, - const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) { - if (!mOptions.generateJavaClassPath) { - return true; - } + std::unique_ptr<IArchiveWriter> MakeArchiveWriter(const StringPiece& out) { + if (options_.output_to_directory) { + return CreateDirectoryArchiveWriter(context_->GetDiagnostics(), out); + } else { + return CreateZipFileArchiveWriter(context_->GetDiagnostics(), out); + } + } - std::string outPath = mOptions.generateJavaClassPath.value(); - file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage))); - if (!file::mkdirs(outPath)) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed to create directory '" << outPath << "'"); - return false; + bool FlattenTable(ResourceTable* table, IArchiveWriter* writer) { + BigBuffer buffer(1024); + TableFlattener flattener(&buffer); + if (!flattener.Consume(context_, table)) { + return false; + } + + if (writer->StartEntry("resources.arsc", ArchiveEntry::kAlign)) { + if (writer->WriteEntry(buffer)) { + if (writer->FinishEntry()) { + return true; } + } + } - file::appendPath(&outPath, "R.java"); + context_->GetDiagnostics()->Error( + DiagMessage() << "failed to write resources.arsc to archive"); + return false; + } - std::ofstream fout(outPath, std::ofstream::binary); - if (!fout) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); - return false; - } + bool FlattenTableToPb(ResourceTable* table, IArchiveWriter* writer) { + // Create the file/zip entry. + if (!writer->StartEntry("resources.arsc.flat", 0)) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed to open"); + return false; + } - JavaClassGenerator generator(mContext, table, javaOptions); - if (!generator.generate(packageNameToGenerate, outPackage, &fout)) { - mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError()); - return false; - } + // Make sure CopyingOutputStreamAdaptor is deleted before we call + // writer->FinishEntry(). + { + // Wrap our IArchiveWriter with an adaptor that implements the + // ZeroCopyOutputStream interface. + CopyingOutputStreamAdaptor adaptor(writer); - if (!fout) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); - } - return true; + std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table); + if (!pb_table->SerializeToZeroCopyStream(&adaptor)) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed to write"); + return false; + } } - bool writeManifestJavaFile(xml::XmlResource* manifestXml) { - if (!mOptions.generateJavaClassPath) { - return true; - } + if (!writer->FinishEntry()) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed to finish entry"); + return false; + } + return true; + } - std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass( - mContext->getDiagnostics(), manifestXml); + bool WriteJavaFile(ResourceTable* table, + const StringPiece& package_name_to_generate, + const StringPiece& out_package, + const JavaClassGeneratorOptions& java_options) { + if (!options_.generate_java_class_path) { + return true; + } - if (!manifestClass) { - // Something bad happened, but we already logged it, so exit. - return false; - } + std::string out_path = options_.generate_java_class_path.value(); + file::AppendPath(&out_path, file::PackageToPath(out_package)); + if (!file::mkdirs(out_path)) { + context_->GetDiagnostics()->Error( + DiagMessage() << "failed to create directory '" << out_path << "'"); + return false; + } - if (manifestClass->empty()) { - // Empty Manifest class, no need to generate it. - return true; - } + file::AppendPath(&out_path, "R.java"); - // Add any JavaDoc annotations to the generated class. - for (const std::string& annotation : mOptions.javadocAnnotations) { - std::string properAnnotation = "@"; - properAnnotation += annotation; - manifestClass->getCommentBuilder()->appendComment(properAnnotation); - } + std::ofstream fout(out_path, std::ofstream::binary); + if (!fout) { + context_->GetDiagnostics()->Error( + DiagMessage() << "failed writing to '" << out_path << "': " + << android::base::SystemErrorCodeToString(errno)); + return false; + } - const std::string packageUtf8 = util::utf16ToUtf8(mContext->getCompilationPackage()); + JavaClassGenerator generator(context_, table, java_options); + if (!generator.Generate(package_name_to_generate, out_package, &fout)) { + context_->GetDiagnostics()->Error(DiagMessage(out_path) + << generator.getError()); + return false; + } - std::string outPath = mOptions.generateJavaClassPath.value(); - file::appendPath(&outPath, file::packageToPath(packageUtf8)); + if (!fout) { + context_->GetDiagnostics()->Error( + DiagMessage() << "failed writing to '" << out_path << "': " + << android::base::SystemErrorCodeToString(errno)); + } + return true; + } - if (!file::mkdirs(outPath)) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed to create directory '" << outPath << "'"); - return false; - } + bool WriteManifestJavaFile(xml::XmlResource* manifest_xml) { + if (!options_.generate_java_class_path) { + return true; + } - file::appendPath(&outPath, "Manifest.java"); + std::unique_ptr<ClassDefinition> manifest_class = + GenerateManifestClass(context_->GetDiagnostics(), manifest_xml); - std::ofstream fout(outPath, std::ofstream::binary); - if (!fout) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); - return false; - } + if (!manifest_class) { + // Something bad happened, but we already logged it, so exit. + return false; + } - if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); - return false; - } - return true; + if (manifest_class->empty()) { + // Empty Manifest class, no need to generate it. + return true; } - bool writeProguardFile(const proguard::KeepSet& keepSet) { - if (!mOptions.generateProguardRulesPath) { - return true; - } + // Add any JavaDoc annotations to the generated class. + for (const std::string& annotation : options_.javadoc_annotations) { + std::string proper_annotation = "@"; + proper_annotation += annotation; + manifest_class->GetCommentBuilder()->AppendComment(proper_annotation); + } - const std::string& outPath = mOptions.generateProguardRulesPath.value(); - std::ofstream fout(outPath, std::ofstream::binary); - if (!fout) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno)); - return false; - } + const std::string& package_utf8 = context_->GetCompilationPackage(); - proguard::writeKeepSet(&fout, keepSet); - if (!fout) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); - return false; - } - return true; + std::string out_path = options_.generate_java_class_path.value(); + file::AppendPath(&out_path, file::PackageToPath(package_utf8)); + + if (!file::mkdirs(out_path)) { + context_->GetDiagnostics()->Error( + DiagMessage() << "failed to create directory '" << out_path << "'"); + return false; } - std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input, - std::string* outError) { - std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create( - input, outError); - if (!collection) { - return {}; - } - return loadTablePbFromCollection(collection.get()); + file::AppendPath(&out_path, "Manifest.java"); + + std::ofstream fout(out_path, std::ofstream::binary); + if (!fout) { + context_->GetDiagnostics()->Error( + DiagMessage() << "failed writing to '" << out_path << "': " + << android::base::SystemErrorCodeToString(errno)); + return false; } - std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) { - io::IFile* file = collection->findFile("resources.arsc.flat"); - if (!file) { - return {}; - } + if (!ClassDefinition::WriteJavaFile(manifest_class.get(), package_utf8, + true, &fout)) { + context_->GetDiagnostics()->Error( + DiagMessage() << "failed writing to '" << out_path << "': " + << android::base::SystemErrorCodeToString(errno)); + return false; + } + return true; + } - std::unique_ptr<io::IData> data = file->openAsData(); - return loadTableFromPb(file->getSource(), data->data(), data->size(), - mContext->getDiagnostics()); + bool WriteProguardFile(const Maybe<std::string>& out, + const proguard::KeepSet& keep_set) { + if (!out) { + return true; } - bool mergeStaticLibrary(const std::string& input, bool override) { - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input); - } + const std::string& out_path = out.value(); + std::ofstream fout(out_path, std::ofstream::binary); + if (!fout) { + context_->GetDiagnostics()->Error( + DiagMessage() << "failed to open '" << out_path << "': " + << android::base::SystemErrorCodeToString(errno)); + return false; + } - std::string errorStr; - std::unique_ptr<io::ZipFileCollection> collection = - io::ZipFileCollection::create(input, &errorStr); - if (!collection) { - mContext->getDiagnostics()->error(DiagMessage(input) << errorStr); - return false; - } + proguard::WriteKeepSet(&fout, keep_set); + if (!fout) { + context_->GetDiagnostics()->Error( + DiagMessage() << "failed writing to '" << out_path << "': " + << android::base::SystemErrorCodeToString(errno)); + return false; + } + return true; + } - std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get()); - if (!table) { - mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library"); - return false; - } + std::unique_ptr<ResourceTable> LoadStaticLibrary(const std::string& input, + std::string* out_error) { + std::unique_ptr<io::ZipFileCollection> collection = + io::ZipFileCollection::Create(input, out_error); + if (!collection) { + return {}; + } + return LoadTablePbFromCollection(collection.get()); + } - ResourceTablePackage* pkg = table->findPackageById(0x7f); - if (!pkg) { - mContext->getDiagnostics()->error(DiagMessage(input) - << "static library has no package"); - return false; - } + std::unique_ptr<ResourceTable> LoadTablePbFromCollection( + io::IFileCollection* collection) { + io::IFile* file = collection->FindFile("resources.arsc.flat"); + if (!file) { + return {}; + } - bool result; - if (mOptions.noStaticLibPackages) { - // Merge all resources as if they were in the compilation package. This is the old - // behaviour of aapt. + std::unique_ptr<io::IData> data = file->OpenAsData(); + return LoadTableFromPb(file->GetSource(), data->data(), data->size(), + context_->GetDiagnostics()); + } - // Add the package to the set of --extra-packages so we emit an R.java for each - // library package. - if (!pkg->name.empty()) { - mOptions.extraJavaPackages.insert(pkg->name); - } + bool MergeStaticLibrary(const std::string& input, bool override) { + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note(DiagMessage() + << "merging static library " << input); + } - pkg->name = u""; - if (override) { - result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get()); - } else { - result = mTableMerger->merge(Source(input), table.get(), collection.get()); - } + std::string error_str; + std::unique_ptr<io::ZipFileCollection> collection = + io::ZipFileCollection::Create(input, &error_str); + if (!collection) { + context_->GetDiagnostics()->Error(DiagMessage(input) << error_str); + return false; + } - } else { - // This is the proper way to merge libraries, where the package name is preserved - // and resource names are mangled. - result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(), - collection.get()); - } + std::unique_ptr<ResourceTable> table = + LoadTablePbFromCollection(collection.get()); + if (!table) { + context_->GetDiagnostics()->Error(DiagMessage(input) + << "invalid static library"); + return false; + } + + ResourceTablePackage* pkg = table->FindPackageById(0x7f); + if (!pkg) { + context_->GetDiagnostics()->Error(DiagMessage(input) + << "static library has no package"); + return false; + } + + bool result; + if (options_.no_static_lib_packages) { + // Merge all resources as if they were in the compilation package. This is + // the old behavior of aapt. + + // Add the package to the set of --extra-packages so we emit an R.java for + // each library package. + if (!pkg->name.empty()) { + options_.extra_java_packages.insert(pkg->name); + } + + pkg->name = ""; + if (override) { + result = table_merger_->MergeOverlay(Source(input), table.get(), + collection.get()); + } else { + result = + table_merger_->Merge(Source(input), table.get(), collection.get()); + } - if (!result) { - return false; - } + } else { + // This is the proper way to merge libraries, where the package name is + // preserved and resource names are mangled. + result = table_merger_->MergeAndMangle(Source(input), pkg->name, + table.get(), collection.get()); + } - // Make sure to move the collection into the set of IFileCollections. - mCollections.push_back(std::move(collection)); - return true; + if (!result) { + return false; } - bool mergeResourceTable(io::IFile* file, bool override) { - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage() << "merging resource table " - << file->getSource()); - } + // Make sure to move the collection into the set of IFileCollections. + collections_.push_back(std::move(collection)); + return true; + } - std::unique_ptr<io::IData> data = file->openAsData(); - if (!data) { - mContext->getDiagnostics()->error(DiagMessage(file->getSource()) - << "failed to open file"); - return false; - } + bool MergeResourceTable(io::IFile* file, bool override) { + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note( + DiagMessage() << "merging resource table " << file->GetSource()); + } - std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(), - data->data(), data->size(), - mContext->getDiagnostics()); - if (!table) { - return false; - } + std::unique_ptr<io::IData> data = file->OpenAsData(); + if (!data) { + context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + << "failed to open file"); + return false; + } - bool result = false; - if (override) { - result = mTableMerger->mergeOverlay(file->getSource(), table.get()); - } else { - result = mTableMerger->merge(file->getSource(), table.get()); - } - return result; + std::unique_ptr<ResourceTable> table = + LoadTableFromPb(file->GetSource(), data->data(), data->size(), + context_->GetDiagnostics()); + if (!table) { + return false; } - bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) { - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file " - << file->getSource()); - } + bool result = false; + if (override) { + result = table_merger_->MergeOverlay(file->GetSource(), table.get()); + } else { + result = table_merger_->Merge(file->GetSource(), table.get()); + } + return result; + } - bool result = false; - if (override) { - result = mTableMerger->mergeFileOverlay(*fileDesc, file); - } else { - result = mTableMerger->mergeFile(*fileDesc, file); - } + bool MergeCompiledFile(io::IFile* file, ResourceFile* file_desc, + bool override) { + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note( + DiagMessage() << "merging '" << file_desc->name + << "' from compiled file " << file->GetSource()); + } - if (!result) { - return false; - } + bool result = false; + if (override) { + result = table_merger_->MergeFileOverlay(*file_desc, file); + } else { + result = table_merger_->MergeFile(*file_desc, file); + } - // Add the exports of this file to the table. - for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) { - if (exportedSymbol.name.package.empty()) { - exportedSymbol.name.package = mContext->getCompilationPackage(); - } + if (!result) { + return false; + } - ResourceNameRef resName = exportedSymbol.name; + // Add the exports of this file to the table. + for (SourcedResourceName& exported_symbol : file_desc->exported_symbols) { + if (exported_symbol.name.package.empty()) { + exported_symbol.name.package = context_->GetCompilationPackage(); + } - Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName( - exportedSymbol.name); - if (mangledName) { - resName = mangledName.value(); - } + ResourceNameRef res_name = exported_symbol.name; - std::unique_ptr<Id> id = util::make_unique<Id>(); - id->setSource(fileDesc->source.withLine(exportedSymbol.line)); - bool result = mFinalTable.addResourceAllowMangled( - resName, ConfigDescription::defaultConfig(), std::string(), std::move(id), - mContext->getDiagnostics()); - if (!result) { - return false; - } - } - return true; + Maybe<ResourceName> mangled_name = + context_->GetNameMangler()->MangleName(exported_symbol.name); + if (mangled_name) { + res_name = mangled_name.value(); + } + + std::unique_ptr<Id> id = util::make_unique<Id>(); + id->SetSource(file_desc->source.WithLine(exported_symbol.line)); + bool result = final_table_.AddResourceAllowMangled( + res_name, ConfigDescription::DefaultConfig(), std::string(), + std::move(id), context_->GetDiagnostics()); + if (!result) { + return false; + } + } + return true; + } + + /** + * Takes a path to load as a ZIP file and merges the files within into the + * master ResourceTable. + * If override is true, conflicting resources are allowed to override each + * other, in order of last seen. + * + * An io::IFileCollection is created from the ZIP file and added to the set of + * io::IFileCollections that are open. + */ + bool MergeArchive(const std::string& input, bool override) { + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note(DiagMessage() << "merging archive " + << input); + } + + std::string error_str; + std::unique_ptr<io::ZipFileCollection> collection = + io::ZipFileCollection::Create(input, &error_str); + if (!collection) { + context_->GetDiagnostics()->Error(DiagMessage(input) << error_str); + return false; } - /** - * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable. - * If override is true, conflicting resources are allowed to override each other, in order of - * last seen. - * - * An io::IFileCollection is created from the ZIP file and added to the set of - * io::IFileCollections that are open. - */ - bool mergeArchive(const std::string& input, bool override) { - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input); - } + bool error = false; + for (auto iter = collection->Iterator(); iter->HasNext();) { + if (!MergeFile(iter->Next(), override)) { + error = true; + } + } - std::string errorStr; - std::unique_ptr<io::ZipFileCollection> collection = - io::ZipFileCollection::create(input, &errorStr); - if (!collection) { - mContext->getDiagnostics()->error(DiagMessage(input) << errorStr); - return false; - } + // Make sure to move the collection into the set of IFileCollections. + collections_.push_back(std::move(collection)); + return !error; + } + + /** + * Takes a path to load and merge into the master ResourceTable. If override + * is true, + * conflicting resources are allowed to override each other, in order of last + * seen. + * + * If the file path ends with .flata, .jar, .jack, or .zip the file is treated + * as ZIP archive + * and the files within are merged individually. + * + * Otherwise the files is processed on its own. + */ + bool MergePath(const std::string& path, bool override) { + if (util::EndsWith(path, ".flata") || util::EndsWith(path, ".jar") || + util::EndsWith(path, ".jack") || util::EndsWith(path, ".zip")) { + return MergeArchive(path, override); + } else if (util::EndsWith(path, ".apk")) { + return MergeStaticLibrary(path, override); + } + + io::IFile* file = file_collection_->InsertFile(path); + return MergeFile(file, override); + } + + /** + * Takes a file to load and merge into the master ResourceTable. If override + * is true, + * conflicting resources are allowed to override each other, in order of last + * seen. + * + * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and + * merged into the + * master ResourceTable. If the file ends with .flat, then it is treated like + * a compiled file + * and the header data is read and merged into the final ResourceTable. + * + * All other file types are ignored. This is because these files could be + * coming from a zip, + * where we could have other files like classes.dex. + */ + bool MergeFile(io::IFile* file, bool override) { + const Source& src = file->GetSource(); + if (util::EndsWith(src.path, ".arsc.flat")) { + return MergeResourceTable(file, override); + + } else if (util::EndsWith(src.path, ".flat")) { + // Try opening the file and looking for an Export header. + std::unique_ptr<io::IData> data = file->OpenAsData(); + if (!data) { + context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open"); + return false; + } - bool error = false; - for (auto iter = collection->iterator(); iter->hasNext(); ) { - if (!mergeFile(iter->next(), override)) { - error = true; - } - } + CompiledFileInputStream input_stream(data->data(), data->size()); + uint32_t num_files = 0; + if (!input_stream.ReadLittleEndian32(&num_files)) { + context_->GetDiagnostics()->Error(DiagMessage(src) + << "failed read num files"); + return false; + } + + for (uint32_t i = 0; i < num_files; i++) { + pb::CompiledFile compiled_file; + if (!input_stream.ReadCompiledFile(&compiled_file)) { + context_->GetDiagnostics()->Error( + DiagMessage(src) << "failed to read compiled file header"); + return false; + } + + uint64_t offset, len; + if (!input_stream.ReadDataMetaData(&offset, &len)) { + context_->GetDiagnostics()->Error(DiagMessage(src) + << "failed to read data meta data"); + return false; + } + + std::unique_ptr<ResourceFile> resource_file = + DeserializeCompiledFileFromPb(compiled_file, file->GetSource(), + context_->GetDiagnostics()); + if (!resource_file) { + return false; + } + + if (!MergeCompiledFile(file->CreateFileSegment(offset, len), + resource_file.get(), override)) { + return false; + } + } + return true; + } else if (util::EndsWith(src.path, ".xml") || + util::EndsWith(src.path, ".png")) { + // Since AAPT compiles these file types and appends .flat to them, seeing + // their raw extensions is a sign that they weren't compiled. + const StringPiece file_type = + util::EndsWith(src.path, ".xml") ? "XML" : "PNG"; + context_->GetDiagnostics()->Error(DiagMessage(src) + << "uncompiled " << file_type + << " file passed as argument. Must be " + "compiled first into .flat file."); + return false; + } + + // Ignore non .flat files. This could be classes.dex or something else that + // happens + // to be in an archive. + return true; + } + + std::unique_ptr<xml::XmlResource> GenerateSplitManifest( + const AppInfo& app_info, const SplitConstraints& constraints) { + std::unique_ptr<xml::XmlResource> doc = + util::make_unique<xml::XmlResource>(); + + std::unique_ptr<xml::Namespace> namespace_android = + util::make_unique<xml::Namespace>(); + namespace_android->namespace_uri = xml::kSchemaAndroid; + namespace_android->namespace_prefix = "android"; + + std::unique_ptr<xml::Element> manifest_el = + util::make_unique<xml::Element>(); + manifest_el->name = "manifest"; + manifest_el->attributes.push_back( + xml::Attribute{"", "package", app_info.package}); + + if (app_info.version_code) { + manifest_el->attributes.push_back( + xml::Attribute{xml::kSchemaAndroid, "versionCode", + std::to_string(app_info.version_code.value())}); + } + + if (app_info.revision_code) { + manifest_el->attributes.push_back( + xml::Attribute{xml::kSchemaAndroid, "revisionCode", + std::to_string(app_info.revision_code.value())}); + } + + std::stringstream split_name; + split_name << "config." << util::Joiner(constraints.configs, "_"); + + manifest_el->attributes.push_back( + xml::Attribute{"", "split", split_name.str()}); + + std::unique_ptr<xml::Element> application_el = + util::make_unique<xml::Element>(); + application_el->name = "application"; + application_el->attributes.push_back( + xml::Attribute{xml::kSchemaAndroid, "hasCode", "false"}); + + manifest_el->AppendChild(std::move(application_el)); + namespace_android->AppendChild(std::move(manifest_el)); + doc->root = std::move(namespace_android); + return doc; + } + + /** + * Writes the AndroidManifest, ResourceTable, and all XML files referenced by + * the ResourceTable to the IArchiveWriter. + */ + bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, + xml::XmlResource* manifest, ResourceTable* table) { + const bool keep_raw_values = options_.static_lib; + bool result = FlattenXml(manifest, "AndroidManifest.xml", {}, + keep_raw_values, writer, context_); + if (!result) { + return false; + } + + ResourceFileFlattenerOptions file_flattener_options; + file_flattener_options.keep_raw_values = keep_raw_values; + file_flattener_options.do_not_compress_anything = + options_.do_not_compress_anything; + file_flattener_options.extensions_to_not_compress = + options_.extensions_to_not_compress; + file_flattener_options.no_auto_version = options_.no_auto_version; + file_flattener_options.no_version_vectors = options_.no_version_vectors; + file_flattener_options.no_xml_namespaces = options_.no_xml_namespaces; + file_flattener_options.update_proguard_spec = + static_cast<bool>(options_.generate_proguard_rules_path); + + ResourceFileFlattener file_flattener(file_flattener_options, context_, + keep_set); + + if (!file_flattener.Flatten(table, writer)) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed linking file resources"); + return false; + } + + if (options_.static_lib) { + if (!FlattenTableToPb(table, writer)) { + context_->GetDiagnostics()->Error( + DiagMessage() << "failed to write resources.arsc.flat"); + return false; + } + } else { + if (!FlattenTable(table, writer)) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed to write resources.arsc"); + return false; + } + } + return true; + } - // Make sure to move the collection into the set of IFileCollections. - mCollections.push_back(std::move(collection)); - return !error; - } - - /** - * Takes a path to load and merge into the master ResourceTable. If override is true, - * conflicting resources are allowed to override each other, in order of last seen. - * - * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive - * and the files within are merged individually. - * - * Otherwise the files is processed on its own. - */ - bool mergePath(const std::string& path, bool override) { - if (util::stringEndsWith<char>(path, ".flata") || - util::stringEndsWith<char>(path, ".jar") || - util::stringEndsWith<char>(path, ".jack") || - util::stringEndsWith<char>(path, ".zip")) { - return mergeArchive(path, override); - } else if (util::stringEndsWith<char>(path, ".apk")) { - return mergeStaticLibrary(path, override); - } + int Run(const std::vector<std::string>& input_files) { + // Load the AndroidManifest.xml + std::unique_ptr<xml::XmlResource> manifest_xml = + LoadXml(options_.manifest_path, context_->GetDiagnostics()); + if (!manifest_xml) { + return 1; + } - io::IFile* file = mFileCollection->insertFile(path); - return mergeFile(file, override); - } - - /** - * Takes a file to load and merge into the master ResourceTable. If override is true, - * conflicting resources are allowed to override each other, in order of last seen. - * - * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the - * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file - * and the header data is read and merged into the final ResourceTable. - * - * All other file types are ignored. This is because these files could be coming from a zip, - * where we could have other files like classes.dex. - */ - bool mergeFile(io::IFile* file, bool override) { - const Source& src = file->getSource(); - if (util::stringEndsWith<char>(src.path, ".arsc.flat")) { - return mergeResourceTable(file, override); - - } else if (util::stringEndsWith<char>(src.path, ".flat")){ - // Try opening the file and looking for an Export header. - std::unique_ptr<io::IData> data = file->openAsData(); - if (!data) { - mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open"); - return false; - } + // First extract the Package name without modifying it (via + // --rename-manifest-package). + if (Maybe<AppInfo> maybe_app_info = ExtractAppInfoFromManifest( + manifest_xml.get(), context_->GetDiagnostics())) { + const AppInfo& app_info = maybe_app_info.value(); + context_->SetCompilationPackage(app_info.package); + } - std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader( - src, data->data(), data->size(), mContext->getDiagnostics()); - if (resourceFile) { - return mergeCompiledFile(file, resourceFile.get(), override); - } - return false; - } + ManifestFixer manifest_fixer(options_.manifest_fixer_options); + if (!manifest_fixer.Consume(context_, manifest_xml.get())) { + return 1; + } - // Ignore non .flat files. This could be classes.dex or something else that happens - // to be in an archive. - return true; + Maybe<AppInfo> maybe_app_info = ExtractAppInfoFromManifest( + manifest_xml.get(), context_->GetDiagnostics()); + if (!maybe_app_info) { + return 1; } - int run(const std::vector<std::string>& inputFiles) { - // Load the AndroidManifest.xml - std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath, - mContext->getDiagnostics()); - if (!manifestXml) { - return 1; - } + const AppInfo& app_info = maybe_app_info.value(); + if (app_info.min_sdk_version) { + if (Maybe<int> maybe_min_sdk_version = ResourceUtils::ParseSdkVersion( + app_info.min_sdk_version.value())) { + context_->SetMinSdkVersion(maybe_min_sdk_version.value()); + } + } - if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) { - mContext->setCompilationPackage(maybeAppInfo.value().package); - } else { - mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) - << "no package specified in <manifest> tag"); - return 1; - } + context_->SetNameManglerPolicy( + NameManglerPolicy{context_->GetCompilationPackage()}); + if (context_->GetCompilationPackage() == "android") { + context_->SetPackageId(0x01); + } else { + context_->SetPackageId(0x7f); + } - if (!util::isJavaPackageName(mContext->getCompilationPackage())) { - mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) - << "invalid package name '" - << mContext->getCompilationPackage() - << "'"); - return 1; - } + if (!LoadSymbolsFromIncludePaths()) { + return 1; + } - mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() }); + TableMergerOptions table_merger_options; + table_merger_options.auto_add_overlay = options_.auto_add_overlay; + table_merger_ = util::make_unique<TableMerger>(context_, &final_table_, + table_merger_options); - if (mContext->getCompilationPackage() == u"android") { - mContext->setPackageId(0x01); - } else { - mContext->setPackageId(0x7f); - } + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note(DiagMessage() + << "linking package '" + << context_->GetCompilationPackage() + << "' with package ID " << std::hex + << (int)context_->GetPackageId()); + } - if (!loadSymbolsFromIncludePaths()) { - return 1; - } + for (const std::string& input : input_files) { + if (!MergePath(input, false)) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed parsing input"); + return 1; + } + } - TableMergerOptions tableMergerOptions; - tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay; - mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions); + for (const std::string& input : options_.overlay_files) { + if (!MergePath(input, true)) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed parsing overlays"); + return 1; + } + } - if (mContext->verbose()) { - mContext->getDiagnostics()->note( - DiagMessage() << "linking package '" << mContext->getCompilationPackage() - << "' with package ID " << std::hex - << (int) mContext->getPackageId()); - } + if (!VerifyNoExternalPackages()) { + return 1; + } + if (!options_.static_lib) { + PrivateAttributeMover mover; + if (!mover.Consume(context_, &final_table_)) { + context_->GetDiagnostics()->Error( + DiagMessage() << "failed moving private attributes"); + return 1; + } - for (const std::string& input : inputFiles) { - if (!mergePath(input, false)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input"); - return 1; - } - } + // Assign IDs if we are building a regular app. + IdAssigner id_assigner(&options_.stable_id_map); + if (!id_assigner.Consume(context_, &final_table_)) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed assigning IDs"); + return 1; + } - for (const std::string& input : mOptions.overlayFiles) { - if (!mergePath(input, true)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays"); - return 1; + // Now grab each ID and emit it as a file. + if (options_.resource_id_map_path) { + for (auto& package : final_table_.packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + ResourceName name(package->name, type->type, entry->name); + // The IDs are guaranteed to exist. + options_.stable_id_map[std::move(name)] = ResourceId( + package->id.value(), type->id.value(), entry->id.value()); } + } } - if (!verifyNoExternalPackages()) { - return 1; + if (!WriteStableIdMapToPath(context_->GetDiagnostics(), + options_.stable_id_map, + options_.resource_id_map_path.value())) { + return 1; } + } + } else { + // Static libs are merged with other apps, and ID collisions are bad, so + // verify that + // no IDs have been set. + if (!VerifyNoIdsSet()) { + return 1; + } + } - if (!mOptions.staticLib) { - PrivateAttributeMover mover; - if (!mover.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed moving private attributes"); - return 1; - } - } + // Add the names to mangle based on our source merge earlier. + context_->SetNameManglerPolicy(NameManglerPolicy{ + context_->GetCompilationPackage(), table_merger_->merged_packages()}); - if (!mOptions.staticLib) { - // Assign IDs if we are building a regular app. - IdAssigner idAssigner; - if (!idAssigner.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs"); - return 1; - } - } else { - // Static libs are merged with other apps, and ID collisions are bad, so verify that - // no IDs have been set. - if (!verifyNoIdsSet()) { - return 1; - } - } + // Add our table to the symbol table. + context_->GetExternalSymbols()->PrependSource( + util::make_unique<ResourceTableSymbolSource>(&final_table_)); - // Add the names to mangle based on our source merge earlier. - mContext->setNameManglerPolicy(NameManglerPolicy{ - mContext->getCompilationPackage(), mTableMerger->getMergedPackages() }); + ReferenceLinker linker; + if (!linker.Consume(context_, &final_table_)) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed linking references"); + return 1; + } - // Add our table to the symbol table. - mContext->getExternalSymbols()->prependSource( - util::make_unique<ResourceTableSymbolSource>(&mFinalTable)); + if (options_.static_lib) { + if (!options_.products.empty()) { + context_->GetDiagnostics() + ->Warn(DiagMessage() + << "can't select products when building static library"); + } + } else { + ProductFilter product_filter(options_.products); + if (!product_filter.Consume(context_, &final_table_)) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed stripping products"); + return 1; + } + } - { - ReferenceLinker linker; - if (!linker.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed linking references"); - return 1; - } + if (!options_.no_auto_version) { + AutoVersioner versioner; + if (!versioner.Consume(context_, &final_table_)) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed versioning styles"); + return 1; + } + } - if (mOptions.staticLib) { - if (!mOptions.products.empty()) { - mContext->getDiagnostics()->warn( - DiagMessage() << "can't select products when building static library"); - } - - if (mOptions.tableSplitterOptions.configFilter != nullptr || - mOptions.tableSplitterOptions.preferredDensity) { - mContext->getDiagnostics()->warn( - DiagMessage() << "can't strip resources when building static library"); - } - } else { - ProductFilter productFilter(mOptions.products); - if (!productFilter.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products"); - return 1; - } - - // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file - // level. - TableSplitter tableSplitter({}, mOptions.tableSplitterOptions); - if (!tableSplitter.verifySplitConstraints(mContext)) { - return 1; - } - tableSplitter.splitTable(&mFinalTable); - } - } + if (!options_.static_lib && context_->GetMinSdkVersion() > 0) { + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note( + DiagMessage() << "collapsing resource versions for minimum SDK " + << context_->GetMinSdkVersion()); + } - proguard::KeepSet proguardKeepSet; + VersionCollapser collapser; + if (!collapser.Consume(context_, &final_table_)) { + return 1; + } + } - std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(); - if (!archiveWriter) { - mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive"); - return 1; - } + if (!options_.no_resource_deduping) { + ResourceDeduper deduper; + if (!deduper.Consume(context_, &final_table_)) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed deduping resources"); + return 1; + } + } - bool error = false; - { - ManifestFixer manifestFixer(mOptions.manifestFixerOptions); - if (!manifestFixer.consume(mContext, manifestXml.get())) { - error = true; - } + proguard::KeepSet proguard_keep_set; + proguard::KeepSet proguard_main_dex_keep_set; - // AndroidManifest.xml has no resource name, but the CallSite is built from the name - // (aka, which package the AndroidManifest.xml is coming from). - // So we give it a package name so it can see local resources. - manifestXml->file.name.package = mContext->getCompilationPackage(); - - XmlReferenceLinker manifestLinker; - if (manifestLinker.consume(mContext, manifestXml.get())) { - if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath), - manifestXml.get(), - &proguardKeepSet)) { - error = true; - } - - if (mOptions.generateJavaClassPath) { - if (!writeManifestJavaFile(manifestXml.get())) { - error = true; - } - } - - const bool keepRawValues = mOptions.staticLib; - bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {}, - keepRawValues, archiveWriter.get(), mContext); - if (!result) { - error = true; - } - } else { - error = true; - } - } + if (options_.static_lib) { + if (options_.table_splitter_options.config_filter != nullptr || + options_.table_splitter_options.preferred_density) { + context_->GetDiagnostics() + ->Warn(DiagMessage() + << "can't strip resources when building static library"); + } + } else { + // Adjust the SplitConstraints so that their SDK version is stripped if it + // is less + // than or equal to the minSdk. Otherwise the resources that have had + // their SDK version + // stripped due to minSdk won't ever match. + std::vector<SplitConstraints> adjusted_constraints_list; + adjusted_constraints_list.reserve(options_.split_constraints.size()); + for (const SplitConstraints& constraints : options_.split_constraints) { + SplitConstraints adjusted_constraints; + for (const ConfigDescription& config : constraints.configs) { + if (config.sdkVersion <= context_->GetMinSdkVersion()) { + adjusted_constraints.configs.insert(config.CopyWithoutSdkVersion()); + } else { + adjusted_constraints.configs.insert(config); + } + } + adjusted_constraints_list.push_back(std::move(adjusted_constraints)); + } + + TableSplitter table_splitter(adjusted_constraints_list, + options_.table_splitter_options); + if (!table_splitter.VerifySplitConstraints(context_)) { + return 1; + } + table_splitter.SplitTable(&final_table_); - if (error) { - mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest"); - return 1; + // Now we need to write out the Split APKs. + auto path_iter = options_.split_paths.begin(); + auto split_constraints_iter = adjusted_constraints_list.begin(); + for (std::unique_ptr<ResourceTable>& split_table : + table_splitter.splits()) { + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note( + DiagMessage(*path_iter) + << "generating split with configurations '" + << util::Joiner(split_constraints_iter->configs, ", ") << "'"); } - ResourceFileFlattenerOptions fileFlattenerOptions; - fileFlattenerOptions.keepRawValues = mOptions.staticLib; - fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything; - fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress; - fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion; - fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors; - ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet); - - if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) { - mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources"); - return 1; + std::unique_ptr<IArchiveWriter> archive_writer = + MakeArchiveWriter(*path_iter); + if (!archive_writer) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed to create archive"); + return 1; } - if (!mOptions.noAutoVersion) { - AutoVersioner versioner; - if (!versioner.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles"); - return 1; - } - } + // Generate an AndroidManifest.xml for each split. + std::unique_ptr<xml::XmlResource> split_manifest = + GenerateSplitManifest(app_info, *split_constraints_iter); - if (mOptions.staticLib) { - if (!flattenTableToPb(&mFinalTable, archiveWriter.get())) { - mContext->getDiagnostics()->error(DiagMessage() - << "failed to write resources.arsc.flat"); - return 1; - } - } else { - if (!flattenTable(&mFinalTable, archiveWriter.get())) { - mContext->getDiagnostics()->error(DiagMessage() - << "failed to write resources.arsc"); - return 1; - } + XmlReferenceLinker linker; + if (!linker.Consume(context_, split_manifest.get())) { + context_->GetDiagnostics()->Error( + DiagMessage() << "failed to create Split AndroidManifest.xml"); + return 1; } - if (mOptions.generateJavaClassPath) { - JavaClassGeneratorOptions options; - options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; - options.javadocAnnotations = mOptions.javadocAnnotations; - - if (mOptions.staticLib || mOptions.generateNonFinalIds) { - options.useFinal = false; - } - - const StringPiece16 actualPackage = mContext->getCompilationPackage(); - StringPiece16 outputPackage = mContext->getCompilationPackage(); - if (mOptions.customJavaPackage) { - // Override the output java package to the custom one. - outputPackage = mOptions.customJavaPackage.value(); - } + if (!WriteApk(archive_writer.get(), &proguard_keep_set, + split_manifest.get(), split_table.get())) { + return 1; + } - if (mOptions.privateSymbols) { - // If we defined a private symbols package, we only emit Public symbols - // to the original package, and private and public symbols to the private package. + ++path_iter; + ++split_constraints_iter; + } + } - options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; - if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(), - outputPackage, options)) { - return 1; - } + // Start writing the base APK. + std::unique_ptr<IArchiveWriter> archive_writer = + MakeArchiveWriter(options_.output_path); + if (!archive_writer) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed to create archive"); + return 1; + } - options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; - outputPackage = mOptions.privateSymbols.value(); - } + bool error = false; + { + // AndroidManifest.xml has no resource name, but the CallSite is built + // from the name + // (aka, which package the AndroidManifest.xml is coming from). + // So we give it a package name so it can see local resources. + manifest_xml->file.name.package = context_->GetCompilationPackage(); - if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) { - return 1; - } + XmlReferenceLinker manifest_linker; + if (manifest_linker.Consume(context_, manifest_xml.get())) { + if (options_.generate_proguard_rules_path && + !proguard::CollectProguardRulesForManifest( + Source(options_.manifest_path), manifest_xml.get(), + &proguard_keep_set)) { + error = true; + } - for (const std::u16string& extraPackage : mOptions.extraJavaPackages) { - if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) { - return 1; - } - } + if (options_.generate_main_dex_proguard_rules_path && + !proguard::CollectProguardRulesForManifest( + Source(options_.manifest_path), manifest_xml.get(), + &proguard_main_dex_keep_set, true)) { + error = true; } - if (mOptions.generateProguardRulesPath) { - if (!writeProguardFile(proguardKeepSet)) { - return 1; - } + if (options_.generate_java_class_path) { + if (!WriteManifestJavaFile(manifest_xml.get())) { + error = true; + } } - if (mContext->verbose()) { - DebugPrintTableOptions debugPrintTableOptions; - debugPrintTableOptions.showSources = true; - Debug::printTable(&mFinalTable, debugPrintTableOptions); + if (options_.no_xml_namespaces) { + // PackageParser will fail if URIs are removed from + // AndroidManifest.xml. + XmlNamespaceRemover namespace_remover(true /* keepUris */); + if (!namespace_remover.Consume(context_, manifest_xml.get())) { + error = true; + } } - return 0; + } else { + error = true; + } } -private: - LinkOptions mOptions; - LinkContext* mContext; - ResourceTable mFinalTable; - - std::unique_ptr<TableMerger> mTableMerger; - - // A pointer to the FileCollection representing the filesystem (not archives). - std::unique_ptr<io::FileCollection> mFileCollection; - - // A vector of IFileCollections. This is mainly here to keep ownership of the collections. - std::vector<std::unique_ptr<io::IFileCollection>> mCollections; - - // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable - // can use these. - std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes; -}; - -int link(const std::vector<StringPiece>& args) { - LinkContext context; - LinkOptions options; - Maybe<std::string> privateSymbolsPackage; - Maybe<std::string> minSdkVersion, targetSdkVersion; - Maybe<std::string> renameManifestPackage, renameInstrumentationTargetPackage; - Maybe<std::string> versionCode, versionName; - Maybe<std::string> customJavaPackage; - std::vector<std::string> extraJavaPackages; - Maybe<std::string> configs; - Maybe<std::string> preferredDensity; - Maybe<std::string> productList; - bool legacyXFlag = false; - bool requireLocalization = false; - bool verbose = false; - Flags flags = Flags() - .requiredFlag("-o", "Output path", &options.outputPath) - .requiredFlag("--manifest", "Path to the Android manifest to build", - &options.manifestPath) - .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths) - .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n" - "The last conflicting resource given takes precedence.", - &options.overlayFiles) - .optionalFlag("--java", "Directory in which to generate R.java", - &options.generateJavaClassPath) - .optionalFlag("--proguard", "Output file for generated Proguard rules", - &options.generateProguardRulesPath) - .optionalSwitch("--no-auto-version", - "Disables automatic style and layout SDK versioning", - &options.noAutoVersion) - .optionalSwitch("--no-version-vectors", - "Disables automatic versioning of vector drawables. Use this only\n" - "when building with vector drawable support library", - &options.noVersionVectors) - .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01", - &legacyXFlag) - .optionalSwitch("-z", "Require localization of strings marked 'suggested'", - &requireLocalization) - .optionalFlag("-c", "Comma separated list of configurations to include. The default\n" - "is all configurations", &configs) - .optionalFlag("--preferred-density", - "Selects the closest matching density and strips out all others.", - &preferredDensity) - .optionalFlag("--product", "Comma separated list of product names to keep", - &productList) - .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified " - "by -o", - &options.outputToDirectory) - .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for " - "AndroidManifest.xml", &minSdkVersion) - .optionalFlag("--target-sdk-version", "Default target SDK version to use for " - "AndroidManifest.xml", &targetSdkVersion) - .optionalFlag("--version-code", "Version code (integer) to inject into the " - "AndroidManifest.xml if none is present", &versionCode) - .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml " - "if none is present", &versionName) - .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib) - .optionalSwitch("--no-static-lib-packages", - "Merge all library resources under the app's package", - &options.noStaticLibPackages) - .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n" - "This is implied when --static-lib is specified.", - &options.generateNonFinalIds) - .optionalFlag("--private-symbols", "Package name to use when generating R.java for " - "private symbols.\n" - "If not specified, public and private symbols will use the application's " - "package name", &privateSymbolsPackage) - .optionalFlag("--custom-package", "Custom Java package under which to generate R.java", - &customJavaPackage) - .optionalFlagList("--extra-packages", "Generate the same R.java but with different " - "package names", &extraJavaPackages) - .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all " - "generated Java classes", &options.javadocAnnotations) - .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in " - "overlays without <add-resource> tags", &options.autoAddOverlay) - .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml", - &renameManifestPackage) - .optionalFlag("--rename-instrumentation-target-package", - "Changes the name of the target package for instrumentation. Most useful " - "when used\nin conjunction with --rename-manifest-package", - &renameInstrumentationTargetPackage) - .optionalFlagList("-0", "File extensions not to compress", - &options.extensionsToNotCompress) - .optionalSwitch("-v", "Enables verbose logging", &verbose); - - if (!flags.parse("aapt2 link", args, &std::cerr)) { - return 1; + if (error) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed processing manifest"); + return 1; } - // Expand all argument-files passed into the command line. These start with '@'. - std::vector<std::string> argList; - for (const std::string& arg : flags.getArgs()) { - if (util::stringStartsWith<char>(arg, "@")) { - const std::string path = arg.substr(1, arg.size() - 1); - std::string error; - if (!file::appendArgsFromFile(path, &argList, &error)) { - context.getDiagnostics()->error(DiagMessage(path) << error); - return 1; - } - } else { - argList.push_back(arg); - } + if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), + &final_table_)) { + return 1; } - if (verbose) { - context.setVerbose(verbose); - } + if (options_.generate_java_class_path) { + JavaClassGeneratorOptions options; + options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; + options.javadoc_annotations = options_.javadoc_annotations; - if (privateSymbolsPackage) { - options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value()); - } + if (options_.static_lib || options_.generate_non_final_ids) { + options.use_final = false; + } - if (minSdkVersion) { - options.manifestFixerOptions.minSdkVersionDefault = - util::utf8ToUtf16(minSdkVersion.value()); - } + const StringPiece actual_package = context_->GetCompilationPackage(); + StringPiece output_package = context_->GetCompilationPackage(); + if (options_.custom_java_package) { + // Override the output java package to the custom one. + output_package = options_.custom_java_package.value(); + } - if (targetSdkVersion) { - options.manifestFixerOptions.targetSdkVersionDefault = - util::utf8ToUtf16(targetSdkVersion.value()); - } + if (options_.private_symbols) { + // If we defined a private symbols package, we only emit Public symbols + // to the original package, and private and public symbols to the + // private package. - if (renameManifestPackage) { - options.manifestFixerOptions.renameManifestPackage = - util::utf8ToUtf16(renameManifestPackage.value()); - } + options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; + if (!WriteJavaFile(&final_table_, context_->GetCompilationPackage(), + output_package, options)) { + return 1; + } - if (renameInstrumentationTargetPackage) { - options.manifestFixerOptions.renameInstrumentationTargetPackage = - util::utf8ToUtf16(renameInstrumentationTargetPackage.value()); - } + options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; + output_package = options_.private_symbols.value(); + } - if (versionCode) { - options.manifestFixerOptions.versionCodeDefault = util::utf8ToUtf16(versionCode.value()); - } + if (!WriteJavaFile(&final_table_, actual_package, output_package, + options)) { + return 1; + } - if (versionName) { - options.manifestFixerOptions.versionNameDefault = util::utf8ToUtf16(versionName.value()); + for (const std::string& extra_package : options_.extra_java_packages) { + if (!WriteJavaFile(&final_table_, actual_package, extra_package, + options)) { + return 1; + } + } } - if (customJavaPackage) { - options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value()); + if (!WriteProguardFile(options_.generate_proguard_rules_path, + proguard_keep_set)) { + return 1; } - // Populate the set of extra packages for which to generate R.java. - for (std::string& extraPackage : extraJavaPackages) { - // A given package can actually be a colon separated list of packages. - for (StringPiece package : util::split(extraPackage, ':')) { - options.extraJavaPackages.insert(util::utf8ToUtf16(package)); - } + if (!WriteProguardFile(options_.generate_main_dex_proguard_rules_path, + proguard_main_dex_keep_set)) { + return 1; } - if (productList) { - for (StringPiece product : util::tokenize<char>(productList.value(), ',')) { - if (product != "" && product != "default") { - options.products.insert(product.toString()); - } - } + if (context_->IsVerbose()) { + DebugPrintTableOptions debug_print_table_options; + debug_print_table_options.show_sources = true; + Debug::PrintTable(&final_table_, debug_print_table_options); } + return 0; + } - AxisConfigFilter filter; - if (configs) { - for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) { - ConfigDescription config; - LocaleValue lv; - if (lv.initFromFilterString(configStr)) { - lv.writeTo(&config); - } else if (!ConfigDescription::parse(configStr, &config)) { - context.getDiagnostics()->error( - DiagMessage() << "invalid config '" << configStr << "' for -c option"); - return 1; - } - - if (config.density != 0) { - context.getDiagnostics()->warn( - DiagMessage() << "ignoring density '" << config << "' for -c option"); - } else { - filter.addConfig(config); - } - } + private: + LinkOptions options_; + LinkContext* context_; + ResourceTable final_table_; - options.tableSplitterOptions.configFilter = &filter; - } + std::unique_ptr<TableMerger> table_merger_; - if (preferredDensity) { - ConfigDescription preferredDensityConfig; - if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) { - context.getDiagnostics()->error(DiagMessage() << "invalid density '" - << preferredDensity.value() - << "' for --preferred-density option"); - return 1; - } + // A pointer to the FileCollection representing the filesystem (not archives). + std::unique_ptr<io::FileCollection> file_collection_; - // Clear the version that can be automatically added. - preferredDensityConfig.sdkVersion = 0; + // A vector of IFileCollections. This is mainly here to keep ownership of the + // collections. + std::vector<std::unique_ptr<io::IFileCollection>> collections_; - if (preferredDensityConfig.diff(ConfigDescription::defaultConfig()) - != ConfigDescription::CONFIG_DENSITY) { - context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '" - << preferredDensity.value() << "'. " - << "Preferred density must only be a density value"); - return 1; - } - options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density; - } + // A vector of ResourceTables. This is here to retain ownership, so that the + // SymbolTable + // can use these. + std::vector<std::unique_ptr<ResourceTable>> static_table_includes_; +}; - // Turn off auto versioning for static-libs. - if (options.staticLib) { - options.noAutoVersion = true; - options.noVersionVectors = true; +int Link(const std::vector<StringPiece>& args) { + LinkContext context; + LinkOptions options; + std::vector<std::string> overlay_arg_list; + std::vector<std::string> extra_java_packages; + Maybe<std::string> configs; + Maybe<std::string> preferred_density; + Maybe<std::string> product_list; + bool legacy_x_flag = false; + bool require_localization = false; + bool verbose = false; + Maybe<std::string> stable_id_file_path; + std::vector<std::string> split_args; + Flags flags = + Flags() + .RequiredFlag("-o", "Output path", &options.output_path) + .RequiredFlag("--manifest", "Path to the Android manifest to build", + &options.manifest_path) + .OptionalFlagList("-I", "Adds an Android APK to link against", + &options.include_paths) + .OptionalFlagList( + "-R", + "Compilation unit to link, using `overlay` semantics.\n" + "The last conflicting resource given takes precedence.", + &overlay_arg_list) + .OptionalFlag("--java", "Directory in which to generate R.java", + &options.generate_java_class_path) + .OptionalFlag("--proguard", + "Output file for generated Proguard rules", + &options.generate_proguard_rules_path) + .OptionalFlag( + "--proguard-main-dex", + "Output file for generated Proguard rules for the main dex", + &options.generate_main_dex_proguard_rules_path) + .OptionalSwitch("--no-auto-version", + "Disables automatic style and layout SDK versioning", + &options.no_auto_version) + .OptionalSwitch("--no-version-vectors", + "Disables automatic versioning of vector drawables. " + "Use this only\n" + "when building with vector drawable support library", + &options.no_version_vectors) + .OptionalSwitch("--no-resource-deduping", + "Disables automatic deduping of resources with\n" + "identical values across compatible configurations.", + &options.no_resource_deduping) + .OptionalSwitch( + "-x", + "Legacy flag that specifies to use the package identifier 0x01", + &legacy_x_flag) + .OptionalSwitch("-z", + "Require localization of strings marked 'suggested'", + &require_localization) + .OptionalFlag( + "-c", + "Comma separated list of configurations to include. The default\n" + "is all configurations", + &configs) + .OptionalFlag( + "--preferred-density", + "Selects the closest matching density and strips out all others.", + &preferred_density) + .OptionalFlag("--product", + "Comma separated list of product names to keep", + &product_list) + .OptionalSwitch("--output-to-dir", + "Outputs the APK contents to a directory specified " + "by -o", + &options.output_to_directory) + .OptionalSwitch("--no-xml-namespaces", + "Removes XML namespace prefix and URI " + "information from AndroidManifest.xml\nand XML " + "binaries in res/*.", + &options.no_xml_namespaces) + .OptionalFlag("--min-sdk-version", + "Default minimum SDK version to use for " + "AndroidManifest.xml", + &options.manifest_fixer_options.min_sdk_version_default) + .OptionalFlag( + "--target-sdk-version", + "Default target SDK version to use for " + "AndroidManifest.xml", + &options.manifest_fixer_options.target_sdk_version_default) + .OptionalFlag("--version-code", + "Version code (integer) to inject into the " + "AndroidManifest.xml if none is present", + &options.manifest_fixer_options.version_code_default) + .OptionalFlag("--version-name", + "Version name to inject into the AndroidManifest.xml " + "if none is present", + &options.manifest_fixer_options.version_name_default) + .OptionalSwitch("--static-lib", "Generate a static Android library", + &options.static_lib) + .OptionalSwitch("--no-static-lib-packages", + "Merge all library resources under the app's package", + &options.no_static_lib_packages) + .OptionalSwitch("--non-final-ids", + "Generates R.java without the final modifier.\n" + "This is implied when --static-lib is specified.", + &options.generate_non_final_ids) + .OptionalFlag("--stable-ids", + "File containing a list of name to ID mapping.", + &stable_id_file_path) + .OptionalFlag( + "--emit-ids", + "Emit a file at the given path with a list of name to ID\n" + "mappings, suitable for use with --stable-ids.", + &options.resource_id_map_path) + .OptionalFlag("--private-symbols", + "Package name to use when generating R.java for " + "private symbols.\n" + "If not specified, public and private symbols will use " + "the application's " + "package name", + &options.private_symbols) + .OptionalFlag("--custom-package", + "Custom Java package under which to generate R.java", + &options.custom_java_package) + .OptionalFlagList("--extra-packages", + "Generate the same R.java but with different " + "package names", + &extra_java_packages) + .OptionalFlagList("--add-javadoc-annotation", + "Adds a JavaDoc annotation to all " + "generated Java classes", + &options.javadoc_annotations) + .OptionalSwitch("--auto-add-overlay", + "Allows the addition of new resources in " + "overlays without <add-resource> tags", + &options.auto_add_overlay) + .OptionalFlag("--rename-manifest-package", + "Renames the package in AndroidManifest.xml", + &options.manifest_fixer_options.rename_manifest_package) + .OptionalFlag( + "--rename-instrumentation-target-package", + "Changes the name of the target package for instrumentation. " + "Most useful " + "when used\nin conjunction with --rename-manifest-package", + &options.manifest_fixer_options + .rename_instrumentation_target_package) + .OptionalFlagList("-0", "File extensions not to compress", + &options.extensions_to_not_compress) + .OptionalFlagList( + "--split", + "Split resources matching a set of configs out to a " + "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]", + &split_args) + .OptionalSwitch("-v", "Enables verbose logging", &verbose); + + if (!flags.Parse("aapt2 link", args, &std::cerr)) { + return 1; + } + + // Expand all argument-files passed into the command line. These start with + // '@'. + std::vector<std::string> arg_list; + for (const std::string& arg : flags.GetArgs()) { + if (util::StartsWith(arg, "@")) { + const std::string path = arg.substr(1, arg.size() - 1); + std::string error; + if (!file::AppendArgsFromFile(path, &arg_list, &error)) { + context.GetDiagnostics()->Error(DiagMessage(path) << error); + return 1; + } + } else { + arg_list.push_back(arg); } + } - LinkCommand cmd(&context, options); - return cmd.run(argList); + // Expand all argument-files passed to -R. + for (const std::string& arg : overlay_arg_list) { + if (util::StartsWith(arg, "@")) { + const std::string path = arg.substr(1, arg.size() - 1); + std::string error; + if (!file::AppendArgsFromFile(path, &options.overlay_files, &error)) { + context.GetDiagnostics()->Error(DiagMessage(path) << error); + return 1; + } + } else { + options.overlay_files.push_back(arg); + } + } + + if (verbose) { + context.SetVerbose(verbose); + } + + // Populate the set of extra packages for which to generate R.java. + for (std::string& extra_package : extra_java_packages) { + // A given package can actually be a colon separated list of packages. + for (StringPiece package : util::Split(extra_package, ':')) { + options.extra_java_packages.insert(package.ToString()); + } + } + + if (product_list) { + for (StringPiece product : util::Tokenize(product_list.value(), ',')) { + if (product != "" && product != "default") { + options.products.insert(product.ToString()); + } + } + } + + AxisConfigFilter filter; + if (configs) { + for (const StringPiece& config_str : util::Tokenize(configs.value(), ',')) { + ConfigDescription config; + LocaleValue lv; + if (lv.InitFromFilterString(config_str)) { + lv.WriteTo(&config); + } else if (!ConfigDescription::Parse(config_str, &config)) { + context.GetDiagnostics()->Error(DiagMessage() << "invalid config '" + << config_str + << "' for -c option"); + return 1; + } + + if (config.density != 0) { + context.GetDiagnostics()->Warn(DiagMessage() << "ignoring density '" + << config + << "' for -c option"); + } else { + filter.AddConfig(config); + } + } + + options.table_splitter_options.config_filter = &filter; + } + + if (preferred_density) { + ConfigDescription preferred_density_config; + if (!ConfigDescription::Parse(preferred_density.value(), + &preferred_density_config)) { + context.GetDiagnostics()->Error( + DiagMessage() << "invalid density '" << preferred_density.value() + << "' for --preferred-density option"); + return 1; + } + + // Clear the version that can be automatically added. + preferred_density_config.sdkVersion = 0; + + if (preferred_density_config.diff(ConfigDescription::DefaultConfig()) != + ConfigDescription::CONFIG_DENSITY) { + context.GetDiagnostics()->Error( + DiagMessage() << "invalid preferred density '" + << preferred_density.value() << "'. " + << "Preferred density must only be a density value"); + return 1; + } + options.table_splitter_options.preferred_density = + preferred_density_config.density; + } + + if (!options.static_lib && stable_id_file_path) { + if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path.value(), + &options.stable_id_map)) { + return 1; + } + } + + // Populate some default no-compress extensions that are already compressed. + options.extensions_to_not_compress.insert( + {".jpg", ".jpeg", ".png", ".gif", ".wav", ".mp2", ".mp3", ".ogg", + ".aac", ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl", + ".imy", ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2", + ".3gpp2", ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"}); + + // Parse the split parameters. + for (const std::string& split_arg : split_args) { + options.split_paths.push_back({}); + options.split_constraints.push_back({}); + if (!ParseSplitParameter(split_arg, context.GetDiagnostics(), + &options.split_paths.back(), + &options.split_constraints.back())) { + return 1; + } + } + + // Turn off auto versioning for static-libs. + if (options.static_lib) { + options.no_auto_version = true; + options.no_version_vectors = true; + } + + LinkCommand cmd(&context, options); + return cmd.Run(arg_list); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h index ec532aba465f..4687d2c01d68 100644 --- a/tools/aapt2/link/Linkers.h +++ b/tools/aapt2/link/Linkers.h @@ -17,12 +17,15 @@ #ifndef AAPT_LINKER_LINKERS_H #define AAPT_LINKER_LINKERS_H +#include <set> +#include <unordered_set> + +#include "android-base/macros.h" + #include "Resource.h" #include "process/IResourceTableConsumer.h" #include "xml/XmlDom.h" -#include <set> - namespace aapt { class ResourceTable; @@ -30,74 +33,174 @@ class ResourceEntry; struct ConfigDescription; /** - * Defines the location in which a value exists. This determines visibility of other - * package's private symbols. + * Defines the location in which a value exists. This determines visibility of + * other package's private symbols. */ struct CallSite { - ResourceNameRef resource; + ResourceNameRef resource; }; /** - * Determines whether a versioned resource should be created. If a versioned resource already - * exists, it takes precedence. + * Determines whether a versioned resource should be created. If a versioned + * resource already exists, it takes precedence. */ -bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config, - const int sdkVersionToGenerate); +bool ShouldGenerateVersionedResource(const ResourceEntry* entry, + const ConfigDescription& config, + const int sdk_version_to_generate); + +class AutoVersioner : public IResourceTableConsumer { + public: + AutoVersioner() = default; -struct AutoVersioner : public IResourceTableConsumer { - bool consume(IAaptContext* context, ResourceTable* table) override; + bool Consume(IAaptContext* context, ResourceTable* table) override; + + private: + DISALLOW_COPY_AND_ASSIGN(AutoVersioner); }; -struct XmlAutoVersioner : public IXmlResourceConsumer { - bool consume(IAaptContext* context, xml::XmlResource* resource) override; +class VersionCollapser : public IResourceTableConsumer { + public: + VersionCollapser() = default; + + bool Consume(IAaptContext* context, ResourceTable* table) override; + + private: + DISALLOW_COPY_AND_ASSIGN(VersionCollapser); }; /** - * If any attribute resource values are defined as public, this consumer will move all private - * attribute resource values to a private ^private-attr type, avoiding backwards compatibility + * Removes duplicated key-value entries from dominated resources. + */ +class ResourceDeduper : public IResourceTableConsumer { + public: + ResourceDeduper() = default; + + bool Consume(IAaptContext* context, ResourceTable* table) override; + + private: + DISALLOW_COPY_AND_ASSIGN(ResourceDeduper); +}; + +/** + * If any attribute resource values are defined as public, this consumer will + * move all private + * attribute resource values to a private ^private-attr type, avoiding backwards + * compatibility * issues with new apps running on old platforms. * - * The Android platform ignores resource attributes it doesn't recognize, so an app developer can - * use new attributes in their layout XML files without worrying about versioning. This assumption - * actually breaks on older platforms. OEMs may add private attributes that are used internally. - * AAPT originally assigned all private attributes IDs immediately proceeding the public attributes' + * The Android platform ignores resource attributes it doesn't recognize, so an + * app developer can + * use new attributes in their layout XML files without worrying about + * versioning. This assumption + * actually breaks on older platforms. OEMs may add private attributes that are + * used internally. + * AAPT originally assigned all private attributes IDs immediately proceeding + * the public attributes' * IDs. * - * This means that on a newer Android platform, an ID previously assigned to a private attribute + * This means that on a newer Android platform, an ID previously assigned to a + * private attribute * may end up assigned to a public attribute. * - * App developers assume using the newer attribute is safe on older platforms because it will - * be ignored. Instead, the platform thinks the new attribute is an older, private attribute and - * will interpret it as such. This leads to unintended styling and exceptions thrown due to + * App developers assume using the newer attribute is safe on older platforms + * because it will + * be ignored. Instead, the platform thinks the new attribute is an older, + * private attribute and + * will interpret it as such. This leads to unintended styling and exceptions + * thrown due to * unexpected types. * - * By moving the private attributes to a completely different type, this ID conflict will never + * By moving the private attributes to a completely different type, this ID + * conflict will never * occur. */ -struct PrivateAttributeMover : public IResourceTableConsumer { - bool consume(IAaptContext* context, ResourceTable* table) override; +class PrivateAttributeMover : public IResourceTableConsumer { + public: + PrivateAttributeMover() = default; + + bool Consume(IAaptContext* context, ResourceTable* table) override; + + private: + DISALLOW_COPY_AND_ASSIGN(PrivateAttributeMover); +}; + +class ResourceConfigValue; + +class ProductFilter : public IResourceTableConsumer { + public: + using ResourceConfigValueIter = + std::vector<std::unique_ptr<ResourceConfigValue>>::iterator; + + explicit ProductFilter(std::unordered_set<std::string> products) + : products_(products) {} + + ResourceConfigValueIter SelectProductToKeep( + const ResourceNameRef& name, const ResourceConfigValueIter begin, + const ResourceConfigValueIter end, IDiagnostics* diag); + + bool Consume(IAaptContext* context, ResourceTable* table) override; + + private: + std::unordered_set<std::string> products_; + + DISALLOW_COPY_AND_ASSIGN(ProductFilter); +}; + +class XmlAutoVersioner : public IXmlResourceConsumer { + public: + XmlAutoVersioner() = default; + + bool Consume(IAaptContext* context, xml::XmlResource* resource) override; + + private: + DISALLOW_COPY_AND_ASSIGN(XmlAutoVersioner); +}; + +/** + * Removes namespace nodes and URI information from the XmlResource. + * + * Once an XmlResource is processed by this consumer, it is no longer able to + * have its attributes + * parsed. As such, this XmlResource must have already been processed by + * XmlReferenceLinker. + */ +class XmlNamespaceRemover : public IXmlResourceConsumer { + public: + explicit XmlNamespaceRemover(bool keep_uris = false) + : keep_uris_(keep_uris){}; + + bool Consume(IAaptContext* context, xml::XmlResource* resource) override; + + private: + DISALLOW_COPY_AND_ASSIGN(XmlNamespaceRemover); + + bool keep_uris_; }; /** - * Resolves attributes in the XmlResource and compiles string values to resource values. + * Resolves attributes in the XmlResource and compiles string values to resource + * values. * Once an XmlResource is processed by this linker, it is ready to be flattened. */ class XmlReferenceLinker : public IXmlResourceConsumer { -private: - std::set<int> mSdkLevelsFound; - -public: - bool consume(IAaptContext* context, xml::XmlResource* resource) override; - - /** - * Once the XmlResource has been consumed, this returns the various SDK levels in which - * framework attributes used within the XML document were defined. - */ - inline const std::set<int>& getSdkLevels() const { - return mSdkLevelsFound; - } + public: + XmlReferenceLinker() = default; + + bool Consume(IAaptContext* context, xml::XmlResource* resource) override; + + /** + * Once the XmlResource has been consumed, this returns the various SDK levels + * in which + * framework attributes used within the XML document were defined. + */ + inline const std::set<int>& sdk_levels() const { return sdk_levels_found_; } + + private: + DISALLOW_COPY_AND_ASSIGN(XmlReferenceLinker); + + std::set<int> sdk_levels_found_; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_LINKER_LINKERS_H */ diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 77a949f1339d..36a34941347f 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -14,8 +14,13 @@ * limitations under the License. */ -#include "ResourceUtils.h" #include "link/ManifestFixer.h" + +#include <unordered_set> + +#include "android-base/logging.h" + +#include "ResourceUtils.h" #include "util/Util.h" #include "xml/XmlActionExecutor.h" #include "xml/XmlDom.h" @@ -23,272 +28,315 @@ namespace aapt { /** - * This is how PackageManager builds class names from AndroidManifest.xml entries. + * This is how PackageManager builds class names from AndroidManifest.xml + * entries. */ -static bool nameIsJavaClassName(xml::Element* el, xml::Attribute* attr, +static bool NameIsJavaClassName(xml::Element* el, xml::Attribute* attr, SourcePathDiagnostics* diag) { - std::u16string className = attr->value; - if (className.find(u'.') == std::u16string::npos) { - // There is no '.', so add one to the beginning. - className = u"."; - className += attr->value; - } + // We allow unqualified class names (ie: .HelloActivity) + // Since we don't know the package name, we can just make a fake one here and + // the test will be identical as long as the real package name is valid too. + Maybe<std::string> fully_qualified_class_name = + util::GetFullyQualifiedClassName("a", attr->value); + + StringPiece qualified_class_name = fully_qualified_class_name + ? fully_qualified_class_name.value() + : attr->value; + + if (!util::IsJavaClassName(qualified_class_name)) { + diag->Error(DiagMessage(el->line_number) + << "attribute 'android:name' in <" << el->name + << "> tag must be a valid Java class name"); + return false; + } + return true; +} - // We allow unqualified class names (ie: .HelloActivity) - // Since we don't know the package name, we can just make a fake one here and - // the test will be identical as long as the real package name is valid too. - Maybe<std::u16string> fullyQualifiedClassName = - util::getFullyQualifiedClassName(u"a", className); - - StringPiece16 qualifiedClassName = fullyQualifiedClassName - ? fullyQualifiedClassName.value() : className; - if (!util::isJavaClassName(qualifiedClassName)) { - diag->error(DiagMessage(el->lineNumber) - << "attribute 'android:name' in <" - << el->name << "> tag must be a valid Java class name"); - return false; - } - return true; +static bool OptionalNameIsJavaClassName(xml::Element* el, + SourcePathDiagnostics* diag) { + if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) { + return NameIsJavaClassName(el, attr, diag); + } + return true; } -static bool optionalNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) { - if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name")) { - return nameIsJavaClassName(el, attr, diag); - } - return true; +static bool RequiredNameIsJavaClassName(xml::Element* el, + SourcePathDiagnostics* diag) { + if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) { + return NameIsJavaClassName(el, attr, diag); + } + diag->Error(DiagMessage(el->line_number) + << "<" << el->name << "> is missing attribute 'android:name'"); + return false; } -static bool requiredNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) { - if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name")) { - return nameIsJavaClassName(el, attr, diag); - } - diag->error(DiagMessage(el->lineNumber) - << "<" << el->name << "> is missing attribute 'android:name'"); +static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) { + xml::Attribute* attr = el->FindAttribute({}, "package"); + if (!attr) { + diag->Error(DiagMessage(el->line_number) + << "<manifest> tag is missing 'package' attribute"); + return false; + } else if (ResourceUtils::IsReference(attr->value)) { + diag->Error( + DiagMessage(el->line_number) + << "attribute 'package' in <manifest> tag must not be a reference"); return false; + } else if (!util::IsJavaPackageName(attr->value)) { + diag->Error(DiagMessage(el->line_number) + << "attribute 'package' in <manifest> tag is not a valid Java " + "package name: '" + << attr->value << "'"); + return false; + } + return true; } -static bool verifyManifest(xml::Element* el, SourcePathDiagnostics* diag) { - xml::Attribute* attr = el->findAttribute({}, u"package"); - if (!attr) { - diag->error(DiagMessage(el->lineNumber) << "<manifest> tag is missing 'package' attribute"); - return false; - } else if (ResourceUtils::isReference(attr->value)) { - diag->error(DiagMessage(el->lineNumber) - << "attribute 'package' in <manifest> tag must not be a reference"); - return false; - } else if (!util::isJavaPackageName(attr->value)) { - diag->error(DiagMessage(el->lineNumber) - << "attribute 'package' in <manifest> tag is not a valid Java package name: '" - << attr->value << "'"); - return false; +/** + * The coreApp attribute in <manifest> is not a regular AAPT attribute, so type + * checking on it is manual. + */ +static bool FixCoreAppAttribute(xml::Element* el, SourcePathDiagnostics* diag) { + if (xml::Attribute* attr = el->FindAttribute("", "coreApp")) { + std::unique_ptr<BinaryPrimitive> result = + ResourceUtils::TryParseBool(attr->value); + if (!result) { + diag->Error(DiagMessage(el->line_number) + << "attribute coreApp must be a boolean"); + return false; } - return true; + attr->compiled_value = std::move(result); + } + return true; } -bool ManifestFixer::buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag) { - // First verify some options. - if (mOptions.renameManifestPackage) { - if (!util::isJavaPackageName(mOptions.renameManifestPackage.value())) { - diag->error(DiagMessage() << "invalid manifest package override '" - << mOptions.renameManifestPackage.value() << "'"); - return false; - } +bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, + IDiagnostics* diag) { + // First verify some options. + if (options_.rename_manifest_package) { + if (!util::IsJavaPackageName(options_.rename_manifest_package.value())) { + diag->Error(DiagMessage() << "invalid manifest package override '" + << options_.rename_manifest_package.value() + << "'"); + return false; + } + } + + if (options_.rename_instrumentation_target_package) { + if (!util::IsJavaPackageName( + options_.rename_instrumentation_target_package.value())) { + diag->Error(DiagMessage() + << "invalid instrumentation target package override '" + << options_.rename_instrumentation_target_package.value() + << "'"); + return false; + } + } + + // Common intent-filter actions. + xml::XmlNodeAction intent_filter_action; + intent_filter_action["action"]; + intent_filter_action["category"]; + intent_filter_action["data"]; + + // Common meta-data actions. + xml::XmlNodeAction meta_data_action; + + // Manifest actions. + xml::XmlNodeAction& manifest_action = (*executor)["manifest"]; + manifest_action.Action(VerifyManifest); + manifest_action.Action(FixCoreAppAttribute); + manifest_action.Action([&](xml::Element* el) -> bool { + if (options_.version_name_default) { + if (el->FindAttribute(xml::kSchemaAndroid, "versionName") == nullptr) { + el->attributes.push_back( + xml::Attribute{xml::kSchemaAndroid, "versionName", + options_.version_name_default.value()}); + } } - if (mOptions.renameInstrumentationTargetPackage) { - if (!util::isJavaPackageName(mOptions.renameInstrumentationTargetPackage.value())) { - diag->error(DiagMessage() << "invalid instrumentation target package override '" - << mOptions.renameInstrumentationTargetPackage.value() << "'"); - return false; - } + if (options_.version_code_default) { + if (el->FindAttribute(xml::kSchemaAndroid, "versionCode") == nullptr) { + el->attributes.push_back( + xml::Attribute{xml::kSchemaAndroid, "versionCode", + options_.version_code_default.value()}); + } + } + return true; + }); + + // Meta tags. + manifest_action["eat-comment"]; + + // Uses-sdk actions. + manifest_action["uses-sdk"].Action([&](xml::Element* el) -> bool { + if (options_.min_sdk_version_default && + el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion") == nullptr) { + // There was no minSdkVersion defined and we have a default to assign. + el->attributes.push_back( + xml::Attribute{xml::kSchemaAndroid, "minSdkVersion", + options_.min_sdk_version_default.value()}); } - // Common intent-filter actions. - xml::XmlNodeAction intentFilterAction; - intentFilterAction[u"action"]; - intentFilterAction[u"category"]; - intentFilterAction[u"data"]; - - // Common meta-data actions. - xml::XmlNodeAction metaDataAction; - - // Manifest actions. - xml::XmlNodeAction& manifestAction = (*executor)[u"manifest"]; - manifestAction.action(verifyManifest); - manifestAction.action([&](xml::Element* el) -> bool { - if (mOptions.versionNameDefault) { - if (el->findAttribute(xml::kSchemaAndroid, u"versionName") == nullptr) { - el->attributes.push_back(xml::Attribute{ - xml::kSchemaAndroid, - u"versionName", - mOptions.versionNameDefault.value() }); - } - } + if (options_.target_sdk_version_default && + el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion") == nullptr) { + // There was no targetSdkVersion defined and we have a default to assign. + el->attributes.push_back( + xml::Attribute{xml::kSchemaAndroid, "targetSdkVersion", + options_.target_sdk_version_default.value()}); + } + return true; + }); - if (mOptions.versionCodeDefault) { - if (el->findAttribute(xml::kSchemaAndroid, u"versionCode") == nullptr) { - el->attributes.push_back(xml::Attribute{ - xml::kSchemaAndroid, - u"versionCode", - mOptions.versionCodeDefault.value() }); - } - } - return true; - }); - - // Meta tags. - manifestAction[u"eat-comment"]; - - // Uses-sdk actions. - manifestAction[u"uses-sdk"].action([&](xml::Element* el) -> bool { - if (mOptions.minSdkVersionDefault && - el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion") == nullptr) { - // There was no minSdkVersion defined and we have a default to assign. - el->attributes.push_back(xml::Attribute{ - xml::kSchemaAndroid, u"minSdkVersion", - mOptions.minSdkVersionDefault.value() }); - } + // Instrumentation actions. + manifest_action["instrumentation"].Action([&](xml::Element* el) -> bool { + if (!options_.rename_instrumentation_target_package) { + return true; + } - if (mOptions.targetSdkVersionDefault && - el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion") == nullptr) { - // There was no targetSdkVersion defined and we have a default to assign. - el->attributes.push_back(xml::Attribute{ - xml::kSchemaAndroid, u"targetSdkVersion", - mOptions.targetSdkVersionDefault.value() }); - } - return true; - }); + if (xml::Attribute* attr = + el->FindAttribute(xml::kSchemaAndroid, "targetPackage")) { + attr->value = options_.rename_instrumentation_target_package.value(); + } + return true; + }); - // Instrumentation actions. - manifestAction[u"instrumentation"].action([&](xml::Element* el) -> bool { - if (!mOptions.renameInstrumentationTargetPackage) { - return true; - } + manifest_action["original-package"]; + manifest_action["protected-broadcast"]; + manifest_action["uses-permission"]; + manifest_action["permission"]; + manifest_action["permission-tree"]; + manifest_action["permission-group"]; - if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"targetPackage")) { - attr->value = mOptions.renameInstrumentationTargetPackage.value(); - } - return true; - }); - - manifestAction[u"original-package"]; - manifestAction[u"protected-broadcast"]; - manifestAction[u"uses-permission"]; - manifestAction[u"permission"]; - manifestAction[u"permission-tree"]; - manifestAction[u"permission-group"]; - - manifestAction[u"uses-configuration"]; - manifestAction[u"uses-feature"]; - manifestAction[u"supports-screens"]; - manifestAction[u"compatible-screens"]; - manifestAction[u"supports-gl-texture"]; - - // Application actions. - xml::XmlNodeAction& applicationAction = (*executor)[u"manifest"][u"application"]; - applicationAction.action(optionalNameIsJavaClassName); - - // Uses library actions. - applicationAction[u"uses-library"]; - - // Activity actions. - applicationAction[u"activity"].action(requiredNameIsJavaClassName); - applicationAction[u"activity"][u"intent-filter"] = intentFilterAction; - applicationAction[u"activity"][u"meta-data"] = metaDataAction; - - // Activity alias actions. - applicationAction[u"activity-alias"][u"intent-filter"] = intentFilterAction; - applicationAction[u"activity-alias"][u"meta-data"] = metaDataAction; - - // Service actions. - applicationAction[u"service"].action(requiredNameIsJavaClassName); - applicationAction[u"service"][u"intent-filter"] = intentFilterAction; - applicationAction[u"service"][u"meta-data"] = metaDataAction; - - // Receiver actions. - applicationAction[u"receiver"].action(requiredNameIsJavaClassName); - applicationAction[u"receiver"][u"intent-filter"] = intentFilterAction; - applicationAction[u"receiver"][u"meta-data"] = metaDataAction; - - // Provider actions. - applicationAction[u"provider"].action(requiredNameIsJavaClassName); - applicationAction[u"provider"][u"grant-uri-permissions"]; - applicationAction[u"provider"][u"meta-data"] = metaDataAction; - applicationAction[u"provider"][u"path-permissions"]; - return true; -} + manifest_action["uses-configuration"]; + manifest_action["uses-feature"]; + manifest_action["supports-screens"]; -class FullyQualifiedClassNameVisitor : public xml::Visitor { -public: - using xml::Visitor::visit; + manifest_action["compatible-screens"]; + manifest_action["compatible-screens"]["screen"]; - FullyQualifiedClassNameVisitor(const StringPiece16& package) : mPackage(package) { - } + manifest_action["supports-gl-texture"]; - void visit(xml::Element* el) override { - for (xml::Attribute& attr : el->attributes) { - if (Maybe<std::u16string> newValue = - util::getFullyQualifiedClassName(mPackage, attr.value)) { - attr.value = std::move(newValue.value()); - } - } + // Application actions. + xml::XmlNodeAction& application_action = manifest_action["application"]; + application_action.Action(OptionalNameIsJavaClassName); - // Super implementation to iterate over the children. - xml::Visitor::visit(el); - } + // Uses library actions. + application_action["uses-library"]; -private: - StringPiece16 mPackage; -}; + // Meta-data. + application_action["meta-data"] = meta_data_action; -static bool renameManifestPackage(const StringPiece16& packageOverride, xml::Element* manifestEl) { - xml::Attribute* attr = manifestEl->findAttribute({}, u"package"); + // Activity actions. + application_action["activity"].Action(RequiredNameIsJavaClassName); + application_action["activity"]["intent-filter"] = intent_filter_action; + application_action["activity"]["meta-data"] = meta_data_action; - // We've already verified that the manifest element is present, with a package name specified. - assert(attr); + // Activity alias actions. + application_action["activity-alias"]["intent-filter"] = intent_filter_action; + application_action["activity-alias"]["meta-data"] = meta_data_action; - std::u16string originalPackage = std::move(attr->value); - attr->value = packageOverride.toString(); + // Service actions. + application_action["service"].Action(RequiredNameIsJavaClassName); + application_action["service"]["intent-filter"] = intent_filter_action; + application_action["service"]["meta-data"] = meta_data_action; - FullyQualifiedClassNameVisitor visitor(originalPackage); - manifestEl->accept(&visitor); - return true; + // Receiver actions. + application_action["receiver"].Action(RequiredNameIsJavaClassName); + application_action["receiver"]["intent-filter"] = intent_filter_action; + application_action["receiver"]["meta-data"] = meta_data_action; + + // Provider actions. + application_action["provider"].Action(RequiredNameIsJavaClassName); + application_action["provider"]["intent-filter"] = intent_filter_action; + application_action["provider"]["meta-data"] = meta_data_action; + application_action["provider"]["grant-uri-permissions"]; + application_action["provider"]["path-permissions"]; + + return true; } -bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) { - xml::Element* root = xml::findRootElement(doc->root.get()); - if (!root || !root->namespaceUri.empty() || root->name != u"manifest") { - context->getDiagnostics()->error(DiagMessage(doc->file.source) - << "root tag must be <manifest>"); - return false; +class FullyQualifiedClassNameVisitor : public xml::Visitor { + public: + using xml::Visitor::Visit; + + explicit FullyQualifiedClassNameVisitor(const StringPiece& package) + : package_(package) {} + + void Visit(xml::Element* el) override { + for (xml::Attribute& attr : el->attributes) { + if (attr.namespace_uri == xml::kSchemaAndroid && + class_attributes_.find(attr.name) != class_attributes_.end()) { + if (Maybe<std::string> new_value = + util::GetFullyQualifiedClassName(package_, attr.value)) { + attr.value = std::move(new_value.value()); + } + } } - if ((mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault) - && root->findChild({}, u"uses-sdk") == nullptr) { - // Auto insert a <uses-sdk> element. - std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>(); - usesSdk->name = u"uses-sdk"; - root->addChild(std::move(usesSdk)); - } + // Super implementation to iterate over the children. + xml::Visitor::Visit(el); + } - xml::XmlActionExecutor executor; - if (!buildRules(&executor, context->getDiagnostics())) { - return false; - } + private: + StringPiece package_; + std::unordered_set<StringPiece> class_attributes_ = {"name"}; +}; - if (!executor.execute(xml::XmlActionExecutorPolicy::Whitelist, context->getDiagnostics(), - doc)) { - return false; - } +static bool RenameManifestPackage(const StringPiece& package_override, + xml::Element* manifest_el) { + xml::Attribute* attr = manifest_el->FindAttribute({}, "package"); - if (mOptions.renameManifestPackage) { - // Rename manifest package outside of the XmlActionExecutor. - // We need to extract the old package name and FullyQualify all class names. - if (!renameManifestPackage(mOptions.renameManifestPackage.value(), root)) { - return false; - } + // We've already verified that the manifest element is present, with a package + // name specified. + CHECK(attr != nullptr); + + std::string original_package = std::move(attr->value); + attr->value = package_override.ToString(); + + FullyQualifiedClassNameVisitor visitor(original_package); + manifest_el->Accept(&visitor); + return true; +} + +bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) { + xml::Element* root = xml::FindRootElement(doc->root.get()); + if (!root || !root->namespace_uri.empty() || root->name != "manifest") { + context->GetDiagnostics()->Error(DiagMessage(doc->file.source) + << "root tag must be <manifest>"); + return false; + } + + if ((options_.min_sdk_version_default || + options_.target_sdk_version_default) && + root->FindChild({}, "uses-sdk") == nullptr) { + // Auto insert a <uses-sdk> element. This must be inserted before the + // <application> tag. The device runtime PackageParser will make SDK version + // decisions while parsing <application>. + std::unique_ptr<xml::Element> uses_sdk = util::make_unique<xml::Element>(); + uses_sdk->name = "uses-sdk"; + root->InsertChild(0, std::move(uses_sdk)); + } + + xml::XmlActionExecutor executor; + if (!BuildRules(&executor, context->GetDiagnostics())) { + return false; + } + + if (!executor.Execute(xml::XmlActionExecutorPolicy::kWhitelist, + context->GetDiagnostics(), doc)) { + return false; + } + + if (options_.rename_manifest_package) { + // Rename manifest package outside of the XmlActionExecutor. + // We need to extract the old package name and FullyQualify all class + // names. + if (!RenameManifestPackage(options_.rename_manifest_package.value(), + root)) { + return false; } - return true; + } + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h index 4d9356a933c2..470f65eb01c4 100644 --- a/tools/aapt2/link/ManifestFixer.h +++ b/tools/aapt2/link/ManifestFixer.h @@ -17,22 +17,24 @@ #ifndef AAPT_LINK_MANIFESTFIXER_H #define AAPT_LINK_MANIFESTFIXER_H +#include <string> + +#include "android-base/macros.h" + #include "process/IResourceTableConsumer.h" #include "util/Maybe.h" #include "xml/XmlActionExecutor.h" #include "xml/XmlDom.h" -#include <string> - namespace aapt { struct ManifestFixerOptions { - Maybe<std::u16string> minSdkVersionDefault; - Maybe<std::u16string> targetSdkVersionDefault; - Maybe<std::u16string> renameManifestPackage; - Maybe<std::u16string> renameInstrumentationTargetPackage; - Maybe<std::u16string> versionNameDefault; - Maybe<std::u16string> versionCodeDefault; + Maybe<std::string> min_sdk_version_default; + Maybe<std::string> target_sdk_version_default; + Maybe<std::string> rename_manifest_package; + Maybe<std::string> rename_instrumentation_target_package; + Maybe<std::string> version_name_default; + Maybe<std::string> version_code_default; }; /** @@ -40,18 +42,20 @@ struct ManifestFixerOptions { * where specified with ManifestFixerOptions. */ class ManifestFixer : public IXmlResourceConsumer { -public: - ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) { - } + public: + explicit ManifestFixer(const ManifestFixerOptions& options) + : options_(options) {} + + bool Consume(IAaptContext* context, xml::XmlResource* doc) override; - bool consume(IAaptContext* context, xml::XmlResource* doc) override; + private: + DISALLOW_COPY_AND_ASSIGN(ManifestFixer); -private: - bool buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag); + bool BuildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag); - ManifestFixerOptions mOptions; + ManifestFixerOptions options_; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_LINK_MANIFESTFIXER_H */ diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index f993720b9566..e9bc64acc542 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -15,242 +15,324 @@ */ #include "link/ManifestFixer.h" -#include "test/Builders.h" -#include "test/Context.h" -#include <gtest/gtest.h> +#include "test/Test.h" namespace aapt { struct ManifestFixerTest : public ::testing::Test { - std::unique_ptr<IAaptContext> mContext; - - void SetUp() override { - mContext = test::ContextBuilder() - .setCompilationPackage(u"android") - .setPackageId(0x01) - .setNameManglerPolicy(NameManglerPolicy{ u"android" }) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addSymbol(u"@android:attr/package", ResourceId(0x01010000), - test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_STRING) - .build()) - .addSymbol(u"@android:attr/minSdkVersion", ResourceId(0x01010001), - test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_STRING | - android::ResTable_map::TYPE_INTEGER) - .build()) - .addSymbol(u"@android:attr/targetSdkVersion", ResourceId(0x01010002), - test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_STRING | - android::ResTable_map::TYPE_INTEGER) - .build()) - .addSymbol(u"@android:string/str", ResourceId(0x01060000)) - .build()) - .build(); - } - - std::unique_ptr<xml::XmlResource> verify(const StringPiece& str) { - return verifyWithOptions(str, {}); - } - - std::unique_ptr<xml::XmlResource> verifyWithOptions(const StringPiece& str, - const ManifestFixerOptions& options) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(str); - ManifestFixer fixer(options); - if (fixer.consume(mContext.get(), doc.get())) { - return doc; - } - return {}; + std::unique_ptr<IAaptContext> mContext; + + void SetUp() override { + mContext = + test::ContextBuilder() + .SetCompilationPackage("android") + .SetPackageId(0x01) + .SetNameManglerPolicy(NameManglerPolicy{"android"}) + .AddSymbolSource( + test::StaticSymbolSourceBuilder() + .AddSymbol( + "android:attr/package", ResourceId(0x01010000), + test::AttributeBuilder() + .SetTypeMask(android::ResTable_map::TYPE_STRING) + .Build()) + .AddSymbol( + "android:attr/minSdkVersion", ResourceId(0x01010001), + test::AttributeBuilder() + .SetTypeMask(android::ResTable_map::TYPE_STRING | + android::ResTable_map::TYPE_INTEGER) + .Build()) + .AddSymbol( + "android:attr/targetSdkVersion", ResourceId(0x01010002), + test::AttributeBuilder() + .SetTypeMask(android::ResTable_map::TYPE_STRING | + android::ResTable_map::TYPE_INTEGER) + .Build()) + .AddSymbol("android:string/str", ResourceId(0x01060000)) + .Build()) + .Build(); + } + + std::unique_ptr<xml::XmlResource> Verify(const StringPiece& str) { + return VerifyWithOptions(str, {}); + } + + std::unique_ptr<xml::XmlResource> VerifyWithOptions( + const StringPiece& str, const ManifestFixerOptions& options) { + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(str); + ManifestFixer fixer(options); + if (fixer.Consume(mContext.get(), doc.get())) { + return doc; } + return {}; + } }; TEST_F(ManifestFixerTest, EnsureManifestIsRootTag) { - EXPECT_EQ(nullptr, verify("<other-tag />")); - EXPECT_EQ(nullptr, verify("<ns:manifest xmlns:ns=\"com\" />")); - EXPECT_NE(nullptr, verify("<manifest package=\"android\"></manifest>")); + EXPECT_EQ(nullptr, Verify("<other-tag />")); + EXPECT_EQ(nullptr, Verify("<ns:manifest xmlns:ns=\"com\" />")); + EXPECT_NE(nullptr, Verify("<manifest package=\"android\"></manifest>")); } TEST_F(ManifestFixerTest, EnsureManifestHasPackage) { - EXPECT_NE(nullptr, verify("<manifest package=\"android\" />")); - EXPECT_NE(nullptr, verify("<manifest package=\"com.android\" />")); - EXPECT_NE(nullptr, verify("<manifest package=\"com.android.google\" />")); - EXPECT_EQ(nullptr, verify("<manifest package=\"com.android.google.Class$1\" />")); - EXPECT_EQ(nullptr, - verify("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" " - "android:package=\"com.android\" />")); - EXPECT_EQ(nullptr, verify("<manifest package=\"@string/str\" />")); + EXPECT_NE(nullptr, Verify("<manifest package=\"android\" />")); + EXPECT_NE(nullptr, Verify("<manifest package=\"com.android\" />")); + EXPECT_NE(nullptr, Verify("<manifest package=\"com.android.google\" />")); + EXPECT_EQ(nullptr, + Verify("<manifest package=\"com.android.google.Class$1\" />")); + EXPECT_EQ(nullptr, Verify("<manifest " + "xmlns:android=\"http://schemas.android.com/apk/" + "res/android\" " + "android:package=\"com.android\" />")); + EXPECT_EQ(nullptr, Verify("<manifest package=\"@string/str\" />")); } TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { - ManifestFixerOptions options = { std::u16string(u"8"), std::u16string(u"22") }; + ManifestFixerOptions options = {std::string("8"), std::string("22")}; - std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF( + std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" /> - </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); - - xml::Element* el; - xml::Attribute* attr; - - el = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, el); - el = el->findChild({}, u"uses-sdk"); - ASSERT_NE(nullptr, el); - attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(u"7", attr->value); - attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(u"21", attr->value); - - doc = verifyWithOptions(R"EOF( + </manifest>)EOF", + options); + ASSERT_NE(nullptr, doc); + + xml::Element* el; + xml::Attribute* attr; + + el = xml::FindRootElement(doc.get()); + ASSERT_NE(nullptr, el); + el = el->FindChild({}, "uses-sdk"); + ASSERT_NE(nullptr, el); + attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("7", attr->value); + attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("21", attr->value); + + doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <uses-sdk android:targetSdkVersion="21" /> - </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); - - el = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, el); - el = el->findChild({}, u"uses-sdk"); - ASSERT_NE(nullptr, el); - attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(u"8", attr->value); - attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(u"21", attr->value); - - doc = verifyWithOptions(R"EOF( + </manifest>)EOF", + options); + ASSERT_NE(nullptr, doc); + + el = xml::FindRootElement(doc.get()); + ASSERT_NE(nullptr, el); + el = el->FindChild({}, "uses-sdk"); + ASSERT_NE(nullptr, el); + attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("8", attr->value); + attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("21", attr->value); + + doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <uses-sdk /> - </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); - - el = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, el); - el = el->findChild({}, u"uses-sdk"); - ASSERT_NE(nullptr, el); - attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(u"8", attr->value); - attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(u"22", attr->value); - - doc = verifyWithOptions(R"EOF( + </manifest>)EOF", + options); + ASSERT_NE(nullptr, doc); + + el = xml::FindRootElement(doc.get()); + ASSERT_NE(nullptr, el); + el = el->FindChild({}, "uses-sdk"); + ASSERT_NE(nullptr, el); + attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("8", attr->value); + attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("22", attr->value); + + doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android" />)EOF", options); - ASSERT_NE(nullptr, doc); - - el = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, el); - el = el->findChild({}, u"uses-sdk"); - ASSERT_NE(nullptr, el); - attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(u"8", attr->value); - attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(u"22", attr->value); + package="android" />)EOF", + options); + ASSERT_NE(nullptr, doc); + + el = xml::FindRootElement(doc.get()); + ASSERT_NE(nullptr, el); + el = el->FindChild({}, "uses-sdk"); + ASSERT_NE(nullptr, el); + attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("8", attr->value); + attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("22", attr->value); +} + +TEST_F(ManifestFixerTest, UsesSdkMustComeBeforeApplication) { + ManifestFixerOptions options = {std::string("8"), std::string("22")}; + std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application android:name=".MainApplication" /> + </manifest>)EOF", + options); + ASSERT_NE(nullptr, doc); + + xml::Element* manifest_el = xml::FindRootElement(doc.get()); + ASSERT_NE(nullptr, manifest_el); + ASSERT_EQ("manifest", manifest_el->name); + + xml::Element* application_el = manifest_el->FindChild("", "application"); + ASSERT_NE(nullptr, application_el); + + xml::Element* uses_sdk_el = manifest_el->FindChild("", "uses-sdk"); + ASSERT_NE(nullptr, uses_sdk_el); + + // Check that the uses_sdk_el comes before application_el in the children + // vector. + // Since there are no namespaces here, these children are direct descendants + // of manifest. + auto uses_sdk_iter = + std::find_if(manifest_el->children.begin(), manifest_el->children.end(), + [&](const std::unique_ptr<xml::Node>& child) { + return child.get() == uses_sdk_el; + }); + + auto application_iter = + std::find_if(manifest_el->children.begin(), manifest_el->children.end(), + [&](const std::unique_ptr<xml::Node>& child) { + return child.get() == application_el; + }); + + ASSERT_NE(manifest_el->children.end(), uses_sdk_iter); + ASSERT_NE(manifest_el->children.end(), application_iter); + + // The distance should be positive, meaning uses_sdk_iter comes before + // application_iter. + EXPECT_GT(std::distance(uses_sdk_iter, application_iter), 0); } TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) { - ManifestFixerOptions options; - options.renameManifestPackage = std::u16string(u"com.android"); + ManifestFixerOptions options; + options.rename_manifest_package = std::string("com.android"); - std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF( + std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application android:name=".MainApplication" text="hello"> <activity android:name=".activity.Start" /> <receiver android:name="com.google.android.Receiver" /> </application> - </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); + </manifest>)EOF", + options); + ASSERT_NE(nullptr, doc); - xml::Element* manifestEl = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, manifestEl); + xml::Element* manifestEl = xml::FindRootElement(doc.get()); + ASSERT_NE(nullptr, manifestEl); - xml::Attribute* attr = nullptr; + xml::Attribute* attr = nullptr; - attr = manifestEl->findAttribute({}, u"package"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::u16string(u"com.android"), attr->value); + attr = manifestEl->FindAttribute({}, "package"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::string("com.android"), attr->value); - xml::Element* applicationEl = manifestEl->findChild({}, u"application"); - ASSERT_NE(nullptr, applicationEl); + xml::Element* applicationEl = manifestEl->FindChild({}, "application"); + ASSERT_NE(nullptr, applicationEl); - attr = applicationEl->findAttribute(xml::kSchemaAndroid, u"name"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::u16string(u"android.MainApplication"), attr->value); + attr = applicationEl->FindAttribute(xml::kSchemaAndroid, "name"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::string("android.MainApplication"), attr->value); - attr = applicationEl->findAttribute({}, u"text"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::u16string(u"hello"), attr->value); + attr = applicationEl->FindAttribute({}, "text"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::string("hello"), attr->value); - xml::Element* el; - el = applicationEl->findChild({}, u"activity"); - ASSERT_NE(nullptr, el); + xml::Element* el; + el = applicationEl->FindChild({}, "activity"); + ASSERT_NE(nullptr, el); - attr = el->findAttribute(xml::kSchemaAndroid, u"name"); - ASSERT_NE(nullptr, el); - EXPECT_EQ(std::u16string(u"android.activity.Start"), attr->value); + attr = el->FindAttribute(xml::kSchemaAndroid, "name"); + ASSERT_NE(nullptr, el); + EXPECT_EQ(std::string("android.activity.Start"), attr->value); - el = applicationEl->findChild({}, u"receiver"); - ASSERT_NE(nullptr, el); + el = applicationEl->FindChild({}, "receiver"); + ASSERT_NE(nullptr, el); - attr = el->findAttribute(xml::kSchemaAndroid, u"name"); - ASSERT_NE(nullptr, el); - EXPECT_EQ(std::u16string(u"com.google.android.Receiver"), attr->value); + attr = el->FindAttribute(xml::kSchemaAndroid, "name"); + ASSERT_NE(nullptr, el); + EXPECT_EQ(std::string("com.google.android.Receiver"), attr->value); } -TEST_F(ManifestFixerTest, RenameManifestInstrumentationPackageAndFullyQualifyTarget) { - ManifestFixerOptions options; - options.renameInstrumentationTargetPackage = std::u16string(u"com.android"); +TEST_F(ManifestFixerTest, + RenameManifestInstrumentationPackageAndFullyQualifyTarget) { + ManifestFixerOptions options; + options.rename_instrumentation_target_package = std::string("com.android"); - std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF( + std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <instrumentation android:targetPackage="android" /> - </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); + </manifest>)EOF", + options); + ASSERT_NE(nullptr, doc); - xml::Element* manifestEl = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, manifestEl); + xml::Element* manifest_el = xml::FindRootElement(doc.get()); + ASSERT_NE(nullptr, manifest_el); - xml::Element* instrumentationEl = manifestEl->findChild({}, u"instrumentation"); - ASSERT_NE(nullptr, instrumentationEl); + xml::Element* instrumentation_el = + manifest_el->FindChild({}, "instrumentation"); + ASSERT_NE(nullptr, instrumentation_el); - xml::Attribute* attr = instrumentationEl->findAttribute(xml::kSchemaAndroid, u"targetPackage"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::u16string(u"com.android"), attr->value); + xml::Attribute* attr = + instrumentation_el->FindAttribute(xml::kSchemaAndroid, "targetPackage"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::string("com.android"), attr->value); } TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) { - ManifestFixerOptions options; - options.versionNameDefault = std::u16string(u"Beta"); - options.versionCodeDefault = std::u16string(u"0x10000000"); + ManifestFixerOptions options; + options.version_name_default = std::string("Beta"); + options.version_code_default = std::string("0x10000000"); - std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF( + std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android" />)EOF", options); - ASSERT_NE(nullptr, doc); + package="android" />)EOF", + options); + ASSERT_NE(nullptr, doc); + + xml::Element* manifest_el = xml::FindRootElement(doc.get()); + ASSERT_NE(nullptr, manifest_el); + + xml::Attribute* attr = + manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::string("Beta"), attr->value); + + attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::string("0x10000000"), attr->value); +} + +TEST_F(ManifestFixerTest, EnsureManifestAttributesAreTyped) { + EXPECT_EQ(nullptr, + Verify("<manifest package=\"android\" coreApp=\"hello\" />")); + EXPECT_EQ(nullptr, + Verify("<manifest package=\"android\" coreApp=\"1dp\" />")); + + std::unique_ptr<xml::XmlResource> doc = + Verify("<manifest package=\"android\" coreApp=\"true\" />"); + ASSERT_NE(nullptr, doc); + + xml::Element* el = xml::FindRootElement(doc.get()); + ASSERT_NE(nullptr, el); - xml::Element* manifestEl = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, manifestEl); + EXPECT_EQ("manifest", el->name); - xml::Attribute* attr = manifestEl->findAttribute(xml::kSchemaAndroid, u"versionName"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::u16string(u"Beta"), attr->value); + xml::Attribute* attr = el->FindAttribute("", "coreApp"); + ASSERT_NE(nullptr, attr); - attr = manifestEl->findAttribute(xml::kSchemaAndroid, u"versionCode"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::u16string(u"0x10000000"), attr->value); + EXPECT_NE(nullptr, attr->compiled_value); + EXPECT_NE(nullptr, ValueCast<BinaryPrimitive>(attr->compiled_value.get())); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp index 3c8af4f81ffe..cc07a6e1925b 100644 --- a/tools/aapt2/link/PrivateAttributeMover.cpp +++ b/tools/aapt2/link/PrivateAttributeMover.cpp @@ -14,67 +14,74 @@ * limitations under the License. */ -#include "ResourceTable.h" #include "link/Linkers.h" #include <algorithm> #include <iterator> +#include "android-base/logging.h" + +#include "ResourceTable.h" + namespace aapt { template <typename InputContainer, typename OutputIterator, typename Predicate> -OutputIterator moveIf(InputContainer& inputContainer, OutputIterator result, - Predicate pred) { - const auto last = inputContainer.end(); - auto newEnd = std::find_if(inputContainer.begin(), inputContainer.end(), pred); - if (newEnd == last) { - return result; - } +OutputIterator move_if(InputContainer& input_container, OutputIterator result, + Predicate pred) { + const auto last = input_container.end(); + auto new_end = + std::find_if(input_container.begin(), input_container.end(), pred); + if (new_end == last) { + return result; + } - *result = std::move(*newEnd); + *result = std::move(*new_end); - auto first = newEnd; - ++first; + auto first = new_end; + ++first; - for (; first != last; ++first) { - if (bool(pred(*first))) { - // We want to move this guy - *result = std::move(*first); - ++result; - } else { - // We want to keep this guy, but we will need to move it up the list to replace - // missing items. - *newEnd = std::move(*first); - ++newEnd; - } + for (; first != last; ++first) { + if (bool(pred(*first))) { + // We want to move this guy + *result = std::move(*first); + ++result; + } else { + // We want to keep this guy, but we will need to move it up the list to + // replace missing items. + *new_end = std::move(*first); + ++new_end; } + } - inputContainer.erase(newEnd, last); - return result; + input_container.erase(new_end, last); + return result; } -bool PrivateAttributeMover::consume(IAaptContext* context, ResourceTable* table) { - for (auto& package : table->packages) { - ResourceTableType* type = package->findType(ResourceType::kAttr); - if (!type) { - continue; - } +bool PrivateAttributeMover::Consume(IAaptContext* context, + ResourceTable* table) { + for (auto& package : table->packages) { + ResourceTableType* type = package->FindType(ResourceType::kAttr); + if (!type) { + continue; + } - if (type->symbolStatus.state != SymbolState::kPublic) { - // No public attributes, so we can safely leave these private attributes where they are. - return true; - } + if (type->symbol_status.state != SymbolState::kPublic) { + // No public attributes, so we can safely leave these private attributes + // where they are. + return true; + } - ResourceTableType* privAttrType = package->findOrCreateType(ResourceType::kAttrPrivate); - assert(privAttrType->entries.empty()); + ResourceTableType* priv_attr_type = + package->FindOrCreateType(ResourceType::kAttrPrivate); + CHECK(priv_attr_type->entries.empty()); - moveIf(type->entries, std::back_inserter(privAttrType->entries), - [](const std::unique_ptr<ResourceEntry>& entry) -> bool { - return entry->symbolStatus.state != SymbolState::kPublic; - }); - break; - } - return true; + move_if(type->entries, std::back_inserter(priv_attr_type->entries), + [](const std::unique_ptr<ResourceEntry>& entry) -> bool { + return entry->symbol_status.state != SymbolState::kPublic; + }); + break; + } + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp index dbe0c92253c1..90c4922625be 100644 --- a/tools/aapt2/link/PrivateAttributeMover_test.cpp +++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp @@ -15,64 +15,66 @@ */ #include "link/Linkers.h" -#include "test/Builders.h" -#include "test/Context.h" -#include <gtest/gtest.h> +#include "test/Test.h" namespace aapt { TEST(PrivateAttributeMoverTest, MovePrivateAttributes) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addSimple(u"@android:attr/publicA") - .addSimple(u"@android:attr/privateA") - .addSimple(u"@android:attr/publicB") - .addSimple(u"@android:attr/privateB") - .setSymbolState(u"@android:attr/publicA", ResourceId(0x01010000), SymbolState::kPublic) - .setSymbolState(u"@android:attr/publicB", ResourceId(0x01010000), SymbolState::kPublic) - .build(); - - PrivateAttributeMover mover; - ASSERT_TRUE(mover.consume(context.get(), table.get())); - - ResourceTablePackage* package = table->findPackage(u"android"); - ASSERT_NE(package, nullptr); - - ResourceTableType* type = package->findType(ResourceType::kAttr); - ASSERT_NE(type, nullptr); - ASSERT_EQ(type->entries.size(), 2u); - EXPECT_NE(type->findEntry(u"publicA"), nullptr); - EXPECT_NE(type->findEntry(u"publicB"), nullptr); - - type = package->findType(ResourceType::kAttrPrivate); - ASSERT_NE(type, nullptr); - ASSERT_EQ(type->entries.size(), 2u); - EXPECT_NE(type->findEntry(u"privateA"), nullptr); - EXPECT_NE(type->findEntry(u"privateB"), nullptr); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddSimple("android:attr/publicA") + .AddSimple("android:attr/privateA") + .AddSimple("android:attr/publicB") + .AddSimple("android:attr/privateB") + .SetSymbolState("android:attr/publicA", ResourceId(0x01010000), + SymbolState::kPublic) + .SetSymbolState("android:attr/publicB", ResourceId(0x01010000), + SymbolState::kPublic) + .Build(); + + PrivateAttributeMover mover; + ASSERT_TRUE(mover.Consume(context.get(), table.get())); + + ResourceTablePackage* package = table->FindPackage("android"); + ASSERT_NE(package, nullptr); + + ResourceTableType* type = package->FindType(ResourceType::kAttr); + ASSERT_NE(type, nullptr); + ASSERT_EQ(type->entries.size(), 2u); + EXPECT_NE(type->FindEntry("publicA"), nullptr); + EXPECT_NE(type->FindEntry("publicB"), nullptr); + + type = package->FindType(ResourceType::kAttrPrivate); + ASSERT_NE(type, nullptr); + ASSERT_EQ(type->entries.size(), 2u); + EXPECT_NE(type->FindEntry("privateA"), nullptr); + EXPECT_NE(type->FindEntry("privateB"), nullptr); } -TEST(PrivateAttributeMoverTest, LeavePrivateAttributesWhenNoPublicAttributesDefined) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); +TEST(PrivateAttributeMoverTest, + LeavePrivateAttributesWhenNoPublicAttributesDefined) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addSimple(u"@android:attr/privateA") - .addSimple(u"@android:attr/privateB") - .build(); + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .AddSimple("android:attr/privateA") + .AddSimple("android:attr/privateB") + .Build(); - PrivateAttributeMover mover; - ASSERT_TRUE(mover.consume(context.get(), table.get())); + PrivateAttributeMover mover; + ASSERT_TRUE(mover.Consume(context.get(), table.get())); - ResourceTablePackage* package = table->findPackage(u"android"); - ASSERT_NE(package, nullptr); + ResourceTablePackage* package = table->FindPackage("android"); + ASSERT_NE(package, nullptr); - ResourceTableType* type = package->findType(ResourceType::kAttr); - ASSERT_NE(type, nullptr); - ASSERT_EQ(type->entries.size(), 2u); + ResourceTableType* type = package->FindType(ResourceType::kAttr); + ASSERT_NE(type, nullptr); + ASSERT_EQ(type->entries.size(), 2u); - type = package->findType(ResourceType::kAttrPrivate); - ASSERT_EQ(type, nullptr); + type = package->FindType(ResourceType::kAttrPrivate); + ASSERT_EQ(type, nullptr); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/ProductFilter.cpp b/tools/aapt2/link/ProductFilter.cpp index 8784e891b293..c1a95ee1bcec 100644 --- a/tools/aapt2/link/ProductFilter.cpp +++ b/tools/aapt2/link/ProductFilter.cpp @@ -14,105 +14,110 @@ * limitations under the License. */ -#include "link/ProductFilter.h" +#include "link/Linkers.h" -namespace aapt { - -ProductFilter::ResourceConfigValueIter -ProductFilter::selectProductToKeep(const ResourceNameRef& name, - const ResourceConfigValueIter begin, - const ResourceConfigValueIter end, - IDiagnostics* diag) { - ResourceConfigValueIter defaultProductIter = end; - ResourceConfigValueIter selectedProductIter = end; - - for (ResourceConfigValueIter iter = begin; iter != end; ++iter) { - ResourceConfigValue* configValue = iter->get(); - if (mProducts.find(configValue->product) != mProducts.end()) { - if (selectedProductIter != end) { - // We have two possible values for this product! - diag->error(DiagMessage(configValue->value->getSource()) - << "selection of product '" << configValue->product - << "' for resource " << name << " is ambiguous"); - - ResourceConfigValue* previouslySelectedConfigValue = selectedProductIter->get(); - diag->note(DiagMessage(previouslySelectedConfigValue->value->getSource()) - << "product '" << previouslySelectedConfigValue->product - << "' is also a candidate"); - return end; - } +#include "ResourceTable.h" - // Select this product. - selectedProductIter = iter; - } - - if (configValue->product.empty() || configValue->product == "default") { - if (defaultProductIter != end) { - // We have two possible default values. - diag->error(DiagMessage(configValue->value->getSource()) - << "multiple default products defined for resource " << name); +namespace aapt { - ResourceConfigValue* previouslyDefaultConfigValue = defaultProductIter->get(); - diag->note(DiagMessage(previouslyDefaultConfigValue->value->getSource()) - << "default product also defined here"); - return end; - } +ProductFilter::ResourceConfigValueIter ProductFilter::SelectProductToKeep( + const ResourceNameRef& name, const ResourceConfigValueIter begin, + const ResourceConfigValueIter end, IDiagnostics* diag) { + ResourceConfigValueIter default_product_iter = end; + ResourceConfigValueIter selected_product_iter = end; + + for (ResourceConfigValueIter iter = begin; iter != end; ++iter) { + ResourceConfigValue* config_value = iter->get(); + if (products_.find(config_value->product) != products_.end()) { + if (selected_product_iter != end) { + // We have two possible values for this product! + diag->Error(DiagMessage(config_value->value->GetSource()) + << "selection of product '" << config_value->product + << "' for resource " << name << " is ambiguous"); + + ResourceConfigValue* previously_selected_config_value = + selected_product_iter->get(); + diag->Note( + DiagMessage(previously_selected_config_value->value->GetSource()) + << "product '" << previously_selected_config_value->product + << "' is also a candidate"); + return end; + } - // Mark the default. - defaultProductIter = iter; - } + // Select this product. + selected_product_iter = iter; } - if (defaultProductIter == end) { - diag->error(DiagMessage() << "no default product defined for resource " << name); + if (config_value->product.empty() || config_value->product == "default") { + if (default_product_iter != end) { + // We have two possible default values. + diag->Error(DiagMessage(config_value->value->GetSource()) + << "multiple default products defined for resource " + << name); + + ResourceConfigValue* previously_default_config_value = + default_product_iter->get(); + diag->Note( + DiagMessage(previously_default_config_value->value->GetSource()) + << "default product also defined here"); return end; - } + } - if (selectedProductIter == end) { - selectedProductIter = defaultProductIter; + // Mark the default. + default_product_iter = iter; } - return selectedProductIter; + } + + if (default_product_iter == end) { + diag->Error(DiagMessage() << "no default product defined for resource " + << name); + return end; + } + + if (selected_product_iter == end) { + selected_product_iter = default_product_iter; + } + return selected_product_iter; } -bool ProductFilter::consume(IAaptContext* context, ResourceTable* table) { - bool error = false; - for (auto& pkg : table->packages) { - for (auto& type : pkg->types) { - for (auto& entry : type->entries) { - std::vector<std::unique_ptr<ResourceConfigValue>> newValues; - - ResourceConfigValueIter iter = entry->values.begin(); - ResourceConfigValueIter startRangeIter = iter; - while (iter != entry->values.end()) { - ++iter; - if (iter == entry->values.end() || - (*iter)->config != (*startRangeIter)->config) { - - // End of the array, or we saw a different config, - // so this must be the end of a range of products. - // Select the product to keep from the set of products defined. - ResourceNameRef name(pkg->name, type->type, entry->name); - auto valueToKeep = selectProductToKeep(name, startRangeIter, iter, - context->getDiagnostics()); - if (valueToKeep == iter) { - // An error occurred, we could not pick a product. - error = true; - } else { - // We selected a product to keep. Move it to the new array. - newValues.push_back(std::move(*valueToKeep)); - } - - // Start the next range of products. - startRangeIter = iter; - } - } - - // Now move the new values in to place. - entry->values = std::move(newValues); +bool ProductFilter::Consume(IAaptContext* context, ResourceTable* table) { + bool error = false; + for (auto& pkg : table->packages) { + for (auto& type : pkg->types) { + for (auto& entry : type->entries) { + std::vector<std::unique_ptr<ResourceConfigValue>> new_values; + + ResourceConfigValueIter iter = entry->values.begin(); + ResourceConfigValueIter start_range_iter = iter; + while (iter != entry->values.end()) { + ++iter; + if (iter == entry->values.end() || + (*iter)->config != (*start_range_iter)->config) { + // End of the array, or we saw a different config, + // so this must be the end of a range of products. + // Select the product to keep from the set of products defined. + ResourceNameRef name(pkg->name, type->type, entry->name); + auto value_to_keep = SelectProductToKeep( + name, start_range_iter, iter, context->GetDiagnostics()); + if (value_to_keep == iter) { + // An error occurred, we could not pick a product. + error = true; + } else { + // We selected a product to keep. Move it to the new array. + new_values.push_back(std::move(*value_to_keep)); } + + // Start the next range of products. + start_range_iter = iter; + } } + + // Now move the new values in to place. + entry->values = std::move(new_values); + } } - return !error; + } + return !error; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/ProductFilter.h b/tools/aapt2/link/ProductFilter.h deleted file mode 100644 index d2edd38289dc..000000000000 --- a/tools/aapt2/link/ProductFilter.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2016 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. - */ - -#ifndef AAPT_LINK_PRODUCTFILTER_H -#define AAPT_LINK_PRODUCTFILTER_H - -#include "ResourceTable.h" -#include "process/IResourceTableConsumer.h" - -#include <android-base/macros.h> -#include <unordered_set> - -namespace aapt { - -class ProductFilter { -public: - using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator; - - ProductFilter(std::unordered_set<std::string> products) : mProducts(products) { } - - ResourceConfigValueIter selectProductToKeep(const ResourceNameRef& name, - const ResourceConfigValueIter begin, - const ResourceConfigValueIter end, - IDiagnostics* diag); - - bool consume(IAaptContext* context, ResourceTable* table); - -private: - std::unordered_set<std::string> mProducts; - - DISALLOW_COPY_AND_ASSIGN(ProductFilter); -}; - -} // namespace aapt - -#endif /* AAPT_LINK_PRODUCTFILTER_H */ diff --git a/tools/aapt2/link/ProductFilter_test.cpp b/tools/aapt2/link/ProductFilter_test.cpp index f4f756ae4519..379ad26836e8 100644 --- a/tools/aapt2/link/ProductFilter_test.cpp +++ b/tools/aapt2/link/ProductFilter_test.cpp @@ -14,123 +14,117 @@ * limitations under the License. */ -#include "link/ProductFilter.h" -#include "test/Builders.h" -#include "test/Context.h" +#include "link/Linkers.h" -#include <gtest/gtest.h> +#include "test/Test.h" namespace aapt { TEST(ProductFilterTest, SelectTwoProducts) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - - const ConfigDescription land = test::parseConfigOrDie("land"); - const ConfigDescription port = test::parseConfigOrDie("port"); - - ResourceTable table; - ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), - land, "", - test::ValueBuilder<Id>() - .setSource(Source("land/default.xml")).build(), - context->getDiagnostics())); - ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), - land, "tablet", - test::ValueBuilder<Id>() - .setSource(Source("land/tablet.xml")).build(), - context->getDiagnostics())); - - ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), - port, "", - test::ValueBuilder<Id>() - .setSource(Source("port/default.xml")).build(), - context->getDiagnostics())); - ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), - port, "tablet", - test::ValueBuilder<Id>() - .setSource(Source("port/tablet.xml")).build(), - context->getDiagnostics())); - - ProductFilter filter({ "tablet" }); - ASSERT_TRUE(filter.consume(context.get(), &table)); - - EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one", - land, "")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one", - land, "tablet")); - EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one", - port, "")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one", - port, "tablet")); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + const ConfigDescription land = test::ParseConfigOrDie("land"); + const ConfigDescription port = test::ParseConfigOrDie("port"); + + ResourceTable table; + ASSERT_TRUE(table.AddResource( + test::ParseNameOrDie("android:string/one"), land, "", + test::ValueBuilder<Id>().SetSource(Source("land/default.xml")).Build(), + context->GetDiagnostics())); + ASSERT_TRUE(table.AddResource( + test::ParseNameOrDie("android:string/one"), land, "tablet", + test::ValueBuilder<Id>().SetSource(Source("land/tablet.xml")).Build(), + context->GetDiagnostics())); + + ASSERT_TRUE(table.AddResource( + test::ParseNameOrDie("android:string/one"), port, "", + test::ValueBuilder<Id>().SetSource(Source("port/default.xml")).Build(), + context->GetDiagnostics())); + ASSERT_TRUE(table.AddResource( + test::ParseNameOrDie("android:string/one"), port, "tablet", + test::ValueBuilder<Id>().SetSource(Source("port/tablet.xml")).Build(), + context->GetDiagnostics())); + + ProductFilter filter({"tablet"}); + ASSERT_TRUE(filter.Consume(context.get(), &table)); + + EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>( + &table, "android:string/one", land, "")); + EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>( + &table, "android:string/one", land, "tablet")); + EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>( + &table, "android:string/one", port, "")); + EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>( + &table, "android:string/one", port, "tablet")); } TEST(ProductFilterTest, SelectDefaultProduct) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - - ResourceTable table; - ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), - ConfigDescription::defaultConfig(), "", - test::ValueBuilder<Id>() - .setSource(Source("default.xml")).build(), - context->getDiagnostics())); - ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), - ConfigDescription::defaultConfig(), "tablet", - test::ValueBuilder<Id>() - .setSource(Source("tablet.xml")).build(), - context->getDiagnostics())); - - ProductFilter filter({}); - ASSERT_TRUE(filter.consume(context.get(), &table)); - - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one", - ConfigDescription::defaultConfig(), - "")); - EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one", - ConfigDescription::defaultConfig(), - "tablet")); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + ResourceTable table; + ASSERT_TRUE(table.AddResource( + test::ParseNameOrDie("android:string/one"), + ConfigDescription::DefaultConfig(), "", + test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build(), + context->GetDiagnostics())); + ASSERT_TRUE(table.AddResource( + test::ParseNameOrDie("android:string/one"), + ConfigDescription::DefaultConfig(), "tablet", + test::ValueBuilder<Id>().SetSource(Source("tablet.xml")).Build(), + context->GetDiagnostics())); + + ProductFilter filter({}); + ASSERT_TRUE(filter.Consume(context.get(), &table)); + + EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>( + &table, "android:string/one", + ConfigDescription::DefaultConfig(), "")); + EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>( + &table, "android:string/one", + ConfigDescription::DefaultConfig(), "tablet")); } TEST(ProductFilterTest, FailOnAmbiguousProduct) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - - ResourceTable table; - ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), - ConfigDescription::defaultConfig(), "", - test::ValueBuilder<Id>() - .setSource(Source("default.xml")).build(), - context->getDiagnostics())); - ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), - ConfigDescription::defaultConfig(), "tablet", - test::ValueBuilder<Id>() - .setSource(Source("tablet.xml")).build(), - context->getDiagnostics())); - ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), - ConfigDescription::defaultConfig(), "no-sdcard", - test::ValueBuilder<Id>() - .setSource(Source("no-sdcard.xml")).build(), - context->getDiagnostics())); - - ProductFilter filter({ "tablet", "no-sdcard" }); - ASSERT_FALSE(filter.consume(context.get(), &table)); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + ResourceTable table; + ASSERT_TRUE(table.AddResource( + test::ParseNameOrDie("android:string/one"), + ConfigDescription::DefaultConfig(), "", + test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build(), + context->GetDiagnostics())); + ASSERT_TRUE(table.AddResource( + test::ParseNameOrDie("android:string/one"), + ConfigDescription::DefaultConfig(), "tablet", + test::ValueBuilder<Id>().SetSource(Source("tablet.xml")).Build(), + context->GetDiagnostics())); + ASSERT_TRUE(table.AddResource( + test::ParseNameOrDie("android:string/one"), + ConfigDescription::DefaultConfig(), "no-sdcard", + test::ValueBuilder<Id>().SetSource(Source("no-sdcard.xml")).Build(), + context->GetDiagnostics())); + + ProductFilter filter({"tablet", "no-sdcard"}); + ASSERT_FALSE(filter.Consume(context.get(), &table)); } TEST(ProductFilterTest, FailOnMultipleDefaults) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - - ResourceTable table; - ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), - ConfigDescription::defaultConfig(), "", - test::ValueBuilder<Id>() - .setSource(Source(".xml")).build(), - context->getDiagnostics())); - ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), - ConfigDescription::defaultConfig(), "default", - test::ValueBuilder<Id>() - .setSource(Source("default.xml")).build(), - context->getDiagnostics())); - - ProductFilter filter({}); - ASSERT_FALSE(filter.consume(context.get(), &table)); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + ResourceTable table; + ASSERT_TRUE(table.AddResource( + test::ParseNameOrDie("android:string/one"), + ConfigDescription::DefaultConfig(), "", + test::ValueBuilder<Id>().SetSource(Source(".xml")).Build(), + context->GetDiagnostics())); + ASSERT_TRUE(table.AddResource( + test::ParseNameOrDie("android:string/one"), + ConfigDescription::DefaultConfig(), "default", + test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build(), + context->GetDiagnostics())); + + ProductFilter filter({}); + ASSERT_FALSE(filter.Consume(context.get(), &table)); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 66eb0df048db..be787b2727ad 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -14,8 +14,12 @@ * limitations under the License. */ +#include "link/ReferenceLinker.h" + +#include "android-base/logging.h" +#include "androidfw/ResourceTypes.h" + #include "Diagnostics.h" -#include "ReferenceLinker.h" #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" @@ -26,309 +30,344 @@ #include "util/Util.h" #include "xml/XmlUtil.h" -#include <androidfw/ResourceTypes.h> -#include <cassert> - namespace aapt { namespace { /** - * The ReferenceLinkerVisitor will follow all references and make sure they point - * to resources that actually exist, either in the local resource table, or as external - * symbols. Once the target resource has been found, the ID of the resource will be assigned + * The ReferenceLinkerVisitor will follow all references and make sure they + * point + * to resources that actually exist, either in the local resource table, or as + * external + * symbols. Once the target resource has been found, the ID of the resource will + * be assigned * to the reference object. * * NOTE: All of the entries in the ResourceTable must be assigned IDs. */ class ReferenceLinkerVisitor : public ValueVisitor { -public: - using ValueVisitor::visit; - - ReferenceLinkerVisitor(IAaptContext* context, SymbolTable* symbols, StringPool* stringPool, - xml::IPackageDeclStack* decl,CallSite* callSite) : - mContext(context), mSymbols(symbols), mPackageDecls(decl), mStringPool(stringPool), - mCallSite(callSite) { + public: + using ValueVisitor::Visit; + + ReferenceLinkerVisitor(IAaptContext* context, SymbolTable* symbols, + StringPool* string_pool, xml::IPackageDeclStack* decl, + CallSite* callsite) + : context_(context), + symbols_(symbols), + package_decls_(decl), + string_pool_(string_pool), + callsite_(callsite) {} + + void Visit(Reference* ref) override { + if (!ReferenceLinker::LinkReference(ref, context_, symbols_, package_decls_, + callsite_)) { + error_ = true; } - - void visit(Reference* ref) override { - if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mPackageDecls, mCallSite)) { - mError = true; - } + } + + /** + * We visit the Style specially because during this phase, values of + * attributes are + * all RawString values. Now that we are expected to resolve all symbols, we + * can + * lookup the attributes to find out which types are allowed for the + * attributes' values. + */ + void Visit(Style* style) override { + if (style->parent) { + Visit(&style->parent.value()); } - /** - * We visit the Style specially because during this phase, values of attributes are - * all RawString values. Now that we are expected to resolve all symbols, we can - * lookup the attributes to find out which types are allowed for the attributes' values. - */ - void visit(Style* style) override { - if (style->parent) { - visit(&style->parent.value()); + for (Style::Entry& entry : style->entries) { + std::string err_str; + + // Transform the attribute reference so that it is using the fully + // qualified package + // name. This will also mark the reference as being able to see private + // resources if + // there was a '*' in the reference or if the package came from the + // private namespace. + Reference transformed_reference = entry.key; + TransformReferenceFromNamespace(package_decls_, + context_->GetCompilationPackage(), + &transformed_reference); + + // Find the attribute in the symbol table and check if it is visible from + // this callsite. + const SymbolTable::Symbol* symbol = + ReferenceLinker::ResolveAttributeCheckVisibility( + transformed_reference, context_->GetNameMangler(), symbols_, + callsite_, &err_str); + if (symbol) { + // Assign our style key the correct ID. + // The ID may not exist. + entry.key.id = symbol->id; + + // Try to convert the value to a more specific, typed value based on the + // attribute it is set to. + entry.value = ParseValueWithAttribute(std::move(entry.value), + symbol->attribute.get()); + + // Link/resolve the final value (mostly if it's a reference). + entry.value->Accept(this); + + // Now verify that the type of this item is compatible with the + // attribute it + // is defined for. We pass `nullptr` as the DiagMessage so that this + // check is + // fast and we avoid creating a DiagMessage when the match is + // successful. + if (!symbol->attribute->Matches(entry.value.get(), nullptr)) { + // The actual type of this item is incompatible with the attribute. + DiagMessage msg(entry.key.GetSource()); + + // Call the matches method again, this time with a DiagMessage so we + // fill + // in the actual error message. + symbol->attribute->Matches(entry.value.get(), &msg); + context_->GetDiagnostics()->Error(msg); + error_ = true; } - for (Style::Entry& entry : style->entries) { - std::string errStr; - - // Transform the attribute reference so that it is using the fully qualified package - // name. This will also mark the reference as being able to see private resources if - // there was a '*' in the reference or if the package came from the private namespace. - Reference transformedReference = entry.key; - transformReferenceFromNamespace(mPackageDecls, mContext->getCompilationPackage(), - &transformedReference); - - // Find the attribute in the symbol table and check if it is visible from this callsite. - const SymbolTable::Symbol* symbol = ReferenceLinker::resolveAttributeCheckVisibility( - transformedReference, mContext->getNameMangler(), mSymbols, mCallSite, &errStr); - if (symbol) { - // Assign our style key the correct ID. - // The ID may not exist. - entry.key.id = symbol->id; - - // Try to convert the value to a more specific, typed value based on the - // attribute it is set to. - entry.value = parseValueWithAttribute(std::move(entry.value), - symbol->attribute.get()); - - // Link/resolve the final value (mostly if it's a reference). - entry.value->accept(this); - - // Now verify that the type of this item is compatible with the attribute it - // is defined for. We pass `nullptr` as the DiagMessage so that this check is - // fast and we avoid creating a DiagMessage when the match is successful. - if (!symbol->attribute->matches(entry.value.get(), nullptr)) { - // The actual type of this item is incompatible with the attribute. - DiagMessage msg(entry.key.getSource()); - - // Call the matches method again, this time with a DiagMessage so we fill - // in the actual error message. - symbol->attribute->matches(entry.value.get(), &msg); - mContext->getDiagnostics()->error(msg); - mError = true; - } - - } else { - DiagMessage msg(entry.key.getSource()); - msg << "style attribute '"; - ReferenceLinker::writeResourceName(&msg, entry.key, transformedReference); - msg << "' " << errStr; - mContext->getDiagnostics()->error(msg); - mError = true; - } - } + } else { + DiagMessage msg(entry.key.GetSource()); + msg << "style attribute '"; + ReferenceLinker::WriteResourceName(&msg, entry.key, + transformed_reference); + msg << "' " << err_str; + context_->GetDiagnostics()->Error(msg); + error_ = true; + } } + } + + bool HasError() { return error_; } + + private: + DISALLOW_COPY_AND_ASSIGN(ReferenceLinkerVisitor); + + /** + * Transform a RawString value into a more specific, appropriate value, based + * on the + * Attribute. If a non RawString value is passed in, this is an identity + * transform. + */ + std::unique_ptr<Item> 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); + + // If we could not parse as any specific type, try a basic STRING. + if (!transformed && + (attr->type_mask & android::ResTable_map::TYPE_STRING)) { + util::StringBuilder string_builder; + string_builder.Append(*raw_string->value); + if (string_builder) { + transformed = util::make_unique<String>( + string_pool_->MakeRef(string_builder.ToString())); + } + } + + if (transformed) { + return transformed; + } + }; + return value; + } + + IAaptContext* context_; + SymbolTable* symbols_; + xml::IPackageDeclStack* package_decls_; + StringPool* string_pool_; + CallSite* callsite_; + bool error_ = false; +}; - bool hasError() { - return mError; - } +class EmptyDeclStack : public xml::IPackageDeclStack { + public: + EmptyDeclStack() = default; -private: - IAaptContext* mContext; - SymbolTable* mSymbols; - xml::IPackageDeclStack* mPackageDecls; - StringPool* mStringPool; - CallSite* mCallSite; - bool mError = false; - - /** - * Transform a RawString value into a more specific, appropriate value, based on the - * Attribute. If a non RawString value is passed in, this is an identity transform. - */ - std::unique_ptr<Item> parseValueWithAttribute(std::unique_ptr<Item> value, - const Attribute* attr) { - if (RawString* rawString = valueCast<RawString>(value.get())) { - std::unique_ptr<Item> transformed = - ResourceUtils::parseItemForAttribute(*rawString->value, attr); - - // If we could not parse as any specific type, try a basic STRING. - if (!transformed && (attr->typeMask & android::ResTable_map::TYPE_STRING)) { - util::StringBuilder stringBuilder; - stringBuilder.append(*rawString->value); - if (stringBuilder) { - transformed = util::make_unique<String>( - mStringPool->makeRef(stringBuilder.str())); - } - } - - if (transformed) { - return transformed; - } - }; - return value; + Maybe<xml::ExtractedPackage> TransformPackageAlias( + const StringPiece& alias, + const StringPiece& local_package) const override { + if (alias.empty()) { + return xml::ExtractedPackage{local_package.ToString(), + true /* private */}; } + return {}; + } + + private: + DISALLOW_COPY_AND_ASSIGN(EmptyDeclStack); }; -} // namespace +} // namespace /** - * The symbol is visible if it is public, or if the reference to it is requesting private access + * The symbol is visible if it is public, or if the reference to it is + * requesting private access * or if the callsite comes from the same package. */ -bool ReferenceLinker::isSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref, - const CallSite& callSite) { - if (!symbol.isPublic && !ref.privateReference) { - if (ref.name) { - return callSite.resource.package == ref.name.value().package; - } else if (ref.id && symbol.id) { - return ref.id.value().packageId() == symbol.id.value().packageId(); - } else { - return false; - } - } - return true; -} - -const SymbolTable::Symbol* ReferenceLinker::resolveSymbol(const Reference& reference, - NameMangler* mangler, - SymbolTable* symbols) { - if (reference.name) { - Maybe<ResourceName> mangled = mangler->mangleName(reference.name.value()); - return symbols->findByName(mangled ? mangled.value() : reference.name.value()); - } else if (reference.id) { - return symbols->findById(reference.id.value()); +bool ReferenceLinker::IsSymbolVisible(const SymbolTable::Symbol& symbol, + const Reference& ref, + const CallSite& callsite) { + if (!symbol.is_public && !ref.private_reference) { + if (ref.name) { + return callsite.resource.package == ref.name.value().package; + } else if (ref.id && symbol.id) { + return ref.id.value().package_id() == symbol.id.value().package_id(); } else { - return nullptr; + return false; } + } + return true; } -const SymbolTable::Symbol* ReferenceLinker::resolveSymbolCheckVisibility( - const Reference& reference, NameMangler* nameMangler, SymbolTable* symbols, - CallSite* callSite, std::string* outError) { - const SymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols); - if (!symbol) { - if (outError) *outError = "not found"; - return nullptr; - } - - if (!isSymbolVisible(*symbol, reference, *callSite)) { - if (outError) *outError = "is private"; - return nullptr; - } - return symbol; +const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol( + const Reference& reference, NameMangler* mangler, SymbolTable* symbols) { + if (reference.name) { + Maybe<ResourceName> mangled = mangler->MangleName(reference.name.value()); + return symbols->FindByName(mangled ? mangled.value() + : reference.name.value()); + } else if (reference.id) { + return symbols->FindById(reference.id.value()); + } else { + return nullptr; + } } -const SymbolTable::Symbol* ReferenceLinker::resolveAttributeCheckVisibility( - const Reference& reference, NameMangler* nameMangler, SymbolTable* symbols, - CallSite* callSite, std::string* outError) { - const SymbolTable::Symbol* symbol = resolveSymbolCheckVisibility(reference, nameMangler, - symbols, callSite, - outError); - if (!symbol) { - return nullptr; - } - - if (!symbol->attribute) { - if (outError) *outError = "is not an attribute"; - return nullptr; - } - return symbol; +const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility( + const Reference& reference, NameMangler* name_mangler, SymbolTable* symbols, + CallSite* callsite, std::string* out_error) { + const SymbolTable::Symbol* symbol = + ResolveSymbol(reference, name_mangler, symbols); + if (!symbol) { + if (out_error) *out_error = "not found"; + return nullptr; + } + + if (!IsSymbolVisible(*symbol, reference, *callsite)) { + if (out_error) *out_error = "is private"; + return nullptr; + } + return symbol; } -Maybe<xml::AaptAttribute> ReferenceLinker::compileXmlAttribute(const Reference& reference, - NameMangler* nameMangler, - SymbolTable* symbols, - CallSite* callSite, - std::string* outError) { - const SymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols); - if (!symbol) { - return {}; - } +const SymbolTable::Symbol* ReferenceLinker::ResolveAttributeCheckVisibility( + const Reference& reference, NameMangler* name_mangler, SymbolTable* symbols, + CallSite* callsite, std::string* out_error) { + const SymbolTable::Symbol* symbol = ResolveSymbolCheckVisibility( + reference, name_mangler, symbols, callsite, out_error); + if (!symbol) { + return nullptr; + } + + if (!symbol->attribute) { + if (out_error) *out_error = "is not an attribute"; + return nullptr; + } + return symbol; +} - if (!symbol->attribute) { - if (outError) *outError = "is not an attribute"; - return {}; - } - return xml::AaptAttribute{ symbol->id, *symbol->attribute }; +Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute( + const Reference& reference, NameMangler* name_mangler, SymbolTable* symbols, + CallSite* callsite, std::string* out_error) { + const SymbolTable::Symbol* symbol = + ResolveSymbol(reference, name_mangler, symbols); + if (!symbol) { + if (out_error) *out_error = "not found"; + return {}; + } + + if (!symbol->attribute) { + if (out_error) *out_error = "is not an attribute"; + return {}; + } + return xml::AaptAttribute{symbol->id, *symbol->attribute}; } -void ReferenceLinker::writeResourceName(DiagMessage* outMsg, const Reference& orig, +void ReferenceLinker::WriteResourceName(DiagMessage* out_msg, + const Reference& orig, const Reference& transformed) { - assert(outMsg); + CHECK(out_msg != nullptr); - if (orig.name) { - *outMsg << orig.name.value(); - if (transformed.name.value() != orig.name.value()) { - *outMsg << " (aka " << transformed.name.value() << ")"; - } - } else { - *outMsg << orig.id.value(); + if (orig.name) { + *out_msg << orig.name.value(); + if (transformed.name.value() != orig.name.value()) { + *out_msg << " (aka " << transformed.name.value() << ")"; } + } else { + *out_msg << orig.id.value(); + } } -bool ReferenceLinker::linkReference(Reference* reference, IAaptContext* context, - SymbolTable* symbols, xml::IPackageDeclStack* decls, - CallSite* callSite) { - assert(reference); - assert(reference->name || reference->id); - - Reference transformedReference = *reference; - transformReferenceFromNamespace(decls, context->getCompilationPackage(), - &transformedReference); - - std::string errStr; - const SymbolTable::Symbol* s = resolveSymbolCheckVisibility( - transformedReference, context->getNameMangler(), symbols, callSite, &errStr); - if (s) { - // The ID may not exist. This is fine because of the possibility of building against - // libraries without assigned IDs. - // Ex: Linking against own resources when building a static library. - reference->id = s->id; - return true; - } - - DiagMessage errorMsg(reference->getSource()); - errorMsg << "resource "; - writeResourceName(&errorMsg, *reference, transformedReference); - errorMsg << " " << errStr; - context->getDiagnostics()->error(errorMsg); - return false; +bool ReferenceLinker::LinkReference(Reference* reference, IAaptContext* context, + SymbolTable* symbols, + xml::IPackageDeclStack* decls, + CallSite* callsite) { + CHECK(reference != nullptr); + CHECK(reference->name || reference->id); + + Reference transformed_reference = *reference; + TransformReferenceFromNamespace(decls, context->GetCompilationPackage(), + &transformed_reference); + + std::string err_str; + const SymbolTable::Symbol* s = ResolveSymbolCheckVisibility( + transformed_reference, context->GetNameMangler(), symbols, callsite, + &err_str); + if (s) { + // The ID may not exist. This is fine because of the possibility of building + // against libraries without assigned IDs. + // Ex: Linking against own resources when building a static library. + reference->id = s->id; + return true; + } + + DiagMessage error_msg(reference->GetSource()); + error_msg << "resource "; + WriteResourceName(&error_msg, *reference, transformed_reference); + error_msg << " " << err_str; + context->GetDiagnostics()->Error(error_msg); + return false; } -namespace { +bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) { + EmptyDeclStack decl_stack; + bool error = false; + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + // Symbol state information may be lost if there is no value for the + // resource. + if (entry->symbol_status.state != SymbolState::kUndefined && + entry->values.empty()) { + context->GetDiagnostics()->Error( + DiagMessage(entry->symbol_status.source) + << "no definition for declared symbol '" + << ResourceNameRef(package->name, type->type, entry->name) + << "'"); + error = true; + } + + CallSite callsite = { + ResourceNameRef(package->name, type->type, entry->name)}; + ReferenceLinkerVisitor visitor(context, context->GetExternalSymbols(), + &table->string_pool, &decl_stack, + &callsite); -struct EmptyDeclStack : public xml::IPackageDeclStack { - Maybe<xml::ExtractedPackage> transformPackageAlias( - const StringPiece16& alias, const StringPiece16& localPackage) const override { - if (alias.empty()) { - return xml::ExtractedPackage{ localPackage.toString(), true /* private */ }; + for (auto& config_value : entry->values) { + config_value->value->Accept(&visitor); } - return {}; - } -}; -} // namespace - -bool ReferenceLinker::consume(IAaptContext* context, ResourceTable* table) { - EmptyDeclStack declStack; - bool error = false; - for (auto& package : table->packages) { - for (auto& type : package->types) { - for (auto& entry : type->entries) { - // Symbol state information may be lost if there is no value for the resource. - if (entry->symbolStatus.state != SymbolState::kUndefined && entry->values.empty()) { - context->getDiagnostics()->error( - DiagMessage(entry->symbolStatus.source) - << "no definition for declared symbol '" - << ResourceNameRef(package->name, type->type, entry->name) - << "'"); - error = true; - } - - CallSite callSite = { ResourceNameRef(package->name, type->type, entry->name) }; - ReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), - &table->stringPool, &declStack, &callSite); - - for (auto& configValue : entry->values) { - configValue->value->accept(&visitor); - } - - if (visitor.hasError()) { - error = true; - } - } + if (visitor.HasError()) { + error = true; } + } } - return !error; + } + return !error; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h index 7993aaf39e47..bdabf249709d 100644 --- a/tools/aapt2/link/ReferenceLinker.h +++ b/tools/aapt2/link/ReferenceLinker.h @@ -17,6 +17,8 @@ #ifndef AAPT_LINKER_REFERENCELINKER_H #define AAPT_LINKER_REFERENCELINKER_H +#include "android-base/macros.h" + #include "Resource.h" #include "ResourceValues.h" #include "ValueVisitor.h" @@ -25,82 +27,91 @@ #include "process/SymbolTable.h" #include "xml/XmlDom.h" -#include <cassert> - namespace aapt { /** - * Resolves all references to resources in the ResourceTable and assigns them IDs. + * Resolves all references to resources in the ResourceTable and assigns them + * IDs. * The ResourceTable must already have IDs assigned to each resource. - * Once the ResourceTable is processed by this linker, it is ready to be flattened. + * Once the ResourceTable is processed by this linker, it is ready to be + * flattened. */ -struct ReferenceLinker : public IResourceTableConsumer { - /** - * Returns true if the symbol is visible by the reference and from the callsite. - */ - static bool isSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref, - const CallSite& callSite); - - /** - * Performs name mangling and looks up the resource in the symbol table. Returns nullptr - * if the symbol was not found. - */ - static const SymbolTable::Symbol* resolveSymbol(const Reference& reference, - NameMangler* mangler, SymbolTable* symbols); - - /** - * Performs name mangling and looks up the resource in the symbol table. If the symbol is - * not visible by the reference at the callsite, nullptr is returned. outError holds - * the error message. - */ - static const SymbolTable::Symbol* resolveSymbolCheckVisibility(const Reference& reference, - NameMangler* nameMangler, - SymbolTable* symbols, - CallSite* callSite, - std::string* outError); - - /** - * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is an attribute. - * That is, the return value will have a non-null value for ISymbolTable::Symbol::attribute. - */ - static const SymbolTable::Symbol* resolveAttributeCheckVisibility(const Reference& reference, - NameMangler* nameMangler, - SymbolTable* symbols, - CallSite* callSite, - std::string* outError); - - /** - * Resolves the attribute reference and returns an xml::AaptAttribute if successful. - * If resolution fails, outError holds the error message. - */ - static Maybe<xml::AaptAttribute> compileXmlAttribute(const Reference& reference, - NameMangler* nameMangler, - SymbolTable* symbols, - CallSite* callSite, - std::string* outError); - - /** - * Writes the resource name to the DiagMessage, using the "orig_name (aka <transformed_name>)" - * syntax. - */ - static void writeResourceName(DiagMessage* outMsg, const Reference& orig, - const Reference& transformed); - - /** - * Transforms the package name of the reference to the fully qualified package name using - * the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible - * to the reference at the callsite, the reference is updated with an ID. - * Returns false on failure, and an error message is logged to the IDiagnostics in the context. - */ - static bool linkReference(Reference* reference, IAaptContext* context, SymbolTable* symbols, - xml::IPackageDeclStack* decls, CallSite* callSite); - - /** - * Links all references in the ResourceTable. - */ - bool consume(IAaptContext* context, ResourceTable* table) override; +class ReferenceLinker : public IResourceTableConsumer { + public: + ReferenceLinker() = default; + + /** + * Returns true if the symbol is visible by the reference and from the + * callsite. + */ + static bool IsSymbolVisible(const SymbolTable::Symbol& symbol, + const Reference& ref, const CallSite& callsite); + + /** + * Performs name mangling and looks up the resource in the symbol table. + * Returns nullptr if the symbol was not found. + */ + static const SymbolTable::Symbol* ResolveSymbol(const Reference& reference, + NameMangler* mangler, + SymbolTable* symbols); + + /** + * Performs name mangling and looks up the resource in the symbol table. If + * the symbol is not visible by the reference at the callsite, nullptr is + * returned. out_error holds the error message. + */ + static const SymbolTable::Symbol* ResolveSymbolCheckVisibility( + const Reference& reference, NameMangler* name_mangler, + SymbolTable* symbols, CallSite* callsite, std::string* out_error); + + /** + * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is + * an attribute. + * That is, the return value will have a non-null value for + * ISymbolTable::Symbol::attribute. + */ + static const SymbolTable::Symbol* ResolveAttributeCheckVisibility( + const Reference& reference, NameMangler* name_mangler, + SymbolTable* symbols, CallSite* callsite, std::string* out_error); + + /** + * Resolves the attribute reference and returns an xml::AaptAttribute if + * successful. + * If resolution fails, outError holds the error message. + */ + static Maybe<xml::AaptAttribute> CompileXmlAttribute( + const Reference& reference, NameMangler* name_mangler, + SymbolTable* symbols, CallSite* callsite, std::string* out_error); + + /** + * Writes the resource name to the DiagMessage, using the + * "orig_name (aka <transformed_name>)" syntax. + */ + static void WriteResourceName(DiagMessage* out_msg, const Reference& orig, + const Reference& transformed); + + /** + * Transforms the package name of the reference to the fully qualified package + * name using + * the xml::IPackageDeclStack, then mangles and looks up the symbol. If the + * symbol is visible + * to the reference at the callsite, the reference is updated with an ID. + * Returns false on failure, and an error message is logged to the + * IDiagnostics in the context. + */ + static bool LinkReference(Reference* reference, IAaptContext* context, + SymbolTable* symbols, xml::IPackageDeclStack* decls, + CallSite* callsite); + + /** + * Links all references in the ResourceTable. + */ + bool Consume(IAaptContext* context, ResourceTable* table) override; + + private: + DISALLOW_COPY_AND_ASSIGN(ReferenceLinker); }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_LINKER_REFERENCELINKER_H */ diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp index 76b23098a35c..4ca36a9e2b22 100644 --- a/tools/aapt2/link/ReferenceLinker_test.cpp +++ b/tools/aapt2/link/ReferenceLinker_test.cpp @@ -15,6 +15,7 @@ */ #include "link/ReferenceLinker.h" + #include "test/Test.h" using android::ResTable_map; @@ -22,206 +23,238 @@ using android::ResTable_map; namespace aapt { TEST(ReferenceLinkerTest, LinkSimpleReferences) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"com.app.test", 0x7f) - .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000), - u"@com.app.test:string/bar") - - // Test use of local reference (w/o package name). - .addReference(u"@com.app.test:string/bar", ResourceId(0x7f020001), u"@string/baz") - - .addReference(u"@com.app.test:string/baz", ResourceId(0x7f020002), - u"@android:string/ok") - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .setCompilationPackage(u"com.app.test") - .setPackageId(0x7f) - .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" }) - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addPublicSymbol(u"@android:string/ok", ResourceId(0x01040034)) - .build()) - .build(); - - ReferenceLinker linker; - ASSERT_TRUE(linker.consume(context.get(), table.get())); - - Reference* ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/foo"); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001)); - - ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/bar"); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f020002)); - - ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/baz"); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x01040034)); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddReference("com.app.test:string/foo", ResourceId(0x7f020000), + "com.app.test:string/bar") + + // Test use of local reference (w/o package name). + .AddReference("com.app.test:string/bar", ResourceId(0x7f020001), + "string/baz") + + .AddReference("com.app.test:string/baz", ResourceId(0x7f020002), + "android:string/ok") + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .SetCompilationPackage("com.app.test") + .SetPackageId(0x7f) + .SetNameManglerPolicy(NameManglerPolicy{"com.app.test"}) + .AddSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource( + test::StaticSymbolSourceBuilder() + .AddPublicSymbol("android:string/ok", ResourceId(0x01040034)) + .Build()) + .Build(); + + ReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context.get(), table.get())); + + Reference* ref = + test::GetValue<Reference>(table.get(), "com.app.test:string/foo"); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001)); + + ref = test::GetValue<Reference>(table.get(), "com.app.test:string/bar"); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f020002)); + + ref = test::GetValue<Reference>(table.get(), "com.app.test:string/baz"); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x01040034)); } TEST(ReferenceLinkerTest, LinkStyleAttributes) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"com.app.test", 0x7f) - .addValue(u"@com.app.test:style/Theme", test::StyleBuilder() - .setParent(u"@android:style/Theme.Material") - .addItem(u"@android:attr/foo", ResourceUtils::tryParseColor(u"#ff00ff")) - .addItem(u"@android:attr/bar", {} /* placeholder */) - .build()) - .build(); - - { - // We need to fill in the value for the attribute android:attr/bar after we build the - // table, because we need access to the string pool. - Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme"); - ASSERT_NE(style, nullptr); - style->entries.back().value = util::make_unique<RawString>( - table->stringPool.makeRef(u"one|two")); - } - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .setCompilationPackage(u"com.app.test") - .setPackageId(0x7f) - .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" }) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addPublicSymbol(u"@android:style/Theme.Material", - ResourceId(0x01060000)) - .addPublicSymbol(u"@android:attr/foo", ResourceId(0x01010001), - test::AttributeBuilder() - .setTypeMask(ResTable_map::TYPE_COLOR) - .build()) - .addPublicSymbol(u"@android:attr/bar", ResourceId(0x01010002), - test::AttributeBuilder() - .setTypeMask(ResTable_map::TYPE_FLAGS) - .addItem(u"one", 0x01) - .addItem(u"two", 0x02) - .build()) - .build()) - .build(); - - ReferenceLinker linker; - ASSERT_TRUE(linker.consume(context.get(), table.get())); - - Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme"); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddValue("com.app.test:style/Theme", + test::StyleBuilder() + .SetParent("android:style/Theme.Material") + .AddItem("android:attr/foo", + ResourceUtils::TryParseColor("#ff00ff")) + .AddItem("android:attr/bar", {} /* placeholder */) + .Build()) + .Build(); + + { + // We need to fill in the value for the attribute android:attr/bar after we + // build the + // table, because we need access to the string pool. + Style* style = + test::GetValue<Style>(table.get(), "com.app.test:style/Theme"); ASSERT_NE(style, nullptr); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().id); - EXPECT_EQ(style->parent.value().id.value(), ResourceId(0x01060000)); - - ASSERT_EQ(2u, style->entries.size()); - - AAPT_ASSERT_TRUE(style->entries[0].key.id); - EXPECT_EQ(style->entries[0].key.id.value(), ResourceId(0x01010001)); - ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[0].value.get()), nullptr); - - AAPT_ASSERT_TRUE(style->entries[1].key.id); - EXPECT_EQ(style->entries[1].key.id.value(), ResourceId(0x01010002)); - ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[1].value.get()), nullptr); + style->entries.back().value = + util::make_unique<RawString>(table->string_pool.MakeRef("one|two")); + } + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .SetCompilationPackage("com.app.test") + .SetPackageId(0x7f) + .SetNameManglerPolicy(NameManglerPolicy{"com.app.test"}) + .AddSymbolSource( + test::StaticSymbolSourceBuilder() + .AddPublicSymbol("android:style/Theme.Material", + ResourceId(0x01060000)) + .AddPublicSymbol("android:attr/foo", ResourceId(0x01010001), + test::AttributeBuilder() + .SetTypeMask(ResTable_map::TYPE_COLOR) + .Build()) + .AddPublicSymbol("android:attr/bar", ResourceId(0x01010002), + test::AttributeBuilder() + .SetTypeMask(ResTable_map::TYPE_FLAGS) + .AddItem("one", 0x01) + .AddItem("two", 0x02) + .Build()) + .Build()) + .Build(); + + ReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context.get(), table.get())); + + Style* style = test::GetValue<Style>(table.get(), "com.app.test:style/Theme"); + ASSERT_NE(style, nullptr); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().id); + EXPECT_EQ(style->parent.value().id.value(), ResourceId(0x01060000)); + + ASSERT_EQ(2u, style->entries.size()); + + AAPT_ASSERT_TRUE(style->entries[0].key.id); + EXPECT_EQ(style->entries[0].key.id.value(), ResourceId(0x01010001)); + ASSERT_NE(ValueCast<BinaryPrimitive>(style->entries[0].value.get()), nullptr); + + AAPT_ASSERT_TRUE(style->entries[1].key.id); + EXPECT_EQ(style->entries[1].key.id.value(), ResourceId(0x01010002)); + ASSERT_NE(ValueCast<BinaryPrimitive>(style->entries[1].value.get()), nullptr); } TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .setCompilationPackage(u"com.app.test") - .setPackageId(0x7f) - .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.android.support" } }) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addPublicSymbol(u"@com.app.test:attr/com.android.support$foo", - ResourceId(0x7f010000), - test::AttributeBuilder() - .setTypeMask(ResTable_map::TYPE_COLOR) - .build()) - .build()) - .build(); - - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"com.app.test", 0x7f) - .addValue(u"@com.app.test:style/Theme", ResourceId(0x7f020000), - test::StyleBuilder().addItem(u"@com.android.support:attr/foo", - ResourceUtils::tryParseColor(u"#ff0000")) - .build()) - .build(); - - ReferenceLinker linker; - ASSERT_TRUE(linker.consume(context.get(), table.get())); - - Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme"); - ASSERT_NE(style, nullptr); - ASSERT_EQ(1u, style->entries.size()); - AAPT_ASSERT_TRUE(style->entries.front().key.id); - EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000)); + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .SetCompilationPackage("com.app.test") + .SetPackageId(0x7f) + .SetNameManglerPolicy( + NameManglerPolicy{"com.app.test", {"com.android.support"}}) + .AddSymbolSource( + test::StaticSymbolSourceBuilder() + .AddPublicSymbol("com.app.test:attr/com.android.support$foo", + ResourceId(0x7f010000), + test::AttributeBuilder() + .SetTypeMask(ResTable_map::TYPE_COLOR) + .Build()) + .Build()) + .Build(); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddValue("com.app.test:style/Theme", ResourceId(0x7f020000), + test::StyleBuilder() + .AddItem("com.android.support:attr/foo", + ResourceUtils::TryParseColor("#ff0000")) + .Build()) + .Build(); + + ReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context.get(), table.get())); + + Style* style = test::GetValue<Style>(table.get(), "com.app.test:style/Theme"); + ASSERT_NE(style, nullptr); + ASSERT_EQ(1u, style->entries.size()); + AAPT_ASSERT_TRUE(style->entries.front().key.id); + EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000)); } TEST(ReferenceLinkerTest, FailToLinkPrivateSymbols) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"com.app.test", 0x7f) - .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000), - u"@android:string/hidden") - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .setCompilationPackage(u"com.app.test") - .setPackageId(0x7f) - .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" }) - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addSymbol(u"@android:string/hidden", ResourceId(0x01040034)) - .build()) - .build(); - - ReferenceLinker linker; - ASSERT_FALSE(linker.consume(context.get(), table.get())); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddReference("com.app.test:string/foo", ResourceId(0x7f020000), + "android:string/hidden") + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .SetCompilationPackage("com.app.test") + .SetPackageId(0x7f) + .SetNameManglerPolicy(NameManglerPolicy{"com.app.test"}) + .AddSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource( + test::StaticSymbolSourceBuilder() + .AddSymbol("android:string/hidden", ResourceId(0x01040034)) + .Build()) + .Build(); + + ReferenceLinker linker; + ASSERT_FALSE(linker.Consume(context.get(), table.get())); } TEST(ReferenceLinkerTest, FailToLinkPrivateMangledSymbols) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"com.app.test", 0x7f) - .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000), - u"@com.app.lib:string/hidden") - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .setCompilationPackage(u"com.app.test") - .setPackageId(0x7f) - .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.app.lib" } }) - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addSymbol(u"@com.app.test:string/com.app.lib$hidden", - ResourceId(0x7f040034)) - .build()) - - .build(); - - ReferenceLinker linker; - ASSERT_FALSE(linker.consume(context.get(), table.get())); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddReference("com.app.test:string/foo", ResourceId(0x7f020000), + "com.app.lib:string/hidden") + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .SetCompilationPackage("com.app.test") + .SetPackageId(0x7f) + .SetNameManglerPolicy( + NameManglerPolicy{"com.app.test", {"com.app.lib"}}) + .AddSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource( + test::StaticSymbolSourceBuilder() + .AddSymbol("com.app.test:string/com.app.lib$hidden", + ResourceId(0x7f040034)) + .Build()) + + .Build(); + + ReferenceLinker linker; + ASSERT_FALSE(linker.Consume(context.get(), table.get())); } TEST(ReferenceLinkerTest, FailToLinkPrivateStyleAttributes) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"com.app.test", 0x7f) - .addValue(u"@com.app.test:style/Theme", test::StyleBuilder() - .addItem(u"@android:attr/hidden", ResourceUtils::tryParseColor(u"#ff00ff")) - .build()) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .setCompilationPackage(u"com.app.test") - .setPackageId(0x7f) - .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" }) - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addSymbol(u"@android:attr/hidden", ResourceId(0x01010001), - test::AttributeBuilder() - .setTypeMask( - android::ResTable_map::TYPE_COLOR) - .build()) - .build()) - .build(); - - ReferenceLinker linker; - ASSERT_FALSE(linker.consume(context.get(), table.get())); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddValue("com.app.test:style/Theme", + test::StyleBuilder() + .AddItem("android:attr/hidden", + ResourceUtils::TryParseColor("#ff00ff")) + .Build()) + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .SetCompilationPackage("com.app.test") + .SetPackageId(0x7f) + .SetNameManglerPolicy(NameManglerPolicy{"com.app.test"}) + .AddSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource( + test::StaticSymbolSourceBuilder() + .AddSymbol("android:attr/hidden", ResourceId(0x01010001), + test::AttributeBuilder() + .SetTypeMask(android::ResTable_map::TYPE_COLOR) + .Build()) + .Build()) + .Build(); + + ReferenceLinker linker; + ASSERT_FALSE(linker.Consume(context.get(), table.get())); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/ResourceDeduper.cpp b/tools/aapt2/link/ResourceDeduper.cpp new file mode 100644 index 000000000000..9431dcee9714 --- /dev/null +++ b/tools/aapt2/link/ResourceDeduper.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "link/Linkers.h" + +#include <algorithm> + +#include "DominatorTree.h" +#include "ResourceTable.h" + +namespace aapt { + +namespace { + +/** + * Remove duplicated key-value entries from dominated resources. + * + * Based on the dominator tree, we can remove a value of an entry if: + * + * 1. The configuration for the entry's value is dominated by a configuration + * with an equivalent entry value. + * 2. All compatible configurations for the entry (those not in conflict and + * unrelated by domination with the configuration for the entry's value) have + * an equivalent entry value. + */ +class DominatedKeyValueRemover : public DominatorTree::BottomUpVisitor { + public: + using Node = DominatorTree::Node; + + explicit DominatedKeyValueRemover(IAaptContext* context, ResourceEntry* entry) + : context_(context), entry_(entry) {} + + void VisitConfig(Node* node) { + Node* parent = node->parent(); + if (!parent) { + return; + } + ResourceConfigValue* node_value = node->value(); + ResourceConfigValue* parent_value = parent->value(); + if (!node_value || !parent_value) { + return; + } + if (!node_value->value->Equals(parent_value->value.get())) { + return; + } + + // Compare compatible configs for this entry and ensure the values are + // equivalent. + const ConfigDescription& node_configuration = node_value->config; + for (const auto& sibling : entry_->values) { + if (!sibling->value) { + // Sibling was already removed. + continue; + } + if (node_configuration.IsCompatibleWith(sibling->config) && + !node_value->value->Equals(sibling->value.get())) { + // The configurations are compatible, but the value is + // different, so we can't remove this value. + return; + } + } + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note( + DiagMessage(node_value->value->GetSource()) + << "removing dominated duplicate resource with name \"" + << entry_->name << "\""); + } + node_value->value = {}; + } + + private: + DISALLOW_COPY_AND_ASSIGN(DominatedKeyValueRemover); + + IAaptContext* context_; + ResourceEntry* entry_; +}; + +static void DedupeEntry(IAaptContext* context, ResourceEntry* entry) { + DominatorTree tree(entry->values); + DominatedKeyValueRemover remover(context, entry); + tree.Accept(&remover); + + // Erase the values that were removed. + entry->values.erase( + std::remove_if( + entry->values.begin(), entry->values.end(), + [](const std::unique_ptr<ResourceConfigValue>& val) -> bool { + return val == nullptr || val->value == nullptr; + }), + entry->values.end()); +} + +} // namespace + +bool ResourceDeduper::Consume(IAaptContext* context, ResourceTable* table) { + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + DedupeEntry(context, entry.get()); + } + } + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/link/ResourceDeduper_test.cpp b/tools/aapt2/link/ResourceDeduper_test.cpp new file mode 100644 index 000000000000..d38059ddd391 --- /dev/null +++ b/tools/aapt2/link/ResourceDeduper_test.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "link/Linkers.h" + +#include "ResourceTable.h" +#include "test/Test.h" + +namespace aapt { + +TEST(ResourceDeduperTest, SameValuesAreDeduped) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + const ConfigDescription default_config = {}; + const ConfigDescription en_config = test::ParseConfigOrDie("en"); + const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21"); + // Chosen because this configuration is compatible with en. + const ConfigDescription land_config = test::ParseConfigOrDie("land"); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddString("android:string/dedupe", ResourceId{}, default_config, + "dedupe") + .AddString("android:string/dedupe", ResourceId{}, en_config, "dedupe") + .AddString("android:string/dedupe", ResourceId{}, land_config, + "dedupe") + .AddString("android:string/dedupe2", ResourceId{}, default_config, + "dedupe") + .AddString("android:string/dedupe2", ResourceId{}, en_config, + "dedupe") + .AddString("android:string/dedupe2", ResourceId{}, en_v21_config, + "keep") + .AddString("android:string/dedupe2", ResourceId{}, land_config, + "dedupe") + .Build(); + + ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); + EXPECT_EQ(nullptr, test::GetValueForConfig<String>( + table.get(), "android:string/dedupe", en_config)); + EXPECT_EQ(nullptr, test::GetValueForConfig<String>( + table.get(), "android:string/dedupe", land_config)); + EXPECT_EQ(nullptr, test::GetValueForConfig<String>( + table.get(), "android:string/dedupe2", en_config)); + EXPECT_NE(nullptr, test::GetValueForConfig<String>( + table.get(), "android:string/dedupe2", en_v21_config)); +} + +TEST(ResourceDeduperTest, DifferentValuesAreKept) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + const ConfigDescription default_config = {}; + const ConfigDescription en_config = test::ParseConfigOrDie("en"); + const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21"); + // Chosen because this configuration is compatible with en. + const ConfigDescription land_config = test::ParseConfigOrDie("land"); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddString("android:string/keep", ResourceId{}, default_config, + "keep") + .AddString("android:string/keep", ResourceId{}, en_config, "keep") + .AddString("android:string/keep", ResourceId{}, en_v21_config, + "keep2") + .AddString("android:string/keep", ResourceId{}, land_config, "keep2") + .Build(); + + ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); + EXPECT_NE(nullptr, test::GetValueForConfig<String>( + table.get(), "android:string/keep", en_config)); + EXPECT_NE(nullptr, test::GetValueForConfig<String>( + table.get(), "android:string/keep", en_v21_config)); + EXPECT_NE(nullptr, test::GetValueForConfig<String>( + table.get(), "android:string/keep", land_config)); +} + +} // namespace aapt diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index 7471e15db41a..d808da31d3b3 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -14,308 +14,383 @@ * limitations under the License. */ +#include "link/TableMerger.h" + +#include "android-base/logging.h" + #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" #include "ValueVisitor.h" -#include "link/TableMerger.h" #include "util/Util.h" -#include <cassert> - namespace aapt { -TableMerger::TableMerger(IAaptContext* context, ResourceTable* outTable, - const TableMergerOptions& options) : - mContext(context), mMasterTable(outTable), mOptions(options) { - // Create the desired package that all tables will be merged into. - mMasterPackage = mMasterTable->createPackage( - mContext->getCompilationPackage(), mContext->getPackageId()); - assert(mMasterPackage && "package name or ID already taken"); +TableMerger::TableMerger(IAaptContext* context, ResourceTable* out_table, + const TableMergerOptions& options) + : context_(context), master_table_(out_table), options_(options) { + // Create the desired package that all tables will be merged into. + master_package_ = master_table_->CreatePackage( + context_->GetCompilationPackage(), context_->GetPackageId()); + CHECK(master_package_ != nullptr) << "package name or ID already taken"; } -bool TableMerger::merge(const Source& src, ResourceTable* table, +bool TableMerger::Merge(const Source& src, ResourceTable* table, io::IFileCollection* collection) { - return mergeImpl(src, table, collection, false /* overlay */, true /* allow new */); + return MergeImpl(src, table, collection, false /* overlay */, + true /* allow new */); } -bool TableMerger::mergeOverlay(const Source& src, ResourceTable* table, +bool TableMerger::MergeOverlay(const Source& src, ResourceTable* table, io::IFileCollection* collection) { - return mergeImpl(src, table, collection, true /* overlay */, mOptions.autoAddOverlay); + return MergeImpl(src, table, collection, true /* overlay */, + options_.auto_add_overlay); } /** * This will merge packages with the same package name (or no package name). */ -bool TableMerger::mergeImpl(const Source& src, ResourceTable* table, - io::IFileCollection* collection, - bool overlay, bool allowNew) { - const uint8_t desiredPackageId = mContext->getPackageId(); - - bool error = false; - for (auto& package : table->packages) { - // Warn of packages with an unrelated ID. - if (package->id && package->id.value() != 0x0 && package->id.value() != desiredPackageId) { - mContext->getDiagnostics()->warn(DiagMessage(src) - << "ignoring package " << package->name); - continue; - } - - if (package->name.empty() || mContext->getCompilationPackage() == package->name) { - FileMergeCallback callback; - if (collection) { - callback = [&](const ResourceNameRef& name, const ConfigDescription& config, - FileReference* newFile, FileReference* oldFile) -> bool { - // The old file's path points inside the APK, so we can use it as is. - io::IFile* f = collection->findFile(util::utf16ToUtf8(*oldFile->path)); - if (!f) { - mContext->getDiagnostics()->error(DiagMessage(src) << "file '" - << *oldFile->path - << "' not found"); - return false; - } - - newFile->file = f; - return true; - }; - } +bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, + io::IFileCollection* collection, bool overlay, + bool allow_new) { + const uint8_t desired_package_id = context_->GetPackageId(); + + bool error = false; + for (auto& package : table->packages) { + // Warn of packages with an unrelated ID. + const Maybe<ResourceId>& id = package->id; + if (id && id.value() != 0x0 && id.value() != desired_package_id) { + context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring package " + << package->name); + continue; + } - // Merge here. Once the entries are merged and mangled, any references to - // them are still valid. This is because un-mangled references are - // mangled, then looked up at resolution time. - // Also, when linking, we convert references with no package name to use - // the compilation package name. - error |= !doMerge(src, table, package.get(), - false /* mangle */, overlay, allowNew, callback); - } + // Only merge an empty package or the package we're building. + // Other packages may exist, which likely contain attribute definitions. + // This is because at compile time it is unknown if the attributes are + // simply + // uses of the attribute or definitions. + if (package->name.empty() || + context_->GetCompilationPackage() == package->name) { + FileMergeCallback callback; + if (collection) { + callback = [&](const ResourceNameRef& name, + const ConfigDescription& config, FileReference* new_file, + FileReference* old_file) -> bool { + // The old file's path points inside the APK, so we can use it as is. + io::IFile* f = collection->FindFile(*old_file->path); + if (!f) { + context_->GetDiagnostics()->Error(DiagMessage(src) + << "file '" << *old_file->path + << "' not found"); + return false; + } + + new_file->file = f; + return true; + }; + } + + // Merge here. Once the entries are merged and mangled, any references to + // them are still valid. This is because un-mangled references are + // mangled, then looked up at resolution time. + // Also, when linking, we convert references with no package name to use + // the compilation package name. + error |= !DoMerge(src, table, package.get(), false /* mangle */, overlay, + allow_new, callback); } - return !error; + } + return !error; } /** * This will merge and mangle resources from a static library. */ -bool TableMerger::mergeAndMangle(const Source& src, const StringPiece16& packageName, - ResourceTable* table, io::IFileCollection* collection) { - bool error = false; - for (auto& package : table->packages) { - // Warn of packages with an unrelated ID. - if (packageName != package->name) { - mContext->getDiagnostics()->warn(DiagMessage(src) - << "ignoring package " << package->name); - continue; - } +bool TableMerger::MergeAndMangle(const Source& src, + const StringPiece& package_name, + ResourceTable* table, + io::IFileCollection* collection) { + bool error = false; + for (auto& package : table->packages) { + // Warn of packages with an unrelated ID. + if (package_name != package->name) { + context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring package " + << package->name); + continue; + } - bool mangle = packageName != mContext->getCompilationPackage(); - mMergedPackages.insert(package->name); - - auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config, - FileReference* newFile, FileReference* oldFile) -> bool { - // The old file's path points inside the APK, so we can use it as is. - io::IFile* f = collection->findFile(util::utf16ToUtf8(*oldFile->path)); - if (!f) { - mContext->getDiagnostics()->error(DiagMessage(src) << "file '" << *oldFile->path - << "' not found"); - return false; - } + bool mangle = package_name != context_->GetCompilationPackage(); + merged_packages_.insert(package->name); + + auto callback = [&]( + const ResourceNameRef& name, const ConfigDescription& config, + FileReference* new_file, FileReference* old_file) -> bool { + // The old file's path points inside the APK, so we can use it as is. + io::IFile* f = collection->FindFile(*old_file->path); + if (!f) { + context_->GetDiagnostics()->Error( + DiagMessage(src) << "file '" << *old_file->path << "' not found"); + return false; + } + + new_file->file = f; + return true; + }; + + error |= !DoMerge(src, table, package.get(), mangle, false /* overlay */, + true /* allow new */, callback); + } + return !error; +} - newFile->file = f; - return true; - }; +static bool MergeType(IAaptContext* context, const Source& src, + ResourceTableType* dst_type, + ResourceTableType* src_type) { + if (dst_type->symbol_status.state < src_type->symbol_status.state) { + // The incoming type's visibility is stronger, so we should override + // the visibility. + if (src_type->symbol_status.state == SymbolState::kPublic) { + // Only copy the ID if the source is public, or else the ID is + // meaningless. + dst_type->id = src_type->id; + } + dst_type->symbol_status = std::move(src_type->symbol_status); + } else if (dst_type->symbol_status.state == SymbolState::kPublic && + src_type->symbol_status.state == SymbolState::kPublic && + dst_type->id && src_type->id && + dst_type->id.value() != src_type->id.value()) { + // Both types are public and have different IDs. + context->GetDiagnostics()->Error(DiagMessage(src) + << "cannot merge type '" << src_type->type + << "': conflicting public IDs"); + return false; + } + return true; +} - error |= !doMerge(src, table, package.get(), - mangle, false /* overlay */, true /* allow new */, callback); +static bool MergeEntry(IAaptContext* context, const Source& src, + ResourceEntry* dst_entry, ResourceEntry* src_entry) { + if (dst_entry->symbol_status.state < src_entry->symbol_status.state) { + // The incoming type's visibility is stronger, so we should override + // the visibility. + if (src_entry->symbol_status.state == SymbolState::kPublic) { + // Only copy the ID if the source is public, or else the ID is + // meaningless. + dst_entry->id = src_entry->id; } - return !error; + dst_entry->symbol_status = std::move(src_entry->symbol_status); + } else if (src_entry->symbol_status.state == SymbolState::kPublic && + dst_entry->symbol_status.state == SymbolState::kPublic && + dst_entry->id && src_entry->id && + dst_entry->id.value() != src_entry->id.value()) { + // Both entries are public and have different IDs. + context->GetDiagnostics()->Error( + DiagMessage(src) << "cannot merge entry '" << src_entry->name + << "': conflicting public IDs"); + return false; + } + return true; } -bool TableMerger::doMerge(const Source& src, - ResourceTable* srcTable, - ResourceTablePackage* srcPackage, - const bool manglePackage, - const bool overlay, - const bool allowNewResources, - FileMergeCallback callback) { - bool error = false; - - for (auto& srcType : srcPackage->types) { - ResourceTableType* dstType = mMasterPackage->findOrCreateType(srcType->type); - if (srcType->symbolStatus.state == SymbolState::kPublic) { - if (dstType->symbolStatus.state == SymbolState::kPublic && dstType->id && srcType->id - && dstType->id.value() == srcType->id.value()) { - // Both types are public and have different IDs. - mContext->getDiagnostics()->error(DiagMessage(src) - << "can not merge type '" - << srcType->type - << "': conflicting public IDs"); - error = true; - continue; - } +/** + * Modified CollisionResolver which will merge Styleables. Used with overlays. + * + * Styleables are not actual resources, but they are treated as such during the + * compilation phase. Styleables don't simply overlay each other, their + * definitions merge + * and accumulate. If both values are Styleables, we just merge them into the + * existing value. + */ +static ResourceTable::CollisionResult ResolveMergeCollision(Value* existing, + Value* incoming) { + if (Styleable* existing_styleable = ValueCast<Styleable>(existing)) { + if (Styleable* incoming_styleable = ValueCast<Styleable>(incoming)) { + // Styleables get merged. + existing_styleable->MergeWith(incoming_styleable); + return ResourceTable::CollisionResult::kKeepOriginal; + } + } + // Delegate to the default handler. + return ResourceTable::ResolveValueCollision(existing, incoming); +} - dstType->symbolStatus = std::move(srcType->symbolStatus); - dstType->id = srcType->id; - } +static ResourceTable::CollisionResult MergeConfigValue( + IAaptContext* context, const ResourceNameRef& res_name, const bool overlay, + ResourceConfigValue* dst_config_value, + ResourceConfigValue* src_config_value) { + using CollisionResult = ResourceTable::CollisionResult; + + Value* dst_value = dst_config_value->value.get(); + Value* src_value = src_config_value->value.get(); + + CollisionResult collision_result; + if (overlay) { + collision_result = ResolveMergeCollision(dst_value, src_value); + } else { + collision_result = + ResourceTable::ResolveValueCollision(dst_value, src_value); + } + + if (collision_result == CollisionResult::kConflict) { + if (overlay) { + return CollisionResult::kTakeNew; + } - for (auto& srcEntry : srcType->entries) { - ResourceEntry* dstEntry; - if (manglePackage) { - std::u16string mangledName = NameMangler::mangleEntry(srcPackage->name, - srcEntry->name); - if (allowNewResources) { - dstEntry = dstType->findOrCreateEntry(mangledName); - } else { - dstEntry = dstType->findEntry(mangledName); - } - } else { - if (allowNewResources) { - dstEntry = dstType->findOrCreateEntry(srcEntry->name); - } else { - dstEntry = dstType->findEntry(srcEntry->name); - } - } + // Error! + context->GetDiagnostics()->Error( + DiagMessage(src_value->GetSource()) + << "resource '" << res_name << "' has a conflicting value for " + << "configuration (" << src_config_value->config << ")"); + context->GetDiagnostics()->Note(DiagMessage(dst_value->GetSource()) + << "originally defined here"); + return CollisionResult::kConflict; + } + return collision_result; +} - if (!dstEntry) { - mContext->getDiagnostics()->error(DiagMessage(src) - << "resource " - << ResourceNameRef(srcPackage->name, - srcType->type, - srcEntry->name) - << " does not override an existing resource"); - mContext->getDiagnostics()->note(DiagMessage(src) - << "define an <add-resource> tag or use " - "--auto-add-overlay"); - error = true; - continue; - } +bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table, + ResourceTablePackage* src_package, + const bool mangle_package, const bool overlay, + const bool allow_new_resources, + const FileMergeCallback& callback) { + bool error = false; + + for (auto& src_type : src_package->types) { + ResourceTableType* dst_type = + master_package_->FindOrCreateType(src_type->type); + if (!MergeType(context_, src, dst_type, src_type.get())) { + error = true; + continue; + } - if (srcEntry->symbolStatus.state != SymbolState::kUndefined) { - if (srcEntry->symbolStatus.state == SymbolState::kPublic) { - if (dstEntry->symbolStatus.state == SymbolState::kPublic && - dstEntry->id && srcEntry->id && - dstEntry->id.value() != srcEntry->id.value()) { - // Both entries are public and have different IDs. - mContext->getDiagnostics()->error(DiagMessage(src) - << "can not merge entry '" - << srcEntry->name - << "': conflicting public IDs"); - error = true; - continue; - } - - if (srcEntry->id) { - dstEntry->id = srcEntry->id; - } - } - - if (dstEntry->symbolStatus.state != SymbolState::kPublic && - dstEntry->symbolStatus.state != srcEntry->symbolStatus.state) { - dstEntry->symbolStatus = std::move(srcEntry->symbolStatus); - } - } + for (auto& src_entry : src_type->entries) { + std::string entry_name = src_entry->name; + if (mangle_package) { + entry_name = + NameMangler::MangleEntry(src_package->name, src_entry->name); + } + + ResourceEntry* dst_entry; + if (allow_new_resources) { + dst_entry = dst_type->FindOrCreateEntry(entry_name); + } else { + dst_entry = dst_type->FindEntry(entry_name); + } + + const ResourceNameRef res_name(src_package->name, src_type->type, + src_entry->name); + + if (!dst_entry) { + context_->GetDiagnostics()->Error( + DiagMessage(src) << "resource " << res_name + << " does not override an existing resource"); + context_->GetDiagnostics()->Note( + DiagMessage(src) << "define an <add-resource> tag or use " + << "--auto-add-overlay"); + error = true; + continue; + } + + if (!MergeEntry(context_, src, dst_entry, src_entry.get())) { + error = true; + continue; + } + + for (auto& src_config_value : src_entry->values) { + using CollisionResult = ResourceTable::CollisionResult; + + ResourceConfigValue* dst_config_value = dst_entry->FindValue( + src_config_value->config, src_config_value->product); + if (dst_config_value) { + CollisionResult collision_result = + MergeConfigValue(context_, res_name, overlay, dst_config_value, + src_config_value.get()); + if (collision_result == CollisionResult::kConflict) { + error = true; + continue; + } else if (collision_result == CollisionResult::kKeepOriginal) { + continue; + } + } else { + dst_config_value = dst_entry->FindOrCreateValue( + src_config_value->config, src_config_value->product); + } - ResourceNameRef resName(mMasterPackage->name, dstType->type, dstEntry->name); - - for (auto& srcValue : srcEntry->values) { - ResourceConfigValue* dstValue = dstEntry->findValue(srcValue->config, - srcValue->product); - if (dstValue) { - const int collisionResult = ResourceTable::resolveValueCollision( - dstValue->value.get(), srcValue->value.get()); - if (collisionResult == 0 && !overlay) { - // Error! - ResourceNameRef resourceName(srcPackage->name, - srcType->type, - srcEntry->name); - - mContext->getDiagnostics()->error(DiagMessage(srcValue->value->getSource()) - << "resource '" << resourceName - << "' has a conflicting value for " - << "configuration (" - << srcValue->config << ")"); - mContext->getDiagnostics()->note(DiagMessage(dstValue->value->getSource()) - << "originally defined here"); - error = true; - continue; - } else if (collisionResult < 0) { - // Keep our existing value. - continue; - } - - } - - if (!dstValue) { - // Force create the entry if we didn't have it. - dstValue = dstEntry->findOrCreateValue(srcValue->config, srcValue->product); - } - - if (FileReference* f = valueCast<FileReference>(srcValue->value.get())) { - std::unique_ptr<FileReference> newFileRef; - if (manglePackage) { - newFileRef = cloneAndMangleFile(srcPackage->name, *f); - } else { - newFileRef = std::unique_ptr<FileReference>(f->clone( - &mMasterTable->stringPool)); - } - - if (callback) { - if (!callback(resName, srcValue->config, newFileRef.get(), f)) { - error = true; - continue; - } - } - dstValue->value = std::move(newFileRef); - - } else { - dstValue->value = std::unique_ptr<Value>(srcValue->value->clone( - &mMasterTable->stringPool)); - } + // Continue if we're taking the new resource. + + if (FileReference* f = + ValueCast<FileReference>(src_config_value->value.get())) { + std::unique_ptr<FileReference> new_file_ref; + if (mangle_package) { + new_file_ref = CloneAndMangleFile(src_package->name, *f); + } else { + new_file_ref = std::unique_ptr<FileReference>( + f->Clone(&master_table_->string_pool)); + } + + if (callback) { + if (!callback(res_name, src_config_value->config, + new_file_ref.get(), f)) { + error = true; + continue; } + } + dst_config_value->value = std::move(new_file_ref); + + } else { + dst_config_value->value = std::unique_ptr<Value>( + src_config_value->value->Clone(&master_table_->string_pool)); } + } } - return !error; + } + return !error; } -std::unique_ptr<FileReference> TableMerger::cloneAndMangleFile(const std::u16string& package, - const FileReference& fileRef) { - - StringPiece16 prefix, entry, suffix; - if (util::extractResFilePathParts(*fileRef.path, &prefix, &entry, &suffix)) { - std::u16string mangledEntry = NameMangler::mangleEntry(package, entry.toString()); - std::u16string newPath = prefix.toString() + mangledEntry + suffix.toString(); - std::unique_ptr<FileReference> newFileRef = util::make_unique<FileReference>( - mMasterTable->stringPool.makeRef(newPath)); - newFileRef->setComment(fileRef.getComment()); - newFileRef->setSource(fileRef.getSource()); - return newFileRef; - } - return std::unique_ptr<FileReference>(fileRef.clone(&mMasterTable->stringPool)); +std::unique_ptr<FileReference> TableMerger::CloneAndMangleFile( + const std::string& package, const FileReference& file_ref) { + StringPiece prefix, entry, suffix; + if (util::ExtractResFilePathParts(*file_ref.path, &prefix, &entry, &suffix)) { + std::string mangled_entry = + NameMangler::MangleEntry(package, entry.ToString()); + std::string newPath = prefix.ToString() + mangled_entry + suffix.ToString(); + std::unique_ptr<FileReference> new_file_ref = + util::make_unique<FileReference>( + master_table_->string_pool.MakeRef(newPath)); + new_file_ref->SetComment(file_ref.GetComment()); + new_file_ref->SetSource(file_ref.GetSource()); + return new_file_ref; + } + return std::unique_ptr<FileReference>( + file_ref.Clone(&master_table_->string_pool)); } -bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay) { - ResourceTable table; - std::u16string path = util::utf8ToUtf16(ResourceUtils::buildResourceFileName(fileDesc, - nullptr)); - std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>( - table.stringPool.makeRef(path)); - fileRef->setSource(fileDesc.source); - fileRef->file = file; - - ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0); - pkg->findOrCreateType(fileDesc.name.type) - ->findOrCreateEntry(fileDesc.name.entry) - ->findOrCreateValue(fileDesc.config, {}) - ->value = std::move(fileRef); - - return doMerge(file->getSource(), &table, pkg, - false /* mangle */, overlay /* overlay */, true /* allow new */, {}); +bool TableMerger::MergeFileImpl(const ResourceFile& file_desc, io::IFile* file, + bool overlay) { + ResourceTable table; + std::string path = ResourceUtils::BuildResourceFileName(file_desc); + std::unique_ptr<FileReference> file_ref = + util::make_unique<FileReference>(table.string_pool.MakeRef(path)); + file_ref->SetSource(file_desc.source); + file_ref->file = file; + + ResourceTablePackage* pkg = table.CreatePackage(file_desc.name.package, 0x0); + pkg->FindOrCreateType(file_desc.name.type) + ->FindOrCreateEntry(file_desc.name.entry) + ->FindOrCreateValue(file_desc.config, {}) + ->value = std::move(file_ref); + + return DoMerge(file->GetSource(), &table, pkg, false /* mangle */, + overlay /* overlay */, true /* allow_new */, {}); } -bool TableMerger::mergeFile(const ResourceFile& fileDesc, io::IFile* file) { - return mergeFileImpl(fileDesc, file, false /* overlay */); +bool TableMerger::MergeFile(const ResourceFile& file_desc, io::IFile* file) { + return MergeFileImpl(file_desc, file, false /* overlay */); } -bool TableMerger::mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file) { - return mergeFileImpl(fileDesc, file, true /* overlay */); +bool TableMerger::MergeFileOverlay(const ResourceFile& file_desc, + io::IFile* file) { + return MergeFileImpl(file_desc, file, true /* overlay */); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h index 80c2a5e69b66..4ab83c3f2de3 100644 --- a/tools/aapt2/link/TableMerger.h +++ b/tools/aapt2/link/TableMerger.h @@ -17,6 +17,11 @@ #ifndef AAPT_TABLEMERGER_H #define AAPT_TABLEMERGER_H +#include <functional> +#include <map> + +#include "android-base/macros.h" + #include "Resource.h" #include "ResourceTable.h" #include "ResourceValues.h" @@ -25,102 +30,114 @@ #include "process/IResourceTableConsumer.h" #include "util/Util.h" -#include <functional> -#include <map> - namespace aapt { struct TableMergerOptions { - /** - * If true, resources in overlays can be added without previously having existed. - */ - bool autoAddOverlay = false; + /** + * If true, resources in overlays can be added without previously having + * existed. + */ + bool auto_add_overlay = false; }; /** - * TableMerger takes resource tables and merges all packages within the tables that have the same + * TableMerger takes resource tables and merges all packages within the tables + * that have the same * package ID. * - * If a package has a different name, all the entries in that table have their names mangled - * to include the package name. This way there are no collisions. In order to do this correctly, - * the TableMerger needs to also mangle any FileReference paths. Once these are mangled, - * the original source path of the file, along with the new destination path is recorded in the + * If a package has a different name, all the entries in that table have their + * names mangled + * to include the package name. This way there are no collisions. In order to do + * this correctly, + * the TableMerger needs to also mangle any FileReference paths. Once these are + * mangled, + * the original source path of the file, along with the new destination path is + * recorded in the * queue returned from getFileMergeQueue(). * - * Once the merging is complete, a separate process can go collect the files from the various - * source APKs and either copy or process their XML and put them in the correct location in + * Once the merging is complete, a separate process can go collect the files + * from the various + * source APKs and either copy or process their XML and put them in the correct + * location in * the final APK. */ class TableMerger { -public: - /** - * Note: The outTable ResourceTable must live longer than this TableMerger. References - * are made to this ResourceTable for efficiency reasons. - */ - TableMerger(IAaptContext* context, ResourceTable* outTable, const TableMergerOptions& options); - - const std::set<std::u16string>& getMergedPackages() const { - return mMergedPackages; - } - - /** - * Merges resources from the same or empty package. This is for local sources. - * An io::IFileCollection is optional and used to find the referenced Files and process them. - */ - bool merge(const Source& src, ResourceTable* table, - io::IFileCollection* collection = nullptr); - - /** - * Merges resources from an overlay ResourceTable. - * An io::IFileCollection is optional and used to find the referenced Files and process them. - */ - bool mergeOverlay(const Source& src, ResourceTable* table, - io::IFileCollection* collection = nullptr); - - /** - * Merges resources from the given package, mangling the name. This is for static libraries. - * An io::IFileCollection is needed in order to find the referenced Files and process them. - */ - bool mergeAndMangle(const Source& src, const StringPiece16& package, ResourceTable* table, - io::IFileCollection* collection); - - /** - * Merges a compiled file that belongs to this same or empty package. This is for local sources. - */ - bool mergeFile(const ResourceFile& fileDesc, io::IFile* file); - - /** - * Merges a compiled file from an overlay, overriding an existing definition. - */ - bool mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file); - -private: - using FileMergeCallback = std::function<bool(const ResourceNameRef&, - const ConfigDescription& config, - FileReference*, FileReference*)>; - - IAaptContext* mContext; - ResourceTable* mMasterTable; - TableMergerOptions mOptions; - ResourceTablePackage* mMasterPackage; - - std::set<std::u16string> mMergedPackages; - - bool mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay); - - bool mergeImpl(const Source& src, ResourceTable* srcTable, io::IFileCollection* collection, - bool overlay, bool allowNew); - - bool doMerge(const Source& src, ResourceTable* srcTable, ResourceTablePackage* srcPackage, - const bool manglePackage, - const bool overlay, - const bool allowNewResources, - FileMergeCallback callback); - - std::unique_ptr<FileReference> cloneAndMangleFile(const std::u16string& package, - const FileReference& value); + public: + /** + * Note: The out_table ResourceTable must live longer than this TableMerger. + * References are made to this ResourceTable for efficiency reasons. + */ + TableMerger(IAaptContext* context, ResourceTable* out_table, + const TableMergerOptions& options); + + const std::set<std::string>& merged_packages() const { + return merged_packages_; + } + + /** + * Merges resources from the same or empty package. This is for local sources. + * An io::IFileCollection is optional and used to find the referenced Files + * and process them. + */ + bool Merge(const Source& src, ResourceTable* table, + io::IFileCollection* collection = nullptr); + + /** + * Merges resources from an overlay ResourceTable. + * An io::IFileCollection is optional and used to find the referenced Files + * and process them. + */ + bool MergeOverlay(const Source& src, ResourceTable* table, + io::IFileCollection* collection = nullptr); + + /** + * Merges resources from the given package, mangling the name. This is for + * static libraries. + * An io::IFileCollection is needed in order to find the referenced Files and + * process them. + */ + bool MergeAndMangle(const Source& src, const StringPiece& package, + ResourceTable* table, io::IFileCollection* collection); + + /** + * Merges a compiled file that belongs to this same or empty package. This is + * for local sources. + */ + bool MergeFile(const ResourceFile& fileDesc, io::IFile* file); + + /** + * Merges a compiled file from an overlay, overriding an existing definition. + */ + bool MergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file); + + private: + DISALLOW_COPY_AND_ASSIGN(TableMerger); + + using FileMergeCallback = std::function<bool(const ResourceNameRef&, + const ConfigDescription& config, + FileReference*, FileReference*)>; + + IAaptContext* context_; + ResourceTable* master_table_; + TableMergerOptions options_; + ResourceTablePackage* master_package_; + std::set<std::string> merged_packages_; + + bool MergeFileImpl(const ResourceFile& file_desc, io::IFile* file, + bool overlay); + + bool MergeImpl(const Source& src, ResourceTable* src_table, + io::IFileCollection* collection, bool overlay, bool allow_new); + + bool DoMerge(const Source& src, ResourceTable* src_table, + ResourceTablePackage* src_package, const bool mangle_package, + const bool overlay, const bool allow_new_resources, + const FileMergeCallback& callback); + + std::unique_ptr<FileReference> CloneAndMangleFile(const std::string& package, + const FileReference& value); }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_TABLEMERGER_H */ diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp index 4a80d3f48777..742f5a7a570a 100644 --- a/tools/aapt2/link/TableMerger_test.cpp +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -14,207 +14,334 @@ * limitations under the License. */ -#include "filter/ConfigFilter.h" -#include "io/FileSystem.h" #include "link/TableMerger.h" -#include "test/Builders.h" -#include "test/Context.h" -#include <gtest/gtest.h> +#include "filter/ConfigFilter.h" +#include "io/FileSystem.h" +#include "test/Test.h" namespace aapt { struct TableMergerTest : public ::testing::Test { - std::unique_ptr<IAaptContext> mContext; + std::unique_ptr<IAaptContext> context_; - void SetUp() override { - mContext = test::ContextBuilder() - // We are compiling this package. - .setCompilationPackage(u"com.app.a") + void SetUp() override { + context_ = + test::ContextBuilder() + // We are compiling this package. + .SetCompilationPackage("com.app.a") - // Merge all packages that have this package ID. - .setPackageId(0x7f) + // Merge all packages that have this package ID. + .SetPackageId(0x7f) - // Mangle all packages that do not have this package name. - .setNameManglerPolicy(NameManglerPolicy{ u"com.app.a", { u"com.app.b" } }) + // Mangle all packages that do not have this package name. + .SetNameManglerPolicy(NameManglerPolicy{"com.app.a", {"com.app.b"}}) - .build(); - } + .Build(); + } }; TEST_F(TableMergerTest, SimpleMerge) { - std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() - .setPackageId(u"com.app.a", 0x7f) - .addReference(u"@com.app.a:id/foo", u"@com.app.a:id/bar") - .addReference(u"@com.app.a:id/bar", u"@com.app.b:id/foo") - .addValue(u"@com.app.a:styleable/view", test::StyleableBuilder() - .addItem(u"@com.app.b:id/foo") - .build()) - .build(); - - std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() - .setPackageId(u"com.app.b", 0x7f) - .addSimple(u"@com.app.b:id/foo") - .build(); - - ResourceTable finalTable; - TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{}); - io::FileCollection collection; - - ASSERT_TRUE(merger.merge({}, tableA.get())); - ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get(), &collection)); - - EXPECT_TRUE(merger.getMergedPackages().count(u"com.app.b") != 0); - - // Entries from com.app.a should not be mangled. - AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/foo"))); - AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/bar"))); - AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:styleable/view"))); - - // The unmangled name should not be present. - AAPT_EXPECT_FALSE(finalTable.findResource(test::parseNameOrDie(u"@com.app.b:id/foo"))); - - // Look for the mangled name. - AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/com.app.b$foo"))); + std::unique_ptr<ResourceTable> table_a = + test::ResourceTableBuilder() + .SetPackageId("com.app.a", 0x7f) + .AddReference("com.app.a:id/foo", "com.app.a:id/bar") + .AddReference("com.app.a:id/bar", "com.app.b:id/foo") + .AddValue( + "com.app.a:styleable/view", + test::StyleableBuilder().AddItem("com.app.b:id/foo").Build()) + .Build(); + + std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() + .SetPackageId("com.app.b", 0x7f) + .AddSimple("com.app.b:id/foo") + .Build(); + + ResourceTable final_table; + TableMerger merger(context_.get(), &final_table, TableMergerOptions{}); + io::FileCollection collection; + + ASSERT_TRUE(merger.Merge({}, table_a.get())); + ASSERT_TRUE( + merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection)); + + EXPECT_TRUE(merger.merged_packages().count("com.app.b") != 0); + + // Entries from com.app.a should not be mangled. + AAPT_EXPECT_TRUE( + final_table.FindResource(test::ParseNameOrDie("com.app.a:id/foo"))); + AAPT_EXPECT_TRUE( + final_table.FindResource(test::ParseNameOrDie("com.app.a:id/bar"))); + AAPT_EXPECT_TRUE(final_table.FindResource( + test::ParseNameOrDie("com.app.a:styleable/view"))); + + // The unmangled name should not be present. + AAPT_EXPECT_FALSE( + final_table.FindResource(test::ParseNameOrDie("com.app.b:id/foo"))); + + // Look for the mangled name. + AAPT_EXPECT_TRUE(final_table.FindResource( + test::ParseNameOrDie("com.app.a:id/com.app.b$foo"))); } TEST_F(TableMergerTest, MergeFile) { - ResourceTable finalTable; - TableMergerOptions options; - options.autoAddOverlay = false; - TableMerger merger(mContext.get(), &finalTable, options); - - ResourceFile fileDesc; - fileDesc.config = test::parseConfigOrDie("hdpi-v4"); - fileDesc.name = test::parseNameOrDie(u"@layout/main"); - fileDesc.source = Source("res/layout-hdpi/main.xml"); - test::TestFile testFile("path/to/res/layout-hdpi/main.xml.flat"); - - ASSERT_TRUE(merger.mergeFile(fileDesc, &testFile)); - - FileReference* file = test::getValueForConfig<FileReference>(&finalTable, - u"@com.app.a:layout/main", - test::parseConfigOrDie("hdpi-v4")); - ASSERT_NE(nullptr, file); - EXPECT_EQ(std::u16string(u"res/layout-hdpi-v4/main.xml"), *file->path); + ResourceTable final_table; + TableMergerOptions options; + options.auto_add_overlay = false; + TableMerger merger(context_.get(), &final_table, options); + + ResourceFile file_desc; + file_desc.config = test::ParseConfigOrDie("hdpi-v4"); + file_desc.name = test::ParseNameOrDie("layout/main"); + file_desc.source = Source("res/layout-hdpi/main.xml"); + test::TestFile test_file("path/to/res/layout-hdpi/main.xml.flat"); + + ASSERT_TRUE(merger.MergeFile(file_desc, &test_file)); + + FileReference* file = test::GetValueForConfig<FileReference>( + &final_table, "com.app.a:layout/main", test::ParseConfigOrDie("hdpi-v4")); + ASSERT_NE(nullptr, file); + EXPECT_EQ(std::string("res/layout-hdpi-v4/main.xml"), *file->path); } TEST_F(TableMergerTest, MergeFileOverlay) { - ResourceTable finalTable; - TableMergerOptions tableMergerOptions; - tableMergerOptions.autoAddOverlay = false; - TableMerger merger(mContext.get(), &finalTable, tableMergerOptions); - - ResourceFile fileDesc; - fileDesc.name = test::parseNameOrDie(u"@xml/foo"); - test::TestFile fileA("path/to/fileA.xml.flat"); - test::TestFile fileB("path/to/fileB.xml.flat"); - - ASSERT_TRUE(merger.mergeFile(fileDesc, &fileA)); - ASSERT_TRUE(merger.mergeFileOverlay(fileDesc, &fileB)); + ResourceTable final_table; + TableMergerOptions options; + options.auto_add_overlay = false; + TableMerger merger(context_.get(), &final_table, options); + + ResourceFile file_desc; + file_desc.name = test::ParseNameOrDie("xml/foo"); + test::TestFile file_a("path/to/fileA.xml.flat"); + test::TestFile file_b("path/to/fileB.xml.flat"); + + ASSERT_TRUE(merger.MergeFile(file_desc, &file_a)); + ASSERT_TRUE(merger.MergeFileOverlay(file_desc, &file_b)); } TEST_F(TableMergerTest, MergeFileReferences) { - std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() - .setPackageId(u"com.app.a", 0x7f) - .addFileReference(u"@com.app.a:xml/file", u"res/xml/file.xml") - .build(); - std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() - .setPackageId(u"com.app.b", 0x7f) - .addFileReference(u"@com.app.b:xml/file", u"res/xml/file.xml") - .build(); - - ResourceTable finalTable; - TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{}); - io::FileCollection collection; - collection.insertFile("res/xml/file.xml"); - - ASSERT_TRUE(merger.merge({}, tableA.get())); - ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get(), &collection)); - - FileReference* f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/file"); - ASSERT_NE(f, nullptr); - EXPECT_EQ(std::u16string(u"res/xml/file.xml"), *f->path); - - f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/com.app.b$file"); - ASSERT_NE(f, nullptr); - EXPECT_EQ(std::u16string(u"res/xml/com.app.b$file.xml"), *f->path); + std::unique_ptr<ResourceTable> table_a = + test::ResourceTableBuilder() + .SetPackageId("com.app.a", 0x7f) + .AddFileReference("com.app.a:xml/file", "res/xml/file.xml") + .Build(); + std::unique_ptr<ResourceTable> table_b = + test::ResourceTableBuilder() + .SetPackageId("com.app.b", 0x7f) + .AddFileReference("com.app.b:xml/file", "res/xml/file.xml") + .Build(); + + ResourceTable final_table; + TableMerger merger(context_.get(), &final_table, TableMergerOptions{}); + io::FileCollection collection; + collection.InsertFile("res/xml/file.xml"); + + ASSERT_TRUE(merger.Merge({}, table_a.get())); + ASSERT_TRUE( + merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection)); + + FileReference* f = + test::GetValue<FileReference>(&final_table, "com.app.a:xml/file"); + ASSERT_NE(f, nullptr); + EXPECT_EQ(std::string("res/xml/file.xml"), *f->path); + + f = test::GetValue<FileReference>(&final_table, + "com.app.a:xml/com.app.b$file"); + ASSERT_NE(f, nullptr); + EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), *f->path); } TEST_F(TableMergerTest, OverrideResourceWithOverlay) { - std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder() - .setPackageId(u"", 0x00) - .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true")) - .build(); - std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder() - .setPackageId(u"", 0x00) - .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"false")) - .build(); - - ResourceTable finalTable; - TableMergerOptions tableMergerOptions; - tableMergerOptions.autoAddOverlay = false; - TableMerger merger(mContext.get(), &finalTable, tableMergerOptions); - - ASSERT_TRUE(merger.merge({}, base.get())); - ASSERT_TRUE(merger.mergeOverlay({}, overlay.get())); - - BinaryPrimitive* foo = test::getValue<BinaryPrimitive>(&finalTable, u"@com.app.a:bool/foo"); - ASSERT_NE(nullptr, foo); - EXPECT_EQ(0x0u, foo->value.data); + std::unique_ptr<ResourceTable> base = + test::ResourceTableBuilder() + .SetPackageId("", 0x00) + .AddValue("bool/foo", ResourceUtils::TryParseBool("true")) + .Build(); + std::unique_ptr<ResourceTable> overlay = + test::ResourceTableBuilder() + .SetPackageId("", 0x00) + .AddValue("bool/foo", ResourceUtils::TryParseBool("false")) + .Build(); + + ResourceTable final_table; + TableMergerOptions options; + options.auto_add_overlay = false; + TableMerger merger(context_.get(), &final_table, options); + + ASSERT_TRUE(merger.Merge({}, base.get())); + ASSERT_TRUE(merger.MergeOverlay({}, overlay.get())); + + BinaryPrimitive* foo = + test::GetValue<BinaryPrimitive>(&final_table, "com.app.a:bool/foo"); + ASSERT_NE(nullptr, foo); + EXPECT_EQ(0x0u, foo->value.data); +} + +TEST_F(TableMergerTest, OverrideSameResourceIdsWithOverlay) { + std::unique_ptr<ResourceTable> base = + test::ResourceTableBuilder() + .SetPackageId("", 0x7f) + .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), + SymbolState::kPublic) + .Build(); + std::unique_ptr<ResourceTable> overlay = + test::ResourceTableBuilder() + .SetPackageId("", 0x7f) + .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), + SymbolState::kPublic) + .Build(); + + ResourceTable final_table; + TableMergerOptions options; + options.auto_add_overlay = false; + TableMerger merger(context_.get(), &final_table, options); + + ASSERT_TRUE(merger.Merge({}, base.get())); + ASSERT_TRUE(merger.MergeOverlay({}, overlay.get())); +} + +TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) { + std::unique_ptr<ResourceTable> base = + test::ResourceTableBuilder() + .SetPackageId("", 0x7f) + .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), + SymbolState::kPublic) + .Build(); + std::unique_ptr<ResourceTable> overlay = + test::ResourceTableBuilder() + .SetPackageId("", 0x7f) + .SetSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001), + SymbolState::kPublic) + .Build(); + + ResourceTable final_table; + TableMergerOptions options; + options.auto_add_overlay = false; + TableMerger merger(context_.get(), &final_table, options); + + ASSERT_TRUE(merger.Merge({}, base.get())); + ASSERT_FALSE(merger.MergeOverlay({}, overlay.get())); +} + +TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) { + std::unique_ptr<ResourceTable> base = + test::ResourceTableBuilder() + .SetPackageId("", 0x7f) + .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), + SymbolState::kPublic) + .Build(); + std::unique_ptr<ResourceTable> overlay = + test::ResourceTableBuilder() + .SetPackageId("", 0x7f) + .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002), + SymbolState::kPublic) + .Build(); + + ResourceTable final_table; + TableMergerOptions options; + options.auto_add_overlay = false; + TableMerger merger(context_.get(), &final_table, options); + + ASSERT_TRUE(merger.Merge({}, base.get())); + ASSERT_FALSE(merger.MergeOverlay({}, overlay.get())); } TEST_F(TableMergerTest, MergeAddResourceFromOverlay) { - std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() - .setPackageId(u"", 0x7f) - .setSymbolState(u"@bool/foo", {}, SymbolState::kUndefined) - .build(); - std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() - .setPackageId(u"", 0x7f) - .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true")) - .build(); - - ResourceTable finalTable; - TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{}); - - ASSERT_TRUE(merger.merge({}, tableA.get())); - ASSERT_TRUE(merger.mergeOverlay({}, tableB.get())); + std::unique_ptr<ResourceTable> table_a = + test::ResourceTableBuilder() + .SetPackageId("", 0x7f) + .SetSymbolState("bool/foo", {}, SymbolState::kUndefined) + .Build(); + std::unique_ptr<ResourceTable> table_b = + test::ResourceTableBuilder() + .SetPackageId("", 0x7f) + .AddValue("bool/foo", ResourceUtils::TryParseBool("true")) + .Build(); + + ResourceTable final_table; + TableMerger merger(context_.get(), &final_table, TableMergerOptions{}); + + ASSERT_TRUE(merger.Merge({}, table_a.get())); + ASSERT_TRUE(merger.MergeOverlay({}, table_b.get())); } TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) { - std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() - .setPackageId(u"", 0x7f) - .build(); - std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() - .setPackageId(u"", 0x7f) - .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true")) - .build(); - - ResourceTable finalTable; - TableMergerOptions options; - options.autoAddOverlay = true; - TableMerger merger(mContext.get(), &finalTable, options); - - ASSERT_TRUE(merger.merge({}, tableA.get())); - ASSERT_TRUE(merger.mergeOverlay({}, tableB.get())); + std::unique_ptr<ResourceTable> table_a = + test::ResourceTableBuilder().SetPackageId("", 0x7f).Build(); + std::unique_ptr<ResourceTable> table_b = + test::ResourceTableBuilder() + .SetPackageId("", 0x7f) + .AddValue("bool/foo", ResourceUtils::TryParseBool("true")) + .Build(); + + ResourceTable final_table; + TableMergerOptions options; + options.auto_add_overlay = true; + TableMerger merger(context_.get(), &final_table, options); + + ASSERT_TRUE(merger.Merge({}, table_a.get())); + ASSERT_TRUE(merger.MergeOverlay({}, table_b.get())); } TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) { - std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() - .setPackageId(u"", 0x7f) - .build(); - std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() - .setPackageId(u"", 0x7f) - .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true")) - .build(); - - ResourceTable finalTable; - TableMergerOptions options; - options.autoAddOverlay = false; - TableMerger merger(mContext.get(), &finalTable, options); - - ASSERT_TRUE(merger.merge({}, tableA.get())); - ASSERT_FALSE(merger.mergeOverlay({}, tableB.get())); + std::unique_ptr<ResourceTable> table_a = + test::ResourceTableBuilder().SetPackageId("", 0x7f).Build(); + std::unique_ptr<ResourceTable> table_b = + test::ResourceTableBuilder() + .SetPackageId("", 0x7f) + .AddValue("bool/foo", ResourceUtils::TryParseBool("true")) + .Build(); + + ResourceTable final_table; + TableMergerOptions options; + options.auto_add_overlay = false; + TableMerger merger(context_.get(), &final_table, options); + + ASSERT_TRUE(merger.Merge({}, table_a.get())); + ASSERT_FALSE(merger.MergeOverlay({}, table_b.get())); +} + +TEST_F(TableMergerTest, OverlaidStyleablesShouldBeMerged) { + std::unique_ptr<ResourceTable> table_a = + test::ResourceTableBuilder() + .SetPackageId("com.app.a", 0x7f) + .AddValue("com.app.a:styleable/Foo", + test::StyleableBuilder() + .AddItem("com.app.a:attr/bar") + .AddItem("com.app.a:attr/foo", ResourceId(0x01010000)) + .Build()) + .Build(); + + std::unique_ptr<ResourceTable> table_b = + test::ResourceTableBuilder() + .SetPackageId("com.app.a", 0x7f) + .AddValue("com.app.a:styleable/Foo", + test::StyleableBuilder() + .AddItem("com.app.a:attr/bat") + .AddItem("com.app.a:attr/foo") + .Build()) + .Build(); + + ResourceTable final_table; + TableMergerOptions options; + options.auto_add_overlay = true; + TableMerger merger(context_.get(), &final_table, options); + + ASSERT_TRUE(merger.Merge({}, table_a.get())); + ASSERT_TRUE(merger.MergeOverlay({}, table_b.get())); + + Styleable* styleable = + test::GetValue<Styleable>(&final_table, "com.app.a:styleable/Foo"); + ASSERT_NE(nullptr, styleable); + + std::vector<Reference> expected_refs = { + Reference(test::ParseNameOrDie("com.app.a:attr/bar")), + Reference(test::ParseNameOrDie("com.app.a:attr/bat")), + Reference(test::ParseNameOrDie("com.app.a:attr/foo"), + ResourceId(0x01010000)), + }; + + EXPECT_EQ(expected_refs, styleable->entries); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/VersionCollapser.cpp b/tools/aapt2/link/VersionCollapser.cpp new file mode 100644 index 000000000000..3df58994333f --- /dev/null +++ b/tools/aapt2/link/VersionCollapser.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "link/Linkers.h" + +#include <algorithm> +#include <vector> + +#include "ResourceTable.h" + +namespace aapt { + +template <typename Iterator, typename Pred> +class FilterIterator { + public: + FilterIterator(Iterator begin, Iterator end, Pred pred = Pred()) + : current_(begin), end_(end), pred_(pred) { + Advance(); + } + + bool HasNext() { return current_ != end_; } + + Iterator NextIter() { + Iterator iter = current_; + ++current_; + Advance(); + return iter; + } + + typename Iterator::reference Next() { return *NextIter(); } + + private: + void Advance() { + for (; current_ != end_; ++current_) { + if (pred_(*current_)) { + return; + } + } + } + + Iterator current_, end_; + Pred pred_; +}; + +template <typename Iterator, typename Pred> +FilterIterator<Iterator, Pred> make_filter_iterator(Iterator begin, + Iterator end = Iterator(), + Pred pred = Pred()) { + return FilterIterator<Iterator, Pred>(begin, end, pred); +} + +/** + * Every Configuration with an SDK version specified that is less than minSdk + * will be removed. + * The exception is when there is no exact matching resource for the minSdk. The + * next smallest + * one will be kept. + */ +static void CollapseVersions(int min_sdk, ResourceEntry* entry) { + // First look for all sdks less than minSdk. + for (auto iter = entry->values.rbegin(); iter != entry->values.rend(); + ++iter) { + // Check if the item was already marked for removal. + if (!(*iter)) { + continue; + } + + const ConfigDescription& config = (*iter)->config; + if (config.sdkVersion <= min_sdk) { + // This is the first configuration we've found with a smaller or equal SDK + // level + // to the minimum. We MUST keep this one, but remove all others we find, + // which get + // overridden by this one. + + ConfigDescription config_without_sdk = config.CopyWithoutSdkVersion(); + auto pred = [&](const std::unique_ptr<ResourceConfigValue>& val) -> bool { + // Check that the value hasn't already been marked for removal. + if (!val) { + return false; + } + + // Only return Configs that differ in SDK version. + config_without_sdk.sdkVersion = val->config.sdkVersion; + return config_without_sdk == val->config && + val->config.sdkVersion <= min_sdk; + }; + + // Remove the rest that match. + auto filter_iter = + make_filter_iterator(iter + 1, entry->values.rend(), pred); + while (filter_iter.HasNext()) { + filter_iter.Next() = {}; + } + } + } + + // Now erase the nullptr values. + entry->values.erase( + std::remove_if(entry->values.begin(), entry->values.end(), + [](const std::unique_ptr<ResourceConfigValue>& val) + -> bool { return val == nullptr; }), + entry->values.end()); + + // Strip the version qualifiers for every resource with version <= minSdk. + // This will ensure + // that the resource entries are all packed together in the same ResTable_type + // struct + // and take up less space in the resources.arsc table. + bool modified = false; + for (std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { + if (config_value->config.sdkVersion != 0 && + config_value->config.sdkVersion <= min_sdk) { + // Override the resource with a Configuration without an SDK. + std::unique_ptr<ResourceConfigValue> new_value = + util::make_unique<ResourceConfigValue>( + config_value->config.CopyWithoutSdkVersion(), + config_value->product); + new_value->value = std::move(config_value->value); + config_value = std::move(new_value); + + modified = true; + } + } + + if (modified) { + // We've modified the keys (ConfigDescription) by changing the sdkVersion to + // 0. We MUST re-sort to ensure ordering guarantees hold. + std::sort(entry->values.begin(), entry->values.end(), + [](const std::unique_ptr<ResourceConfigValue>& a, + const std::unique_ptr<ResourceConfigValue>& b) -> bool { + return a->config.compare(b->config) < 0; + }); + } +} + +bool VersionCollapser::Consume(IAaptContext* context, ResourceTable* table) { + const int min_sdk = context->GetMinSdkVersion(); + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + CollapseVersions(min_sdk, entry.get()); + } + } + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/link/VersionCollapser_test.cpp b/tools/aapt2/link/VersionCollapser_test.cpp new file mode 100644 index 000000000000..1b5592f717d9 --- /dev/null +++ b/tools/aapt2/link/VersionCollapser_test.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "link/Linkers.h" + +#include "test/Test.h" + +namespace aapt { + +static std::unique_ptr<ResourceTable> BuildTableWithConfigs( + const StringPiece& name, std::initializer_list<std::string> list) { + test::ResourceTableBuilder builder; + for (const std::string& item : list) { + builder.AddSimple(name, test::ParseConfigOrDie(item)); + } + return builder.Build(); +} + +TEST(VersionCollapserTest, CollapseVersions) { + std::unique_ptr<IAaptContext> context = + test::ContextBuilder().SetMinSdkVersion(7).Build(); + + const StringPiece res_name = "@android:string/foo"; + + std::unique_ptr<ResourceTable> table = BuildTableWithConfigs( + res_name, + {"land-v4", "land-v5", "sw600dp", "land-v6", "land-v14", "land-v21"}); + + VersionCollapser collapser; + ASSERT_TRUE(collapser.Consume(context.get(), table.get())); + + // These should be removed. + EXPECT_EQ(nullptr, + test::GetValueForConfig<Id>(table.get(), res_name, + test::ParseConfigOrDie("land-v4"))); + EXPECT_EQ(nullptr, + test::GetValueForConfig<Id>(table.get(), res_name, + test::ParseConfigOrDie("land-v5"))); + // This one should be removed because it was renamed to 'land', with the + // version dropped. + EXPECT_EQ(nullptr, + test::GetValueForConfig<Id>(table.get(), res_name, + test::ParseConfigOrDie("land-v6"))); + + // These should remain. + EXPECT_NE(nullptr, + test::GetValueForConfig<Id>(table.get(), res_name, + test::ParseConfigOrDie("sw600dp"))); + + // 'land' should be present because it was renamed from 'land-v6'. + EXPECT_NE(nullptr, + test::GetValueForConfig<Id>(table.get(), res_name, + test::ParseConfigOrDie("land"))); + EXPECT_NE(nullptr, + test::GetValueForConfig<Id>(table.get(), res_name, + test::ParseConfigOrDie("land-v14"))); + EXPECT_NE(nullptr, + test::GetValueForConfig<Id>(table.get(), res_name, + test::ParseConfigOrDie("land-v21"))); +} + +TEST(VersionCollapserTest, CollapseVersionsWhenMinSdkIsHighest) { + std::unique_ptr<IAaptContext> context = + test::ContextBuilder().SetMinSdkVersion(21).Build(); + + const StringPiece res_name = "@android:string/foo"; + + std::unique_ptr<ResourceTable> table = BuildTableWithConfigs( + res_name, {"land-v4", "land-v5", "sw600dp", "land-v6", "land-v14", + "land-v21", "land-v22"}); + VersionCollapser collapser; + ASSERT_TRUE(collapser.Consume(context.get(), table.get())); + + // These should all be removed. + EXPECT_EQ(nullptr, + test::GetValueForConfig<Id>(table.get(), res_name, + test::ParseConfigOrDie("land-v4"))); + EXPECT_EQ(nullptr, + test::GetValueForConfig<Id>(table.get(), res_name, + test::ParseConfigOrDie("land-v5"))); + EXPECT_EQ(nullptr, + test::GetValueForConfig<Id>(table.get(), res_name, + test::ParseConfigOrDie("land-v6"))); + EXPECT_EQ(nullptr, + test::GetValueForConfig<Id>(table.get(), res_name, + test::ParseConfigOrDie("land-v14"))); + + // These should remain. + EXPECT_NE(nullptr, + test::GetValueForConfig<Id>( + table.get(), res_name, + test::ParseConfigOrDie("sw600dp").CopyWithoutSdkVersion())); + + // land-v21 should have been converted to land. + EXPECT_NE(nullptr, + test::GetValueForConfig<Id>(table.get(), res_name, + test::ParseConfigOrDie("land"))); + // land-v22 should remain as-is. + EXPECT_NE(nullptr, + test::GetValueForConfig<Id>(table.get(), res_name, + test::ParseConfigOrDie("land-v22"))); +} + +} // namespace aapt diff --git a/tools/aapt2/link/XmlNamespaceRemover.cpp b/tools/aapt2/link/XmlNamespaceRemover.cpp new file mode 100644 index 000000000000..24aa5660ae30 --- /dev/null +++ b/tools/aapt2/link/XmlNamespaceRemover.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "link/Linkers.h" + +#include <algorithm> + +#include "ResourceTable.h" + +namespace aapt { + +namespace { + +/** + * Visits each xml Node, removing URI references and nested namespaces. + */ +class XmlVisitor : public xml::Visitor { + public: + explicit XmlVisitor(bool keep_uris) : keep_uris_(keep_uris) {} + + void Visit(xml::Element* el) override { + // Strip namespaces + for (auto& child : el->children) { + while (child && xml::NodeCast<xml::Namespace>(child.get())) { + if (child->children.empty()) { + child = {}; + } else { + child = std::move(child->children.front()); + child->parent = el; + } + } + } + el->children.erase( + std::remove_if(el->children.begin(), el->children.end(), + [](const std::unique_ptr<xml::Node>& child) -> bool { + return child == nullptr; + }), + el->children.end()); + + if (!keep_uris_) { + for (xml::Attribute& attr : el->attributes) { + attr.namespace_uri = std::string(); + } + el->namespace_uri = std::string(); + } + xml::Visitor::Visit(el); + } + + private: + DISALLOW_COPY_AND_ASSIGN(XmlVisitor); + + bool keep_uris_; +}; + +} // namespace + +bool XmlNamespaceRemover::Consume(IAaptContext* context, + xml::XmlResource* resource) { + if (!resource->root) { + return false; + } + // Replace any root namespaces until the root is a non-namespace node + while (xml::NodeCast<xml::Namespace>(resource->root.get())) { + if (resource->root->children.empty()) { + break; + } + resource->root = std::move(resource->root->children.front()); + resource->root->parent = nullptr; + } + XmlVisitor visitor(keep_uris_); + resource->root->Accept(&visitor); + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/link/XmlNamespaceRemover_test.cpp b/tools/aapt2/link/XmlNamespaceRemover_test.cpp new file mode 100644 index 000000000000..a176c03a6432 --- /dev/null +++ b/tools/aapt2/link/XmlNamespaceRemover_test.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "link/Linkers.h" + +#include "test/Test.h" + +namespace aapt { + +class XmlUriTestVisitor : public xml::Visitor { + public: + XmlUriTestVisitor() = default; + + void Visit(xml::Element* el) override { + for (const auto& attr : el->attributes) { + EXPECT_EQ(std::string(), attr.namespace_uri); + } + EXPECT_EQ(std::string(), el->namespace_uri); + xml::Visitor::Visit(el); + } + + void Visit(xml::Namespace* ns) override { + EXPECT_EQ(std::string(), ns->namespace_uri); + xml::Visitor::Visit(ns); + } + + private: + DISALLOW_COPY_AND_ASSIGN(XmlUriTestVisitor); +}; + +class XmlNamespaceTestVisitor : public xml::Visitor { + public: + XmlNamespaceTestVisitor() = default; + + void Visit(xml::Namespace* ns) override { + ADD_FAILURE() << "Detected namespace: " << ns->namespace_prefix << "=\"" + << ns->namespace_uri << "\""; + xml::Visitor::Visit(ns); + } + + private: + DISALLOW_COPY_AND_ASSIGN(XmlNamespaceTestVisitor); +}; + +class XmlNamespaceRemoverTest : public ::testing::Test { + public: + void SetUp() override { + context_ = + test::ContextBuilder().SetCompilationPackage("com.app.test").Build(); + } + + protected: + std::unique_ptr<IAaptContext> context_; +}; + +TEST_F(XmlNamespaceRemoverTest, RemoveUris) { + std::unique_ptr<xml::XmlResource> doc = + test::BuildXmlDomForPackageName(context_.get(), R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:text="hello" />)EOF"); + + XmlNamespaceRemover remover; + ASSERT_TRUE(remover.Consume(context_.get(), doc.get())); + + xml::Node* root = doc.get()->root.get(); + ASSERT_NE(root, nullptr); + + XmlUriTestVisitor visitor; + root->Accept(&visitor); +} + +TEST_F(XmlNamespaceRemoverTest, RemoveNamespaces) { + std::unique_ptr<xml::XmlResource> doc = + test::BuildXmlDomForPackageName(context_.get(), R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:foo="http://schemas.android.com/apk/res/foo" + foo:bar="foobar" + android:text="hello" />)EOF"); + + XmlNamespaceRemover remover; + ASSERT_TRUE(remover.Consume(context_.get(), doc.get())); + + xml::Node* root = doc.get()->root.get(); + ASSERT_NE(root, nullptr); + + XmlNamespaceTestVisitor visitor; + root->Accept(&visitor); +} + +TEST_F(XmlNamespaceRemoverTest, RemoveNestedNamespaces) { + std::unique_ptr<xml::XmlResource> doc = + test::BuildXmlDomForPackageName(context_.get(), R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:text="hello"> + <View xmlns:foo="http://schemas.example.com/foo" + android:text="foo"/> + </View>)EOF"); + + XmlNamespaceRemover remover; + ASSERT_TRUE(remover.Consume(context_.get(), doc.get())); + + xml::Node* root = doc.get()->root.get(); + ASSERT_NE(root, nullptr); + + XmlNamespaceTestVisitor visitor; + root->Accept(&visitor); +} + +} // namespace aapt diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index 568bc74895c7..a8198318b69e 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -14,10 +14,11 @@ * limitations under the License. */ +#include "link/Linkers.h" + #include "Diagnostics.h" #include "ResourceUtils.h" #include "SdkConstants.h" -#include "link/Linkers.h" #include "link/ReferenceLinker.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" @@ -29,145 +30,161 @@ namespace aapt { namespace { /** - * Visits all references (including parents of styles, references in styles, arrays, etc) and - * links their symbolic name to their Resource ID, performing mangling and package aliasing + * Visits all references (including parents of styles, references in styles, + * arrays, etc) and + * links their symbolic name to their Resource ID, performing mangling and + * package aliasing * as needed. */ class ReferenceVisitor : public ValueVisitor { -public: - using ValueVisitor::visit; - - ReferenceVisitor(IAaptContext* context, SymbolTable* symbols, xml::IPackageDeclStack* decls, - CallSite* callSite) : - mContext(context), mSymbols(symbols), mDecls(decls), mCallSite(callSite), - mError(false) { + public: + using ValueVisitor::Visit; + + ReferenceVisitor(IAaptContext* context, SymbolTable* symbols, + xml::IPackageDeclStack* decls, CallSite* callsite) + : context_(context), + symbols_(symbols), + decls_(decls), + callsite_(callsite), + error_(false) {} + + void Visit(Reference* ref) override { + if (!ReferenceLinker::LinkReference(ref, context_, symbols_, decls_, + callsite_)) { + error_ = true; } + } - void visit(Reference* ref) override { - if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mDecls, mCallSite)) { - mError = true; - } - } + bool HasError() const { return error_; } - bool hasError() const { - return mError; - } + private: + DISALLOW_COPY_AND_ASSIGN(ReferenceVisitor); -private: - IAaptContext* mContext; - SymbolTable* mSymbols; - xml::IPackageDeclStack* mDecls; - CallSite* mCallSite; - bool mError; + IAaptContext* context_; + SymbolTable* symbols_; + xml::IPackageDeclStack* decls_; + CallSite* callsite_; + bool error_; }; /** * Visits each xml Element and compiles the attributes within. */ class XmlVisitor : public xml::PackageAwareVisitor { -public: - using xml::PackageAwareVisitor::visit; - - XmlVisitor(IAaptContext* context, SymbolTable* symbols, const Source& source, - std::set<int>* sdkLevelsFound, CallSite* callSite) : - mContext(context), mSymbols(symbols), mSource(source), mSdkLevelsFound(sdkLevelsFound), - mCallSite(callSite), mReferenceVisitor(context, symbols, this, callSite) { - } - - void visit(xml::Element* el) override { - const Source source = mSource.withLine(el->lineNumber); - for (xml::Attribute& attr : el->attributes) { - Maybe<xml::ExtractedPackage> maybePackage = - xml::extractPackageFromNamespace(attr.namespaceUri); - if (maybePackage) { - // There is a valid package name for this attribute. We will look this up. - StringPiece16 package = maybePackage.value().package; - if (package.empty()) { - // Empty package means the 'current' or 'local' package. - package = mContext->getCompilationPackage(); - } - - Reference attrRef(ResourceNameRef(package, ResourceType::kAttr, attr.name)); - attrRef.privateReference = maybePackage.value().privateNamespace; - - std::string errStr; - attr.compiledAttribute = ReferenceLinker::compileXmlAttribute( - attrRef, mContext->getNameMangler(), mSymbols, mCallSite, &errStr); - - // Convert the string value into a compiled Value if this is a valid attribute. - if (attr.compiledAttribute) { - if (attr.compiledAttribute.value().id) { - // Record all SDK levels from which the attributes were defined. - const size_t sdkLevel = findAttributeSdkLevel( - attr.compiledAttribute.value().id.value()); - if (sdkLevel > 1) { - mSdkLevelsFound->insert(sdkLevel); - } - } - - const Attribute* attribute = &attr.compiledAttribute.value().attribute; - attr.compiledValue = ResourceUtils::parseItemForAttribute(attr.value, - attribute); - if (!attr.compiledValue && - !(attribute->typeMask & android::ResTable_map::TYPE_STRING)) { - // We won't be able to encode this as a string. - mContext->getDiagnostics()->error( - DiagMessage(source) << "'" << attr.value << "' " - << "is incompatible with attribute " - << package << ":" << attr.name << " " - << *attribute); - mError = true; - } - - } else { - mContext->getDiagnostics()->error(DiagMessage(source) - << "attribute '" << package << ":" - << attr.name << "' " << errStr); - mError = true; - - } - } else { - // We still encode references. - attr.compiledValue = ResourceUtils::tryParseReference(attr.value); - } + public: + using xml::PackageAwareVisitor::Visit; + + XmlVisitor(IAaptContext* context, SymbolTable* symbols, const Source& source, + std::set<int>* sdk_levels_found, CallSite* callsite) + : context_(context), + symbols_(symbols), + source_(source), + sdk_levels_found_(sdk_levels_found), + callsite_(callsite), + reference_visitor_(context, symbols, this, callsite) {} + + void Visit(xml::Element* el) override { + const Source source = source_.WithLine(el->line_number); + for (xml::Attribute& attr : el->attributes) { + Maybe<xml::ExtractedPackage> maybe_package = + xml::ExtractPackageFromNamespace(attr.namespace_uri); + if (maybe_package) { + // There is a valid package name for this attribute. We will look this + // up. + StringPiece package = maybe_package.value().package; + if (package.empty()) { + // Empty package means the 'current' or 'local' package. + package = context_->GetCompilationPackage(); + } - if (attr.compiledValue) { - // With a compiledValue, we must resolve the reference and assign it an ID. - attr.compiledValue->setSource(source); - attr.compiledValue->accept(&mReferenceVisitor); + Reference attr_ref( + ResourceNameRef(package, ResourceType::kAttr, attr.name)); + attr_ref.private_reference = maybe_package.value().private_namespace; + + std::string err_str; + attr.compiled_attribute = ReferenceLinker::CompileXmlAttribute( + attr_ref, context_->GetNameMangler(), symbols_, callsite_, + &err_str); + + // Convert the string value into a compiled Value if this is a valid + // attribute. + if (attr.compiled_attribute) { + if (attr.compiled_attribute.value().id) { + // Record all SDK levels from which the attributes were defined. + const size_t sdk_level = FindAttributeSdkLevel( + attr.compiled_attribute.value().id.value()); + if (sdk_level > 1) { + sdk_levels_found_->insert(sdk_level); } + } + + const Attribute* attribute = + &attr.compiled_attribute.value().attribute; + attr.compiled_value = + ResourceUtils::TryParseItemForAttribute(attr.value, attribute); + if (!attr.compiled_value && + !(attribute->type_mask & android::ResTable_map::TYPE_STRING)) { + // We won't be able to encode this as a string. + context_->GetDiagnostics()->Error( + DiagMessage(source) << "'" << attr.value << "' " + << "is incompatible with attribute " + << package << ":" << attr.name << " " + << *attribute); + error_ = true; + } + + } else { + context_->GetDiagnostics()->Error(DiagMessage(source) + << "attribute '" << package << ":" + << attr.name << "' " << err_str); + error_ = true; } - - // Call the super implementation. - xml::PackageAwareVisitor::visit(el); + } else if (!attr.compiled_value) { + // We still encode references, but only if we haven't manually set this + // to + // another compiled value. + attr.compiled_value = ResourceUtils::TryParseReference(attr.value); + } + + if (attr.compiled_value) { + // With a compiledValue, we must resolve the reference and assign it an + // ID. + attr.compiled_value->SetSource(source); + attr.compiled_value->Accept(&reference_visitor_); + } } - bool hasError() { - return mError || mReferenceVisitor.hasError(); - } + // Call the super implementation. + xml::PackageAwareVisitor::Visit(el); + } -private: - IAaptContext* mContext; - SymbolTable* mSymbols; - Source mSource; - std::set<int>* mSdkLevelsFound; - CallSite* mCallSite; - ReferenceVisitor mReferenceVisitor; - bool mError = false; -}; + bool HasError() { return error_ || reference_visitor_.HasError(); } -} // namespace + private: + DISALLOW_COPY_AND_ASSIGN(XmlVisitor); -bool XmlReferenceLinker::consume(IAaptContext* context, xml::XmlResource* resource) { - mSdkLevelsFound.clear(); - CallSite callSite = { resource->file.name }; - XmlVisitor visitor(context, context->getExternalSymbols(), resource->file.source, - &mSdkLevelsFound, &callSite); - if (resource->root) { - resource->root->accept(&visitor); - return !visitor.hasError(); - } - return false; + IAaptContext* context_; + SymbolTable* symbols_; + Source source_; + std::set<int>* sdk_levels_found_; + CallSite* callsite_; + ReferenceVisitor reference_visitor_; + bool error_ = false; +}; + +} // namespace + +bool XmlReferenceLinker::Consume(IAaptContext* context, + xml::XmlResource* resource) { + sdk_levels_found_.clear(); + CallSite callsite = {resource->file.name}; + XmlVisitor visitor(context, context->GetExternalSymbols(), + resource->file.source, &sdk_levels_found_, &callsite); + if (resource->root) { + resource->root->Accept(&visitor); + return !visitor.HasError(); + } + return false; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp index af9098b9e483..810f63c0410e 100644 --- a/tools/aapt2/link/XmlReferenceLinker_test.cpp +++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp @@ -14,247 +14,286 @@ * limitations under the License. */ -#include <test/Context.h> #include "link/Linkers.h" + #include "test/Test.h" namespace aapt { class XmlReferenceLinkerTest : public ::testing::Test { -public: - void SetUp() override { - mContext = test::ContextBuilder() - .setCompilationPackage(u"com.app.test") - .setNameManglerPolicy( - NameManglerPolicy{ u"com.app.test", { u"com.android.support" } }) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addPublicSymbol(u"@android:attr/layout_width", ResourceId(0x01010000), - test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_ENUM | - android::ResTable_map::TYPE_DIMENSION) - .addItem(u"match_parent", 0xffffffff) - .build()) - .addPublicSymbol(u"@android:attr/background", ResourceId(0x01010001), - test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_COLOR).build()) - .addPublicSymbol(u"@android:attr/attr", ResourceId(0x01010002), - test::AttributeBuilder().build()) - .addPublicSymbol(u"@android:attr/text", ResourceId(0x01010003), - test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_STRING) - .build()) - - // Add one real symbol that was introduces in v21 - .addPublicSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435), - test::AttributeBuilder().build()) - - // Private symbol. - .addSymbol(u"@android:color/hidden", ResourceId(0x01020001)) - - .addPublicSymbol(u"@android:id/id", ResourceId(0x01030000)) - .addSymbol(u"@com.app.test:id/id", ResourceId(0x7f030000)) - .addSymbol(u"@com.app.test:color/green", ResourceId(0x7f020000)) - .addSymbol(u"@com.app.test:color/red", ResourceId(0x7f020001)) - .addSymbol(u"@com.app.test:attr/colorAccent", ResourceId(0x7f010000), - test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_COLOR).build()) - .addPublicSymbol(u"@com.app.test:attr/com.android.support$colorAccent", - ResourceId(0x7f010001), test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_COLOR).build()) - .addPublicSymbol(u"@com.app.test:attr/attr", ResourceId(0x7f010002), - test::AttributeBuilder().build()) - .build()) - .build(); - } - -protected: - std::unique_ptr<IAaptContext> mContext; + public: + void SetUp() override { + context_ = + test::ContextBuilder() + .SetCompilationPackage("com.app.test") + .SetNameManglerPolicy( + NameManglerPolicy{"com.app.test", {"com.android.support"}}) + .AddSymbolSource( + test::StaticSymbolSourceBuilder() + .AddPublicSymbol( + "android:attr/layout_width", ResourceId(0x01010000), + test::AttributeBuilder() + .SetTypeMask(android::ResTable_map::TYPE_ENUM | + android::ResTable_map::TYPE_DIMENSION) + .AddItem("match_parent", 0xffffffff) + .Build()) + .AddPublicSymbol( + "android:attr/background", ResourceId(0x01010001), + test::AttributeBuilder() + .SetTypeMask(android::ResTable_map::TYPE_COLOR) + .Build()) + .AddPublicSymbol("android:attr/attr", + ResourceId(0x01010002), + test::AttributeBuilder().Build()) + .AddPublicSymbol( + "android:attr/text", ResourceId(0x01010003), + test::AttributeBuilder() + .SetTypeMask(android::ResTable_map::TYPE_STRING) + .Build()) + + // Add one real symbol that was introduces in v21 + .AddPublicSymbol("android:attr/colorAccent", + ResourceId(0x01010435), + test::AttributeBuilder().Build()) + + // Private symbol. + .AddSymbol("android:color/hidden", ResourceId(0x01020001)) + + .AddPublicSymbol("android:id/id", ResourceId(0x01030000)) + .AddSymbol("com.app.test:id/id", ResourceId(0x7f030000)) + .AddSymbol("com.app.test:color/green", + ResourceId(0x7f020000)) + .AddSymbol("com.app.test:color/red", ResourceId(0x7f020001)) + .AddSymbol( + "com.app.test:attr/colorAccent", ResourceId(0x7f010000), + test::AttributeBuilder() + .SetTypeMask(android::ResTable_map::TYPE_COLOR) + .Build()) + .AddPublicSymbol( + "com.app.test:attr/com.android.support$colorAccent", + ResourceId(0x7f010001), + test::AttributeBuilder() + .SetTypeMask(android::ResTable_map::TYPE_COLOR) + .Build()) + .AddPublicSymbol("com.app.test:attr/attr", + ResourceId(0x7f010002), + test::AttributeBuilder().Build()) + .Build()) + .Build(); + } + + protected: + std::unique_ptr<IAaptContext> context_; }; TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::BuildXmlDomForPackageName(context_.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:background="@color/green" android:text="hello" class="hello" />)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - - xml::Element* viewEl = xml::findRootElement(doc.get()); - ASSERT_NE(viewEl, nullptr); - - xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", - u"layout_width"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); - EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x01010000)); - ASSERT_NE(xmlAttr->compiledValue, nullptr); - ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr); - - xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", u"background"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); - EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x01010001)); - ASSERT_NE(xmlAttr->compiledValue, nullptr); - Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->name); - EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@color/green")); // Make sure the name - // didn't change. - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f020000)); - - xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", u"text"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - ASSERT_FALSE(xmlAttr->compiledValue); // Strings don't get compiled for memory sake. - - xmlAttr = viewEl->findAttribute(u"", u"class"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_FALSE(xmlAttr->compiledAttribute); - ASSERT_EQ(xmlAttr->compiledValue, nullptr); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); + + xml::Element* view_el = xml::FindRootElement(doc.get()); + ASSERT_NE(view_el, nullptr); + + xml::Attribute* xml_attr = + view_el->FindAttribute(xml::kSchemaAndroid, "layout_width"); + ASSERT_NE(xml_attr, nullptr); + AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); + AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); + EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), + ResourceId(0x01010000)); + ASSERT_NE(xml_attr->compiled_value, nullptr); + ASSERT_NE(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()), + nullptr); + + xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "background"); + ASSERT_NE(xml_attr, nullptr); + AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); + AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); + EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), + ResourceId(0x01010001)); + ASSERT_NE(xml_attr->compiled_value, nullptr); + Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->name); + EXPECT_EQ(ref->name.value(), + test::ParseNameOrDie("color/green")); // Make sure the name + // didn't change. + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f020000)); + + xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "text"); + ASSERT_NE(xml_attr, nullptr); + AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); + ASSERT_FALSE( + xml_attr->compiled_value); // Strings don't get compiled for memory sake. + + xml_attr = view_el->FindAttribute("", "class"); + ASSERT_NE(xml_attr, nullptr); + AAPT_ASSERT_FALSE(xml_attr->compiled_attribute); + ASSERT_EQ(xml_attr->compiled_value, nullptr); } TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::BuildXmlDomForPackageName(context_.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:colorAccent="@android:color/hidden" />)EOF"); - XmlReferenceLinker linker; - ASSERT_FALSE(linker.consume(mContext.get(), doc.get())); + XmlReferenceLinker linker; + ASSERT_FALSE(linker.Consume(context_.get(), doc.get())); } -TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( +TEST_F(XmlReferenceLinkerTest, + PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) { + std::unique_ptr<xml::XmlResource> doc = + test::BuildXmlDomForPackageName(context_.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:colorAccent="@*android:color/hidden" />)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); } TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::BuildXmlDomForPackageName(context_.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:colorAccent="#ffffff" />)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - EXPECT_TRUE(linker.getSdkLevels().count(21) == 1); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); + EXPECT_TRUE(linker.sdk_levels().count(21) == 1); } TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::BuildXmlDomForPackageName(context_.get(), R"EOF( <View xmlns:support="http://schemas.android.com/apk/res/com.android.support" support:colorAccent="#ff0000" />)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - - xml::Element* viewEl = xml::findRootElement(doc.get()); - ASSERT_NE(viewEl, nullptr); - - xml::Attribute* xmlAttr = viewEl->findAttribute( - u"http://schemas.android.com/apk/res/com.android.support", u"colorAccent"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); - EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010001)); - ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); + + xml::Element* view_el = xml::FindRootElement(doc.get()); + ASSERT_NE(view_el, nullptr); + + xml::Attribute* xml_attr = view_el->FindAttribute( + xml::BuildPackageNamespace("com.android.support"), "colorAccent"); + ASSERT_NE(xml_attr, nullptr); + AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); + AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); + EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), + ResourceId(0x7f010001)); + ASSERT_NE(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()), + nullptr); } TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::BuildXmlDomForPackageName(context_.get(), R"EOF( <View xmlns:app="http://schemas.android.com/apk/res-auto" app:colorAccent="@app:color/red" />)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - - xml::Element* viewEl = xml::findRootElement(doc.get()); - ASSERT_NE(viewEl, nullptr); - - xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res-auto", - u"colorAccent"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); - EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010000)); - Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->name); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001)); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); + + xml::Element* view_el = xml::FindRootElement(doc.get()); + ASSERT_NE(view_el, nullptr); + + xml::Attribute* xml_attr = + view_el->FindAttribute(xml::kSchemaAuto, "colorAccent"); + ASSERT_NE(xml_attr, nullptr); + AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); + AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); + EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), + ResourceId(0x7f010000)); + Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->name); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001)); } TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::BuildXmlDomForPackageName(context_.get(), R"EOF( <View xmlns:app="http://schemas.android.com/apk/res/android" app:attr="@app:id/id"> <View xmlns:app="http://schemas.android.com/apk/res/com.app.test" app:attr="@app:id/id"/> </View>)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - - xml::Element* viewEl = xml::findRootElement(doc.get()); - ASSERT_NE(viewEl, nullptr); - - // All attributes and references in this element should be referring to "android" (0x01). - xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", - u"attr"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); - EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x01010002)); - Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x01030000)); - - ASSERT_FALSE(viewEl->getChildElements().empty()); - viewEl = viewEl->getChildElements().front(); - ASSERT_NE(viewEl, nullptr); - - // All attributes and references in this element should be referring to "com.app.test" (0x7f). - xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/com.app.test", u"attr"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); - EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010002)); - ref = valueCast<Reference>(xmlAttr->compiledValue.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000)); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); + + xml::Element* view_el = xml::FindRootElement(doc.get()); + ASSERT_NE(view_el, nullptr); + + // All attributes and references in this element should be referring to + // "android" (0x01). + xml::Attribute* xml_attr = + view_el->FindAttribute(xml::kSchemaAndroid, "attr"); + ASSERT_NE(xml_attr, nullptr); + AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); + AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); + EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), + ResourceId(0x01010002)); + Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x01030000)); + + ASSERT_FALSE(view_el->GetChildElements().empty()); + view_el = view_el->GetChildElements().front(); + ASSERT_NE(view_el, nullptr); + + // All attributes and references in this element should be referring to + // "com.app.test" (0x7f). + xml_attr = view_el->FindAttribute(xml::BuildPackageNamespace("com.app.test"), + "attr"); + ASSERT_NE(xml_attr, nullptr); + AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); + AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); + EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), + ResourceId(0x7f010002)); + ref = ValueCast<Reference>(xml_attr->compiled_value.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000)); } TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::BuildXmlDomForPackageName(context_.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/com.app.test" android:attr="@id/id"/>)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - - xml::Element* viewEl = xml::findRootElement(doc.get()); - ASSERT_NE(viewEl, nullptr); - - // All attributes and references in this element should be referring to "com.app.test" (0x7f). - xml::Attribute* xmlAttr = viewEl->findAttribute( - u"http://schemas.android.com/apk/res/com.app.test", u"attr"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); - EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010002)); - Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000)); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); + + xml::Element* view_el = xml::FindRootElement(doc.get()); + ASSERT_NE(view_el, nullptr); + + // All attributes and references in this element should be referring to + // "com.app.test" (0x7f). + xml::Attribute* xml_attr = view_el->FindAttribute( + xml::BuildPackageNamespace("com.app.test"), "attr"); + ASSERT_NE(xml_attr, nullptr); + AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); + AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); + EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), + ResourceId(0x7f010002)); + Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000)); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h index 9affb836340c..4526a79577d9 100644 --- a/tools/aapt2/process/IResourceTableConsumer.h +++ b/tools/aapt2/process/IResourceTableConsumer.h @@ -17,48 +17,49 @@ #ifndef AAPT_PROCESS_IRESOURCETABLECONSUMER_H #define AAPT_PROCESS_IRESOURCETABLECONSUMER_H +#include <iostream> +#include <list> +#include <sstream> + #include "Diagnostics.h" #include "NameMangler.h" #include "Resource.h" #include "ResourceValues.h" #include "Source.h" -#include <iostream> -#include <list> -#include <sstream> - namespace aapt { class ResourceTable; class SymbolTable; struct IAaptContext { - virtual ~IAaptContext() = default; + virtual ~IAaptContext() = default; - virtual SymbolTable* getExternalSymbols() = 0; - virtual IDiagnostics* getDiagnostics() = 0; - virtual const std::u16string& getCompilationPackage() = 0; - virtual uint8_t getPackageId() = 0; - virtual NameMangler* getNameMangler() = 0; - virtual bool verbose() = 0; + virtual SymbolTable* GetExternalSymbols() = 0; + virtual IDiagnostics* GetDiagnostics() = 0; + virtual const std::string& GetCompilationPackage() = 0; + virtual uint8_t GetPackageId() = 0; + virtual NameMangler* GetNameMangler() = 0; + virtual bool IsVerbose() = 0; + virtual int GetMinSdkVersion() = 0; }; struct IResourceTableConsumer { - virtual ~IResourceTableConsumer() = default; + virtual ~IResourceTableConsumer() = default; - virtual bool consume(IAaptContext* context, ResourceTable* table) = 0; + virtual bool Consume(IAaptContext* context, ResourceTable* table) = 0; }; namespace xml { -struct XmlResource; +class XmlResource; } struct IXmlResourceConsumer { - virtual ~IXmlResourceConsumer() = default; + virtual ~IXmlResourceConsumer() = default; - virtual bool consume(IAaptContext* context, xml::XmlResource* resource) = 0; + virtual bool Consume(IAaptContext* context, xml::XmlResource* resource) = 0; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_PROCESS_IRESOURCETABLECONSUMER_H */ diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index eaaf06f7e530..767384d8d956 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -14,297 +14,288 @@ * limitations under the License. */ +#include "process/SymbolTable.h" + +#include "androidfw/AssetManager.h" +#include "androidfw/ResourceTypes.h" + #include "ConfigDescription.h" #include "Resource.h" +#include "ResourceUtils.h" #include "ValueVisitor.h" -#include "process/SymbolTable.h" #include "util/Util.h" -#include <androidfw/AssetManager.h> -#include <androidfw/ResourceTypes.h> - namespace aapt { -void SymbolTable::appendSource(std::unique_ptr<ISymbolSource> source) { - mSources.push_back(std::move(source)); +void SymbolTable::AppendSource(std::unique_ptr<ISymbolSource> source) { + sources_.push_back(std::move(source)); - // We do not clear the cache, because sources earlier in the list take precedent. + // We do not clear the cache, because sources earlier in the list take + // precedent. } -void SymbolTable::prependSource(std::unique_ptr<ISymbolSource> source) { - mSources.insert(mSources.begin(), std::move(source)); +void SymbolTable::PrependSource(std::unique_ptr<ISymbolSource> source) { + sources_.insert(sources_.begin(), std::move(source)); - // We must clear the cache in case we did a lookup before adding this resource. - mCache.clear(); + // We must clear the cache in case we did a lookup before adding this + // resource. + cache_.clear(); } -const SymbolTable::Symbol* SymbolTable::findByName(const ResourceName& name) { - if (const std::shared_ptr<Symbol>& s = mCache.get(name)) { - return s.get(); +const SymbolTable::Symbol* SymbolTable::FindByName(const ResourceName& name) { + if (const std::shared_ptr<Symbol>& s = cache_.get(name)) { + return s.get(); + } + + // We did not find it in the cache, so look through the sources. + for (auto& symbolSource : sources_) { + std::unique_ptr<Symbol> symbol = symbolSource->FindByName(name); + if (symbol) { + // Take ownership of the symbol into a shared_ptr. We do this because + // LruCache + // doesn't support unique_ptr. + std::shared_ptr<Symbol> shared_symbol = + std::shared_ptr<Symbol>(symbol.release()); + cache_.put(name, shared_symbol); + + if (shared_symbol->id) { + // The symbol has an ID, so we can also cache this! + id_cache_.put(shared_symbol->id.value(), shared_symbol); + } + return shared_symbol.get(); } - - // We did not find it in the cache, so look through the sources. - for (auto& symbolSource : mSources) { - std::unique_ptr<Symbol> symbol = symbolSource->findByName(name); - if (symbol) { - // Take ownership of the symbol into a shared_ptr. We do this because LruCache - // doesn't support unique_ptr. - std::shared_ptr<Symbol> sharedSymbol = std::shared_ptr<Symbol>(symbol.release()); - mCache.put(name, sharedSymbol); - - if (sharedSymbol->id) { - // The symbol has an ID, so we can also cache this! - mIdCache.put(sharedSymbol->id.value(), sharedSymbol); - } - return sharedSymbol.get(); - } - } - return nullptr; + } + return nullptr; } -const SymbolTable::Symbol* SymbolTable::findById(ResourceId id) { - if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) { - return s.get(); +const SymbolTable::Symbol* SymbolTable::FindById(const ResourceId& id) { + if (const std::shared_ptr<Symbol>& s = id_cache_.get(id)) { + return s.get(); + } + + // We did not find it in the cache, so look through the sources. + for (auto& symbolSource : sources_) { + std::unique_ptr<Symbol> symbol = symbolSource->FindById(id); + if (symbol) { + // Take ownership of the symbol into a shared_ptr. We do this because + // LruCache + // doesn't support unique_ptr. + std::shared_ptr<Symbol> shared_symbol = + std::shared_ptr<Symbol>(symbol.release()); + id_cache_.put(id, shared_symbol); + return shared_symbol.get(); } - - // We did not find it in the cache, so look through the sources. - for (auto& symbolSource : mSources) { - std::unique_ptr<Symbol> symbol = symbolSource->findById(id); - if (symbol) { - // Take ownership of the symbol into a shared_ptr. We do this because LruCache - // doesn't support unique_ptr. - std::shared_ptr<Symbol> sharedSymbol = std::shared_ptr<Symbol>(symbol.release()); - mIdCache.put(id, sharedSymbol); - return sharedSymbol.get(); - } - } - return nullptr; + } + return nullptr; } -const SymbolTable::Symbol* SymbolTable::findByReference(const Reference& ref) { - // First try the ID. This is because when we lookup by ID, we only fill in the ID cache. - // Looking up by name fills in the name and ID cache. So a cache miss will cause a failed - // ID lookup, then a successfull name lookup. Subsequent look ups will hit immediately - // because the ID is cached too. - // - // If we looked up by name first, a cache miss would mean we failed to lookup by name, then - // succeeded to lookup by ID. Subsequent lookups will miss then hit. - const SymbolTable::Symbol* symbol = nullptr; - if (ref.id) { - symbol = findById(ref.id.value()); - } - - if (ref.name && !symbol) { - symbol = findByName(ref.name.value()); - } - return symbol; +const SymbolTable::Symbol* SymbolTable::FindByReference(const Reference& ref) { + // First try the ID. This is because when we lookup by ID, we only fill in the + // ID cache. + // Looking up by name fills in the name and ID cache. So a cache miss will + // cause a failed + // ID lookup, then a successful name lookup. Subsequent look ups will hit + // immediately + // because the ID is cached too. + // + // If we looked up by name first, a cache miss would mean we failed to lookup + // by name, then + // succeeded to lookup by ID. Subsequent lookups will miss then hit. + const SymbolTable::Symbol* symbol = nullptr; + if (ref.id) { + symbol = FindById(ref.id.value()); + } + + if (ref.name && !symbol) { + symbol = FindByName(ref.name.value()); + } + return symbol; } -std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::findByName( - const ResourceName& name) { - Maybe<ResourceTable::SearchResult> result = mTable->findResource(name); - if (!result) { - if (name.type == ResourceType::kAttr) { - // Recurse and try looking up a private attribute. - return findByName(ResourceName(name.package, ResourceType::kAttrPrivate, name.entry)); - } +std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName( + const ResourceName& name) { + Maybe<ResourceTable::SearchResult> result = table_->FindResource(name); + if (!result) { + if (name.type == ResourceType::kAttr) { + // Recurse and try looking up a private attribute. + return FindByName( + ResourceName(name.package, ResourceType::kAttrPrivate, name.entry)); + } + return {}; + } + + ResourceTable::SearchResult sr = result.value(); + + std::unique_ptr<SymbolTable::Symbol> symbol = + util::make_unique<SymbolTable::Symbol>(); + symbol->is_public = (sr.entry->symbol_status.state == SymbolState::kPublic); + + if (sr.package->id && sr.type->id && sr.entry->id) { + symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), + sr.entry->id.value()); + } + + if (name.type == ResourceType::kAttr || + name.type == ResourceType::kAttrPrivate) { + const ConfigDescription kDefaultConfig; + ResourceConfigValue* config_value = sr.entry->FindValue(kDefaultConfig); + if (config_value) { + // This resource has an Attribute. + if (Attribute* attr = ValueCast<Attribute>(config_value->value.get())) { + symbol->attribute = std::make_shared<Attribute>(*attr); + } else { return {}; + } } + } + return symbol; +} - ResourceTable::SearchResult sr = result.value(); - - std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>(); - symbol->isPublic = (sr.entry->symbolStatus.state == SymbolState::kPublic); +bool AssetManagerSymbolSource::AddAssetPath(const StringPiece& path) { + int32_t cookie = 0; + return assets_.addAssetPath(android::String8(path.data(), path.size()), + &cookie); +} - if (sr.package->id && sr.type->id && sr.entry->id) { - symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value()); +static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable( + const android::ResTable& table, ResourceId id) { + // Try as a bag. + const android::ResTable::bag_entry* entry; + ssize_t count = table.lockBag(id.id, &entry); + if (count < 0) { + table.unlockBag(entry); + return nullptr; + } + + // We found a resource. + std::unique_ptr<SymbolTable::Symbol> s = + util::make_unique<SymbolTable::Symbol>(); + s->id = id; + + // Check to see if it is an attribute. + for (size_t i = 0; i < (size_t)count; i++) { + if (entry[i].map.name.ident == android::ResTable_map::ATTR_TYPE) { + s->attribute = std::make_shared<Attribute>(false); + s->attribute->type_mask = entry[i].map.value.data; + break; } - - if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) { - const ConfigDescription kDefaultConfig; - ResourceConfigValue* configValue = sr.entry->findValue(kDefaultConfig); - if (configValue) { - // This resource has an Attribute. - if (Attribute* attr = valueCast<Attribute>(configValue->value.get())) { - symbol->attribute = std::make_shared<Attribute>(*attr); - } else { - return {}; - } + } + + if (s->attribute) { + for (size_t i = 0; i < (size_t)count; i++) { + const android::ResTable_map& map_entry = entry[i].map; + if (Res_INTERNALID(map_entry.name.ident)) { + switch (map_entry.name.ident) { + case android::ResTable_map::ATTR_MIN: + s->attribute->min_int = static_cast<int32_t>(map_entry.value.data); + break; + case android::ResTable_map::ATTR_MAX: + s->attribute->max_int = static_cast<int32_t>(map_entry.value.data); + break; } - } - return symbol; -} - -bool AssetManagerSymbolSource::addAssetPath(const StringPiece& path) { - int32_t cookie = 0; - return mAssets.addAssetPath(android::String8(path.data(), path.size()), &cookie); -} + continue; + } -static std::unique_ptr<SymbolTable::Symbol> lookupAttributeInTable(const android::ResTable& table, - ResourceId id) { - // Try as a bag. - const android::ResTable::bag_entry* entry; - ssize_t count = table.lockBag(id.id, &entry); - if (count < 0) { + android::ResTable::resource_name entry_name; + if (!table.getResourceName(map_entry.name.ident, false, &entry_name)) { table.unlockBag(entry); return nullptr; - } + } - // We found a resource. - std::unique_ptr<SymbolTable::Symbol> s = util::make_unique<SymbolTable::Symbol>(); - s->id = id; - - // Check to see if it is an attribute. - for (size_t i = 0; i < (size_t) count; i++) { - if (entry[i].map.name.ident == android::ResTable_map::ATTR_TYPE) { - s->attribute = std::make_shared<Attribute>(false); - s->attribute->typeMask = entry[i].map.value.data; - break; - } - } + Maybe<ResourceName> parsed_name = + ResourceUtils::ToResourceName(entry_name); + if (!parsed_name) { + return nullptr; + } - if (s->attribute) { - for (size_t i = 0; i < (size_t) count; i++) { - const android::ResTable_map& mapEntry = entry[i].map; - if (Res_INTERNALID(mapEntry.name.ident)) { - switch (mapEntry.name.ident) { - case android::ResTable_map::ATTR_MIN: - s->attribute->minInt = static_cast<int32_t>(mapEntry.value.data); - break; - case android::ResTable_map::ATTR_MAX: - s->attribute->maxInt = static_cast<int32_t>(mapEntry.value.data); - break; - } - continue; - } - - android::ResTable::resource_name entryName; - if (!table.getResourceName(mapEntry.name.ident, false, &entryName)) { - table.unlockBag(entry); - return nullptr; - } - - const ResourceType* parsedType = parseResourceType( - StringPiece16(entryName.type, entryName.typeLen)); - if (!parsedType) { - table.unlockBag(entry); - return nullptr; - } - - Attribute::Symbol symbol; - symbol.symbol.name = ResourceName( - StringPiece16(entryName.package, entryName.packageLen), - *parsedType, - StringPiece16(entryName.name, entryName.nameLen)); - symbol.symbol.id = ResourceId(mapEntry.name.ident); - symbol.value = mapEntry.value.data; - s->attribute->symbols.push_back(std::move(symbol)); - } + Attribute::Symbol symbol; + symbol.symbol.name = parsed_name.value(); + symbol.symbol.id = ResourceId(map_entry.name.ident); + symbol.value = map_entry.value.data; + s->attribute->symbols.push_back(std::move(symbol)); } - table.unlockBag(entry); - return s; + } + table.unlockBag(entry); + return s; } -std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findByName( - const ResourceName& name) { - const android::ResTable& table = mAssets.getResources(false); - StringPiece16 typeStr = toString(name.type); - uint32_t typeSpecFlags = 0; - ResourceId resId = table.identifierForName(name.entry.data(), name.entry.size(), - typeStr.data(), typeStr.size(), - name.package.data(), name.package.size(), - &typeSpecFlags); - if (!resId.isValid()) { - return {}; - } +std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( + const ResourceName& name) { + const android::ResTable& table = assets_.getResources(false); - std::unique_ptr<SymbolTable::Symbol> s; - if (name.type == ResourceType::kAttr) { - s = lookupAttributeInTable(table, resId); - } else { - s = util::make_unique<SymbolTable::Symbol>(); - s->id = resId; - } + const std::u16string package16 = util::Utf8ToUtf16(name.package); + const std::u16string type16 = util::Utf8ToUtf16(ToString(name.type)); + const std::u16string entry16 = util::Utf8ToUtf16(name.entry); - if (s) { - s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; - return s; - } + uint32_t type_spec_flags = 0; + ResourceId res_id = table.identifierForName( + entry16.data(), entry16.size(), type16.data(), type16.size(), + package16.data(), package16.size(), &type_spec_flags); + if (!res_id.is_valid()) { return {}; + } + + std::unique_ptr<SymbolTable::Symbol> s; + if (name.type == ResourceType::kAttr) { + s = LookupAttributeInTable(table, res_id); + } else { + s = util::make_unique<SymbolTable::Symbol>(); + s->id = res_id; + } + + if (s) { + s->is_public = + (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; + return s; + } + return {}; } -static Maybe<ResourceName> getResourceName(const android::ResTable& table, ResourceId id) { - android::ResTable::resource_name resName = {}; - if (!table.getResourceName(id.id, true, &resName)) { - return {}; - } - - ResourceName name; - if (resName.package) { - name.package = StringPiece16(resName.package, resName.packageLen).toString(); - } - - const ResourceType* type; - if (resName.type) { - type = parseResourceType(StringPiece16(resName.type, resName.typeLen)); - - } else if (resName.type8) { - type = parseResourceType(util::utf8ToUtf16(StringPiece(resName.type8, resName.typeLen))); - } else { - return {}; - } - - if (!type) { - return {}; - } - - name.type = *type; - - if (resName.name) { - name.entry = StringPiece16(resName.name, resName.nameLen).toString(); - } else if (resName.name8) { - name.entry = util::utf8ToUtf16(StringPiece(resName.name8, resName.nameLen)); - } else { - return {}; - } - - return name; +static Maybe<ResourceName> GetResourceName(const android::ResTable& table, + ResourceId id) { + android::ResTable::resource_name res_name = {}; + if (!table.getResourceName(id.id, true, &res_name)) { + return {}; + } + return ResourceUtils::ToResourceName(res_name); } -std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findById(ResourceId id) { - const android::ResTable& table = mAssets.getResources(false); - Maybe<ResourceName> maybeName = getResourceName(table, id); - if (!maybeName) { - return {}; - } +std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( + ResourceId id) { + const android::ResTable& table = assets_.getResources(false); + Maybe<ResourceName> maybe_name = GetResourceName(table, id); + if (!maybe_name) { + return {}; + } - uint32_t typeSpecFlags = 0; - table.getResourceFlags(id.id, &typeSpecFlags); + uint32_t type_spec_flags = 0; + table.getResourceFlags(id.id, &type_spec_flags); - std::unique_ptr<SymbolTable::Symbol> s; - if (maybeName.value().type == ResourceType::kAttr) { - s = lookupAttributeInTable(table, id); - } else { - s = util::make_unique<SymbolTable::Symbol>(); - s->id = id; - } + std::unique_ptr<SymbolTable::Symbol> s; + if (maybe_name.value().type == ResourceType::kAttr) { + s = LookupAttributeInTable(table, id); + } else { + s = util::make_unique<SymbolTable::Symbol>(); + s->id = id; + } - if (s) { - s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; - return s; - } - return {}; + if (s) { + s->is_public = + (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; + return s; + } + return {}; } -std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findByReference( - const Reference& ref) { - // AssetManager always prefers IDs. - if (ref.id) { - return findById(ref.id.value()); - } else if (ref.name) { - return findByName(ref.name.value()); - } - return {}; +std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByReference( + const Reference& ref) { + // AssetManager always prefers IDs. + if (ref.id) { + return FindById(ref.id.value()); + } else if (ref.name) { + return FindByName(ref.name.value()); + } + return {}; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h index e684bb06f1f5..25f756522580 100644 --- a/tools/aapt2/process/SymbolTable.h +++ b/tools/aapt2/process/SymbolTable.h @@ -17,116 +17,115 @@ #ifndef AAPT_PROCESS_SYMBOLTABLE_H #define AAPT_PROCESS_SYMBOLTABLE_H +#include <algorithm> +#include <memory> +#include <vector> + +#include "android-base/macros.h" +#include "androidfw/AssetManager.h" +#include "utils/JenkinsHash.h" +#include "utils/LruCache.h" + #include "Resource.h" #include "ResourceTable.h" #include "ResourceValues.h" #include "util/Util.h" -#include <utils/JenkinsHash.h> -#include <utils/LruCache.h> - -#include <android-base/macros.h> -#include <androidfw/AssetManager.h> -#include <algorithm> -#include <memory> -#include <vector> - namespace aapt { inline android::hash_t hash_type(const ResourceName& name) { - std::hash<std::u16string> strHash; - android::hash_t hash = 0; - hash = android::JenkinsHashMix(hash, (uint32_t) strHash(name.package)); - hash = android::JenkinsHashMix(hash, (uint32_t) name.type); - hash = android::JenkinsHashMix(hash, (uint32_t) strHash(name.entry)); - return hash; + std::hash<std::string> str_hash; + android::hash_t hash = 0; + hash = android::JenkinsHashMix(hash, (uint32_t)str_hash(name.package)); + hash = android::JenkinsHashMix(hash, (uint32_t)name.type); + hash = android::JenkinsHashMix(hash, (uint32_t)str_hash(name.entry)); + return hash; } inline android::hash_t hash_type(const ResourceId& id) { - return android::hash_type(id.id); + return android::hash_type(id.id); } class ISymbolSource; class SymbolTable { -public: - struct Symbol { - Symbol() : Symbol(Maybe<ResourceId>{}) { - } + public: + struct Symbol { + Symbol() : Symbol(Maybe<ResourceId>{}) {} - Symbol(const Maybe<ResourceId>& i) : Symbol(i, nullptr) { - } + explicit Symbol(const Maybe<ResourceId>& i) : Symbol(i, nullptr) {} - Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr) : - Symbol(i, attr, false) { - } + Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr) + : Symbol(i, attr, false) {} - Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr, bool pub) : - id(i), attribute(attr), isPublic(pub) { - } + Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr, + bool pub) + : id(i), attribute(attr), is_public(pub) {} - Symbol(const Symbol&) = default; - Symbol(Symbol&&) = default; - Symbol& operator=(const Symbol&) = default; - Symbol& operator=(Symbol&&) = default; + Symbol(const Symbol&) = default; + Symbol(Symbol&&) = default; + Symbol& operator=(const Symbol&) = default; + Symbol& operator=(Symbol&&) = default; - Maybe<ResourceId> id; - std::shared_ptr<Attribute> attribute; - bool isPublic = false; - }; + Maybe<ResourceId> id; + std::shared_ptr<Attribute> attribute; + bool is_public = false; + }; - SymbolTable() : mCache(200), mIdCache(200) { - } + SymbolTable() : cache_(200), id_cache_(200) {} - void appendSource(std::unique_ptr<ISymbolSource> source); - void prependSource(std::unique_ptr<ISymbolSource> source); + void AppendSource(std::unique_ptr<ISymbolSource> source); + void PrependSource(std::unique_ptr<ISymbolSource> source); - /** - * Never hold on to the result between calls to findByName or findById. The results - * are typically stored in a cache which may evict entries. - */ - const Symbol* findByName(const ResourceName& name); - const Symbol* findById(ResourceId id); + /** + * Never hold on to the result between calls to FindByName or FindById. The + * results stored in a cache which may evict entries. + */ + const Symbol* FindByName(const ResourceName& name); + const Symbol* FindById(const ResourceId& id); - /** - * Let's the ISymbolSource decide whether looking up by name or ID is faster, if both - * are available. - */ - const Symbol* findByReference(const Reference& ref); + /** + * Let's the ISymbolSource decide whether looking up by name or ID is faster, + * if both are available. + */ + const Symbol* FindByReference(const Reference& ref); -private: - std::vector<std::unique_ptr<ISymbolSource>> mSources; + private: + std::vector<std::unique_ptr<ISymbolSource>> sources_; - // We use shared_ptr because unique_ptr is not supported and - // we need automatic deletion. - android::LruCache<ResourceName, std::shared_ptr<Symbol>> mCache; - android::LruCache<ResourceId, std::shared_ptr<Symbol>> mIdCache; + // We use shared_ptr because unique_ptr is not supported and + // we need automatic deletion. + android::LruCache<ResourceName, std::shared_ptr<Symbol>> cache_; + android::LruCache<ResourceId, std::shared_ptr<Symbol>> id_cache_; - DISALLOW_COPY_AND_ASSIGN(SymbolTable); + DISALLOW_COPY_AND_ASSIGN(SymbolTable); }; /** - * An interface that a symbol source implements in order to surface symbol information + * An interface that a symbol source implements in order to surface symbol + * information * to the symbol table. */ class ISymbolSource { -public: - virtual ~ISymbolSource() = default; - - virtual std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) = 0; - virtual std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) = 0; - - /** - * Default implementation tries the name if it exists, else the ID. - */ - virtual std::unique_ptr<SymbolTable::Symbol> findByReference(const Reference& ref) { - if (ref.name) { - return findByName(ref.name.value()); - } else if (ref.id) { - return findById(ref.id.value()); - } - return {}; + public: + virtual ~ISymbolSource() = default; + + virtual std::unique_ptr<SymbolTable::Symbol> FindByName( + const ResourceName& name) = 0; + virtual std::unique_ptr<SymbolTable::Symbol> FindById(ResourceId id) = 0; + + /** + * Default implementation tries the name if it exists, else the ID. + */ + virtual std::unique_ptr<SymbolTable::Symbol> FindByReference( + const Reference& ref) { + if (ref.name) { + return FindByName(ref.name.value()); + } else if (ref.id) { + return FindById(ref.id.value()); } + return {}; + } }; /** @@ -135,38 +134,40 @@ public: * Lookups by ID are ignored. */ class ResourceTableSymbolSource : public ISymbolSource { -public: - explicit ResourceTableSymbolSource(ResourceTable* table) : mTable(table) { - } + public: + explicit ResourceTableSymbolSource(ResourceTable* table) : table_(table) {} - std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) override; + std::unique_ptr<SymbolTable::Symbol> FindByName( + const ResourceName& name) override; - std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override { - return {}; - } + std::unique_ptr<SymbolTable::Symbol> FindById(ResourceId id) override { + return {}; + } -private: - ResourceTable* mTable; + private: + ResourceTable* table_; - DISALLOW_COPY_AND_ASSIGN(ResourceTableSymbolSource); + DISALLOW_COPY_AND_ASSIGN(ResourceTableSymbolSource); }; class AssetManagerSymbolSource : public ISymbolSource { -public: - AssetManagerSymbolSource() = default; + public: + AssetManagerSymbolSource() = default; - bool addAssetPath(const StringPiece& path); + bool AddAssetPath(const StringPiece& path); - std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) override; - std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override; - std::unique_ptr<SymbolTable::Symbol> findByReference(const Reference& ref) override; + std::unique_ptr<SymbolTable::Symbol> FindByName( + const ResourceName& name) override; + std::unique_ptr<SymbolTable::Symbol> FindById(ResourceId id) override; + std::unique_ptr<SymbolTable::Symbol> FindByReference( + const Reference& ref) override; -private: - android::AssetManager mAssets; + private: + android::AssetManager assets_; - DISALLOW_COPY_AND_ASSIGN(AssetManagerSymbolSource); + DISALLOW_COPY_AND_ASSIGN(AssetManagerSymbolSource); }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_PROCESS_SYMBOLTABLE_H */ diff --git a/tools/aapt2/process/SymbolTable_test.cpp b/tools/aapt2/process/SymbolTable_test.cpp index 34f31be3d0db..9ea0786cbc8e 100644 --- a/tools/aapt2/process/SymbolTable_test.cpp +++ b/tools/aapt2/process/SymbolTable_test.cpp @@ -15,39 +15,44 @@ */ #include "process/SymbolTable.h" + #include "test/Test.h" namespace aapt { TEST(ResourceTableSymbolSourceTest, FindSymbols) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addSimple(u"@android:id/foo", ResourceId(0x01020000)) - .addSimple(u"@android:id/bar") - .addValue(u"@android:attr/foo", ResourceId(0x01010000), - test::AttributeBuilder().build()) - .build(); - - ResourceTableSymbolSource symbolSource(table.get()); - EXPECT_NE(nullptr, symbolSource.findByName(test::parseNameOrDie(u"@android:id/foo"))); - EXPECT_NE(nullptr, symbolSource.findByName(test::parseNameOrDie(u"@android:id/bar"))); - - std::unique_ptr<SymbolTable::Symbol> s = symbolSource.findByName( - test::parseNameOrDie(u"@android:attr/foo")); - ASSERT_NE(nullptr, s); - EXPECT_NE(nullptr, s->attribute); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddSimple("android:id/foo", ResourceId(0x01020000)) + .AddSimple("android:id/bar") + .AddValue("android:attr/foo", ResourceId(0x01010000), + test::AttributeBuilder().Build()) + .Build(); + + ResourceTableSymbolSource symbol_source(table.get()); + EXPECT_NE(nullptr, + symbol_source.FindByName(test::ParseNameOrDie("android:id/foo"))); + EXPECT_NE(nullptr, + symbol_source.FindByName(test::ParseNameOrDie("android:id/bar"))); + + std::unique_ptr<SymbolTable::Symbol> s = + symbol_source.FindByName(test::ParseNameOrDie("android:attr/foo")); + ASSERT_NE(nullptr, s); + EXPECT_NE(nullptr, s->attribute); } TEST(ResourceTableSymbolSourceTest, FindPrivateAttrSymbol) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addValue(u"@android:^attr-private/foo", ResourceId(0x01010000), - test::AttributeBuilder().build()) - .build(); - - ResourceTableSymbolSource symbolSource(table.get()); - std::unique_ptr<SymbolTable::Symbol> s = symbolSource.findByName( - test::parseNameOrDie(u"@android:attr/foo")); - ASSERT_NE(nullptr, s); - EXPECT_NE(nullptr, s->attribute); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddValue("android:^attr-private/foo", ResourceId(0x01010000), + test::AttributeBuilder().Build()) + .Build(); + + ResourceTableSymbolSource symbol_source(table.get()); + std::unique_ptr<SymbolTable::Symbol> s = + symbol_source.FindByName(test::ParseNameOrDie("android:attr/foo")); + ASSERT_NE(nullptr, s); + EXPECT_NE(nullptr, s->attribute); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/proto/ProtoHelpers.cpp b/tools/aapt2/proto/ProtoHelpers.cpp index 99981c52e26f..38bf4e3bd8eb 100644 --- a/tools/aapt2/proto/ProtoHelpers.cpp +++ b/tools/aapt2/proto/ProtoHelpers.cpp @@ -18,120 +18,151 @@ namespace aapt { -void serializeStringPoolToPb(const StringPool& pool, pb::StringPool* outPbPool) { - BigBuffer buffer(1024); - StringPool::flattenUtf8(&buffer, pool); - - std::string* data = outPbPool->mutable_data(); - data->reserve(buffer.size()); - - size_t offset = 0; - for (const BigBuffer::Block& block : buffer) { - data->insert(data->begin() + offset, block.buffer.get(), block.buffer.get() + block.size); - offset += block.size; - } +void SerializeStringPoolToPb(const StringPool& pool, + pb::StringPool* out_pb_pool) { + BigBuffer buffer(1024); + StringPool::FlattenUtf8(&buffer, pool); + + std::string* data = out_pb_pool->mutable_data(); + data->reserve(buffer.size()); + + size_t offset = 0; + for (const BigBuffer::Block& block : buffer) { + data->insert(data->begin() + offset, block.buffer.get(), + block.buffer.get() + block.size); + offset += block.size; + } } -void serializeSourceToPb(const Source& source, StringPool* srcPool, pb::Source* outPbSource) { - StringPool::Ref ref = srcPool->makeRef(util::utf8ToUtf16(source.path)); - outPbSource->set_path_idx(static_cast<uint32_t>(ref.getIndex())); - if (source.line) { - outPbSource->set_line_no(static_cast<uint32_t>(source.line.value())); - } +void SerializeSourceToPb(const Source& source, StringPool* src_pool, + pb::Source* out_pb_source) { + StringPool::Ref ref = src_pool->MakeRef(source.path); + out_pb_source->set_path_idx(static_cast<uint32_t>(ref.index())); + if (source.line) { + out_pb_source->set_line_no(static_cast<uint32_t>(source.line.value())); + } } -void deserializeSourceFromPb(const pb::Source& pbSource, const android::ResStringPool& srcPool, - Source* outSource) { - if (pbSource.has_path_idx()) { - outSource->path = util::getString8(srcPool, pbSource.path_idx()).toString(); - } +void DeserializeSourceFromPb(const pb::Source& pb_source, + const android::ResStringPool& src_pool, + Source* out_source) { + if (pb_source.has_path_idx()) { + out_source->path = util::GetString(src_pool, pb_source.path_idx()); + } - if (pbSource.has_line_no()) { - outSource->line = static_cast<size_t>(pbSource.line_no()); - } + if (pb_source.has_line_no()) { + out_source->line = static_cast<size_t>(pb_source.line_no()); + } } -pb::SymbolStatus_Visibility serializeVisibilityToPb(SymbolState state) { - switch (state) { - case SymbolState::kPrivate: return pb::SymbolStatus_Visibility_Private; - case SymbolState::kPublic: return pb::SymbolStatus_Visibility_Public; - default: break; - } - return pb::SymbolStatus_Visibility_Unknown; +pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state) { + switch (state) { + case SymbolState::kPrivate: + return pb::SymbolStatus_Visibility_Private; + case SymbolState::kPublic: + return pb::SymbolStatus_Visibility_Public; + default: + break; + } + return pb::SymbolStatus_Visibility_Unknown; } -SymbolState deserializeVisibilityFromPb(pb::SymbolStatus_Visibility pbVisibility) { - switch (pbVisibility) { - case pb::SymbolStatus_Visibility_Private: return SymbolState::kPrivate; - case pb::SymbolStatus_Visibility_Public: return SymbolState::kPublic; - default: break; - } - return SymbolState::kUndefined; +SymbolState DeserializeVisibilityFromPb( + pb::SymbolStatus_Visibility pb_visibility) { + switch (pb_visibility) { + case pb::SymbolStatus_Visibility_Private: + return SymbolState::kPrivate; + case pb::SymbolStatus_Visibility_Public: + return SymbolState::kPublic; + default: + break; + } + return SymbolState::kUndefined; } -void serializeConfig(const ConfigDescription& config, pb::ConfigDescription* outPbConfig) { - android::ResTable_config flatConfig = config; - flatConfig.size = sizeof(flatConfig); - flatConfig.swapHtoD(); - outPbConfig->set_data(&flatConfig, sizeof(flatConfig)); +void SerializeConfig(const ConfigDescription& config, + pb::ConfigDescription* out_pb_config) { + android::ResTable_config flat_config = config; + flat_config.size = sizeof(flat_config); + flat_config.swapHtoD(); + out_pb_config->set_data(&flat_config, sizeof(flat_config)); } -bool deserializeConfigDescriptionFromPb(const pb::ConfigDescription& pbConfig, - ConfigDescription* outConfig) { - if (!pbConfig.has_data()) { - return false; - } - - const android::ResTable_config* config; - if (pbConfig.data().size() > sizeof(*config)) { - return false; - } - - config = reinterpret_cast<const android::ResTable_config*>(pbConfig.data().data()); - outConfig->copyFromDtoH(*config); - return true; +bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config, + ConfigDescription* out_config) { + if (!pb_config.has_data()) { + return false; + } + + const android::ResTable_config* config; + if (pb_config.data().size() > sizeof(*config)) { + return false; + } + + config = reinterpret_cast<const android::ResTable_config*>( + pb_config.data().data()); + out_config->copyFromDtoH(*config); + return true; } -pb::Reference_Type serializeReferenceTypeToPb(Reference::Type type) { - switch (type) { - case Reference::Type::kResource: return pb::Reference_Type_Ref; - case Reference::Type::kAttribute: return pb::Reference_Type_Attr; - default: break; - } - return pb::Reference_Type_Ref; +pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type) { + switch (type) { + case Reference::Type::kResource: + return pb::Reference_Type_Ref; + case Reference::Type::kAttribute: + return pb::Reference_Type_Attr; + default: + break; + } + return pb::Reference_Type_Ref; } -Reference::Type deserializeReferenceTypeFromPb(pb::Reference_Type pbType) { - switch (pbType) { - case pb::Reference_Type_Ref: return Reference::Type::kResource; - case pb::Reference_Type_Attr: return Reference::Type::kAttribute; - default: break; - } - return Reference::Type::kResource; +Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type) { + switch (pb_type) { + case pb::Reference_Type_Ref: + return Reference::Type::kResource; + case pb::Reference_Type_Attr: + return Reference::Type::kAttribute; + default: + break; + } + return Reference::Type::kResource; } -pb::Plural_Arity serializePluralEnumToPb(size_t pluralIdx) { - switch (pluralIdx) { - case Plural::Zero: return pb::Plural_Arity_Zero; - case Plural::One: return pb::Plural_Arity_One; - case Plural::Two: return pb::Plural_Arity_Two; - case Plural::Few: return pb::Plural_Arity_Few; - case Plural::Many: return pb::Plural_Arity_Many; - default: break; - } - return pb::Plural_Arity_Other; +pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx) { + switch (plural_idx) { + case Plural::Zero: + return pb::Plural_Arity_Zero; + case Plural::One: + return pb::Plural_Arity_One; + case Plural::Two: + return pb::Plural_Arity_Two; + case Plural::Few: + return pb::Plural_Arity_Few; + case Plural::Many: + return pb::Plural_Arity_Many; + default: + break; + } + return pb::Plural_Arity_Other; } -size_t deserializePluralEnumFromPb(pb::Plural_Arity arity) { - switch (arity) { - case pb::Plural_Arity_Zero: return Plural::Zero; - case pb::Plural_Arity_One: return Plural::One; - case pb::Plural_Arity_Two: return Plural::Two; - case pb::Plural_Arity_Few: return Plural::Few; - case pb::Plural_Arity_Many: return Plural::Many; - default: break; - } - return Plural::Other; +size_t DeserializePluralEnumFromPb(pb::Plural_Arity arity) { + switch (arity) { + case pb::Plural_Arity_Zero: + return Plural::Zero; + case pb::Plural_Arity_One: + return Plural::One; + case pb::Plural_Arity_Two: + return Plural::Two; + case pb::Plural_Arity_Few: + return Plural::Few; + case pb::Plural_Arity_Many: + return Plural::Many; + default: + break; + } + return Plural::Other; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/proto/ProtoHelpers.h b/tools/aapt2/proto/ProtoHelpers.h index 02e67f17c80c..735cda0bde78 100644 --- a/tools/aapt2/proto/ProtoHelpers.h +++ b/tools/aapt2/proto/ProtoHelpers.h @@ -17,36 +17,45 @@ #ifndef AAPT_PROTO_PROTOHELPERS_H #define AAPT_PROTO_PROTOHELPERS_H +#include "androidfw/ResourceTypes.h" + #include "ConfigDescription.h" #include "ResourceTable.h" #include "Source.h" #include "StringPool.h" - #include "proto/frameworks/base/tools/aapt2/Format.pb.h" -#include <androidfw/ResourceTypes.h> - namespace aapt { -void serializeStringPoolToPb(const StringPool& pool, pb::StringPool* outPbPool); +void SerializeStringPoolToPb(const StringPool& pool, + pb::StringPool* out_pb_pool); + +void SerializeSourceToPb(const Source& source, StringPool* src_pool, + pb::Source* out_pb_source); + +void DeserializeSourceFromPb(const pb::Source& pb_source, + const android::ResStringPool& src_pool, + Source* out_source); + +pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state); + +SymbolState DeserializeVisibilityFromPb( + pb::SymbolStatus_Visibility pb_visibility); + +void SerializeConfig(const ConfigDescription& config, + pb::ConfigDescription* out_pb_config); -void serializeSourceToPb(const Source& source, StringPool* srcPool, pb::Source* outPbSource); -void deserializeSourceFromPb(const pb::Source& pbSource, const android::ResStringPool& srcPool, - Source* outSource); +bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config, + ConfigDescription* out_config); -pb::SymbolStatus_Visibility serializeVisibilityToPb(SymbolState state); -SymbolState deserializeVisibilityFromPb(pb::SymbolStatus_Visibility pbVisibility); +pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type); -void serializeConfig(const ConfigDescription& config, pb::ConfigDescription* outPbConfig); -bool deserializeConfigDescriptionFromPb(const pb::ConfigDescription& pbConfig, - ConfigDescription* outConfig); +Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type); -pb::Reference_Type serializeReferenceTypeToPb(Reference::Type type); -Reference::Type deserializeReferenceTypeFromPb(pb::Reference_Type pbType); +pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx); -pb::Plural_Arity serializePluralEnumToPb(size_t pluralIdx); -size_t deserializePluralEnumFromPb(pb::Plural_Arity arity); +size_t DeserializePluralEnumFromPb(pb::Plural_Arity arity); -} // namespace aapt +} // namespace aapt #endif /* AAPT_PROTO_PROTOHELPERS_H */ diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h index 6e224ab00af4..39c50038d599 100644 --- a/tools/aapt2/proto/ProtoSerialize.h +++ b/tools/aapt2/proto/ProtoSerialize.h @@ -17,61 +17,61 @@ #ifndef AAPT_FLATTEN_TABLEPROTOSERIALIZER_H #define AAPT_FLATTEN_TABLEPROTOSERIALIZER_H +#include "android-base/macros.h" +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/io/zero_copy_stream_impl_lite.h" + #include "Diagnostics.h" #include "ResourceTable.h" #include "Source.h" #include "proto/ProtoHelpers.h" -#include <android-base/macros.h> -#include <google/protobuf/io/coded_stream.h> -#include <google/protobuf/io/zero_copy_stream_impl_lite.h> - namespace aapt { -std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table); -std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable, - const Source& source, - IDiagnostics* diag); - -std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& file); -std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(const pb::CompiledFile& pbFile, - const Source& source, - IDiagnostics* diag); +class CompiledFileOutputStream { + public: + explicit CompiledFileOutputStream( + google::protobuf::io::ZeroCopyOutputStream* out); -class CompiledFileOutputStream : public google::protobuf::io::CopyingOutputStream { -public: - CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out, - pb::CompiledFile* pbFile); - bool Write(const void* data, int size) override; - bool Finish(); + void WriteLittleEndian32(uint32_t value); + void WriteCompiledFile(const pb::CompiledFile* compiledFile); + void WriteData(const BigBuffer* buffer); + void WriteData(const void* data, size_t len); + bool HadError(); -private: - bool ensureFileWritten(); + private: + DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream); - google::protobuf::io::CodedOutputStream mOut; - pb::CompiledFile* mPbFile; + void EnsureAlignedWrite(); - DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream); + google::protobuf::io::CodedOutputStream out_; }; class CompiledFileInputStream { -public: - CompiledFileInputStream(const void* data, size_t size); + public: + explicit CompiledFileInputStream(const void* data, size_t size); - const pb::CompiledFile* CompiledFile(); + bool ReadLittleEndian32(uint32_t* outVal); + bool ReadCompiledFile(pb::CompiledFile* outVal); + bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen); - const void* data(); - size_t size(); + private: + DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream); -private: - google::protobuf::io::CodedInputStream mIn; - std::unique_ptr<pb::CompiledFile> mPbFile; - const uint8_t* mData; - size_t mSize; + void EnsureAlignedRead(); - DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream); + google::protobuf::io::CodedInputStream in_; }; -} // namespace aapt +std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table); +std::unique_ptr<ResourceTable> DeserializeTableFromPb( + const pb::ResourceTable& pbTable, const Source& source, IDiagnostics* diag); + +std::unique_ptr<pb::CompiledFile> SerializeCompiledFileToPb( + const ResourceFile& file); +std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb( + const pb::CompiledFile& pbFile, const Source& source, IDiagnostics* diag); + +} // namespace aapt #endif /* AAPT_FLATTEN_TABLEPROTOSERIALIZER_H */ diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp index 1ec48f09f228..d93d495b106f 100644 --- a/tools/aapt2/proto/TableProtoDeserializer.cpp +++ b/tools/aapt2/proto/TableProtoDeserializer.cpp @@ -14,509 +14,476 @@ * limitations under the License. */ +#include "proto/ProtoSerialize.h" + +#include "android-base/logging.h" +#include "androidfw/ResourceTypes.h" + #include "ResourceTable.h" #include "ResourceUtils.h" #include "ValueVisitor.h" #include "proto/ProtoHelpers.h" -#include "proto/ProtoSerialize.h" - -#include <androidfw/ResourceTypes.h> namespace aapt { namespace { class ReferenceIdToNameVisitor : public ValueVisitor { -public: - using ValueVisitor::visit; - - ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceNameRef>* mapping) : - mMapping(mapping) { - assert(mMapping); + public: + using ValueVisitor::Visit; + + explicit ReferenceIdToNameVisitor( + const std::map<ResourceId, ResourceNameRef>* mapping) + : mapping_(mapping) { + CHECK(mapping_ != nullptr); + } + + void Visit(Reference* reference) override { + if (!reference->id || !reference->id.value().is_valid()) { + return; } - void visit(Reference* reference) override { - if (!reference->id || !reference->id.value().isValid()) { - return; - } - - ResourceId id = reference->id.value(); - auto cacheIter = mMapping->find(id); - if (cacheIter != mMapping->end()) { - reference->name = cacheIter->second.toResourceName(); - } + ResourceId id = reference->id.value(); + auto cache_iter = mapping_->find(id); + if (cache_iter != mapping_->end()) { + reference->name = cache_iter->second.ToResourceName(); } + } -private: - const std::map<ResourceId, ResourceNameRef>* mMapping; + private: + const std::map<ResourceId, ResourceNameRef>* mapping_; }; class PackagePbDeserializer { -public: - PackagePbDeserializer(const android::ResStringPool* valuePool, - const android::ResStringPool* sourcePool, - const android::ResStringPool* symbolPool, - const Source& source, IDiagnostics* diag) : - mValuePool(valuePool), mSourcePool(sourcePool), mSymbolPool(symbolPool), - mSource(source), mDiag(diag) { + public: + PackagePbDeserializer(const android::ResStringPool* valuePool, + const android::ResStringPool* sourcePool, + const android::ResStringPool* symbolPool, + const Source& source, IDiagnostics* diag) + : value_pool_(valuePool), + source_pool_(sourcePool), + symbol_pool_(symbolPool), + source_(source), + diag_(diag) {} + + public: + bool DeserializeFromPb(const pb::Package& pbPackage, ResourceTable* table) { + Maybe<uint8_t> id; + if (pbPackage.has_package_id()) { + id = static_cast<uint8_t>(pbPackage.package_id()); } -public: - bool deserializeFromPb(const pb::Package& pbPackage, ResourceTable* table) { - Maybe<uint8_t> id; - if (pbPackage.has_package_id()) { - id = static_cast<uint8_t>(pbPackage.package_id()); - } - - std::map<ResourceId, ResourceNameRef> idIndex; + std::map<ResourceId, ResourceNameRef> idIndex; - ResourceTablePackage* pkg = table->createPackage( - util::utf8ToUtf16(pbPackage.package_name()), id); - for (const pb::Type& pbType : pbPackage.types()) { - const ResourceType* resType = parseResourceType(util::utf8ToUtf16(pbType.name())); - if (!resType) { - mDiag->error(DiagMessage(mSource) << "unknown type '" << pbType.name() << "'"); - return {}; + ResourceTablePackage* pkg = + table->CreatePackage(pbPackage.package_name(), id); + for (const pb::Type& pbType : pbPackage.types()) { + const ResourceType* resType = ParseResourceType(pbType.name()); + if (!resType) { + diag_->Error(DiagMessage(source_) << "unknown type '" << pbType.name() + << "'"); + return {}; + } + + ResourceTableType* type = pkg->FindOrCreateType(*resType); + + for (const pb::Entry& pbEntry : pbType.entries()) { + ResourceEntry* entry = type->FindOrCreateEntry(pbEntry.name()); + + // Deserialize the symbol status (public/private with source and + // comments). + if (pbEntry.has_symbol_status()) { + const pb::SymbolStatus& pbStatus = pbEntry.symbol_status(); + if (pbStatus.has_source()) { + DeserializeSourceFromPb(pbStatus.source(), *source_pool_, + &entry->symbol_status.source); + } + + if (pbStatus.has_comment()) { + entry->symbol_status.comment = pbStatus.comment(); + } + + SymbolState visibility = + DeserializeVisibilityFromPb(pbStatus.visibility()); + entry->symbol_status.state = visibility; + + if (visibility == SymbolState::kPublic) { + // This is a public symbol, we must encode the ID now if there is + // one. + if (pbEntry.has_id()) { + entry->id = static_cast<uint16_t>(pbEntry.id()); } - ResourceTableType* type = pkg->findOrCreateType(*resType); - - for (const pb::Entry& pbEntry : pbType.entries()) { - ResourceEntry* entry = type->findOrCreateEntry(util::utf8ToUtf16(pbEntry.name())); - - // Deserialize the symbol status (public/private with source and comments). - if (pbEntry.has_symbol_status()) { - const pb::SymbolStatus& pbStatus = pbEntry.symbol_status(); - if (pbStatus.has_source()) { - deserializeSourceFromPb(pbStatus.source(), *mSourcePool, - &entry->symbolStatus.source); - } - - if (pbStatus.has_comment()) { - entry->symbolStatus.comment = util::utf8ToUtf16(pbStatus.comment()); - } - - SymbolState visibility = deserializeVisibilityFromPb(pbStatus.visibility()); - entry->symbolStatus.state = visibility; - - if (visibility == SymbolState::kPublic) { - // This is a public symbol, we must encode the ID now if there is one. - if (pbEntry.has_id()) { - entry->id = static_cast<uint16_t>(pbEntry.id()); - } - - if (type->symbolStatus.state != SymbolState::kPublic) { - // If the type has not been made public, do so now. - type->symbolStatus.state = SymbolState::kPublic; - if (pbType.has_id()) { - type->id = static_cast<uint8_t>(pbType.id()); - } - } - } else if (visibility == SymbolState::kPrivate) { - if (type->symbolStatus.state == SymbolState::kUndefined) { - type->symbolStatus.state = SymbolState::kPrivate; - } - } - } - - ResourceId resId(pbPackage.package_id(), pbType.id(), pbEntry.id()); - if (resId.isValid()) { - idIndex[resId] = ResourceNameRef(pkg->name, type->type, entry->name); - } - - for (const pb::ConfigValue& pbConfigValue : pbEntry.config_values()) { - const pb::ConfigDescription& pbConfig = pbConfigValue.config(); - - ConfigDescription config; - if (!deserializeConfigDescriptionFromPb(pbConfig, &config)) { - mDiag->error(DiagMessage(mSource) << "invalid configuration"); - return {}; - } - - ResourceConfigValue* configValue = entry->findOrCreateValue(config, - pbConfig.product()); - if (configValue->value) { - // Duplicate config. - mDiag->error(DiagMessage(mSource) << "duplicate configuration"); - return {}; - } - - configValue->value = deserializeValueFromPb(pbConfigValue.value(), - config, &table->stringPool); - if (!configValue->value) { - return {}; - } - } + if (type->symbol_status.state != SymbolState::kPublic) { + // If the type has not been made public, do so now. + type->symbol_status.state = SymbolState::kPublic; + if (pbType.has_id()) { + type->id = static_cast<uint8_t>(pbType.id()); + } + } + } else if (visibility == SymbolState::kPrivate) { + if (type->symbol_status.state == SymbolState::kUndefined) { + type->symbol_status.state = SymbolState::kPrivate; } + } } - ReferenceIdToNameVisitor visitor(&idIndex); - visitAllValuesInPackage(pkg, &visitor); - return true; - } - -private: - std::unique_ptr<Item> deserializeItemFromPb(const pb::Item& pbItem, - const ConfigDescription& config, - StringPool* pool) { - if (pbItem.has_ref()) { - const pb::Reference& pbRef = pbItem.ref(); - std::unique_ptr<Reference> ref = util::make_unique<Reference>(); - if (!deserializeReferenceFromPb(pbRef, ref.get())) { - return {}; - } - return std::move(ref); - - } else if (pbItem.has_prim()) { - const pb::Primitive& pbPrim = pbItem.prim(); - android::Res_value prim = {}; - prim.dataType = static_cast<uint8_t>(pbPrim.type()); - prim.data = pbPrim.data(); - return util::make_unique<BinaryPrimitive>(prim); - - } else if (pbItem.has_id()) { - return util::make_unique<Id>(); - - } else if (pbItem.has_str()) { - const uint32_t idx = pbItem.str().idx(); - StringPiece16 str = util::getString(*mValuePool, idx); - - const android::ResStringPool_span* spans = mValuePool->styleAt(idx); - if (spans && spans->name.index != android::ResStringPool_span::END) { - StyleString styleStr = { str.toString() }; - while (spans->name.index != android::ResStringPool_span::END) { - styleStr.spans.push_back(Span{ - util::getString(*mValuePool, spans->name.index).toString(), - spans->firstChar, - spans->lastChar - }); - spans++; - } - return util::make_unique<StyledString>( - pool->makeRef(styleStr, StringPool::Context{ 1, config })); - } - return util::make_unique<String>( - pool->makeRef(str, StringPool::Context{ 1, config })); - - } else if (pbItem.has_raw_str()) { - const uint32_t idx = pbItem.raw_str().idx(); - StringPiece16 str = util::getString(*mValuePool, idx); - return util::make_unique<RawString>( - pool->makeRef(str, StringPool::Context{ 1, config })); - - } else if (pbItem.has_file()) { - const uint32_t idx = pbItem.file().path_idx(); - StringPiece16 str = util::getString(*mValuePool, idx); - return util::make_unique<FileReference>( - pool->makeRef(str, StringPool::Context{ 0, config })); - - } else { - mDiag->error(DiagMessage(mSource) << "unknown item"); + ResourceId resId(pbPackage.package_id(), pbType.id(), pbEntry.id()); + if (resId.is_valid()) { + idIndex[resId] = ResourceNameRef(pkg->name, type->type, entry->name); } - return {}; - } - std::unique_ptr<Value> deserializeValueFromPb(const pb::Value& pbValue, - const ConfigDescription& config, - StringPool* pool) { - const bool isWeak = pbValue.has_weak() ? pbValue.weak() : false; + for (const pb::ConfigValue& pbConfigValue : pbEntry.config_values()) { + const pb::ConfigDescription& pbConfig = pbConfigValue.config(); - std::unique_ptr<Value> value; - if (pbValue.has_item()) { - value = deserializeItemFromPb(pbValue.item(), config, pool); - if (!value) { - return {}; - } + ConfigDescription config; + if (!DeserializeConfigDescriptionFromPb(pbConfig, &config)) { + diag_->Error(DiagMessage(source_) << "invalid configuration"); + return {}; + } - } else if (pbValue.has_compound_value()) { - const pb::CompoundValue pbCompoundValue = pbValue.compound_value(); - if (pbCompoundValue.has_attr()) { - const pb::Attribute& pbAttr = pbCompoundValue.attr(); - std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak); - attr->typeMask = pbAttr.format_flags(); - attr->minInt = pbAttr.min_int(); - attr->maxInt = pbAttr.max_int(); - for (const pb::Attribute_Symbol& pbSymbol : pbAttr.symbols()) { - Attribute::Symbol symbol; - deserializeItemCommon(pbSymbol, &symbol.symbol); - if (!deserializeReferenceFromPb(pbSymbol.name(), &symbol.symbol)) { - return {}; - } - symbol.value = pbSymbol.value(); - attr->symbols.push_back(std::move(symbol)); - } - value = std::move(attr); - - } else if (pbCompoundValue.has_style()) { - const pb::Style& pbStyle = pbCompoundValue.style(); - std::unique_ptr<Style> style = util::make_unique<Style>(); - if (pbStyle.has_parent()) { - style->parent = Reference(); - if (!deserializeReferenceFromPb(pbStyle.parent(), &style->parent.value())) { - return {}; - } - - if (pbStyle.has_parent_source()) { - Source parentSource; - deserializeSourceFromPb(pbStyle.parent_source(), *mSourcePool, - &parentSource); - style->parent.value().setSource(std::move(parentSource)); - } - } - - for (const pb::Style_Entry& pbEntry : pbStyle.entries()) { - Style::Entry entry; - deserializeItemCommon(pbEntry, &entry.key); - if (!deserializeReferenceFromPb(pbEntry.key(), &entry.key)) { - return {}; - } - - entry.value = deserializeItemFromPb(pbEntry.item(), config, pool); - if (!entry.value) { - return {}; - } - - deserializeItemCommon(pbEntry, entry.value.get()); - style->entries.push_back(std::move(entry)); - } - value = std::move(style); - - } else if (pbCompoundValue.has_styleable()) { - const pb::Styleable& pbStyleable = pbCompoundValue.styleable(); - std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); - for (const pb::Styleable_Entry& pbEntry : pbStyleable.entries()) { - Reference attrRef; - deserializeItemCommon(pbEntry, &attrRef); - deserializeReferenceFromPb(pbEntry.attr(), &attrRef); - styleable->entries.push_back(std::move(attrRef)); - } - value = std::move(styleable); - - } else if (pbCompoundValue.has_array()) { - const pb::Array& pbArray = pbCompoundValue.array(); - std::unique_ptr<Array> array = util::make_unique<Array>(); - for (const pb::Array_Entry& pbEntry : pbArray.entries()) { - std::unique_ptr<Item> item = deserializeItemFromPb(pbEntry.item(), config, - pool); - if (!item) { - return {}; - } - - deserializeItemCommon(pbEntry, item.get()); - array->items.push_back(std::move(item)); - } - value = std::move(array); - - } else if (pbCompoundValue.has_plural()) { - const pb::Plural& pbPlural = pbCompoundValue.plural(); - std::unique_ptr<Plural> plural = util::make_unique<Plural>(); - for (const pb::Plural_Entry& pbEntry : pbPlural.entries()) { - size_t pluralIdx = deserializePluralEnumFromPb(pbEntry.arity()); - plural->values[pluralIdx] = deserializeItemFromPb(pbEntry.item(), config, - pool); - if (!plural->values[pluralIdx]) { - return {}; - } - - deserializeItemCommon(pbEntry, plural->values[pluralIdx].get()); - } - value = std::move(plural); - - } else { - mDiag->error(DiagMessage(mSource) << "unknown compound value"); - return {}; - } - } else { - mDiag->error(DiagMessage(mSource) << "unknown value"); + ResourceConfigValue* configValue = + entry->FindOrCreateValue(config, pbConfig.product()); + if (configValue->value) { + // Duplicate config. + diag_->Error(DiagMessage(source_) << "duplicate configuration"); return {}; - } + } - assert(value && "forgot to set value"); + configValue->value = DeserializeValueFromPb( + pbConfigValue.value(), config, &table->string_pool); + if (!configValue->value) { + return {}; + } + } + } + } - value->setWeak(isWeak); - deserializeItemCommon(pbValue, value.get()); - return value; + ReferenceIdToNameVisitor visitor(&idIndex); + VisitAllValuesInPackage(pkg, &visitor); + return true; + } + + private: + std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, + const ConfigDescription& config, + StringPool* pool) { + if (pb_item.has_ref()) { + const pb::Reference& pb_ref = pb_item.ref(); + std::unique_ptr<Reference> ref = util::make_unique<Reference>(); + if (!DeserializeReferenceFromPb(pb_ref, ref.get())) { + return {}; + } + return std::move(ref); + + } else if (pb_item.has_prim()) { + const pb::Primitive& pb_prim = pb_item.prim(); + android::Res_value prim = {}; + prim.dataType = static_cast<uint8_t>(pb_prim.type()); + prim.data = pb_prim.data(); + return util::make_unique<BinaryPrimitive>(prim); + + } else if (pb_item.has_id()) { + return util::make_unique<Id>(); + + } else if (pb_item.has_str()) { + const uint32_t idx = pb_item.str().idx(); + const std::string str = util::GetString(*value_pool_, idx); + + const android::ResStringPool_span* spans = value_pool_->styleAt(idx); + if (spans && spans->name.index != android::ResStringPool_span::END) { + StyleString style_str = {str}; + while (spans->name.index != android::ResStringPool_span::END) { + style_str.spans.push_back( + Span{util::GetString(*value_pool_, spans->name.index), + spans->firstChar, spans->lastChar}); + spans++; + } + return util::make_unique<StyledString>(pool->MakeRef( + style_str, + StringPool::Context(StringPool::Context::kStylePriority, config))); + } + return util::make_unique<String>( + pool->MakeRef(str, StringPool::Context(config))); + + } else if (pb_item.has_raw_str()) { + const uint32_t idx = pb_item.raw_str().idx(); + const std::string str = util::GetString(*value_pool_, idx); + return util::make_unique<RawString>( + pool->MakeRef(str, StringPool::Context(config))); + + } else if (pb_item.has_file()) { + const uint32_t idx = pb_item.file().path_idx(); + const std::string str = util::GetString(*value_pool_, idx); + return util::make_unique<FileReference>(pool->MakeRef( + str, + StringPool::Context(StringPool::Context::kHighPriority, config))); + + } else { + diag_->Error(DiagMessage(source_) << "unknown item"); } + return {}; + } - bool deserializeReferenceFromPb(const pb::Reference& pbRef, Reference* outRef) { - outRef->referenceType = deserializeReferenceTypeFromPb(pbRef.type()); - outRef->privateReference = pbRef.private_(); + std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value, + const ConfigDescription& config, + StringPool* pool) { + const bool is_weak = pb_value.has_weak() ? pb_value.weak() : false; - if (!pbRef.has_id() && !pbRef.has_symbol_idx()) { - return false; + std::unique_ptr<Value> value; + if (pb_value.has_item()) { + value = DeserializeItemFromPb(pb_value.item(), config, pool); + if (!value) { + return {}; + } + + } else if (pb_value.has_compound_value()) { + const pb::CompoundValue& pb_compound_value = pb_value.compound_value(); + if (pb_compound_value.has_attr()) { + const pb::Attribute& pb_attr = pb_compound_value.attr(); + std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(is_weak); + attr->type_mask = pb_attr.format_flags(); + attr->min_int = pb_attr.min_int(); + attr->max_int = pb_attr.max_int(); + for (const pb::Attribute_Symbol& pb_symbol : pb_attr.symbols()) { + Attribute::Symbol symbol; + DeserializeItemCommon(pb_symbol, &symbol.symbol); + if (!DeserializeReferenceFromPb(pb_symbol.name(), &symbol.symbol)) { + return {}; + } + symbol.value = pb_symbol.value(); + attr->symbols.push_back(std::move(symbol)); } - - if (pbRef.has_id()) { - outRef->id = ResourceId(pbRef.id()); + value = std::move(attr); + + } else if (pb_compound_value.has_style()) { + const pb::Style& pb_style = pb_compound_value.style(); + std::unique_ptr<Style> style = util::make_unique<Style>(); + if (pb_style.has_parent()) { + style->parent = Reference(); + if (!DeserializeReferenceFromPb(pb_style.parent(), + &style->parent.value())) { + return {}; + } + + if (pb_style.has_parent_source()) { + Source parent_source; + DeserializeSourceFromPb(pb_style.parent_source(), *source_pool_, + &parent_source); + style->parent.value().SetSource(std::move(parent_source)); + } } - if (pbRef.has_symbol_idx()) { - StringPiece16 strSymbol = util::getString(*mSymbolPool, pbRef.symbol_idx()); - ResourceNameRef nameRef; - if (!ResourceUtils::parseResourceName(strSymbol, &nameRef, nullptr)) { - mDiag->error(DiagMessage(mSource) << "invalid reference name '" - << strSymbol << "'"); - return false; - } + for (const pb::Style_Entry& pb_entry : pb_style.entries()) { + Style::Entry entry; + DeserializeItemCommon(pb_entry, &entry.key); + if (!DeserializeReferenceFromPb(pb_entry.key(), &entry.key)) { + return {}; + } + + entry.value = DeserializeItemFromPb(pb_entry.item(), config, pool); + if (!entry.value) { + return {}; + } - outRef->name = nameRef.toResourceName(); + DeserializeItemCommon(pb_entry, entry.value.get()); + style->entries.push_back(std::move(entry)); } - return true; - } + value = std::move(style); + + } else if (pb_compound_value.has_styleable()) { + const pb::Styleable& pb_styleable = pb_compound_value.styleable(); + std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); + for (const pb::Styleable_Entry& pb_entry : pb_styleable.entries()) { + Reference attr_ref; + DeserializeItemCommon(pb_entry, &attr_ref); + DeserializeReferenceFromPb(pb_entry.attr(), &attr_ref); + styleable->entries.push_back(std::move(attr_ref)); + } + value = std::move(styleable); + + } else if (pb_compound_value.has_array()) { + const pb::Array& pb_array = pb_compound_value.array(); + std::unique_ptr<Array> array = util::make_unique<Array>(); + for (const pb::Array_Entry& pb_entry : pb_array.entries()) { + std::unique_ptr<Item> item = + DeserializeItemFromPb(pb_entry.item(), config, pool); + if (!item) { + return {}; + } - template <typename T> - void deserializeItemCommon(const T& pbItem, Value* outValue) { - if (pbItem.has_source()) { - Source source; - deserializeSourceFromPb(pbItem.source(), *mSourcePool, &source); - outValue->setSource(std::move(source)); + DeserializeItemCommon(pb_entry, item.get()); + array->items.push_back(std::move(item)); } + value = std::move(array); + + } else if (pb_compound_value.has_plural()) { + const pb::Plural& pb_plural = pb_compound_value.plural(); + std::unique_ptr<Plural> plural = util::make_unique<Plural>(); + for (const pb::Plural_Entry& pb_entry : pb_plural.entries()) { + size_t pluralIdx = DeserializePluralEnumFromPb(pb_entry.arity()); + plural->values[pluralIdx] = + DeserializeItemFromPb(pb_entry.item(), config, pool); + if (!plural->values[pluralIdx]) { + return {}; + } - if (pbItem.has_comment()) { - outValue->setComment(util::utf8ToUtf16(pbItem.comment())); + DeserializeItemCommon(pb_entry, plural->values[pluralIdx].get()); } - } + value = std::move(plural); -private: - const android::ResStringPool* mValuePool; - const android::ResStringPool* mSourcePool; - const android::ResStringPool* mSymbolPool; - const Source mSource; - IDiagnostics* mDiag; -}; + } else { + diag_->Error(DiagMessage(source_) << "unknown compound value"); + return {}; + } + } else { + diag_->Error(DiagMessage(source_) << "unknown value"); + return {}; + } -} // namespace + CHECK(value) << "forgot to set value"; -std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable, - const Source& source, - IDiagnostics* diag) { - // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which - // causes errors when qualifying it with android:: - using namespace android; + value->SetWeak(is_weak); + DeserializeItemCommon(pb_value, value.get()); + return value; + } - std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); + bool DeserializeReferenceFromPb(const pb::Reference& pb_ref, + Reference* out_ref) { + out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type()); + out_ref->private_reference = pb_ref.private_(); - if (!pbTable.has_string_pool()) { - diag->error(DiagMessage(source) << "no string pool found"); - return {}; + if (!pb_ref.has_id() && !pb_ref.has_symbol_idx()) { + return false; } - ResStringPool valuePool; - status_t result = valuePool.setTo(pbTable.string_pool().data().data(), - pbTable.string_pool().data().size()); - if (result != NO_ERROR) { - diag->error(DiagMessage(source) << "invalid string pool"); - return {}; + if (pb_ref.has_id()) { + out_ref->id = ResourceId(pb_ref.id()); } - ResStringPool sourcePool; - if (pbTable.has_source_pool()) { - result = sourcePool.setTo(pbTable.source_pool().data().data(), - pbTable.source_pool().data().size()); - if (result != NO_ERROR) { - diag->error(DiagMessage(source) << "invalid source pool"); - return {}; - } + if (pb_ref.has_symbol_idx()) { + const std::string str_symbol = + util::GetString(*symbol_pool_, pb_ref.symbol_idx()); + ResourceNameRef name_ref; + if (!ResourceUtils::ParseResourceName(str_symbol, &name_ref, nullptr)) { + diag_->Error(DiagMessage(source_) << "invalid reference name '" + << str_symbol << "'"); + return false; + } + + out_ref->name = name_ref.ToResourceName(); } - - ResStringPool symbolPool; - if (pbTable.has_symbol_pool()) { - result = symbolPool.setTo(pbTable.symbol_pool().data().data(), - pbTable.symbol_pool().data().size()); - if (result != NO_ERROR) { - diag->error(DiagMessage(source) << "invalid symbol pool"); - return {}; - } + return true; + } + + template <typename T> + void DeserializeItemCommon(const T& pb_item, Value* out_value) { + if (pb_item.has_source()) { + Source source; + DeserializeSourceFromPb(pb_item.source(), *source_pool_, &source); + out_value->SetSource(std::move(source)); } - PackagePbDeserializer packagePbDeserializer(&valuePool, &sourcePool, &symbolPool, source, diag); - for (const pb::Package& pbPackage : pbTable.packages()) { - if (!packagePbDeserializer.deserializeFromPb(pbPackage, table.get())) { - return {}; - } + if (pb_item.has_comment()) { + out_value->SetComment(pb_item.comment()); } - return table; -} - -std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(const pb::CompiledFile& pbFile, - const Source& source, - IDiagnostics* diag) { - std::unique_ptr<ResourceFile> file = util::make_unique<ResourceFile>(); - - ResourceNameRef nameRef; + } + + private: + const android::ResStringPool* value_pool_; + const android::ResStringPool* source_pool_; + const android::ResStringPool* symbol_pool_; + const Source source_; + IDiagnostics* diag_; +}; - // Need to create an lvalue here so that nameRef can point to something real. - std::u16string utf16Name = util::utf8ToUtf16(pbFile.resource_name()); - if (!ResourceUtils::parseResourceName(utf16Name, &nameRef)) { - diag->error(DiagMessage(source) << "invalid resource name in compiled file header: " - << pbFile.resource_name()); - return {}; - } - file->name = nameRef.toResourceName(); - file->source.path = pbFile.source_path(); - deserializeConfigDescriptionFromPb(pbFile.config(), &file->config); - - for (const pb::CompiledFile_Symbol& pbSymbol : pbFile.exported_symbols()) { - // Need to create an lvalue here so that nameRef can point to something real. - utf16Name = util::utf8ToUtf16(pbSymbol.resource_name()); - if (!ResourceUtils::parseResourceName(utf16Name, &nameRef)) { - diag->error(DiagMessage(source) << "invalid resource name for exported symbol in " - "compiled file header: " - << pbFile.resource_name()); - return {}; - } - file->exportedSymbols.push_back( - SourcedResourceName{ nameRef.toResourceName(), pbSymbol.line_no() }); +} // namespace + +std::unique_ptr<ResourceTable> DeserializeTableFromPb( + const pb::ResourceTable& pb_table, const Source& source, + IDiagnostics* diag) { + // We import the android namespace because on Windows NO_ERROR is a macro, not + // an enum, which + // causes errors when qualifying it with android:: + using namespace android; + + std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); + + if (!pb_table.has_string_pool()) { + diag->Error(DiagMessage(source) << "no string pool found"); + return {}; + } + + ResStringPool value_pool; + status_t result = value_pool.setTo(pb_table.string_pool().data().data(), + pb_table.string_pool().data().size()); + if (result != NO_ERROR) { + diag->Error(DiagMessage(source) << "invalid string pool"); + return {}; + } + + ResStringPool source_pool; + if (pb_table.has_source_pool()) { + result = source_pool.setTo(pb_table.source_pool().data().data(), + pb_table.source_pool().data().size()); + if (result != NO_ERROR) { + diag->Error(DiagMessage(source) << "invalid source pool"); + return {}; } - return file; -} - -CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size) : - mIn(static_cast<const uint8_t*>(data), size), mPbFile(), - mData(static_cast<const uint8_t*>(data)), mSize(size) { -} + } -const pb::CompiledFile* CompiledFileInputStream::CompiledFile() { - if (!mPbFile) { - std::unique_ptr<pb::CompiledFile> pbFile = util::make_unique<pb::CompiledFile>(); - uint64_t pbSize = 0u; - if (!mIn.ReadLittleEndian64(&pbSize)) { - return nullptr; - } - mIn.PushLimit(static_cast<int>(pbSize)); - if (!pbFile->ParsePartialFromCodedStream(&mIn)) { - return nullptr; - } - - const size_t padding = 4 - (pbSize & 0x03); - const size_t offset = sizeof(uint64_t) + pbSize + padding; - if (offset > mSize) { - return nullptr; - } - - mData += offset; - mSize -= offset; - mPbFile = std::move(pbFile); + ResStringPool symbol_pool; + if (pb_table.has_symbol_pool()) { + result = symbol_pool.setTo(pb_table.symbol_pool().data().data(), + pb_table.symbol_pool().data().size()); + if (result != NO_ERROR) { + diag->Error(DiagMessage(source) << "invalid symbol pool"); + return {}; } - return mPbFile.get(); -} + } -const void* CompiledFileInputStream::data() { - if (!mPbFile) { - if (!CompiledFile()) { - return nullptr; - } + PackagePbDeserializer package_pb_deserializer(&value_pool, &source_pool, + &symbol_pool, source, diag); + for (const pb::Package& pb_package : pb_table.packages()) { + if (!package_pb_deserializer.DeserializeFromPb(pb_package, table.get())) { + return {}; } - return mData; + } + return table; } -size_t CompiledFileInputStream::size() { - if (!mPbFile) { - if (!CompiledFile()) { - return 0; - } +std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb( + const pb::CompiledFile& pb_file, const Source& source, IDiagnostics* diag) { + std::unique_ptr<ResourceFile> file = util::make_unique<ResourceFile>(); + + ResourceNameRef name_ref; + + // Need to create an lvalue here so that nameRef can point to something real. + if (!ResourceUtils::ParseResourceName(pb_file.resource_name(), &name_ref)) { + diag->Error(DiagMessage(source) + << "invalid resource name in compiled file header: " + << pb_file.resource_name()); + return {}; + } + file->name = name_ref.ToResourceName(); + file->source.path = pb_file.source_path(); + DeserializeConfigDescriptionFromPb(pb_file.config(), &file->config); + + for (const pb::CompiledFile_Symbol& pb_symbol : pb_file.exported_symbols()) { + // Need to create an lvalue here so that nameRef can point to something + // real. + if (!ResourceUtils::ParseResourceName(pb_symbol.resource_name(), + &name_ref)) { + diag->Error(DiagMessage(source) + << "invalid resource name for exported symbol in " + "compiled file header: " + << pb_file.resource_name()); + return {}; } - return mSize; + file->exported_symbols.push_back( + SourcedResourceName{name_ref.ToResourceName(), pb_symbol.line_no()}); + } + return file; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp index 5d1b72b0ebbd..0d0e46da4ec8 100644 --- a/tools/aapt2/proto/TableProtoSerializer.cpp +++ b/tools/aapt2/proto/TableProtoSerializer.cpp @@ -22,184 +22,196 @@ #include "proto/ProtoSerialize.h" #include "util/BigBuffer.h" +#include <android-base/logging.h> + +using google::protobuf::io::CodedOutputStream; +using google::protobuf::io::CodedInputStream; +using google::protobuf::io::ZeroCopyOutputStream; + namespace aapt { namespace { class PbSerializerVisitor : public RawValueVisitor { -public: - using RawValueVisitor::visit; - - /** - * Constructor to use when expecting to serialize any value. - */ - PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool, pb::Value* outPbValue) : - mSourcePool(sourcePool), mSymbolPool(symbolPool), mOutPbValue(outPbValue), - mOutPbItem(nullptr) { - } - - /** - * Constructor to use when expecting to serialize an Item. - */ - PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool, pb::Item* outPbItem) : - mSourcePool(sourcePool), mSymbolPool(symbolPool), mOutPbValue(nullptr), - mOutPbItem(outPbItem) { + public: + using RawValueVisitor::Visit; + + /** + * Constructor to use when expecting to serialize any value. + */ + PbSerializerVisitor(StringPool* source_pool, StringPool* symbol_pool, + pb::Value* out_pb_value) + : source_pool_(source_pool), + symbol_pool_(symbol_pool), + out_pb_value_(out_pb_value), + out_pb_item_(nullptr) {} + + /** + * Constructor to use when expecting to serialize an Item. + */ + PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool, + pb::Item* outPbItem) + : source_pool_(sourcePool), + symbol_pool_(symbolPool), + out_pb_value_(nullptr), + out_pb_item_(outPbItem) {} + + void Visit(Reference* ref) override { + SerializeReferenceToPb(*ref, pb_item()->mutable_ref()); + } + + void Visit(String* str) override { + pb_item()->mutable_str()->set_idx(str->value.index()); + } + + void Visit(StyledString* str) override { + pb_item()->mutable_str()->set_idx(str->value.index()); + } + + void Visit(FileReference* file) override { + pb_item()->mutable_file()->set_path_idx(file->path.index()); + } + + void Visit(Id* id) override { pb_item()->mutable_id(); } + + void Visit(RawString* raw_str) override { + pb_item()->mutable_raw_str()->set_idx(raw_str->value.index()); + } + + void Visit(BinaryPrimitive* prim) override { + android::Res_value val = {}; + prim->Flatten(&val); + + pb::Primitive* pb_prim = pb_item()->mutable_prim(); + pb_prim->set_type(val.dataType); + pb_prim->set_data(val.data); + } + + void VisitItem(Item* item) override { LOG(FATAL) << "unimplemented item"; } + + void Visit(Attribute* attr) override { + pb::Attribute* pb_attr = pb_compound_value()->mutable_attr(); + pb_attr->set_format_flags(attr->type_mask); + pb_attr->set_min_int(attr->min_int); + pb_attr->set_max_int(attr->max_int); + + for (auto& symbol : attr->symbols) { + pb::Attribute_Symbol* pb_symbol = pb_attr->add_symbols(); + SerializeItemCommonToPb(symbol.symbol, pb_symbol); + SerializeReferenceToPb(symbol.symbol, pb_symbol->mutable_name()); + pb_symbol->set_value(symbol.value); } - - void visit(Reference* ref) override { - serializeReferenceToPb(*ref, getPbItem()->mutable_ref()); + } + + void Visit(Style* style) override { + pb::Style* pb_style = pb_compound_value()->mutable_style(); + if (style->parent) { + SerializeReferenceToPb(style->parent.value(), pb_style->mutable_parent()); + SerializeSourceToPb(style->parent.value().GetSource(), source_pool_, + pb_style->mutable_parent_source()); } - void visit(String* str) override { - getPbItem()->mutable_str()->set_idx(str->value.getIndex()); - } + for (Style::Entry& entry : style->entries) { + pb::Style_Entry* pb_entry = pb_style->add_entries(); + SerializeReferenceToPb(entry.key, pb_entry->mutable_key()); - void visit(StyledString* str) override { - getPbItem()->mutable_str()->set_idx(str->value.getIndex()); + pb::Item* pb_item = pb_entry->mutable_item(); + SerializeItemCommonToPb(entry.key, pb_entry); + PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_item); + entry.value->Accept(&sub_visitor); } - - void visit(FileReference* file) override { - getPbItem()->mutable_file()->set_path_idx(file->path.getIndex()); + } + + void Visit(Styleable* styleable) override { + pb::Styleable* pb_styleable = pb_compound_value()->mutable_styleable(); + for (Reference& entry : styleable->entries) { + pb::Styleable_Entry* pb_entry = pb_styleable->add_entries(); + SerializeItemCommonToPb(entry, pb_entry); + SerializeReferenceToPb(entry, pb_entry->mutable_attr()); } - - void visit(Id* id) override { - getPbItem()->mutable_id(); - } - - void visit(RawString* rawStr) override { - getPbItem()->mutable_raw_str()->set_idx(rawStr->value.getIndex()); - } - - void visit(BinaryPrimitive* prim) override { - android::Res_value val = {}; - prim->flatten(&val); - - pb::Primitive* pbPrim = getPbItem()->mutable_prim(); - pbPrim->set_type(val.dataType); - pbPrim->set_data(val.data); - } - - void visitItem(Item* item) override { - assert(false && "unimplemented item"); + } + + void Visit(Array* array) override { + pb::Array* pb_array = pb_compound_value()->mutable_array(); + for (auto& value : array->items) { + pb::Array_Entry* pb_entry = pb_array->add_entries(); + SerializeItemCommonToPb(*value, pb_entry); + PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, + pb_entry->mutable_item()); + value->Accept(&sub_visitor); } - - void visit(Attribute* attr) override { - pb::Attribute* pbAttr = getPbCompoundValue()->mutable_attr(); - pbAttr->set_format_flags(attr->typeMask); - pbAttr->set_min_int(attr->minInt); - pbAttr->set_max_int(attr->maxInt); - - for (auto& symbol : attr->symbols) { - pb::Attribute_Symbol* pbSymbol = pbAttr->add_symbols(); - serializeItemCommonToPb(symbol.symbol, pbSymbol); - serializeReferenceToPb(symbol.symbol, pbSymbol->mutable_name()); - pbSymbol->set_value(symbol.value); - } + } + + void Visit(Plural* plural) override { + pb::Plural* pb_plural = pb_compound_value()->mutable_plural(); + const size_t count = plural->values.size(); + for (size_t i = 0; i < count; i++) { + if (!plural->values[i]) { + // No plural value set here. + continue; + } + + pb::Plural_Entry* pb_entry = pb_plural->add_entries(); + pb_entry->set_arity(SerializePluralEnumToPb(i)); + pb::Item* pb_element = pb_entry->mutable_item(); + SerializeItemCommonToPb(*plural->values[i], pb_entry); + PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_element); + plural->values[i]->Accept(&sub_visitor); } + } - void visit(Style* style) override { - pb::Style* pbStyle = getPbCompoundValue()->mutable_style(); - if (style->parent) { - serializeReferenceToPb(style->parent.value(), pbStyle->mutable_parent()); - serializeSourceToPb(style->parent.value().getSource(), - mSourcePool, - pbStyle->mutable_parent_source()); - } - - for (Style::Entry& entry : style->entries) { - pb::Style_Entry* pbEntry = pbStyle->add_entries(); - serializeReferenceToPb(entry.key, pbEntry->mutable_key()); + private: + DISALLOW_COPY_AND_ASSIGN(PbSerializerVisitor); - pb::Item* pbItem = pbEntry->mutable_item(); - serializeItemCommonToPb(entry.key, pbEntry); - PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbItem); - entry.value->accept(&subVisitor); - } + pb::Item* pb_item() { + if (out_pb_value_) { + return out_pb_value_->mutable_item(); } - - void visit(Styleable* styleable) override { - pb::Styleable* pbStyleable = getPbCompoundValue()->mutable_styleable(); - for (Reference& entry : styleable->entries) { - pb::Styleable_Entry* pbEntry = pbStyleable->add_entries(); - serializeItemCommonToPb(entry, pbEntry); - serializeReferenceToPb(entry, pbEntry->mutable_attr()); - } + return out_pb_item_; + } + + pb::CompoundValue* pb_compound_value() { + CHECK(out_pb_value_ != nullptr); + return out_pb_value_->mutable_compound_value(); + } + + template <typename T> + void SerializeItemCommonToPb(const Item& item, T* pb_item) { + SerializeSourceToPb(item.GetSource(), source_pool_, + pb_item->mutable_source()); + if (!item.GetComment().empty()) { + pb_item->set_comment(item.GetComment()); } + } - void visit(Array* array) override { - pb::Array* pbArray = getPbCompoundValue()->mutable_array(); - for (auto& value : array->items) { - pb::Array_Entry* pbEntry = pbArray->add_entries(); - serializeItemCommonToPb(*value, pbEntry); - PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbEntry->mutable_item()); - value->accept(&subVisitor); - } + void SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref) { + if (ref.id) { + pb_ref->set_id(ref.id.value().id); } - void visit(Plural* plural) override { - pb::Plural* pbPlural = getPbCompoundValue()->mutable_plural(); - const size_t count = plural->values.size(); - for (size_t i = 0; i < count; i++) { - if (!plural->values[i]) { - // No plural value set here. - continue; - } - - pb::Plural_Entry* pbEntry = pbPlural->add_entries(); - pbEntry->set_arity(serializePluralEnumToPb(i)); - pb::Item* pbElement = pbEntry->mutable_item(); - serializeItemCommonToPb(*plural->values[i], pbEntry); - PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbElement); - plural->values[i]->accept(&subVisitor); - } + if (ref.name) { + StringPool::Ref symbol_ref = + symbol_pool_->MakeRef(ref.name.value().ToString()); + pb_ref->set_symbol_idx(static_cast<uint32_t>(symbol_ref.index())); } -private: - pb::Item* getPbItem() { - if (mOutPbValue) { - return mOutPbValue->mutable_item(); - } - return mOutPbItem; - } + pb_ref->set_private_(ref.private_reference); + pb_ref->set_type(SerializeReferenceTypeToPb(ref.reference_type)); + } - pb::CompoundValue* getPbCompoundValue() { - assert(mOutPbValue); - return mOutPbValue->mutable_compound_value(); - } - - template <typename T> - void serializeItemCommonToPb(const Item& item, T* pbItem) { - serializeSourceToPb(item.getSource(), mSourcePool, pbItem->mutable_source()); - if (!item.getComment().empty()) { - pbItem->set_comment(util::utf16ToUtf8(item.getComment())); - } - } - - void serializeReferenceToPb(const Reference& ref, pb::Reference* pbRef) { - if (ref.id) { - pbRef->set_id(ref.id.value().id); - } - - if (ref.name) { - StringPool::Ref symbolRef = mSymbolPool->makeRef(ref.name.value().toString()); - pbRef->set_symbol_idx(static_cast<uint32_t>(symbolRef.getIndex())); - } - - pbRef->set_private_(ref.privateReference); - pbRef->set_type(serializeReferenceTypeToPb(ref.referenceType)); - } - - StringPool* mSourcePool; - StringPool* mSymbolPool; - pb::Value* mOutPbValue; - pb::Item* mOutPbItem; + StringPool* source_pool_; + StringPool* symbol_pool_; + pb::Value* out_pb_value_; + pb::Item* out_pb_item_; }; -} // namespace +} // namespace -std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table) { - // We must do this before writing the resources, since the string pool IDs may change. - table->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { +std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) { + // We must do this before writing the resources, since the string pool IDs may + // change. + table->string_pool.Sort( + [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { int diff = a.context.priority - b.context.priority; if (diff < 0) return true; if (diff > 0) return false; @@ -207,116 +219,195 @@ std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table) { if (diff < 0) return true; if (diff > 0) return false; return a.value < b.value; - }); - table->stringPool.prune(); + }); + table->string_pool.Prune(); - std::unique_ptr<pb::ResourceTable> pbTable = util::make_unique<pb::ResourceTable>(); - serializeStringPoolToPb(table->stringPool, pbTable->mutable_string_pool()); + auto pb_table = util::make_unique<pb::ResourceTable>(); + SerializeStringPoolToPb(table->string_pool, pb_table->mutable_string_pool()); - StringPool sourcePool, symbolPool; + StringPool source_pool, symbol_pool; - for (auto& package : table->packages) { - pb::Package* pbPackage = pbTable->add_packages(); - if (package->id) { - pbPackage->set_package_id(package->id.value()); + for (auto& package : table->packages) { + pb::Package* pb_package = pb_table->add_packages(); + if (package->id) { + pb_package->set_package_id(package->id.value()); + } + pb_package->set_package_name(package->name); + + for (auto& type : package->types) { + pb::Type* pb_type = pb_package->add_types(); + if (type->id) { + pb_type->set_id(type->id.value()); + } + pb_type->set_name(ToString(type->type).ToString()); + + for (auto& entry : type->entries) { + pb::Entry* pb_entry = pb_type->add_entries(); + if (entry->id) { + pb_entry->set_id(entry->id.value()); } - pbPackage->set_package_name(util::utf16ToUtf8(package->name)); - - for (auto& type : package->types) { - pb::Type* pbType = pbPackage->add_types(); - if (type->id) { - pbType->set_id(type->id.value()); - } - pbType->set_name(util::utf16ToUtf8(toString(type->type))); - - for (auto& entry : type->entries) { - pb::Entry* pbEntry = pbType->add_entries(); - if (entry->id) { - pbEntry->set_id(entry->id.value()); - } - pbEntry->set_name(util::utf16ToUtf8(entry->name)); - - // Write the SymbolStatus struct. - pb::SymbolStatus* pbStatus = pbEntry->mutable_symbol_status(); - pbStatus->set_visibility(serializeVisibilityToPb(entry->symbolStatus.state)); - serializeSourceToPb(entry->symbolStatus.source, &sourcePool, - pbStatus->mutable_source()); - pbStatus->set_comment(util::utf16ToUtf8(entry->symbolStatus.comment)); - - for (auto& configValue : entry->values) { - pb::ConfigValue* pbConfigValue = pbEntry->add_config_values(); - serializeConfig(configValue->config, pbConfigValue->mutable_config()); - if (!configValue->product.empty()) { - pbConfigValue->mutable_config()->set_product(configValue->product); - } - - pb::Value* pbValue = pbConfigValue->mutable_value(); - serializeSourceToPb(configValue->value->getSource(), &sourcePool, - pbValue->mutable_source()); - if (!configValue->value->getComment().empty()) { - pbValue->set_comment(util::utf16ToUtf8(configValue->value->getComment())); - } - - if (configValue->value->isWeak()) { - pbValue->set_weak(true); - } - - PbSerializerVisitor visitor(&sourcePool, &symbolPool, pbValue); - configValue->value->accept(&visitor); - } - } + pb_entry->set_name(entry->name); + + // Write the SymbolStatus struct. + pb::SymbolStatus* pb_status = pb_entry->mutable_symbol_status(); + pb_status->set_visibility( + SerializeVisibilityToPb(entry->symbol_status.state)); + SerializeSourceToPb(entry->symbol_status.source, &source_pool, + pb_status->mutable_source()); + pb_status->set_comment(entry->symbol_status.comment); + + for (auto& config_value : entry->values) { + pb::ConfigValue* pb_config_value = pb_entry->add_config_values(); + SerializeConfig(config_value->config, + pb_config_value->mutable_config()); + if (!config_value->product.empty()) { + pb_config_value->mutable_config()->set_product( + config_value->product); + } + + pb::Value* pb_value = pb_config_value->mutable_value(); + SerializeSourceToPb(config_value->value->GetSource(), &source_pool, + pb_value->mutable_source()); + if (!config_value->value->GetComment().empty()) { + pb_value->set_comment(config_value->value->GetComment()); + } + + if (config_value->value->IsWeak()) { + pb_value->set_weak(true); + } + + PbSerializerVisitor visitor(&source_pool, &symbol_pool, pb_value); + config_value->value->Accept(&visitor); } + } } + } - serializeStringPoolToPb(sourcePool, pbTable->mutable_source_pool()); - serializeStringPoolToPb(symbolPool, pbTable->mutable_symbol_pool()); - return pbTable; + SerializeStringPoolToPb(source_pool, pb_table->mutable_source_pool()); + SerializeStringPoolToPb(symbol_pool, pb_table->mutable_symbol_pool()); + return pb_table; } -std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& file) { - std::unique_ptr<pb::CompiledFile> pbFile = util::make_unique<pb::CompiledFile>(); - pbFile->set_resource_name(util::utf16ToUtf8(file.name.toString())); - pbFile->set_source_path(file.source.path); - serializeConfig(file.config, pbFile->mutable_config()); +std::unique_ptr<pb::CompiledFile> SerializeCompiledFileToPb( + const ResourceFile& file) { + auto pb_file = util::make_unique<pb::CompiledFile>(); + pb_file->set_resource_name(file.name.ToString()); + pb_file->set_source_path(file.source.path); + SerializeConfig(file.config, pb_file->mutable_config()); + + for (const SourcedResourceName& exported : file.exported_symbols) { + pb::CompiledFile_Symbol* pb_symbol = pb_file->add_exported_symbols(); + pb_symbol->set_resource_name(exported.name.ToString()); + pb_symbol->set_line_no(exported.line); + } + return pb_file; +} - for (const SourcedResourceName& exported : file.exportedSymbols) { - pb::CompiledFile_Symbol* pbSymbol = pbFile->add_exported_symbols(); - pbSymbol->set_resource_name(util::utf16ToUtf8(exported.name.toString())); - pbSymbol->set_line_no(exported.line); - } - return pbFile; +CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out) + : out_(out) {} + +void CompiledFileOutputStream::EnsureAlignedWrite() { + const int padding = out_.ByteCount() % 4; + if (padding > 0) { + uint32_t zero = 0u; + out_.WriteRaw(&zero, padding); + } } -CompiledFileOutputStream::CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out, - pb::CompiledFile* pbFile) : - mOut(out), mPbFile(pbFile) { +void CompiledFileOutputStream::WriteLittleEndian32(uint32_t val) { + EnsureAlignedWrite(); + out_.WriteLittleEndian32(val); } -bool CompiledFileOutputStream::ensureFileWritten() { - if (mPbFile) { - const uint64_t pbSize = mPbFile->ByteSize(); - mOut.WriteLittleEndian64(pbSize); - mPbFile->SerializeWithCachedSizes(&mOut); - const size_t padding = 4 - (pbSize & 0x03); - if (padding > 0) { - uint32_t zero = 0u; - mOut.WriteRaw(&zero, padding); - } - mPbFile = nullptr; - } - return !mOut.HadError(); +void CompiledFileOutputStream::WriteCompiledFile( + const pb::CompiledFile* compiled_file) { + EnsureAlignedWrite(); + out_.WriteLittleEndian64(static_cast<uint64_t>(compiled_file->ByteSize())); + compiled_file->SerializeWithCachedSizes(&out_); } -bool CompiledFileOutputStream::Write(const void* data, int size) { - if (!ensureFileWritten()) { - return false; - } - mOut.WriteRaw(data, size); - return !mOut.HadError(); +void CompiledFileOutputStream::WriteData(const BigBuffer* buffer) { + EnsureAlignedWrite(); + out_.WriteLittleEndian64(static_cast<uint64_t>(buffer->size())); + for (const BigBuffer::Block& block : *buffer) { + out_.WriteRaw(block.buffer.get(), block.size); + } +} + +void CompiledFileOutputStream::WriteData(const void* data, size_t len) { + EnsureAlignedWrite(); + out_.WriteLittleEndian64(static_cast<uint64_t>(len)); + out_.WriteRaw(data, len); +} + +bool CompiledFileOutputStream::HadError() { return out_.HadError(); } + +CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size) + : in_(static_cast<const uint8_t*>(data), size) {} + +void CompiledFileInputStream::EnsureAlignedRead() { + const int padding = in_.CurrentPosition() % 4; + if (padding > 0) { + // Reads are always 4 byte aligned. + in_.Skip(padding); + } +} + +bool CompiledFileInputStream::ReadLittleEndian32(uint32_t* out_val) { + EnsureAlignedRead(); + return in_.ReadLittleEndian32(out_val); +} + +bool CompiledFileInputStream::ReadCompiledFile(pb::CompiledFile* out_val) { + EnsureAlignedRead(); + + google::protobuf::uint64 pb_size = 0u; + if (!in_.ReadLittleEndian64(&pb_size)) { + return false; + } + + CodedInputStream::Limit l = in_.PushLimit(static_cast<int>(pb_size)); + + // Check that we haven't tried to read past the end. + if (static_cast<uint64_t>(in_.BytesUntilLimit()) != pb_size) { + in_.PopLimit(l); + in_.PushLimit(0); + return false; + } + + if (!out_val->ParsePartialFromCodedStream(&in_)) { + in_.PopLimit(l); + in_.PushLimit(0); + return false; + } + + in_.PopLimit(l); + return true; } -bool CompiledFileOutputStream::Finish() { - return ensureFileWritten(); +bool CompiledFileInputStream::ReadDataMetaData(uint64_t* out_offset, + uint64_t* out_len) { + EnsureAlignedRead(); + + google::protobuf::uint64 pb_size = 0u; + if (!in_.ReadLittleEndian64(&pb_size)) { + return false; + } + + // Check that we aren't trying to read past the end. + if (pb_size > static_cast<uint64_t>(in_.BytesUntilLimit())) { + in_.PushLimit(0); + return false; + } + + uint64_t offset = static_cast<uint64_t>(in_.CurrentPosition()); + if (!in_.Skip(pb_size)) { + return false; + } + + *out_offset = offset; + *out_len = pb_size; + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp index dd995d858d77..fdd5197167ef 100644 --- a/tools/aapt2/proto/TableProtoSerializer_test.cpp +++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp @@ -14,161 +14,211 @@ * limitations under the License. */ -#include "ResourceTable.h" #include "proto/ProtoSerialize.h" -#include "test/Builders.h" -#include "test/Common.h" -#include "test/Context.h" -#include <gtest/gtest.h> +#include "ResourceTable.h" +#include "test/Test.h" + +using ::google::protobuf::io::StringOutputStream; namespace aapt { TEST(TableProtoSerializer, SerializeSinglePackage) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId(u"com.app.a", 0x7f) - .addFileReference(u"@com.app.a:layout/main", ResourceId(0x7f020000), - u"res/layout/main.xml") - .addReference(u"@com.app.a:layout/other", ResourceId(0x7f020001), - u"@com.app.a:layout/main") - .addString(u"@com.app.a:string/text", {}, u"hi") - .addValue(u"@com.app.a:id/foo", {}, util::make_unique<Id>()) - .build(); - - Symbol publicSymbol; - publicSymbol.state = SymbolState::kPublic; - ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@com.app.a:layout/main"), - ResourceId(0x7f020000), - publicSymbol, context->getDiagnostics())); - - Id* id = test::getValue<Id>(table.get(), u"@com.app.a:id/foo"); - ASSERT_NE(nullptr, id); - - // Make a plural. - std::unique_ptr<Plural> plural = util::make_unique<Plural>(); - plural->values[Plural::One] = util::make_unique<String>(table->stringPool.makeRef(u"one")); - ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:plurals/hey"), - ConfigDescription{}, std::string(), std::move(plural), - context->getDiagnostics())); - - // Make a resource with different products. - ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:integer/one"), - test::parseConfigOrDie("land"), std::string(), - test::buildPrimitive(android::Res_value::TYPE_INT_DEC, 123u), - context->getDiagnostics())); - ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:integer/one"), - test::parseConfigOrDie("land"), std::string("tablet"), - test::buildPrimitive(android::Res_value::TYPE_INT_DEC, 321u), - context->getDiagnostics())); - - // Make a reference with both resource name and resource ID. - // The reference should point to a resource outside of this table to test that both - // name and id get serialized. - Reference expectedRef; - expectedRef.name = test::parseNameOrDie(u"@android:layout/main"); - expectedRef.id = ResourceId(0x01020000); - ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:layout/abc"), - ConfigDescription::defaultConfig(), std::string(), - util::make_unique<Reference>(expectedRef), - context->getDiagnostics())); - - std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table.get()); - ASSERT_NE(nullptr, pbTable); - - std::unique_ptr<ResourceTable> newTable = deserializeTableFromPb(*pbTable, - Source{ "test" }, - context->getDiagnostics()); - ASSERT_NE(nullptr, newTable); - - Id* newId = test::getValue<Id>(newTable.get(), u"@com.app.a:id/foo"); - ASSERT_NE(nullptr, newId); - EXPECT_EQ(id->isWeak(), newId->isWeak()); - - Maybe<ResourceTable::SearchResult> result = newTable->findResource( - test::parseNameOrDie(u"@com.app.a:layout/main")); - AAPT_ASSERT_TRUE(result); - EXPECT_EQ(SymbolState::kPublic, result.value().type->symbolStatus.state); - EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state); - - // Find the product-dependent values - BinaryPrimitive* prim = test::getValueForConfigAndProduct<BinaryPrimitive>( - newTable.get(), u"@com.app.a:integer/one", test::parseConfigOrDie("land"), ""); - ASSERT_NE(nullptr, prim); - EXPECT_EQ(123u, prim->value.data); - - prim = test::getValueForConfigAndProduct<BinaryPrimitive>( - newTable.get(), u"@com.app.a:integer/one", test::parseConfigOrDie("land"), "tablet"); - ASSERT_NE(nullptr, prim); - EXPECT_EQ(321u, prim->value.data); - - Reference* actualRef = test::getValue<Reference>(newTable.get(), u"@com.app.a:layout/abc"); - ASSERT_NE(nullptr, actualRef); - AAPT_ASSERT_TRUE(actualRef->name); - AAPT_ASSERT_TRUE(actualRef->id); - EXPECT_EQ(expectedRef.name.value(), actualRef->name.value()); - EXPECT_EQ(expectedRef.id.value(), actualRef->id.value()); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.a", 0x7f) + .AddFileReference("com.app.a:layout/main", ResourceId(0x7f020000), + "res/layout/main.xml") + .AddReference("com.app.a:layout/other", ResourceId(0x7f020001), + "com.app.a:layout/main") + .AddString("com.app.a:string/text", {}, "hi") + .AddValue("com.app.a:id/foo", {}, util::make_unique<Id>()) + .Build(); + + Symbol public_symbol; + public_symbol.state = SymbolState::kPublic; + ASSERT_TRUE(table->SetSymbolState( + test::ParseNameOrDie("com.app.a:layout/main"), ResourceId(0x7f020000), + public_symbol, context->GetDiagnostics())); + + Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo"); + ASSERT_NE(nullptr, id); + + // Make a plural. + std::unique_ptr<Plural> plural = util::make_unique<Plural>(); + plural->values[Plural::One] = + util::make_unique<String>(table->string_pool.MakeRef("one")); + ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:plurals/hey"), + ConfigDescription{}, {}, std::move(plural), + context->GetDiagnostics())); + + // Make a resource with different products. + ASSERT_TRUE(table->AddResource( + test::ParseNameOrDie("com.app.a:integer/one"), + test::ParseConfigOrDie("land"), {}, + test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 123u), + context->GetDiagnostics())); + ASSERT_TRUE(table->AddResource( + test::ParseNameOrDie("com.app.a:integer/one"), + test::ParseConfigOrDie("land"), "tablet", + test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 321u), + context->GetDiagnostics())); + + // Make a reference with both resource name and resource ID. + // The reference should point to a resource outside of this table to test that + // both + // name and id get serialized. + Reference expected_ref; + expected_ref.name = test::ParseNameOrDie("android:layout/main"); + expected_ref.id = ResourceId(0x01020000); + ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:layout/abc"), + ConfigDescription::DefaultConfig(), {}, + util::make_unique<Reference>(expected_ref), + context->GetDiagnostics())); + + std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table.get()); + ASSERT_NE(nullptr, pb_table); + + std::unique_ptr<ResourceTable> new_table = DeserializeTableFromPb( + *pb_table, Source{"test"}, context->GetDiagnostics()); + ASSERT_NE(nullptr, new_table); + + Id* new_id = test::GetValue<Id>(new_table.get(), "com.app.a:id/foo"); + ASSERT_NE(nullptr, new_id); + EXPECT_EQ(id->IsWeak(), new_id->IsWeak()); + + Maybe<ResourceTable::SearchResult> result = + new_table->FindResource(test::ParseNameOrDie("com.app.a:layout/main")); + AAPT_ASSERT_TRUE(result); + EXPECT_EQ(SymbolState::kPublic, result.value().type->symbol_status.state); + EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state); + + // Find the product-dependent values + BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( + new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), + ""); + ASSERT_NE(nullptr, prim); + EXPECT_EQ(123u, prim->value.data); + + prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( + new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), + "tablet"); + ASSERT_NE(nullptr, prim); + EXPECT_EQ(321u, prim->value.data); + + Reference* actual_ref = + test::GetValue<Reference>(new_table.get(), "com.app.a:layout/abc"); + ASSERT_NE(nullptr, actual_ref); + AAPT_ASSERT_TRUE(actual_ref->name); + AAPT_ASSERT_TRUE(actual_ref->id); + EXPECT_EQ(expected_ref.name.value(), actual_ref->name.value()); + EXPECT_EQ(expected_ref.id.value(), actual_ref->id.value()); } TEST(TableProtoSerializer, SerializeFileHeader) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + ResourceFile f; + f.config = test::ParseConfigOrDie("hdpi-v9"); + f.name = test::ParseNameOrDie("com.app.a:layout/main"); + f.source.path = "res/layout-hdpi-v9/main.xml"; + f.exported_symbols.push_back( + SourcedResourceName{test::ParseNameOrDie("id/unchecked"), 23u}); + + const std::string expected_data1 = "123"; + const std::string expected_data2 = "1234"; + + std::string output_str; + { + std::unique_ptr<pb::CompiledFile> pb_file1 = SerializeCompiledFileToPb(f); + + f.name.entry = "__" + f.name.entry + "$0"; + std::unique_ptr<pb::CompiledFile> pb_file2 = SerializeCompiledFileToPb(f); + + StringOutputStream out_stream(&output_str); + CompiledFileOutputStream out_file_stream(&out_stream); + out_file_stream.WriteLittleEndian32(2); + out_file_stream.WriteCompiledFile(pb_file1.get()); + out_file_stream.WriteData(expected_data1.data(), expected_data1.size()); + out_file_stream.WriteCompiledFile(pb_file2.get()); + out_file_stream.WriteData(expected_data2.data(), expected_data2.size()); + ASSERT_FALSE(out_file_stream.HadError()); + } - ResourceFile f; - f.config = test::parseConfigOrDie("hdpi-v9"); - f.name = test::parseNameOrDie(u"@com.app.a:layout/main"); - f.source.path = "res/layout-hdpi-v9/main.xml"; - f.exportedSymbols.push_back(SourcedResourceName{ test::parseNameOrDie(u"@+id/unchecked"), 23u }); + CompiledFileInputStream in_file_stream(output_str.data(), output_str.size()); + uint32_t num_files = 0; + ASSERT_TRUE(in_file_stream.ReadLittleEndian32(&num_files)); + ASSERT_EQ(2u, num_files); - const std::string expectedData = "1234"; + // Read the first compiled file. - std::unique_ptr<pb::CompiledFile> pbFile = serializeCompiledFileToPb(f); + pb::CompiledFile new_pb_file; + ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_file)); - std::string outputStr; - { - google::protobuf::io::StringOutputStream outStream(&outputStr); - CompiledFileOutputStream outFileStream(&outStream, pbFile.get()); + std::unique_ptr<ResourceFile> file = DeserializeCompiledFileFromPb( + new_pb_file, Source("test"), context->GetDiagnostics()); + ASSERT_NE(nullptr, file); - ASSERT_TRUE(outFileStream.Write(expectedData.data(), expectedData.size())); - ASSERT_TRUE(outFileStream.Finish()); - } + uint64_t offset, len; + ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len)); - CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size()); - const pb::CompiledFile* newPbFile = inFileStream.CompiledFile(); - ASSERT_NE(nullptr, newPbFile); + std::string actual_data(output_str.data() + offset, len); + EXPECT_EQ(expected_data1, actual_data); - std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(*newPbFile, Source{ "test" }, - context->getDiagnostics()); - ASSERT_NE(nullptr, file); + // Expect the data to be aligned. + EXPECT_EQ(0u, offset & 0x03); - std::string actualData((const char*)inFileStream.data(), inFileStream.size()); - EXPECT_EQ(expectedData, actualData); - EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(inFileStream.data()) & 0x03); + ASSERT_EQ(1u, file->exported_symbols.size()); + EXPECT_EQ(test::ParseNameOrDie("id/unchecked"), + file->exported_symbols[0].name); - ASSERT_EQ(1u, file->exportedSymbols.size()); - EXPECT_EQ(test::parseNameOrDie(u"@+id/unchecked"), file->exportedSymbols[0].name); + // Read the second compiled file. + + ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_file)); + + file = DeserializeCompiledFileFromPb(new_pb_file, Source("test"), + context->GetDiagnostics()); + ASSERT_NE(nullptr, file); + + ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len)); + + actual_data = std::string(output_str.data() + offset, len); + EXPECT_EQ(expected_data2, actual_data); + + // Expect the data to be aligned. + EXPECT_EQ(0u, offset & 0x03); } TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) { - ResourceFile f; - std::unique_ptr<pb::CompiledFile> pbFile = serializeCompiledFileToPb(f); + ResourceFile f; + std::unique_ptr<pb::CompiledFile> pb_file = SerializeCompiledFileToPb(f); + + const std::string expected_data = "1234"; + + std::string output_str; + { + StringOutputStream out_stream(&output_str); + CompiledFileOutputStream out_file_stream(&out_stream); + out_file_stream.WriteLittleEndian32(1); + out_file_stream.WriteCompiledFile(pb_file.get()); + out_file_stream.WriteData(expected_data.data(), expected_data.size()); + ASSERT_FALSE(out_file_stream.HadError()); + } - const std::string expectedData = "1234"; + output_str[4] = 0xff; - std::string outputStr; - { - google::protobuf::io::StringOutputStream outStream(&outputStr); - CompiledFileOutputStream outFileStream(&outStream, pbFile.get()); + CompiledFileInputStream in_file_stream(output_str.data(), output_str.size()); - ASSERT_TRUE(outFileStream.Write(expectedData.data(), expectedData.size())); - ASSERT_TRUE(outFileStream.Finish()); - } + uint32_t num_files = 0; + EXPECT_TRUE(in_file_stream.ReadLittleEndian32(&num_files)); + EXPECT_EQ(1u, num_files); - outputStr[0] = 0xff; + pb::CompiledFile new_pb_file; + EXPECT_FALSE(in_file_stream.ReadCompiledFile(&new_pb_file)); - CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size()); - EXPECT_EQ(nullptr, inFileStream.CompiledFile()); - EXPECT_EQ(nullptr, inFileStream.data()); - EXPECT_EQ(0u, inFileStream.size()); + uint64_t offset, len; + EXPECT_FALSE(in_file_stream.ReadDataMetaData(&offset, &len)); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md new file mode 100644 index 000000000000..ac411b15ae83 --- /dev/null +++ b/tools/aapt2/readme.md @@ -0,0 +1,40 @@ +# Android Asset Packaging Tool 2.0 (AAPT2) release notes + +## Version 2.3 +### `aapt2` +- Support new `font` resource type. + +## Version 2.2 +### `aapt2 compile ...` +- Added support for inline complex XML resources. See + https://developer.android.com/guide/topics/resources/complex-xml-resources.html +### `aapt link ...` +- Duplicate resource filtering: removes duplicate resources in dominated configurations + that are always identical when selected at runtime. This can be disabled with + `--no-resource-deduping`. + +## Version 2.1 +### `aapt2 link ...` +- Configuration Split APK support: supports splitting resources that match a set of + configurations to a separate APK which can be loaded alongside the base APK on + API 21+ devices. This is done using the flag + `--split path/to/split.apk:<config1>[,<config2>,...]`. +- SDK version resource filtering: Resources with an SDK version qualifier that is unreachable + at runtime due to the minimum SDK level declared by the AndroidManifest.xml are stripped. + +## Version 2.0 +### `aapt2 compile ...` +- Pseudo-localization: generates pseudolocalized versions of default strings when the + `--pseudo-localize` option is specified. +- Legacy mode: treats some class of errors as warnings in order to be more compatible + with AAPT when `--legacy` is specified. +- Compile directory: treats the input file as a directory when `--dir` is + specified. This will emit a zip of compiled files, one for each file in the directory. + The directory must follow the Android resource directory structure + (res/values-[qualifiers]/file.ext). + +### `aapt2 link ...` +- Automatic attribute versioning: adds version qualifiers to resources that use attributes + introduced in a later SDK level. This can be disabled with `--no-auto-version`. +- Min SDK resource filtering: removes resources that can't possibly be selected at runtime due + to the application's minimum supported SDK level. diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp index 4bfdb1205e19..7aad86fa7257 100644 --- a/tools/aapt2/split/TableSplitter.cpp +++ b/tools/aapt2/split/TableSplitter.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "ConfigDescription.h" -#include "ResourceTable.h" #include "split/TableSplitter.h" #include <algorithm> @@ -23,243 +21,271 @@ #include <set> #include <unordered_map> #include <vector> +#include "android-base/logging.h" + +#include "ConfigDescription.h" +#include "ResourceTable.h" +#include "util/Util.h" namespace aapt { using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>; -using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>; +using ConfigDensityGroups = + std::map<ConfigDescription, std::vector<ResourceConfigValue*>>; -static ConfigDescription copyWithoutDensity(const ConfigDescription& config) { - ConfigDescription withoutDensity = config; - withoutDensity.density = 0; - return withoutDensity; +static ConfigDescription CopyWithoutDensity(const ConfigDescription& config) { + ConfigDescription without_density = config; + without_density.density = 0; + return without_density; } /** * Selects values that match exactly the constraints given. */ class SplitValueSelector { -public: - SplitValueSelector(const SplitConstraints& constraints) { - for (const ConfigDescription& config : constraints.configs) { - if (config.density == 0) { - mDensityIndependentConfigs.insert(config); - } else { - mDensityDependentConfigToDensityMap[copyWithoutDensity(config)] = config.density; - } - } + public: + explicit SplitValueSelector(const SplitConstraints& constraints) { + for (const ConfigDescription& config : constraints.configs) { + if (config.density == 0) { + density_independent_configs_.insert(config); + } else { + density_dependent_config_to_density_map_[CopyWithoutDensity(config)] = + config.density; + } } + } - std::vector<ResourceConfigValue*> selectValues(const ConfigDensityGroups& densityGroups, - ConfigClaimedMap* claimedValues) { - std::vector<ResourceConfigValue*> selected; + std::vector<ResourceConfigValue*> SelectValues( + const ConfigDensityGroups& density_groups, + ConfigClaimedMap* claimed_values) { + std::vector<ResourceConfigValue*> selected; - // Select the regular values. - for (auto& entry : *claimedValues) { - // Check if the entry has a density. - ResourceConfigValue* configValue = entry.first; - if (configValue->config.density == 0 && !entry.second) { - // This is still available. - if (mDensityIndependentConfigs.find(configValue->config) != - mDensityIndependentConfigs.end()) { - selected.push_back(configValue); + // Select the regular values. + for (auto& entry : *claimed_values) { + // Check if the entry has a density. + ResourceConfigValue* config_value = entry.first; + if (config_value->config.density == 0 && !entry.second) { + // This is still available. + if (density_independent_configs_.find(config_value->config) != + density_independent_configs_.end()) { + selected.push_back(config_value); - // Mark the entry as taken. - entry.second = true; - } - } + // Mark the entry as taken. + entry.second = true; } + } + } - // Now examine the densities - for (auto& entry : densityGroups) { - // We do not care if the value is claimed, since density values can be - // in multiple splits. - const ConfigDescription& config = entry.first; - const std::vector<ResourceConfigValue*>& relatedValues = entry.second; - - auto densityValueIter = mDensityDependentConfigToDensityMap.find(config); - if (densityValueIter != mDensityDependentConfigToDensityMap.end()) { - // Select the best one! - ConfigDescription targetDensity = config; - targetDensity.density = densityValueIter->second; - - ResourceConfigValue* bestValue = nullptr; - for (ResourceConfigValue* thisValue : relatedValues) { - if (!bestValue || - thisValue->config.isBetterThan(bestValue->config, &targetDensity)) { - bestValue = thisValue; - } + // Now examine the densities + for (auto& entry : density_groups) { + // We do not care if the value is claimed, since density values can be + // in multiple splits. + const ConfigDescription& config = entry.first; + const std::vector<ResourceConfigValue*>& related_values = entry.second; + auto density_value_iter = + density_dependent_config_to_density_map_.find(config); + if (density_value_iter != + density_dependent_config_to_density_map_.end()) { + // Select the best one! + ConfigDescription target_density = config; + target_density.density = density_value_iter->second; - // When we select one of these, they are all claimed such that the base - // doesn't include any anymore. - (*claimedValues)[thisValue] = true; - } - assert(bestValue); - selected.push_back(bestValue); - } + ResourceConfigValue* best_value = nullptr; + for (ResourceConfigValue* this_value : related_values) { + if (!best_value || + this_value->config.isBetterThan(best_value->config, + &target_density)) { + best_value = this_value; + } } - return selected; + CHECK(best_value != nullptr); + + // When we select one of these, they are all claimed such that the base + // doesn't include any anymore. + (*claimed_values)[best_value] = true; + selected.push_back(best_value); + } } + return selected; + } -private: - std::set<ConfigDescription> mDensityIndependentConfigs; - std::map<ConfigDescription, uint16_t> mDensityDependentConfigToDensityMap; + private: + DISALLOW_COPY_AND_ASSIGN(SplitValueSelector); + + std::set<ConfigDescription> density_independent_configs_; + std::map<ConfigDescription, uint16_t> + density_dependent_config_to_density_map_; }; /** - * Marking non-preferred densities as claimed will make sure the base doesn't include them, + * Marking non-preferred densities as claimed will make sure the base doesn't + * include them, * leaving only the preferred density behind. */ -static void markNonPreferredDensitiesAsClaimed(uint16_t preferredDensity, - const ConfigDensityGroups& densityGroups, - ConfigClaimedMap* configClaimedMap) { - for (auto& entry : densityGroups) { - const ConfigDescription& config = entry.first; - const std::vector<ResourceConfigValue*>& relatedValues = entry.second; +static void MarkNonPreferredDensitiesAsClaimed( + uint16_t preferred_density, const ConfigDensityGroups& density_groups, + ConfigClaimedMap* config_claimed_map) { + for (auto& entry : density_groups) { + const ConfigDescription& config = entry.first; + const std::vector<ResourceConfigValue*>& related_values = entry.second; - ConfigDescription targetDensity = config; - targetDensity.density = preferredDensity; - ResourceConfigValue* bestValue = nullptr; - for (ResourceConfigValue* thisValue : relatedValues) { - if (!bestValue) { - bestValue = thisValue; - } else if (thisValue->config.isBetterThan(bestValue->config, &targetDensity)) { - // Claim the previous value so that it is not included in the base. - (*configClaimedMap)[bestValue] = true; - bestValue = thisValue; - } else { - // Claim this value so that it is not included in the base. - (*configClaimedMap)[thisValue] = true; - } - } - assert(bestValue); + ConfigDescription target_density = config; + target_density.density = preferred_density; + ResourceConfigValue* best_value = nullptr; + for (ResourceConfigValue* this_value : related_values) { + if (!best_value) { + best_value = this_value; + } else if (this_value->config.isBetterThan(best_value->config, + &target_density)) { + // Claim the previous value so that it is not included in the base. + (*config_claimed_map)[best_value] = true; + best_value = this_value; + } else { + // Claim this value so that it is not included in the base. + (*config_claimed_map)[this_value] = true; + } } + CHECK(best_value != nullptr); + } } - -bool TableSplitter::verifySplitConstraints(IAaptContext* context) { - bool error = false; - for (size_t i = 0; i < mSplitConstraints.size(); i++) { - for (size_t j = i + 1; j < mSplitConstraints.size(); j++) { - for (const ConfigDescription& config : mSplitConstraints[i].configs) { - if (mSplitConstraints[j].configs.find(config) != - mSplitConstraints[j].configs.end()) { - context->getDiagnostics()->error(DiagMessage() << "config '" << config - << "' appears in multiple splits, " - << "target split ambiguous"); - error = true; - } - } +bool TableSplitter::VerifySplitConstraints(IAaptContext* context) { + bool error = false; + for (size_t i = 0; i < split_constraints_.size(); i++) { + for (size_t j = i + 1; j < split_constraints_.size(); j++) { + for (const ConfigDescription& config : split_constraints_[i].configs) { + if (split_constraints_[j].configs.find(config) != + split_constraints_[j].configs.end()) { + context->GetDiagnostics()->Error(DiagMessage() + << "config '" << config + << "' appears in multiple splits, " + << "target split ambiguous"); + error = true; } + } } - return !error; + } + return !error; } -void TableSplitter::splitTable(ResourceTable* originalTable) { - const size_t splitCount = mSplitConstraints.size(); - for (auto& pkg : originalTable->packages) { - // Initialize all packages for splits. - for (size_t idx = 0; idx < splitCount; idx++) { - ResourceTable* splitTable = mSplits[idx].get(); - splitTable->createPackage(pkg->name, pkg->id); - } - - for (auto& type : pkg->types) { - if (type->type == ResourceType::kMipmap) { - // Always keep mipmaps. - continue; - } +void TableSplitter::SplitTable(ResourceTable* original_table) { + const size_t split_count = split_constraints_.size(); + for (auto& pkg : original_table->packages) { + // Initialize all packages for splits. + for (size_t idx = 0; idx < split_count; idx++) { + ResourceTable* split_table = splits_[idx].get(); + split_table->CreatePackage(pkg->name, pkg->id); + } - for (auto& entry : type->entries) { - if (mConfigFilter) { - // First eliminate any resource that we definitely don't want. - for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) { - if (!mConfigFilter->match(configValue->config)) { - // null out the entry. We will clean up and remove nulls at the end - // for performance reasons. - configValue.reset(); - } - } - } + for (auto& type : pkg->types) { + if (type->type == ResourceType::kMipmap) { + // Always keep mipmaps. + continue; + } - // Organize the values into two separate buckets. Those that are density-dependent - // and those that are density-independent. - // One density technically matches all density, it's just that some densities - // match better. So we need to be aware of the full set of densities to make this - // decision. - ConfigDensityGroups densityGroups; - ConfigClaimedMap configClaimedMap; - for (const std::unique_ptr<ResourceConfigValue>& configValue : entry->values) { - if (configValue) { - configClaimedMap[configValue.get()] = false; + for (auto& entry : type->entries) { + if (options_.config_filter) { + // First eliminate any resource that we definitely don't want. + for (std::unique_ptr<ResourceConfigValue>& config_value : + entry->values) { + if (!options_.config_filter->Match(config_value->config)) { + // null out the entry. We will clean up and remove nulls at the + // end for performance reasons. + config_value.reset(); + } + } + } - if (configValue->config.density != 0) { - // Create a bucket for this density-dependent config. - densityGroups[copyWithoutDensity(configValue->config)] - .push_back(configValue.get()); - } - } - } + // Organize the values into two separate buckets. Those that are + // density-dependent + // and those that are density-independent. + // One density technically matches all density, it's just that some + // densities + // match better. So we need to be aware of the full set of densities to + // make this + // decision. + ConfigDensityGroups density_groups; + ConfigClaimedMap config_claimed_map; + for (const std::unique_ptr<ResourceConfigValue>& config_value : + entry->values) { + if (config_value) { + config_claimed_map[config_value.get()] = false; - // First we check all the splits. If it doesn't match one of the splits, we - // leave it in the base. - for (size_t idx = 0; idx < splitCount; idx++) { - const SplitConstraints& splitConstraint = mSplitConstraints[idx]; - ResourceTable* splitTable = mSplits[idx].get(); + if (config_value->config.density != 0) { + // Create a bucket for this density-dependent config. + density_groups[CopyWithoutDensity(config_value->config)] + .push_back(config_value.get()); + } + } + } - // Select the values we want from this entry for this split. - SplitValueSelector selector(splitConstraint); - std::vector<ResourceConfigValue*> selectedValues = - selector.selectValues(densityGroups, &configClaimedMap); + // First we check all the splits. If it doesn't match one of the splits, + // we + // leave it in the base. + for (size_t idx = 0; idx < split_count; idx++) { + const SplitConstraints& split_constraint = split_constraints_[idx]; + ResourceTable* split_table = splits_[idx].get(); - // No need to do any work if we selected nothing. - if (!selectedValues.empty()) { - // Create the same resource structure in the split. We do this lazily - // because we might not have actual values for each type/entry. - ResourceTablePackage* splitPkg = splitTable->findPackage(pkg->name); - ResourceTableType* splitType = splitPkg->findOrCreateType(type->type); - if (!splitType->id) { - splitType->id = type->id; - splitType->symbolStatus = type->symbolStatus; - } + // Select the values we want from this entry for this split. + SplitValueSelector selector(split_constraint); + std::vector<ResourceConfigValue*> selected_values = + selector.SelectValues(density_groups, &config_claimed_map); - ResourceEntry* splitEntry = splitType->findOrCreateEntry(entry->name); - if (!splitEntry->id) { - splitEntry->id = entry->id; - splitEntry->symbolStatus = entry->symbolStatus; - } + // No need to do any work if we selected nothing. + if (!selected_values.empty()) { + // Create the same resource structure in the split. We do this + // lazily because we might not have actual values for each + // type/entry. + ResourceTablePackage* split_pkg = + split_table->FindPackage(pkg->name); + ResourceTableType* split_type = + split_pkg->FindOrCreateType(type->type); + if (!split_type->id) { + split_type->id = type->id; + split_type->symbol_status = type->symbol_status; + } - // Copy the selected values into the new Split Entry. - for (ResourceConfigValue* configValue : selectedValues) { - ResourceConfigValue* newConfigValue = splitEntry->findOrCreateValue( - configValue->config, configValue->product); - newConfigValue->value = std::unique_ptr<Value>( - configValue->value->clone(&splitTable->stringPool)); - } - } - } + ResourceEntry* split_entry = + split_type->FindOrCreateEntry(entry->name); + if (!split_entry->id) { + split_entry->id = entry->id; + split_entry->symbol_status = entry->symbol_status; + } - if (mPreferredDensity) { - markNonPreferredDensitiesAsClaimed(mPreferredDensity.value(), - densityGroups, - &configClaimedMap); - } + // Copy the selected values into the new Split Entry. + for (ResourceConfigValue* config_value : selected_values) { + ResourceConfigValue* new_config_value = + split_entry->FindOrCreateValue(config_value->config, + config_value->product); + new_config_value->value = std::unique_ptr<Value>( + config_value->value->Clone(&split_table->string_pool)); + } + } + } - // All splits are handled, now check to see what wasn't claimed and remove - // whatever exists in other splits. - for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) { - if (configValue && configClaimedMap[configValue.get()]) { - // Claimed, remove from base. - configValue.reset(); - } - } + if (options_.preferred_density) { + MarkNonPreferredDensitiesAsClaimed(options_.preferred_density.value(), + density_groups, + &config_claimed_map); + } - // Now erase all nullptrs. - entry->values.erase( - std::remove(entry->values.begin(), entry->values.end(), nullptr), - entry->values.end()); - } + // All splits are handled, now check to see what wasn't claimed and + // remove + // whatever exists in other splits. + for (std::unique_ptr<ResourceConfigValue>& config_value : + entry->values) { + if (config_value && config_claimed_map[config_value.get()]) { + // Claimed, remove from base. + config_value.reset(); + } } + + // Now erase all nullptrs. + entry->values.erase( + std::remove(entry->values.begin(), entry->values.end(), nullptr), + entry->values.end()); + } } + } } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/split/TableSplitter.h b/tools/aapt2/split/TableSplitter.h index 15e0764c4259..1ae327120796 100644 --- a/tools/aapt2/split/TableSplitter.h +++ b/tools/aapt2/split/TableSplitter.h @@ -17,62 +17,58 @@ #ifndef AAPT_SPLIT_TABLESPLITTER_H #define AAPT_SPLIT_TABLESPLITTER_H +#include <set> +#include <vector> +#include "android-base/macros.h" + #include "ConfigDescription.h" #include "ResourceTable.h" #include "filter/ConfigFilter.h" #include "process/IResourceTableConsumer.h" -#include <android-base/macros.h> -#include <set> -#include <vector> - namespace aapt { struct SplitConstraints { - std::set<ConfigDescription> configs; + std::set<ConfigDescription> configs; }; struct TableSplitterOptions { - /** - * The preferred density to keep in the table, stripping out all others. - */ - Maybe<uint16_t> preferredDensity; + /** + * The preferred density to keep in the table, stripping out all others. + */ + Maybe<uint16_t> preferred_density; - /** - * Configuration filter that determines which resource configuration values end up in - * the final table. - */ - IConfigFilter* configFilter = nullptr; + /** + * Configuration filter that determines which resource configuration values + * end up in + * the final table. + */ + IConfigFilter* config_filter = nullptr; }; class TableSplitter { -public: - TableSplitter(const std::vector<SplitConstraints>& splits, - const TableSplitterOptions& options) : - mSplitConstraints(splits), mPreferredDensity(options.preferredDensity), - mConfigFilter(options.configFilter) { - for (size_t i = 0; i < mSplitConstraints.size(); i++) { - mSplits.push_back(util::make_unique<ResourceTable>()); - } + public: + TableSplitter(const std::vector<SplitConstraints>& splits, + const TableSplitterOptions& options) + : split_constraints_(splits), options_(options) { + for (size_t i = 0; i < split_constraints_.size(); i++) { + splits_.push_back(util::make_unique<ResourceTable>()); } + } - bool verifySplitConstraints(IAaptContext* context); + bool VerifySplitConstraints(IAaptContext* context); - void splitTable(ResourceTable* originalTable); + void SplitTable(ResourceTable* original_table); - const std::vector<std::unique_ptr<ResourceTable>>& getSplits() { - return mSplits; - } + std::vector<std::unique_ptr<ResourceTable>>& splits() { return splits_; } -private: - std::vector<SplitConstraints> mSplitConstraints; - std::vector<std::unique_ptr<ResourceTable>> mSplits; - Maybe<uint16_t> mPreferredDensity; - IConfigFilter* mConfigFilter; + private: + std::vector<SplitConstraints> split_constraints_; + std::vector<std::unique_ptr<ResourceTable>> splits_; + TableSplitterOptions options_; - DISALLOW_COPY_AND_ASSIGN(TableSplitter); + DISALLOW_COPY_AND_ASSIGN(TableSplitter); }; - } #endif /* AAPT_SPLIT_TABLESPLITTER_H */ diff --git a/tools/aapt2/split/TableSplitter_test.cpp b/tools/aapt2/split/TableSplitter_test.cpp index 74ca32e04a30..088dac374458 100644 --- a/tools/aapt2/split/TableSplitter_test.cpp +++ b/tools/aapt2/split/TableSplitter_test.cpp @@ -15,93 +15,192 @@ */ #include "split/TableSplitter.h" -#include "test/Builders.h" -#include "test/Common.h" -#include <gtest/gtest.h> +#include "test/Test.h" namespace aapt { TEST(TableSplitterTest, NoSplitPreferredDensity) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addFileReference(u"@android:drawable/icon", u"res/drawable-mdpi/icon.png", - test::parseConfigOrDie("mdpi")) - .addFileReference(u"@android:drawable/icon", u"res/drawable-hdpi/icon.png", - test::parseConfigOrDie("hdpi")) - .addFileReference(u"@android:drawable/icon", u"res/drawable-xhdpi/icon.png", - test::parseConfigOrDie("xhdpi")) - .addFileReference(u"@android:drawable/icon", u"res/drawable-xxhdpi/icon.png", - test::parseConfigOrDie("xxhdpi")) - .addSimple(u"@android:string/one", {}) - .build(); - - TableSplitterOptions options; - options.preferredDensity = ConfigDescription::DENSITY_XHIGH; - TableSplitter splitter({}, options); - splitter.splitTable(table.get()); - - EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(), - u"@android:drawable/icon", - test::parseConfigOrDie("mdpi"))); - EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(), - u"@android:drawable/icon", - test::parseConfigOrDie("hdpi"))); - EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(table.get(), - u"@android:drawable/icon", - test::parseConfigOrDie("xhdpi"))); - EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(), - u"@android:drawable/icon", - test::parseConfigOrDie("xxhdpi"))); - EXPECT_NE(nullptr, test::getValue<Id>(table.get(), u"@android:string/one")); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddFileReference("android:drawable/icon", + "res/drawable-mdpi/icon.png", + test::ParseConfigOrDie("mdpi")) + .AddFileReference("android:drawable/icon", + "res/drawable-hdpi/icon.png", + test::ParseConfigOrDie("hdpi")) + .AddFileReference("android:drawable/icon", + "res/drawable-xhdpi/icon.png", + test::ParseConfigOrDie("xhdpi")) + .AddFileReference("android:drawable/icon", + "res/drawable-xxhdpi/icon.png", + test::ParseConfigOrDie("xxhdpi")) + .AddSimple("android:string/one") + .Build(); + + TableSplitterOptions options; + options.preferred_density = ConfigDescription::DENSITY_XHIGH; + TableSplitter splitter({}, options); + splitter.SplitTable(table.get()); + + EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>( + table.get(), "android:drawable/icon", + test::ParseConfigOrDie("mdpi"))); + EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>( + table.get(), "android:drawable/icon", + test::ParseConfigOrDie("hdpi"))); + EXPECT_NE(nullptr, test::GetValueForConfig<FileReference>( + table.get(), "android:drawable/icon", + test::ParseConfigOrDie("xhdpi"))); + EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>( + table.get(), "android:drawable/icon", + test::ParseConfigOrDie("xxhdpi"))); + EXPECT_NE(nullptr, test::GetValue<Id>(table.get(), "android:string/one")); +} + +TEST(TableSplitterTest, SplitTableByDensity) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddFileReference("android:drawable/foo", "res/drawable-mdpi/foo.png", + test::ParseConfigOrDie("mdpi")) + .AddFileReference("android:drawable/foo", "res/drawable-hdpi/foo.png", + test::ParseConfigOrDie("hdpi")) + .AddFileReference("android:drawable/foo", + "res/drawable-xhdpi/foo.png", + test::ParseConfigOrDie("xhdpi")) + .AddFileReference("android:drawable/foo", + "res/drawable-xxhdpi/foo.png", + test::ParseConfigOrDie("xxhdpi")) + .Build(); + + std::vector<SplitConstraints> constraints; + constraints.push_back(SplitConstraints{{test::ParseConfigOrDie("mdpi")}}); + constraints.push_back(SplitConstraints{{test::ParseConfigOrDie("hdpi")}}); + constraints.push_back(SplitConstraints{{test::ParseConfigOrDie("xhdpi")}}); + + TableSplitter splitter(constraints, TableSplitterOptions{}); + splitter.SplitTable(table.get()); + + ASSERT_EQ(3u, splitter.splits().size()); + + ResourceTable* split_one = splitter.splits()[0].get(); + ResourceTable* split_two = splitter.splits()[1].get(); + ResourceTable* split_three = splitter.splits()[2].get(); + + // Just xxhdpi should be in the base. + EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>( + table.get(), "android:drawable/foo", + test::ParseConfigOrDie("mdpi"))); + EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>( + table.get(), "android:drawable/foo", + test::ParseConfigOrDie("hdpi"))); + EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>( + table.get(), "android:drawable/foo", + test::ParseConfigOrDie("xhdpi"))); + EXPECT_NE(nullptr, test::GetValueForConfig<FileReference>( + table.get(), "android:drawable/foo", + test::ParseConfigOrDie("xxhdpi"))); + + // Each split should have one and only one drawable. + EXPECT_NE(nullptr, test::GetValueForConfig<FileReference>( + split_one, "android:drawable/foo", + test::ParseConfigOrDie("mdpi"))); + EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>( + split_one, "android:drawable/foo", + test::ParseConfigOrDie("hdpi"))); + EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>( + split_one, "android:drawable/foo", + test::ParseConfigOrDie("xhdpi"))); + EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>( + split_one, "android:drawable/foo", + test::ParseConfigOrDie("xxhdpi"))); + + EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>( + split_two, "android:drawable/foo", + test::ParseConfigOrDie("mdpi"))); + EXPECT_NE(nullptr, test::GetValueForConfig<FileReference>( + split_two, "android:drawable/foo", + test::ParseConfigOrDie("hdpi"))); + EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>( + split_two, "android:drawable/foo", + test::ParseConfigOrDie("xhdpi"))); + EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>( + split_two, "android:drawable/foo", + test::ParseConfigOrDie("xxhdpi"))); + + EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>( + split_three, "android:drawable/foo", + test::ParseConfigOrDie("mdpi"))); + EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>( + split_three, "android:drawable/foo", + test::ParseConfigOrDie("hdpi"))); + EXPECT_NE(nullptr, test::GetValueForConfig<FileReference>( + split_three, "android:drawable/foo", + test::ParseConfigOrDie("xhdpi"))); + EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>( + split_three, "android:drawable/foo", + test::ParseConfigOrDie("xxhdpi"))); } TEST(TableSplitterTest, SplitTableByConfigAndDensity) { - ResourceTable table; - - const ResourceName foo = test::parseNameOrDie(u"@android:string/foo"); - ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-hdpi"), {}, - util::make_unique<Id>(), - test::getDiagnostics())); - ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-xhdpi"), {}, - util::make_unique<Id>(), - test::getDiagnostics())); - ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-xxhdpi"), {}, - util::make_unique<Id>(), - test::getDiagnostics())); - - std::vector<SplitConstraints> constraints; - constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("land-mdpi") } }); - constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("land-xhdpi") } }); - - TableSplitter splitter(constraints, TableSplitterOptions{}); - splitter.splitTable(&table); - - ASSERT_EQ(2u, splitter.getSplits().size()); - - ResourceTable* splitOne = splitter.getSplits()[0].get(); - ResourceTable* splitTwo = splitter.getSplits()[1].get(); - - // Since a split was defined, all densities should be gone from base. - EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, u"@android:string/foo", - test::parseConfigOrDie("land-hdpi"))); - EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, u"@android:string/foo", - test::parseConfigOrDie("land-xhdpi"))); - EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, u"@android:string/foo", - test::parseConfigOrDie("land-xxhdpi"))); - - EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitOne, u"@android:string/foo", - test::parseConfigOrDie("land-hdpi"))); - EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, u"@android:string/foo", - test::parseConfigOrDie("land-xhdpi"))); - EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, u"@android:string/foo", - test::parseConfigOrDie("land-xxhdpi"))); - - EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, u"@android:string/foo", - test::parseConfigOrDie("land-hdpi"))); - EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitTwo, u"@android:string/foo", - test::parseConfigOrDie("land-xhdpi"))); - EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, u"@android:string/foo", - test::parseConfigOrDie("land-xxhdpi"))); + ResourceTable table; + + const ResourceName foo = test::ParseNameOrDie("android:string/foo"); + ASSERT_TRUE(table.AddResource(foo, test::ParseConfigOrDie("land-hdpi"), {}, + util::make_unique<Id>(), + test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(foo, test::ParseConfigOrDie("land-xhdpi"), {}, + util::make_unique<Id>(), + test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(foo, test::ParseConfigOrDie("land-xxhdpi"), {}, + util::make_unique<Id>(), + test::GetDiagnostics())); + + std::vector<SplitConstraints> constraints; + constraints.push_back( + SplitConstraints{{test::ParseConfigOrDie("land-mdpi")}}); + constraints.push_back( + SplitConstraints{{test::ParseConfigOrDie("land-xhdpi")}}); + + TableSplitter splitter(constraints, TableSplitterOptions{}); + splitter.SplitTable(&table); + + ASSERT_EQ(2u, splitter.splits().size()); + + ResourceTable* split_one = splitter.splits()[0].get(); + ResourceTable* split_two = splitter.splits()[1].get(); + + // All but the xxhdpi resource should be gone, since there were closer matches + // in land-xhdpi. + EXPECT_EQ(nullptr, + test::GetValueForConfig<Id>(&table, "android:string/foo", + test::ParseConfigOrDie("land-hdpi"))); + EXPECT_EQ(nullptr, + test::GetValueForConfig<Id>(&table, "android:string/foo", + test::ParseConfigOrDie("land-xhdpi"))); + EXPECT_NE(nullptr, + test::GetValueForConfig<Id>(&table, "android:string/foo", + test::ParseConfigOrDie("land-xxhdpi"))); + + EXPECT_NE(nullptr, + test::GetValueForConfig<Id>(split_one, "android:string/foo", + test::ParseConfigOrDie("land-hdpi"))); + EXPECT_EQ(nullptr, + test::GetValueForConfig<Id>(split_one, "android:string/foo", + test::ParseConfigOrDie("land-xhdpi"))); + EXPECT_EQ(nullptr, + test::GetValueForConfig<Id>(split_one, "android:string/foo", + test::ParseConfigOrDie("land-xxhdpi"))); + + EXPECT_EQ(nullptr, + test::GetValueForConfig<Id>(split_two, "android:string/foo", + test::ParseConfigOrDie("land-hdpi"))); + EXPECT_NE(nullptr, + test::GetValueForConfig<Id>(split_two, "android:string/foo", + test::ParseConfigOrDie("land-xhdpi"))); + EXPECT_EQ(nullptr, + test::GetValueForConfig<Id>(split_two, "android:string/foo", + test::ParseConfigOrDie("land-xxhdpi"))); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index 8eb4bc88168d..9377306dc1b6 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -17,240 +17,269 @@ #ifndef AAPT_TEST_BUILDERS_H #define AAPT_TEST_BUILDERS_H +#include <memory> + +#include "android-base/logging.h" +#include "android-base/macros.h" + #include "ResourceTable.h" #include "ResourceValues.h" #include "test/Common.h" #include "util/Util.h" #include "xml/XmlDom.h" -#include <memory> - namespace aapt { namespace test { class ResourceTableBuilder { -private: - DummyDiagnosticsImpl mDiagnostics; - std::unique_ptr<ResourceTable> mTable = util::make_unique<ResourceTable>(); - -public: - ResourceTableBuilder() = default; - - StringPool* getStringPool() { - return &mTable->stringPool; - } - - ResourceTableBuilder& setPackageId(const StringPiece16& packageName, uint8_t id) { - ResourceTablePackage* package = mTable->createPackage(packageName, id); - assert(package); - return *this; - } - - ResourceTableBuilder& addSimple(const StringPiece16& name, const ResourceId id = {}) { - return addValue(name, id, util::make_unique<Id>()); - } - - ResourceTableBuilder& addReference(const StringPiece16& name, const StringPiece16& ref) { - return addReference(name, {}, ref); - } - - ResourceTableBuilder& addReference(const StringPiece16& name, const ResourceId id, - const StringPiece16& ref) { - return addValue(name, id, util::make_unique<Reference>(parseNameOrDie(ref))); - } - - ResourceTableBuilder& addString(const StringPiece16& name, const StringPiece16& str) { - return addString(name, {}, str); - } - - ResourceTableBuilder& addString(const StringPiece16& name, const ResourceId id, - const StringPiece16& str) { - return addValue(name, id, util::make_unique<String>(mTable->stringPool.makeRef(str))); - } - - ResourceTableBuilder& addString(const StringPiece16& name, const ResourceId id, - const ConfigDescription& config, const StringPiece16& str) { - return addValue(name, id, config, - util::make_unique<String>(mTable->stringPool.makeRef(str))); - } - - ResourceTableBuilder& addFileReference(const StringPiece16& name, const StringPiece16& path) { - return addFileReference(name, {}, path); - } - - ResourceTableBuilder& addFileReference(const StringPiece16& name, const ResourceId id, - const StringPiece16& path) { - return addValue(name, id, - util::make_unique<FileReference>(mTable->stringPool.makeRef(path))); - } - - ResourceTableBuilder& addFileReference(const StringPiece16& name, const StringPiece16& path, - const ConfigDescription& config) { - return addValue(name, {}, config, - util::make_unique<FileReference>(mTable->stringPool.makeRef(path))); - } - - ResourceTableBuilder& addValue(const StringPiece16& name, - std::unique_ptr<Value> value) { - return addValue(name, {}, std::move(value)); - } - - ResourceTableBuilder& addValue(const StringPiece16& name, const ResourceId id, - std::unique_ptr<Value> value) { - return addValue(name, id, {}, std::move(value)); - } - - ResourceTableBuilder& addValue(const StringPiece16& name, const ResourceId id, - const ConfigDescription& config, - std::unique_ptr<Value> value) { - ResourceName resName = parseNameOrDie(name); - bool result = mTable->addResourceAllowMangled(resName, id, config, std::string(), - std::move(value), &mDiagnostics); - assert(result); - return *this; - } - - ResourceTableBuilder& setSymbolState(const StringPiece16& name, ResourceId id, - SymbolState state) { - ResourceName resName = parseNameOrDie(name); - Symbol symbol; - symbol.state = state; - bool result = mTable->setSymbolStateAllowMangled(resName, id, symbol, &mDiagnostics); - assert(result); - return *this; - } - - std::unique_ptr<ResourceTable> build() { - return std::move(mTable); - } + public: + ResourceTableBuilder() = default; + + StringPool* string_pool() { return &table_->string_pool; } + + ResourceTableBuilder& SetPackageId(const StringPiece& package_name, + uint8_t id) { + ResourceTablePackage* package = table_->CreatePackage(package_name, id); + CHECK(package != nullptr); + return *this; + } + + ResourceTableBuilder& AddSimple(const StringPiece& name, + const ResourceId& id = {}) { + return AddValue(name, id, util::make_unique<Id>()); + } + + ResourceTableBuilder& AddSimple(const StringPiece& name, + const ConfigDescription& config, + const ResourceId& id = {}) { + return AddValue(name, config, id, util::make_unique<Id>()); + } + + ResourceTableBuilder& AddReference(const StringPiece& name, + const StringPiece& ref) { + return AddReference(name, {}, ref); + } + + ResourceTableBuilder& AddReference(const StringPiece& name, + const ResourceId& id, + const StringPiece& ref) { + return AddValue(name, id, + util::make_unique<Reference>(ParseNameOrDie(ref))); + } + + ResourceTableBuilder& AddString(const StringPiece& name, + const StringPiece& str) { + return AddString(name, {}, str); + } + + ResourceTableBuilder& AddString(const StringPiece& name, const ResourceId& id, + const StringPiece& str) { + return AddValue( + name, id, util::make_unique<String>(table_->string_pool.MakeRef(str))); + } + + ResourceTableBuilder& AddString(const StringPiece& name, const ResourceId& id, + const ConfigDescription& config, + const StringPiece& str) { + return AddValue(name, config, id, util::make_unique<String>( + table_->string_pool.MakeRef(str))); + } + + ResourceTableBuilder& AddFileReference(const StringPiece& name, + const StringPiece& path) { + return AddFileReference(name, {}, path); + } + + ResourceTableBuilder& AddFileReference(const StringPiece& name, + const ResourceId& id, + const StringPiece& path) { + return AddValue(name, id, util::make_unique<FileReference>( + table_->string_pool.MakeRef(path))); + } + + ResourceTableBuilder& AddFileReference(const StringPiece& name, + const StringPiece& path, + const ConfigDescription& config) { + return AddValue(name, config, {}, util::make_unique<FileReference>( + table_->string_pool.MakeRef(path))); + } + + ResourceTableBuilder& AddValue(const StringPiece& name, + std::unique_ptr<Value> value) { + return AddValue(name, {}, std::move(value)); + } + + ResourceTableBuilder& AddValue(const StringPiece& name, const ResourceId& id, + std::unique_ptr<Value> value) { + return AddValue(name, {}, id, std::move(value)); + } + + ResourceTableBuilder& AddValue(const StringPiece& name, + const ConfigDescription& config, + const ResourceId& id, + std::unique_ptr<Value> value) { + ResourceName res_name = ParseNameOrDie(name); + CHECK(table_->AddResourceAllowMangled(res_name, id, config, {}, + std::move(value), &diagnostics_)); + return *this; + } + + ResourceTableBuilder& SetSymbolState(const StringPiece& name, + const ResourceId& id, + SymbolState state) { + ResourceName res_name = ParseNameOrDie(name); + Symbol symbol; + symbol.state = state; + CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol, + &diagnostics_)); + return *this; + } + + std::unique_ptr<ResourceTable> Build() { return std::move(table_); } + + private: + DISALLOW_COPY_AND_ASSIGN(ResourceTableBuilder); + + DummyDiagnosticsImpl diagnostics_; + std::unique_ptr<ResourceTable> table_ = util::make_unique<ResourceTable>(); }; -inline std::unique_ptr<Reference> buildReference(const StringPiece16& ref, - Maybe<ResourceId> id = {}) { - std::unique_ptr<Reference> reference = util::make_unique<Reference>(parseNameOrDie(ref)); - reference->id = id; - return reference; +inline std::unique_ptr<Reference> BuildReference( + const StringPiece& ref, const Maybe<ResourceId>& id = {}) { + std::unique_ptr<Reference> reference = + util::make_unique<Reference>(ParseNameOrDie(ref)); + reference->id = id; + return reference; } -inline std::unique_ptr<BinaryPrimitive> buildPrimitive(uint8_t type, uint32_t data) { - android::Res_value value = {}; - value.size = sizeof(value); - value.dataType = type; - value.data = data; - return util::make_unique<BinaryPrimitive>(value); +inline std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, + uint32_t data) { + android::Res_value value = {}; + value.size = sizeof(value); + value.dataType = type; + value.data = data; + return util::make_unique<BinaryPrimitive>(value); } template <typename T> class ValueBuilder { -private: - std::unique_ptr<Value> mValue; - -public: - template <typename... Args> - ValueBuilder(Args&&... args) : mValue(new T{ std::forward<Args>(args)... }) { - } - - template <typename... Args> - ValueBuilder& setSource(Args&&... args) { - mValue->setSource(Source{ std::forward<Args>(args)... }); - return *this; - } - - ValueBuilder& setComment(const StringPiece16& str) { - mValue->setComment(str); - return *this; - } - - std::unique_ptr<Value> build() { - return std::move(mValue); - } + public: + template <typename... Args> + explicit ValueBuilder(Args&&... args) + : value_(new T{std::forward<Args>(args)...}) {} + + template <typename... Args> + ValueBuilder& SetSource(Args&&... args) { + value_->SetSource(Source{std::forward<Args>(args)...}); + return *this; + } + + ValueBuilder& SetComment(const StringPiece& str) { + value_->SetComment(str); + return *this; + } + + std::unique_ptr<Value> Build() { return std::move(value_); } + + private: + DISALLOW_COPY_AND_ASSIGN(ValueBuilder); + + std::unique_ptr<Value> value_; }; class AttributeBuilder { -private: - std::unique_ptr<Attribute> mAttr; - -public: - AttributeBuilder(bool weak = false) : mAttr(util::make_unique<Attribute>(weak)) { - mAttr->typeMask = android::ResTable_map::TYPE_ANY; - } - - AttributeBuilder& setTypeMask(uint32_t typeMask) { - mAttr->typeMask = typeMask; - return *this; - } - - AttributeBuilder& addItem(const StringPiece16& name, uint32_t value) { - mAttr->symbols.push_back(Attribute::Symbol{ - Reference(ResourceName{ {}, ResourceType::kId, name.toString()}), - value}); - return *this; - } - - std::unique_ptr<Attribute> build() { - return std::move(mAttr); - } + public: + explicit AttributeBuilder(bool weak = false) + : attr_(util::make_unique<Attribute>(weak)) { + attr_->type_mask = android::ResTable_map::TYPE_ANY; + } + + AttributeBuilder& SetTypeMask(uint32_t typeMask) { + attr_->type_mask = typeMask; + return *this; + } + + AttributeBuilder& AddItem(const StringPiece& name, uint32_t value) { + attr_->symbols.push_back(Attribute::Symbol{ + Reference(ResourceName({}, ResourceType::kId, name)), value}); + return *this; + } + + std::unique_ptr<Attribute> Build() { return std::move(attr_); } + + private: + DISALLOW_COPY_AND_ASSIGN(AttributeBuilder); + + std::unique_ptr<Attribute> attr_; }; class StyleBuilder { -private: - std::unique_ptr<Style> mStyle = util::make_unique<Style>(); - -public: - StyleBuilder& setParent(const StringPiece16& str) { - mStyle->parent = Reference(parseNameOrDie(str)); - return *this; - } - - StyleBuilder& addItem(const StringPiece16& str, std::unique_ptr<Item> value) { - mStyle->entries.push_back(Style::Entry{ Reference(parseNameOrDie(str)), std::move(value) }); - return *this; - } - - StyleBuilder& addItem(const StringPiece16& str, ResourceId id, std::unique_ptr<Item> value) { - addItem(str, std::move(value)); - mStyle->entries.back().key.id = id; - return *this; - } - - std::unique_ptr<Style> build() { - return std::move(mStyle); - } + public: + StyleBuilder() = default; + + StyleBuilder& SetParent(const StringPiece& str) { + style_->parent = Reference(ParseNameOrDie(str)); + return *this; + } + + StyleBuilder& AddItem(const StringPiece& str, std::unique_ptr<Item> value) { + style_->entries.push_back( + Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)}); + return *this; + } + + StyleBuilder& AddItem(const StringPiece& str, const ResourceId& id, + std::unique_ptr<Item> value) { + AddItem(str, std::move(value)); + style_->entries.back().key.id = id; + return *this; + } + + std::unique_ptr<Style> Build() { return std::move(style_); } + + private: + DISALLOW_COPY_AND_ASSIGN(StyleBuilder); + + std::unique_ptr<Style> style_ = util::make_unique<Style>(); }; class StyleableBuilder { -private: - std::unique_ptr<Styleable> mStyleable = util::make_unique<Styleable>(); - -public: - StyleableBuilder& addItem(const StringPiece16& str, Maybe<ResourceId> id = {}) { - mStyleable->entries.push_back(Reference(parseNameOrDie(str))); - mStyleable->entries.back().id = id; - return *this; - } - - std::unique_ptr<Styleable> build() { - return std::move(mStyleable); - } + public: + StyleableBuilder() = default; + + StyleableBuilder& AddItem(const StringPiece& str, + const Maybe<ResourceId>& id = {}) { + styleable_->entries.push_back(Reference(ParseNameOrDie(str))); + styleable_->entries.back().id = id; + return *this; + } + + std::unique_ptr<Styleable> Build() { return std::move(styleable_); } + + private: + DISALLOW_COPY_AND_ASSIGN(StyleableBuilder); + + std::unique_ptr<Styleable> styleable_ = util::make_unique<Styleable>(); }; -inline std::unique_ptr<xml::XmlResource> buildXmlDom(const StringPiece& str) { - std::stringstream in; - in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str; - StdErrDiagnostics diag; - std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, Source("test.xml")); - assert(doc); - return doc; +inline std::unique_ptr<xml::XmlResource> BuildXmlDom(const StringPiece& str) { + std::stringstream in; + in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str; + StdErrDiagnostics diag; + std::unique_ptr<xml::XmlResource> doc = + xml::Inflate(&in, &diag, Source("test.xml")); + CHECK(doc != nullptr) << "failed to parse inline XML string"; + return doc; } -inline std::unique_ptr<xml::XmlResource> buildXmlDomForPackageName(IAaptContext* context, - const StringPiece& str) { - std::unique_ptr<xml::XmlResource> doc = buildXmlDom(str); - doc->file.name.package = context->getCompilationPackage(); - return doc; +inline std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName( + IAaptContext* context, const StringPiece& str) { + std::unique_ptr<xml::XmlResource> doc = BuildXmlDom(str); + doc->file.name.package = context->GetCompilationPackage(); + return doc; } -} // namespace test -} // namespace aapt +} // namespace test +} // namespace aapt #endif /* AAPT_TEST_BUILDERS_H */ diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index faccd47783ff..36892017f2d3 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -17,6 +17,12 @@ #ifndef AAPT_TEST_COMMON_H #define AAPT_TEST_COMMON_H +#include <iostream> + +#include "android-base/logging.h" +#include "android-base/macros.h" +#include "gtest/gtest.h" + #include "ConfigDescription.h" #include "Debug.h" #include "ResourceTable.h" @@ -26,11 +32,9 @@ #include "process/IResourceTableConsumer.h" #include "util/StringPiece.h" -#include <gtest/gtest.h> -#include <iostream> - // -// GTEST 1.7 doesn't explicitly cast to bool, which causes explicit operators to fail to compile. +// GTEST 1.7 doesn't explicitly cast to bool, which causes explicit operators to +// fail to compile. // #define AAPT_ASSERT_TRUE(v) ASSERT_TRUE(bool(v)) #define AAPT_ASSERT_FALSE(v) ASSERT_FALSE(bool(v)) @@ -41,81 +45,84 @@ namespace aapt { namespace test { struct DummyDiagnosticsImpl : public IDiagnostics { - void log(Level level, DiagMessageActual& actualMsg) override { - switch (level) { - case Level::Note: - return; - - case Level::Warn: - std::cerr << actualMsg.source << ": warn: " << actualMsg.message << "." << std::endl; - break; - - case Level::Error: - std::cerr << actualMsg.source << ": error: " << actualMsg.message << "." << std::endl; - break; - } + void Log(Level level, DiagMessageActual& actual_msg) override { + switch (level) { + case Level::Note: + return; + + case Level::Warn: + std::cerr << actual_msg.source << ": warn: " << actual_msg.message + << "." << std::endl; + break; + + case Level::Error: + std::cerr << actual_msg.source << ": error: " << actual_msg.message + << "." << std::endl; + break; } + } }; -inline IDiagnostics* getDiagnostics() { - static DummyDiagnosticsImpl diag; - return &diag; +inline IDiagnostics* GetDiagnostics() { + static DummyDiagnosticsImpl diag; + return &diag; } -inline ResourceName parseNameOrDie(const StringPiece16& str) { - ResourceNameRef ref; - bool result = ResourceUtils::tryParseReference(str, &ref); - assert(result && "invalid resource name"); - return ref.toResourceName(); +inline ResourceName ParseNameOrDie(const StringPiece& str) { + ResourceNameRef ref; + CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name"; + return ref.ToResourceName(); } -inline ConfigDescription parseConfigOrDie(const StringPiece& str) { - ConfigDescription config; - bool result = ConfigDescription::parse(str, &config); - assert(result && "invalid configuration"); - return config; +inline ConfigDescription ParseConfigOrDie(const StringPiece& str) { + ConfigDescription config; + CHECK(ConfigDescription::Parse(str, &config)) << "invalid configuration"; + return config; } -template <typename T> T* getValueForConfigAndProduct(ResourceTable* table, - const StringPiece16& resName, - const ConfigDescription& config, - const StringPiece& product) { - Maybe<ResourceTable::SearchResult> result = table->findResource(parseNameOrDie(resName)); - if (result) { - ResourceConfigValue* configValue = result.value().entry->findValue(config, product); - if (configValue) { - return valueCast<T>(configValue->value.get()); - } +template <typename T> +T* GetValueForConfigAndProduct(ResourceTable* table, + const StringPiece& res_name, + const ConfigDescription& config, + const StringPiece& product) { + Maybe<ResourceTable::SearchResult> result = + table->FindResource(ParseNameOrDie(res_name)); + if (result) { + ResourceConfigValue* config_value = + result.value().entry->FindValue(config, product); + if (config_value) { + return ValueCast<T>(config_value->value.get()); } - return nullptr; + } + return nullptr; } -template <typename T> T* getValueForConfig(ResourceTable* table, const StringPiece16& resName, - const ConfigDescription& config) { - return getValueForConfigAndProduct<T>(table, resName, config, {}); +template <typename T> +T* GetValueForConfig(ResourceTable* table, const StringPiece& res_name, + const ConfigDescription& config) { + return GetValueForConfigAndProduct<T>(table, res_name, config, {}); } -template <typename T> T* getValue(ResourceTable* table, const StringPiece16& resName) { - return getValueForConfig<T>(table, resName, {}); +template <typename T> +T* GetValue(ResourceTable* table, const StringPiece& res_name) { + return GetValueForConfig<T>(table, res_name, {}); } class TestFile : public io::IFile { -private: - Source mSource; + public: + explicit TestFile(const StringPiece& path) : source_(path) {} -public: - TestFile(const StringPiece& path) : mSource(path) {} + std::unique_ptr<io::IData> OpenAsData() override { return {}; } - std::unique_ptr<io::IData> openAsData() override { - return {}; - } + const Source& GetSource() const override { return source_; } - const Source& getSource() const override { - return mSource; - } + private: + DISALLOW_COPY_AND_ASSIGN(TestFile); + + Source source_; }; -} // namespace test -} // namespace aapt +} // namespace test +} // namespace aapt #endif /* AAPT_TEST_COMMON_H */ diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h index 96752d33dd9a..7986329ab640 100644 --- a/tools/aapt2/test/Context.h +++ b/tools/aapt2/test/Context.h @@ -17,159 +17,162 @@ #ifndef AAPT_TEST_CONTEXT_H #define AAPT_TEST_CONTEXT_H -#include "NameMangler.h" -#include "util/Util.h" +#include <list> + +#include "android-base/logging.h" +#include "android-base/macros.h" +#include "NameMangler.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" #include "test/Common.h" - -#include <cassert> -#include <list> +#include "util/Util.h" namespace aapt { namespace test { class Context : public IAaptContext { -public: - SymbolTable* getExternalSymbols() override { - return &mSymbols; - } - - IDiagnostics* getDiagnostics() override { - return &mDiagnostics; - } - - const std::u16string& getCompilationPackage() override { - assert(mCompilationPackage && "package name not set"); - return mCompilationPackage.value(); - } - - uint8_t getPackageId() override { - assert(mPackageId && "package ID not set"); - return mPackageId.value(); - } + public: + Context() = default; - NameMangler* getNameMangler() override { - return &mNameMangler; - } + SymbolTable* GetExternalSymbols() override { return &symbols_; } - bool verbose() override { - return false; - } + IDiagnostics* GetDiagnostics() override { return &diagnostics_; } -private: - friend class ContextBuilder; + const std::string& GetCompilationPackage() override { + CHECK(bool(compilation_package_)) << "package name not set"; + return compilation_package_.value(); + } - Context() : mNameMangler({}) { - } + uint8_t GetPackageId() override { + CHECK(bool(package_id_)) << "package ID not set"; + return package_id_.value(); + } - Maybe<std::u16string> mCompilationPackage; - Maybe<uint8_t> mPackageId; - StdErrDiagnostics mDiagnostics; - SymbolTable mSymbols; - NameMangler mNameMangler; -}; + NameMangler* GetNameMangler() override { return &name_mangler_; } -class ContextBuilder { -private: - std::unique_ptr<Context> mContext = std::unique_ptr<Context>(new Context()); + bool IsVerbose() override { return false; } -public: - ContextBuilder& setCompilationPackage(const StringPiece16& package) { - mContext->mCompilationPackage = package.toString(); - return *this; - } + int GetMinSdkVersion() override { return min_sdk_version_; } - ContextBuilder& setPackageId(uint8_t id) { - mContext->mPackageId = id; - return *this; - } + private: + DISALLOW_COPY_AND_ASSIGN(Context); - ContextBuilder& setNameManglerPolicy(NameManglerPolicy policy) { - mContext->mNameMangler = NameMangler(policy); - return *this; - } + friend class ContextBuilder; - ContextBuilder& addSymbolSource(std::unique_ptr<ISymbolSource> src) { - mContext->getExternalSymbols()->appendSource(std::move(src)); - return *this; - } + Maybe<std::string> compilation_package_; + Maybe<uint8_t> package_id_; + StdErrDiagnostics diagnostics_; + SymbolTable symbols_; + NameMangler name_mangler_ = NameMangler({}); + int min_sdk_version_ = 0; +}; - std::unique_ptr<Context> build() { - return std::move(mContext); - } +class ContextBuilder { + public: + ContextBuilder& SetCompilationPackage(const StringPiece& package) { + context_->compilation_package_ = package.ToString(); + return *this; + } + + ContextBuilder& SetPackageId(uint8_t id) { + context_->package_id_ = id; + return *this; + } + + ContextBuilder& SetNameManglerPolicy(const NameManglerPolicy& policy) { + context_->name_mangler_ = NameMangler(policy); + return *this; + } + + ContextBuilder& AddSymbolSource(std::unique_ptr<ISymbolSource> src) { + context_->GetExternalSymbols()->AppendSource(std::move(src)); + return *this; + } + + ContextBuilder& SetMinSdkVersion(int min_sdk) { + context_->min_sdk_version_ = min_sdk; + return *this; + } + + std::unique_ptr<Context> Build() { return std::move(context_); } + + private: + std::unique_ptr<Context> context_ = std::unique_ptr<Context>(new Context()); }; class StaticSymbolSourceBuilder { -public: - StaticSymbolSourceBuilder& addPublicSymbol(const StringPiece16& name, ResourceId id, - std::unique_ptr<Attribute> attr = {}) { - std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>( - id, std::move(attr), true); - mSymbolSource->mNameMap[parseNameOrDie(name)] = symbol.get(); - mSymbolSource->mIdMap[id] = symbol.get(); - mSymbolSource->mSymbols.push_back(std::move(symbol)); - return *this; + public: + StaticSymbolSourceBuilder& AddPublicSymbol( + const StringPiece& name, ResourceId id, + std::unique_ptr<Attribute> attr = {}) { + std::unique_ptr<SymbolTable::Symbol> symbol = + util::make_unique<SymbolTable::Symbol>(id, std::move(attr), true); + symbol_source_->name_map_[ParseNameOrDie(name)] = symbol.get(); + symbol_source_->id_map_[id] = symbol.get(); + symbol_source_->symbols_.push_back(std::move(symbol)); + return *this; + } + + StaticSymbolSourceBuilder& AddSymbol(const StringPiece& name, ResourceId id, + std::unique_ptr<Attribute> attr = {}) { + std::unique_ptr<SymbolTable::Symbol> symbol = + util::make_unique<SymbolTable::Symbol>(id, std::move(attr), false); + symbol_source_->name_map_[ParseNameOrDie(name)] = symbol.get(); + symbol_source_->id_map_[id] = symbol.get(); + symbol_source_->symbols_.push_back(std::move(symbol)); + return *this; + } + + std::unique_ptr<ISymbolSource> Build() { return std::move(symbol_source_); } + + private: + class StaticSymbolSource : public ISymbolSource { + public: + StaticSymbolSource() = default; + + std::unique_ptr<SymbolTable::Symbol> FindByName( + const ResourceName& name) override { + auto iter = name_map_.find(name); + if (iter != name_map_.end()) { + return CloneSymbol(iter->second); + } + return nullptr; } - StaticSymbolSourceBuilder& addSymbol(const StringPiece16& name, ResourceId id, - std::unique_ptr<Attribute> attr = {}) { - std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>( - id, std::move(attr), false); - mSymbolSource->mNameMap[parseNameOrDie(name)] = symbol.get(); - mSymbolSource->mIdMap[id] = symbol.get(); - mSymbolSource->mSymbols.push_back(std::move(symbol)); - return *this; + std::unique_ptr<SymbolTable::Symbol> FindById(ResourceId id) override { + auto iter = id_map_.find(id); + if (iter != id_map_.end()) { + return CloneSymbol(iter->second); + } + return nullptr; } - std::unique_ptr<ISymbolSource> build() { - return std::move(mSymbolSource); + std::list<std::unique_ptr<SymbolTable::Symbol>> symbols_; + std::map<ResourceName, SymbolTable::Symbol*> name_map_; + std::map<ResourceId, SymbolTable::Symbol*> id_map_; + + private: + std::unique_ptr<SymbolTable::Symbol> CloneSymbol(SymbolTable::Symbol* sym) { + std::unique_ptr<SymbolTable::Symbol> clone = + util::make_unique<SymbolTable::Symbol>(); + clone->id = sym->id; + if (sym->attribute) { + clone->attribute = + std::unique_ptr<Attribute>(sym->attribute->Clone(nullptr)); + } + clone->is_public = sym->is_public; + return clone; } -private: - class StaticSymbolSource : public ISymbolSource { - public: - StaticSymbolSource() = default; - - std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) override { - auto iter = mNameMap.find(name); - if (iter != mNameMap.end()) { - return cloneSymbol(iter->second); - } - return nullptr; - } - - std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override { - auto iter = mIdMap.find(id); - if (iter != mIdMap.end()) { - return cloneSymbol(iter->second); - } - return nullptr; - } - - std::list<std::unique_ptr<SymbolTable::Symbol>> mSymbols; - std::map<ResourceName, SymbolTable::Symbol*> mNameMap; - std::map<ResourceId, SymbolTable::Symbol*> mIdMap; - - private: - std::unique_ptr<SymbolTable::Symbol> cloneSymbol(SymbolTable::Symbol* sym) { - std::unique_ptr<SymbolTable::Symbol> clone = util::make_unique<SymbolTable::Symbol>(); - clone->id = sym->id; - if (sym->attribute) { - clone->attribute = std::unique_ptr<Attribute>(sym->attribute->clone(nullptr)); - } - clone->isPublic = sym->isPublic; - return clone; - } - - DISALLOW_COPY_AND_ASSIGN(StaticSymbolSource); - }; - - std::unique_ptr<StaticSymbolSource> mSymbolSource = util::make_unique<StaticSymbolSource>(); + DISALLOW_COPY_AND_ASSIGN(StaticSymbolSource); + }; + + std::unique_ptr<StaticSymbolSource> symbol_source_ = + util::make_unique<StaticSymbolSource>(); }; -} // namespace test -} // namespace aapt +} // namespace test +} // namespace aapt #endif /* AAPT_TEST_CONTEXT_H */ diff --git a/tools/aapt2/test/Test.h b/tools/aapt2/test/Test.h index d4845cfc19b7..ec07432fa51e 100644 --- a/tools/aapt2/test/Test.h +++ b/tools/aapt2/test/Test.h @@ -17,16 +17,10 @@ #ifndef AAPT_TEST_TEST_H #define AAPT_TEST_TEST_H +#include "gtest/gtest.h" + #include "test/Builders.h" #include "test/Common.h" #include "test/Context.h" -#include <gtest/gtest.h> - -namespace aapt { -namespace test { - -} // namespace test -} // namespace aapt - -#endif // AAPT_TEST_TEST_H +#endif // AAPT_TEST_TEST_H diff --git a/tools/aapt2/tools/consumers/__init__.py b/tools/aapt2/tools/consumers/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/tools/aapt2/tools/consumers/__init__.py diff --git a/tools/aapt2/tools/consumers/duplicates.py b/tools/aapt2/tools/consumers/duplicates.py new file mode 100644 index 000000000000..4f70b6cae057 --- /dev/null +++ b/tools/aapt2/tools/consumers/duplicates.py @@ -0,0 +1,132 @@ +""" +Looks for duplicate resource definitions and removes all but the last one. +""" + +import os.path +import xml.parsers.expat + +class DuplicateRemover: + def matches(self, file_path): + dirname, basename = os.path.split(file_path) + dirname = os.path.split(dirname)[1] + return dirname.startswith("values") and basename.endswith(".xml") + + def consume(self, xml_path, input): + parser = xml.parsers.expat.ParserCreate("utf-8") + parser.returns_unicode = True + tracker = ResourceDefinitionLocator(parser) + parser.StartElementHandler = tracker.start_element + parser.EndElementHandler = tracker.end_element + parser.Parse(input) + + # Treat the input as UTF-8 or else column numbers will be wrong. + input_lines = input.decode('utf-8').splitlines(True) + + # Extract the duplicate resource definitions, ignoring the last definition + # which will take precedence and be left intact. + duplicates = [] + for res_name, entries in tracker.resource_definitions.iteritems(): + if len(entries) > 1: + duplicates += entries[:-1] + + # Sort the duplicates so that they are in order. That way we only do one pass. + duplicates = sorted(duplicates, key=lambda x: x.start) + + last_line_no = 0 + last_col_no = 0 + output_lines = [] + current_line = "" + for definition in duplicates: + print "{0}: removing duplicate resource '{1}'".format(xml_path, definition.name) + + if last_line_no < definition.start[0]: + # The next definition is on a new line, so write what we have + # to the output. + new_line = current_line + input_lines[last_line_no][last_col_no:] + if not new_line.isspace(): + output_lines.append(new_line) + current_line = "" + last_col_no = 0 + last_line_no += 1 + + # Copy all the lines up until this one. + for line_to_copy in xrange(last_line_no, definition.start[0]): + output_lines.append(input_lines[line_to_copy]) + + # Add to the existing line we're building, by including the prefix of this line + # and skipping the lines and characters until the end of this duplicate + # definition. + last_line_no = definition.start[0] + current_line += input_lines[last_line_no][last_col_no:definition.start[1]] + last_line_no = definition.end[0] + last_col_no = definition.end[1] + + new_line = current_line + input_lines[last_line_no][last_col_no:] + if not new_line.isspace(): + output_lines.append(new_line) + current_line = "" + last_line_no += 1 + last_col_no = 0 + + for line_to_copy in xrange(last_line_no, len(input_lines)): + output_lines.append(input_lines[line_to_copy]) + + if len(duplicates) > 0: + print "deduped {0}".format(xml_path) + return "".join(output_lines).encode("utf-8") + return input + +class Duplicate: + """A small struct to maintain the positions of a Duplicate resource definition.""" + def __init__(self, name, product, depth, start, end): + self.name = name + self.product = product + self.depth = depth + self.start = start + self.end = end + +class ResourceDefinitionLocator: + """Callback class for xml.parsers.expat which records resource definitions and their + locations. + """ + def __init__(self, parser): + self.resource_definitions = {} + self._parser = parser + self._depth = 0 + self._current_resource = None + + def start_element(self, tag_name, attrs): + self._depth += 1 + if self._depth == 2 and tag_name not in ["public", "java-symbol", "eat-comment", "skip"]: + resource_name = None + product = "" + try: + product = attrs["product"] + except KeyError: + pass + + if tag_name == "item": + resource_name = "{0}/{1}".format(attrs["type"], attrs["name"]) + else: + resource_name = "{0}/{1}".format(tag_name, attrs["name"]) + self._current_resource = Duplicate( + resource_name, + product, + self._depth, + (self._parser.CurrentLineNumber - 1, self._parser.CurrentColumnNumber), + None) + + def end_element(self, tag_name): + if self._current_resource and self._depth == self._current_resource.depth: + # Record the end position of the element, which is the length of the name + # plus the </> symbols (len("</>") == 3). + self._current_resource.end = (self._parser.CurrentLineNumber - 1, + self._parser.CurrentColumnNumber + 3 + len(tag_name)) + key_name = "{0}:{1}".format(self._current_resource.name, + self._current_resource.product) + try: + self.resource_definitions[key_name] += [self._current_resource] + except KeyError: + self.resource_definitions[key_name] = [self._current_resource] + self._current_resource = None + self._depth -= 1 diff --git a/tools/aapt2/tools/consumers/positional_arguments.py b/tools/aapt2/tools/consumers/positional_arguments.py new file mode 100644 index 000000000000..176e4c9ded60 --- /dev/null +++ b/tools/aapt2/tools/consumers/positional_arguments.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +""" +Looks for strings with multiple substitution arguments (%d, &s, etc) +and replaces them with positional arguments (%1$d, %2$s). +""" + +import os.path +import re +import xml.parsers.expat + +class PositionalArgumentFixer: + def matches(self, file_path): + dirname, basename = os.path.split(file_path) + dirname = os.path.split(dirname)[1] + return dirname.startswith("values") and basename.endswith(".xml") + + def consume(self, xml_path, input): + parser = xml.parsers.expat.ParserCreate("utf-8") + locator = SubstitutionArgumentLocator(parser) + parser.returns_unicode = True + parser.StartElementHandler = locator.start_element + parser.EndElementHandler = locator.end_element + parser.CharacterDataHandler = locator.character_data + parser.Parse(input) + + if len(locator.arguments) > 0: + output = "" + last_index = 0 + for arg in locator.arguments: + output += input[last_index:arg.start] + output += "%{0}$".format(arg.number) + last_index = arg.start + 1 + output += input[last_index:] + print "fixed {0}".format(xml_path) + return output + return input + +class Argument: + def __init__(self, start, number): + self.start = start + self.number = number + +class SubstitutionArgumentLocator: + """Callback class for xml.parsers.expat which records locations of + substitution arguments in strings when there are more than 1 of + them in a single <string> tag (and they are not positional). + """ + def __init__(self, parser): + self.arguments = [] + self._parser = parser + self._depth = 0 + self._within_string = False + self._current_arguments = [] + self._next_number = 1 + + def start_element(self, tag_name, attrs): + self._depth += 1 + if self._depth == 2 and tag_name == "string" and "translateable" not in attrs: + self._within_string = True + + def character_data(self, data): + if self._within_string: + for m in re.finditer("%[-#+ 0,(]?\d*[bBhHsScCdoxXeEfgGaAtTn]", data): + start, end = m.span() + self._current_arguments.append(\ + Argument(self._parser.CurrentByteIndex + start, self._next_number)) + self._next_number += 1 + + def end_element(self, tag_name): + if self._within_string and self._depth == 2: + if len(self._current_arguments) > 1: + self.arguments += self._current_arguments + self._current_arguments = [] + self._within_string = False + self._next_number = 1 + self._depth -= 1 diff --git a/tools/aapt2/tools/fix_resources.py b/tools/aapt2/tools/fix_resources.py new file mode 100644 index 000000000000..b6fcd915d9eb --- /dev/null +++ b/tools/aapt2/tools/fix_resources.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python + +""" +Scans each resource file in res/ applying various transformations +to fix invalid resource files. +""" + +import os +import os.path +import sys +import tempfile + +from consumers.duplicates import DuplicateRemover +from consumers.positional_arguments import PositionalArgumentFixer + +def do_it(res_path, consumers): + for file_path in enumerate_files(res_path): + eligible_consumers = filter(lambda c: c.matches(file_path), consumers) + if len(eligible_consumers) > 0: + print "checking {0} ...".format(file_path) + + original_contents = read_contents(file_path) + contents = original_contents + for c in eligible_consumers: + contents = c.consume(file_path, contents) + if original_contents != contents: + write_contents(file_path, contents) + +def enumerate_files(res_path): + """Enumerates all files in the resource directory.""" + values_directories = os.listdir(res_path) + values_directories = map(lambda f: os.path.join(res_path, f), values_directories) + all_files = [] + for dir in values_directories: + files = os.listdir(dir) + files = map(lambda f: os.path.join(dir, f), files) + for f in files: + yield f + +def read_contents(file_path): + """Reads the contents of file_path without decoding.""" + with open(file_path) as fin: + return fin.read() + +def write_contents(file_path, contents): + """Writes the bytes in contents to file_path by first writing to a temporary, then + renaming the temporary to file_path, ensuring a consistent write. + """ + dirname, basename = os.path.split(file_path) + temp_name = "" + with tempfile.NamedTemporaryFile(prefix=basename, dir=dirname, delete=False) as temp: + temp_name = temp.name + temp.write(contents) + os.rename(temp.name, file_path) + +if __name__ == '__main__': + if len(sys.argv) < 2: + print >> sys.stderr, "please specify a path to a resource directory" + sys.exit(1) + + res_path = os.path.abspath(sys.argv[1]) + print "looking in {0} ...".format(res_path) + do_it(res_path, [DuplicateRemover(), PositionalArgumentFixer()]) diff --git a/tools/aapt2/public_attr_map.py b/tools/aapt2/tools/public_attr_map.py index 92136a8d9acd..92136a8d9acd 100644 --- a/tools/aapt2/public_attr_map.py +++ b/tools/aapt2/tools/public_attr_map.py diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp index ec4675167676..aeabcff011ed 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.cpp +++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp @@ -14,22 +14,25 @@ * limitations under the License. */ +#include "unflatten/BinaryResourceParser.h" + +#include <algorithm> +#include <map> +#include <string> + +#include "android-base/logging.h" +#include "android-base/macros.h" +#include "androidfw/ResourceTypes.h" +#include "androidfw/TypeWrappers.h" + #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" #include "Source.h" #include "ValueVisitor.h" -#include "unflatten/BinaryResourceParser.h" #include "unflatten/ResChunkPullParser.h" #include "util/Util.h" -#include <androidfw/ResourceTypes.h> -#include <androidfw/TypeWrappers.h> -#include <android-base/macros.h> -#include <algorithm> -#include <map> -#include <string> - namespace aapt { using namespace android; @@ -41,533 +44,552 @@ namespace { * given a mapping from resource ID to resource name. */ class ReferenceIdToNameVisitor : public ValueVisitor { -private: - const std::map<ResourceId, ResourceName>* mMapping; + public: + using ValueVisitor::Visit; -public: - using ValueVisitor::visit; + explicit ReferenceIdToNameVisitor( + const std::map<ResourceId, ResourceName>* mapping) + : mapping_(mapping) { + CHECK(mapping_ != nullptr); + } - ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping) : - mMapping(mapping) { - assert(mMapping); + void Visit(Reference* reference) override { + if (!reference->id || !reference->id.value().is_valid()) { + return; } - void visit(Reference* reference) override { - if (!reference->id || !reference->id.value().isValid()) { - return; - } - - ResourceId id = reference->id.value(); - auto cacheIter = mMapping->find(id); - if (cacheIter != mMapping->end()) { - reference->name = cacheIter->second; - reference->id = {}; - } + ResourceId id = reference->id.value(); + auto cache_iter = mapping_->find(id); + if (cache_iter != mapping_->end()) { + reference->name = cache_iter->second; } -}; + } -} // namespace + private: + DISALLOW_COPY_AND_ASSIGN(ReferenceIdToNameVisitor); -BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table, - const Source& source, const void* data, size_t len) : - mContext(context), mTable(table), mSource(source), mData(data), mDataLen(len) { -} - -bool BinaryResourceParser::parse() { - ResChunkPullParser parser(mData, mDataLen); - - bool error = false; - while(ResChunkPullParser::isGoodEvent(parser.next())) { - if (parser.getChunk()->type != android::RES_TABLE_TYPE) { - mContext->getDiagnostics()->warn(DiagMessage(mSource) - << "unknown chunk of type '" - << (int) parser.getChunk()->type << "'"); - continue; - } - - if (!parseTable(parser.getChunk())) { - error = true; - } - } + const std::map<ResourceId, ResourceName>* mapping_; +}; - if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "corrupt resource table: " - << parser.getLastError()); - return false; - } - return !error; +} // namespace + +BinaryResourceParser::BinaryResourceParser(IAaptContext* context, + ResourceTable* table, + const Source& source, + const void* data, size_t len) + : context_(context), + table_(table), + source_(source), + data_(data), + data_len_(len) {} + +bool BinaryResourceParser::Parse() { + ResChunkPullParser parser(data_, data_len_); + + bool error = false; + while (ResChunkPullParser::IsGoodEvent(parser.Next())) { + if (parser.chunk()->type != android::RES_TABLE_TYPE) { + context_->GetDiagnostics()->Warn(DiagMessage(source_) + << "unknown chunk of type '" + << (int)parser.chunk()->type << "'"); + continue; + } + + if (!ParseTable(parser.chunk())) { + error = true; + } + } + + if (parser.event() == ResChunkPullParser::Event::kBadDocument) { + context_->GetDiagnostics()->Error( + DiagMessage(source_) << "corrupt resource table: " << parser.error()); + return false; + } + return !error; } /** - * Parses the resource table, which contains all the packages, types, and entries. + * Parses the resource table, which contains all the packages, types, and + * entries. */ -bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) { - const ResTable_header* tableHeader = convertTo<ResTable_header>(chunk); - if (!tableHeader) { - mContext->getDiagnostics()->error(DiagMessage(mSource) << "corrupt ResTable_header chunk"); - return false; - } +bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) { + const ResTable_header* table_header = ConvertTo<ResTable_header>(chunk); + if (!table_header) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "corrupt ResTable_header chunk"); + return false; + } + + ResChunkPullParser parser(GetChunkData(&table_header->header), + GetChunkDataLen(&table_header->header)); + while (ResChunkPullParser::IsGoodEvent(parser.Next())) { + switch (util::DeviceToHost16(parser.chunk()->type)) { + case android::RES_STRING_POOL_TYPE: + if (value_pool_.getError() == NO_INIT) { + status_t err = value_pool_.setTo( + parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); + if (err != NO_ERROR) { + context_->GetDiagnostics()->Error( + DiagMessage(source_) << "corrupt string pool in ResTable: " + << value_pool_.getError()); + return false; + } - ResChunkPullParser parser(getChunkData(&tableHeader->header), - getChunkDataLen(&tableHeader->header)); - while (ResChunkPullParser::isGoodEvent(parser.next())) { - switch (util::deviceToHost16(parser.getChunk()->type)) { - case android::RES_STRING_POOL_TYPE: - if (mValuePool.getError() == NO_INIT) { - status_t err = mValuePool.setTo(parser.getChunk(), - util::deviceToHost32(parser.getChunk()->size)); - if (err != NO_ERROR) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "corrupt string pool in ResTable: " - << mValuePool.getError()); - return false; - } - - // Reserve some space for the strings we are going to add. - mTable->stringPool.hintWillAdd(mValuePool.size(), mValuePool.styleCount()); - } else { - mContext->getDiagnostics()->warn(DiagMessage(mSource) - << "unexpected string pool in ResTable"); - } - break; - - case android::RES_TABLE_PACKAGE_TYPE: - if (!parsePackage(parser.getChunk())) { - return false; - } - break; - - default: - mContext->getDiagnostics() - ->warn(DiagMessage(mSource) - << "unexpected chunk type " - << (int) util::deviceToHost16(parser.getChunk()->type)); - break; + // Reserve some space for the strings we are going to add. + table_->string_pool.HintWillAdd(value_pool_.size(), + value_pool_.styleCount()); + } else { + context_->GetDiagnostics()->Warn( + DiagMessage(source_) << "unexpected string pool in ResTable"); } - } + break; - if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "corrupt resource table: " << parser.getLastError()); - return false; - } - return true; -} - - -bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) { - const ResTable_package* packageHeader = convertTo<ResTable_package>(chunk); - if (!packageHeader) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "corrupt ResTable_package chunk"); - return false; - } - - uint32_t packageId = util::deviceToHost32(packageHeader->id); - if (packageId > std::numeric_limits<uint8_t>::max()) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "package ID is too big (" << packageId << ")"); - return false; - } - - // Extract the package name. - size_t len = strnlen16((const char16_t*) packageHeader->name, arraysize(packageHeader->name)); - std::u16string packageName; - packageName.resize(len); - for (size_t i = 0; i < len; i++) { - packageName[i] = util::deviceToHost16(packageHeader->name[i]); - } - - ResourceTablePackage* package = mTable->createPackage(packageName, (uint8_t) packageId); - if (!package) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "incompatible package '" << packageName - << "' with ID " << packageId); - return false; - } - - // There can be multiple packages in a table, so - // clear the type and key pool in case they were set from a previous package. - mTypePool.uninit(); - mKeyPool.uninit(); - - ResChunkPullParser parser(getChunkData(&packageHeader->header), - getChunkDataLen(&packageHeader->header)); - while (ResChunkPullParser::isGoodEvent(parser.next())) { - switch (util::deviceToHost16(parser.getChunk()->type)) { - case android::RES_STRING_POOL_TYPE: - if (mTypePool.getError() == NO_INIT) { - status_t err = mTypePool.setTo(parser.getChunk(), - util::deviceToHost32(parser.getChunk()->size)); - if (err != NO_ERROR) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "corrupt type string pool in " - << "ResTable_package: " - << mTypePool.getError()); - return false; - } - } else if (mKeyPool.getError() == NO_INIT) { - status_t err = mKeyPool.setTo(parser.getChunk(), - util::deviceToHost32(parser.getChunk()->size)); - if (err != NO_ERROR) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "corrupt key string pool in " - << "ResTable_package: " - << mKeyPool.getError()); - return false; - } - } else { - mContext->getDiagnostics()->warn(DiagMessage(mSource) << "unexpected string pool"); - } - break; - - case android::RES_TABLE_TYPE_SPEC_TYPE: - if (!parseTypeSpec(parser.getChunk())) { - return false; - } - break; - - case android::RES_TABLE_TYPE_TYPE: - if (!parseType(package, parser.getChunk())) { - return false; - } - break; - - default: - mContext->getDiagnostics() - ->warn(DiagMessage(mSource) - << "unexpected chunk type " - << (int) util::deviceToHost16(parser.getChunk()->type)); - break; + case android::RES_TABLE_PACKAGE_TYPE: + if (!ParsePackage(parser.chunk())) { + return false; } - } - - if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "corrupt ResTable_package: " - << parser.getLastError()); - return false; - } - - // Now go through the table and change local resource ID references to - // symbolic references. - ReferenceIdToNameVisitor visitor(&mIdIndex); - visitAllValuesInTable(mTable, &visitor); - return true; -} - -bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) { - if (mTypePool.getError() != NO_ERROR) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "missing type string pool"); - return false; - } - - const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk); - if (!typeSpec) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "corrupt ResTable_typeSpec chunk"); - return false; - } - - if (typeSpec->id == 0) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "ResTable_typeSpec has invalid id: " << typeSpec->id); - return false; - } - return true; + break; + + default: + context_->GetDiagnostics()->Warn( + DiagMessage(source_) + << "unexpected chunk type " + << (int)util::DeviceToHost16(parser.chunk()->type)); + break; + } + } + + if (parser.event() == ResChunkPullParser::Event::kBadDocument) { + context_->GetDiagnostics()->Error( + DiagMessage(source_) << "corrupt resource table: " << parser.error()); + return false; + } + return true; } -bool BinaryResourceParser::parseType(const ResourceTablePackage* package, - const ResChunk_header* chunk) { - if (mTypePool.getError() != NO_ERROR) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "missing type string pool"); - return false; - } - - if (mKeyPool.getError() != NO_ERROR) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "missing key string pool"); - return false; - } - - const ResTable_type* type = convertTo<ResTable_type>(chunk); - if (!type) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "corrupt ResTable_type chunk"); - return false; - } - - if (type->id == 0) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "ResTable_type has invalid id: " << (int) type->id); - return false; - } - - ConfigDescription config; - config.copyFromDtoH(type->config); - - StringPiece16 typeStr16 = util::getString(mTypePool, type->id - 1); - - const ResourceType* parsedType = parseResourceType(typeStr16); - if (!parsedType) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "invalid type name '" << typeStr16 - << "' for type with ID " << (int) type->id); - return false; - } - - TypeVariant tv(type); - for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) { - const ResTable_entry* entry = *it; - if (!entry) { - continue; - } - - const ResourceName name(package->name, *parsedType, - util::getString(mKeyPool, - util::deviceToHost32(entry->key.index)).toString()); - - const ResourceId resId(package->id.value(), type->id, static_cast<uint16_t>(it.index())); - - std::unique_ptr<Value> resourceValue; - if (entry->flags & ResTable_entry::FLAG_COMPLEX) { - const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry); - - // TODO(adamlesinski): Check that the entry count is valid. - resourceValue = parseMapEntry(name, config, mapEntry); - } else { - const Res_value* value = (const Res_value*)( - (const uint8_t*) entry + util::deviceToHost32(entry->size)); - resourceValue = parseValue(name, config, value, entry->flags); - } - - if (!resourceValue) { - mContext->getDiagnostics()->error(DiagMessage(mSource) - << "failed to parse value for resource " << name - << " (" << resId << ") with configuration '" - << config << "'"); +bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { + const ResTable_package* package_header = ConvertTo<ResTable_package>(chunk); + if (!package_header) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "corrupt ResTable_package chunk"); + return false; + } + + uint32_t package_id = util::DeviceToHost32(package_header->id); + if (package_id > std::numeric_limits<uint8_t>::max()) { + context_->GetDiagnostics()->Error( + DiagMessage(source_) << "package ID is too big (" << package_id << ")"); + return false; + } + + // Extract the package name. + size_t len = strnlen16((const char16_t*)package_header->name, + arraysize(package_header->name)); + std::u16string package_name; + package_name.resize(len); + for (size_t i = 0; i < len; i++) { + package_name[i] = util::DeviceToHost16(package_header->name[i]); + } + + ResourceTablePackage* package = table_->CreatePackage( + util::Utf16ToUtf8(package_name), static_cast<uint8_t>(package_id)); + if (!package) { + context_->GetDiagnostics()->Error( + DiagMessage(source_) << "incompatible package '" << package_name + << "' with ID " << package_id); + return false; + } + + // There can be multiple packages in a table, so + // clear the type and key pool in case they were set from a previous package. + type_pool_.uninit(); + key_pool_.uninit(); + + ResChunkPullParser parser(GetChunkData(&package_header->header), + GetChunkDataLen(&package_header->header)); + while (ResChunkPullParser::IsGoodEvent(parser.Next())) { + switch (util::DeviceToHost16(parser.chunk()->type)) { + case android::RES_STRING_POOL_TYPE: + if (type_pool_.getError() == NO_INIT) { + status_t err = type_pool_.setTo( + parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); + if (err != NO_ERROR) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "corrupt type string pool in " + << "ResTable_package: " + << type_pool_.getError()); return false; - } - - if (!mTable->addResourceAllowMangled(name, config, {}, std::move(resourceValue), - mContext->getDiagnostics())) { + } + } else if (key_pool_.getError() == NO_INIT) { + status_t err = key_pool_.setTo( + parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); + if (err != NO_ERROR) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "corrupt key string pool in " + << "ResTable_package: " + << key_pool_.getError()); return false; + } + } else { + context_->GetDiagnostics()->Warn(DiagMessage(source_) + << "unexpected string pool"); } + break; - if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) { - Symbol symbol; - symbol.state = SymbolState::kPublic; - symbol.source = mSource.withLine(0); - if (!mTable->setSymbolStateAllowMangled(name, resId, symbol, - mContext->getDiagnostics())) { - return false; - } + case android::RES_TABLE_TYPE_SPEC_TYPE: + if (!ParseTypeSpec(parser.chunk())) { + return false; } + break; - // Add this resource name->id mapping to the index so - // that we can resolve all ID references to name references. - auto cacheIter = mIdIndex.find(resId); - if (cacheIter == mIdIndex.end()) { - mIdIndex.insert({ resId, name }); + case android::RES_TABLE_TYPE_TYPE: + if (!ParseType(package, parser.chunk())) { + return false; } - } - return true; + break; + + default: + context_->GetDiagnostics()->Warn( + DiagMessage(source_) + << "unexpected chunk type " + << (int)util::DeviceToHost16(parser.chunk()->type)); + break; + } + } + + if (parser.event() == ResChunkPullParser::Event::kBadDocument) { + context_->GetDiagnostics()->Error( + DiagMessage(source_) << "corrupt ResTable_package: " << parser.error()); + return false; + } + + // Now go through the table and change local resource ID references to + // symbolic references. + ReferenceIdToNameVisitor visitor(&id_index_); + VisitAllValuesInTable(table_, &visitor); + return true; } -std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& name, - const ConfigDescription& config, - const Res_value* value, - uint16_t flags) { - if (name.type == ResourceType::kId) { - return util::make_unique<Id>(); - } +bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) { + if (type_pool_.getError() != NO_ERROR) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "missing type string pool"); + return false; + } + + const ResTable_typeSpec* type_spec = ConvertTo<ResTable_typeSpec>(chunk); + if (!type_spec) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "corrupt ResTable_typeSpec chunk"); + return false; + } + + if (type_spec->id == 0) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "ResTable_typeSpec has invalid id: " + << type_spec->id); + return false; + } + return true; +} - const uint32_t data = util::deviceToHost32(value->data); - - if (value->dataType == Res_value::TYPE_STRING) { - StringPiece16 str = util::getString(mValuePool, data); - - const ResStringPool_span* spans = mValuePool.styleAt(data); - - // Check if the string has a valid style associated with it. - if (spans != nullptr && spans->name.index != ResStringPool_span::END) { - StyleString styleStr = { str.toString() }; - while (spans->name.index != ResStringPool_span::END) { - styleStr.spans.push_back(Span{ - util::getString(mValuePool, spans->name.index).toString(), - spans->firstChar, - spans->lastChar - }); - spans++; - } - return util::make_unique<StyledString>(mTable->stringPool.makeRef( - styleStr, StringPool::Context{1, config})); - } else { - if (name.type != ResourceType::kString && - util::stringStartsWith<char16_t>(str, u"res/")) { - // This must be a FileReference. - return util::make_unique<FileReference>(mTable->stringPool.makeRef( - str, StringPool::Context{ 0, config })); - } - - // There are no styles associated with this string, so treat it as - // a simple string. - return util::make_unique<String>(mTable->stringPool.makeRef( - str, StringPool::Context{1, config})); - } +bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, + const ResChunk_header* chunk) { + if (type_pool_.getError() != NO_ERROR) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "missing type string pool"); + return false; + } + + if (key_pool_.getError() != NO_ERROR) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "missing key string pool"); + return false; + } + + const ResTable_type* type = ConvertTo<ResTable_type>(chunk); + if (!type) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "corrupt ResTable_type chunk"); + return false; + } + + if (type->id == 0) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "ResTable_type has invalid id: " + << (int)type->id); + return false; + } + + ConfigDescription config; + config.copyFromDtoH(type->config); + + const std::string type_str = util::GetString(type_pool_, type->id - 1); + + const ResourceType* parsed_type = ParseResourceType(type_str); + if (!parsed_type) { + context_->GetDiagnostics()->Error( + DiagMessage(source_) << "invalid type name '" << type_str + << "' for type with ID " << (int)type->id); + return false; + } + + TypeVariant tv(type); + for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) { + const ResTable_entry* entry = *it; + if (!entry) { + continue; + } + + const ResourceName name( + package->name, *parsed_type, + util::GetString(key_pool_, util::DeviceToHost32(entry->key.index))); + + const ResourceId res_id(package->id.value(), type->id, + static_cast<uint16_t>(it.index())); + + std::unique_ptr<Value> resource_value; + if (entry->flags & ResTable_entry::FLAG_COMPLEX) { + const ResTable_map_entry* mapEntry = + static_cast<const ResTable_map_entry*>(entry); + + // TODO(adamlesinski): Check that the entry count is valid. + resource_value = ParseMapEntry(name, config, mapEntry); + } else { + const Res_value* value = + (const Res_value*)((const uint8_t*)entry + + util::DeviceToHost32(entry->size)); + resource_value = ParseValue(name, config, value, entry->flags); + } + + if (!resource_value) { + context_->GetDiagnostics()->Error( + DiagMessage(source_) << "failed to parse value for resource " << name + << " (" << res_id << ") with configuration '" + << config << "'"); + return false; + } + + if (!table_->AddResourceAllowMangled(name, config, {}, + std::move(resource_value), + context_->GetDiagnostics())) { + return false; + } + + if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) { + Symbol symbol; + symbol.state = SymbolState::kPublic; + symbol.source = source_.WithLine(0); + if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, + context_->GetDiagnostics())) { + return false; + } } - if (value->dataType == Res_value::TYPE_REFERENCE || - value->dataType == Res_value::TYPE_ATTRIBUTE) { - const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ? - Reference::Type::kResource : Reference::Type::kAttribute; - - if (data == 0) { - // A reference of 0, must be the magic @null reference. - Res_value nullType = {}; - nullType.dataType = Res_value::TYPE_REFERENCE; - return util::make_unique<BinaryPrimitive>(nullType); - } - - // This is a normal reference. - return util::make_unique<Reference>(data, type); + // Add this resource name->id mapping to the index so + // that we can resolve all ID references to name references. + auto cache_iter = id_index_.find(res_id); + if (cache_iter == id_index_.end()) { + id_index_.insert({res_id, name}); } - - // Treat this as a raw binary primitive. - return util::make_unique<BinaryPrimitive>(*value); + } + return true; } -std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef& name, - const ConfigDescription& config, - const ResTable_map_entry* map) { - switch (name.type) { - case ResourceType::kStyle: - return parseStyle(name, config, map); - case ResourceType::kAttrPrivate: - // fallthrough - case ResourceType::kAttr: - return parseAttr(name, config, map); - case ResourceType::kArray: - return parseArray(name, config, map); - case ResourceType::kPlurals: - return parsePlural(name, config, map); - default: - assert(false && "unknown map type"); - break; - } - return {}; +std::unique_ptr<Item> BinaryResourceParser::ParseValue( + const ResourceNameRef& name, const ConfigDescription& config, + const Res_value* value, uint16_t flags) { + if (name.type == ResourceType::kId) { + return util::make_unique<Id>(); + } + + const uint32_t data = util::DeviceToHost32(value->data); + + if (value->dataType == Res_value::TYPE_STRING) { + const std::string str = util::GetString(value_pool_, data); + + const ResStringPool_span* spans = value_pool_.styleAt(data); + + // Check if the string has a valid style associated with it. + if (spans != nullptr && spans->name.index != ResStringPool_span::END) { + StyleString style_str = {str}; + while (spans->name.index != ResStringPool_span::END) { + style_str.spans.push_back( + Span{util::GetString(value_pool_, spans->name.index), + spans->firstChar, spans->lastChar}); + spans++; + } + return util::make_unique<StyledString>(table_->string_pool.MakeRef( + style_str, + StringPool::Context(StringPool::Context::kStylePriority, config))); + } else { + if (name.type != ResourceType::kString && util::StartsWith(str, "res/")) { + // This must be a FileReference. + return util::make_unique<FileReference>(table_->string_pool.MakeRef( + str, + StringPool::Context(StringPool::Context::kHighPriority, config))); + } + + // There are no styles associated with this string, so treat it as + // a simple string. + return util::make_unique<String>( + table_->string_pool.MakeRef(str, StringPool::Context(config))); + } + } + + if (value->dataType == Res_value::TYPE_REFERENCE || + value->dataType == Res_value::TYPE_ATTRIBUTE) { + const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) + ? Reference::Type::kResource + : Reference::Type::kAttribute; + + if (data == 0) { + // A reference of 0, must be the magic @null reference. + Res_value null_type = {}; + null_type.dataType = Res_value::TYPE_REFERENCE; + return util::make_unique<BinaryPrimitive>(null_type); + } + + // This is a normal reference. + return util::make_unique<Reference>(data, type); + } + + // Treat this as a raw binary primitive. + return util::make_unique<BinaryPrimitive>(*value); } -std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name, - const ConfigDescription& config, - const ResTable_map_entry* map) { - std::unique_ptr<Style> style = util::make_unique<Style>(); - if (util::deviceToHost32(map->parent.ident) != 0) { - // The parent is a regular reference to a resource. - style->parent = Reference(util::deviceToHost32(map->parent.ident)); - } - - for (const ResTable_map& mapEntry : map) { - if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) { - continue; - } - - Style::Entry styleEntry; - styleEntry.key = Reference(util::deviceToHost32(mapEntry.name.ident)); - styleEntry.value = parseValue(name, config, &mapEntry.value, 0); - if (!styleEntry.value) { - return {}; - } - style->entries.push_back(std::move(styleEntry)); - } - return style; +std::unique_ptr<Value> BinaryResourceParser::ParseMapEntry( + const ResourceNameRef& name, const ConfigDescription& config, + const ResTable_map_entry* map) { + switch (name.type) { + case ResourceType::kStyle: + return ParseStyle(name, config, map); + case ResourceType::kAttrPrivate: + // fallthrough + case ResourceType::kAttr: + return ParseAttr(name, config, map); + case ResourceType::kArray: + return ParseArray(name, config, map); + case ResourceType::kPlurals: + return ParsePlural(name, config, map); + default: + LOG(FATAL) << "unknown map type"; + break; + } + return {}; } -std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef& name, - const ConfigDescription& config, - const ResTable_map_entry* map) { - const bool isWeak = (util::deviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0; - std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak); - - // First we must discover what type of attribute this is. Find the type mask. - auto typeMaskIter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool { - return util::deviceToHost32(entry.name.ident) == ResTable_map::ATTR_TYPE; - }); - - if (typeMaskIter != end(map)) { - attr->typeMask = util::deviceToHost32(typeMaskIter->value.data); - } - - for (const ResTable_map& mapEntry : map) { - if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) { - switch (util::deviceToHost32(mapEntry.name.ident)) { - case ResTable_map::ATTR_MIN: - attr->minInt = static_cast<int32_t>(mapEntry.value.data); - break; - case ResTable_map::ATTR_MAX: - attr->maxInt = static_cast<int32_t>(mapEntry.value.data); - break; - } - continue; - } - - if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) { - Attribute::Symbol symbol; - symbol.value = util::deviceToHost32(mapEntry.value.data); - symbol.symbol = Reference(util::deviceToHost32(mapEntry.name.ident)); - attr->symbols.push_back(std::move(symbol)); - } - } - - // TODO(adamlesinski): Find i80n, attributes. - return attr; +std::unique_ptr<Style> BinaryResourceParser::ParseStyle( + const ResourceNameRef& name, const ConfigDescription& config, + const ResTable_map_entry* map) { + std::unique_ptr<Style> style = util::make_unique<Style>(); + if (util::DeviceToHost32(map->parent.ident) != 0) { + // The parent is a regular reference to a resource. + style->parent = Reference(util::DeviceToHost32(map->parent.ident)); + } + + for (const ResTable_map& map_entry : map) { + if (Res_INTERNALID(util::DeviceToHost32(map_entry.name.ident))) { + continue; + } + + Style::Entry style_entry; + style_entry.key = Reference(util::DeviceToHost32(map_entry.name.ident)); + style_entry.value = ParseValue(name, config, &map_entry.value, 0); + if (!style_entry.value) { + return {}; + } + style->entries.push_back(std::move(style_entry)); + } + return style; } -std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name, - const ConfigDescription& config, - const ResTable_map_entry* map) { - std::unique_ptr<Array> array = util::make_unique<Array>(); - for (const ResTable_map& mapEntry : map) { - array->items.push_back(parseValue(name, config, &mapEntry.value, 0)); - } - return array; +std::unique_ptr<Attribute> BinaryResourceParser::ParseAttr( + const ResourceNameRef& name, const ConfigDescription& config, + const ResTable_map_entry* map) { + const bool is_weak = + (util::DeviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0; + std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(is_weak); + + // First we must discover what type of attribute this is. Find the type mask. + auto type_mask_iter = + std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool { + return util::DeviceToHost32(entry.name.ident) == + ResTable_map::ATTR_TYPE; + }); + + if (type_mask_iter != end(map)) { + attr->type_mask = util::DeviceToHost32(type_mask_iter->value.data); + } + + for (const ResTable_map& map_entry : map) { + if (Res_INTERNALID(util::DeviceToHost32(map_entry.name.ident))) { + switch (util::DeviceToHost32(map_entry.name.ident)) { + case ResTable_map::ATTR_MIN: + attr->min_int = static_cast<int32_t>(map_entry.value.data); + break; + case ResTable_map::ATTR_MAX: + attr->max_int = static_cast<int32_t>(map_entry.value.data); + break; + } + continue; + } + + if (attr->type_mask & + (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) { + Attribute::Symbol symbol; + symbol.value = util::DeviceToHost32(map_entry.value.data); + symbol.symbol = Reference(util::DeviceToHost32(map_entry.name.ident)); + attr->symbols.push_back(std::move(symbol)); + } + } + + // TODO(adamlesinski): Find i80n, attributes. + return attr; } -std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& name, - const ConfigDescription& config, - const ResTable_map_entry* map) { - std::unique_ptr<Plural> plural = util::make_unique<Plural>(); - for (const ResTable_map& mapEntry : map) { - std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0); - if (!item) { - return {}; - } +std::unique_ptr<Array> BinaryResourceParser::ParseArray( + const ResourceNameRef& name, const ConfigDescription& config, + const ResTable_map_entry* map) { + std::unique_ptr<Array> array = util::make_unique<Array>(); + for (const ResTable_map& map_entry : map) { + array->items.push_back(ParseValue(name, config, &map_entry.value, 0)); + } + return array; +} - switch (util::deviceToHost32(mapEntry.name.ident)) { - case ResTable_map::ATTR_ZERO: - plural->values[Plural::Zero] = std::move(item); - break; - case ResTable_map::ATTR_ONE: - plural->values[Plural::One] = std::move(item); - break; - case ResTable_map::ATTR_TWO: - plural->values[Plural::Two] = std::move(item); - break; - case ResTable_map::ATTR_FEW: - plural->values[Plural::Few] = std::move(item); - break; - case ResTable_map::ATTR_MANY: - plural->values[Plural::Many] = std::move(item); - break; - case ResTable_map::ATTR_OTHER: - plural->values[Plural::Other] = std::move(item); - break; - } - } - return plural; +std::unique_ptr<Plural> BinaryResourceParser::ParsePlural( + const ResourceNameRef& name, const ConfigDescription& config, + const ResTable_map_entry* map) { + std::unique_ptr<Plural> plural = util::make_unique<Plural>(); + for (const ResTable_map& map_entry : map) { + std::unique_ptr<Item> item = ParseValue(name, config, &map_entry.value, 0); + if (!item) { + return {}; + } + + switch (util::DeviceToHost32(map_entry.name.ident)) { + case ResTable_map::ATTR_ZERO: + plural->values[Plural::Zero] = std::move(item); + break; + case ResTable_map::ATTR_ONE: + plural->values[Plural::One] = std::move(item); + break; + case ResTable_map::ATTR_TWO: + plural->values[Plural::Two] = std::move(item); + break; + case ResTable_map::ATTR_FEW: + plural->values[Plural::Few] = std::move(item); + break; + case ResTable_map::ATTR_MANY: + plural->values[Plural::Many] = std::move(item); + break; + case ResTable_map::ATTR_OTHER: + plural->values[Plural::Other] = std::move(item); + break; + } + } + return plural; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h index 12bc13db38f2..dc668fdab7b6 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.h +++ b/tools/aapt2/unflatten/BinaryResourceParser.h @@ -17,16 +17,17 @@ #ifndef AAPT_BINARY_RESOURCE_PARSER_H #define AAPT_BINARY_RESOURCE_PARSER_H +#include <string> + +#include "android-base/macros.h" +#include "androidfw/ResourceTypes.h" + #include "ResourceTable.h" #include "ResourceValues.h" #include "Source.h" - #include "process/IResourceTableConsumer.h" #include "util/Util.h" -#include <androidfw/ResourceTypes.h> -#include <string> - namespace aapt { struct SymbolTable_entry; @@ -39,80 +40,86 @@ struct SymbolTable_entry; * chunks and types. */ class BinaryResourceParser { -public: - /* - * Creates a parser, which will read `len` bytes from `data`, and - * add any resources parsed to `table`. `source` is for logging purposes. - */ - BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source, - const void* data, size_t dataLen); - - BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy. - - /* - * Parses the binary resource table and returns true if successful. - */ - bool parse(); - -private: - bool parseTable(const android::ResChunk_header* chunk); - bool parsePackage(const android::ResChunk_header* chunk); - bool parseTypeSpec(const android::ResChunk_header* chunk); - bool parseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk); - - std::unique_ptr<Item> parseValue(const ResourceNameRef& name, const ConfigDescription& config, - const android::Res_value* value, uint16_t flags); - - std::unique_ptr<Value> parseMapEntry(const ResourceNameRef& name, - const ConfigDescription& config, - const android::ResTable_map_entry* map); - - std::unique_ptr<Style> parseStyle(const ResourceNameRef& name, const ConfigDescription& config, + public: + /* + * Creates a parser, which will read `len` bytes from `data`, and + * add any resources parsed to `table`. `source` is for logging purposes. + */ + BinaryResourceParser(IAaptContext* context, ResourceTable* table, + const Source& source, const void* data, size_t data_len); + + /* + * Parses the binary resource table and returns true if successful. + */ + bool Parse(); + + private: + DISALLOW_COPY_AND_ASSIGN(BinaryResourceParser); + + bool ParseTable(const android::ResChunk_header* chunk); + bool ParsePackage(const android::ResChunk_header* chunk); + bool ParseTypeSpec(const android::ResChunk_header* chunk); + bool ParseType(const ResourceTablePackage* package, + const android::ResChunk_header* chunk); + + std::unique_ptr<Item> ParseValue(const ResourceNameRef& name, + const ConfigDescription& config, + const android::Res_value* value, + uint16_t flags); + + std::unique_ptr<Value> ParseMapEntry(const ResourceNameRef& name, + const ConfigDescription& config, + const android::ResTable_map_entry* map); + + std::unique_ptr<Style> ParseStyle(const ResourceNameRef& name, + const ConfigDescription& config, + const android::ResTable_map_entry* map); + + std::unique_ptr<Attribute> ParseAttr(const ResourceNameRef& name, + const ConfigDescription& config, + const android::ResTable_map_entry* map); + + std::unique_ptr<Array> ParseArray(const ResourceNameRef& name, + const ConfigDescription& config, + const android::ResTable_map_entry* map); + + std::unique_ptr<Plural> ParsePlural(const ResourceNameRef& name, + const ConfigDescription& config, const android::ResTable_map_entry* map); - std::unique_ptr<Attribute> parseAttr(const ResourceNameRef& name, - const ConfigDescription& config, - const android::ResTable_map_entry* map); - - std::unique_ptr<Array> parseArray(const ResourceNameRef& name, const ConfigDescription& config, - const android::ResTable_map_entry* map); - - std::unique_ptr<Plural> parsePlural(const ResourceNameRef& name, - const ConfigDescription& config, - const android::ResTable_map_entry* map); - - /** - * If the mapEntry is a special type that denotes meta data (source, comment), then it is - * read and added to the Value. - * Returns true if the mapEntry was meta data. - */ - bool collectMetaData(const android::ResTable_map& mapEntry, Value* value); + /** + * If the mapEntry is a special type that denotes meta data (source, comment), + * then it is + * read and added to the Value. + * Returns true if the mapEntry was meta data. + */ + bool CollectMetaData(const android::ResTable_map& map_entry, Value* value); - IAaptContext* mContext; - ResourceTable* mTable; + IAaptContext* context_; + ResourceTable* table_; - const Source mSource; + const Source source_; - const void* mData; - const size_t mDataLen; + const void* data_; + const size_t data_len_; - // The standard value string pool for resource values. - android::ResStringPool mValuePool; + // The standard value string pool for resource values. + android::ResStringPool value_pool_; - // The string pool that holds the names of the types defined - // in this table. - android::ResStringPool mTypePool; + // The string pool that holds the names of the types defined + // in this table. + android::ResStringPool type_pool_; - // The string pool that holds the names of the entries defined - // in this table. - android::ResStringPool mKeyPool; + // The string pool that holds the names of the entries defined + // in this table. + android::ResStringPool key_pool_; - // A mapping of resource ID to resource name. When we finish parsing - // we use this to convert all resource IDs to symbolic references. - std::map<ResourceId, ResourceName> mIdIndex; + // A mapping of resource ID to resource name. When we finish parsing + // we use this to convert all resource IDs to symbolic references. + std::map<ResourceId, ResourceName> id_index_; }; -} // namespace aapt +} // namespace aapt namespace android { @@ -121,13 +128,14 @@ namespace android { */ inline const ResTable_map* begin(const ResTable_map_entry* map) { - return (const ResTable_map*)((const uint8_t*) map + aapt::util::deviceToHost32(map->size)); + return (const ResTable_map*)((const uint8_t*)map + + aapt::util::DeviceToHost32(map->size)); } inline const ResTable_map* end(const ResTable_map_entry* map) { - return begin(map) + aapt::util::deviceToHost32(map->count); + return begin(map) + aapt::util::DeviceToHost32(map->count); } -} // namespace android +} // namespace android -#endif // AAPT_BINARY_RESOURCE_PARSER_H +#endif // AAPT_BINARY_RESOURCE_PARSER_H diff --git a/tools/aapt2/unflatten/ResChunkPullParser.cpp b/tools/aapt2/unflatten/ResChunkPullParser.cpp index 6f8bb1b29b62..5d71ff315874 100644 --- a/tools/aapt2/unflatten/ResChunkPullParser.cpp +++ b/tools/aapt2/unflatten/ResChunkPullParser.cpp @@ -15,55 +15,60 @@ */ #include "unflatten/ResChunkPullParser.h" -#include "util/Util.h" -#include <androidfw/ResourceTypes.h> #include <cstddef> +#include "android-base/logging.h" +#include "androidfw/ResourceTypes.h" + +#include "util/Util.h" + namespace aapt { using android::ResChunk_header; -ResChunkPullParser::Event ResChunkPullParser::next() { - if (!isGoodEvent(mEvent)) { - return mEvent; - } +ResChunkPullParser::Event ResChunkPullParser::Next() { + if (!IsGoodEvent(event_)) { + return event_; + } - if (mEvent == Event::StartDocument) { - mCurrentChunk = mData; - } else { - mCurrentChunk = (const ResChunk_header*) - (((const char*) mCurrentChunk) + util::deviceToHost32(mCurrentChunk->size)); - } + if (event_ == Event::kStartDocument) { + current_chunk_ = data_; + } else { + current_chunk_ = + (const ResChunk_header*)(((const char*)current_chunk_) + + util::DeviceToHost32(current_chunk_->size)); + } - const std::ptrdiff_t diff = (const char*) mCurrentChunk - (const char*) mData; - assert(diff >= 0 && "diff is negative"); - const size_t offset = static_cast<const size_t>(diff); + const std::ptrdiff_t diff = (const char*)current_chunk_ - (const char*)data_; + CHECK(diff >= 0) << "diff is negative"; + const size_t offset = static_cast<const size_t>(diff); - if (offset == mLen) { - mCurrentChunk = nullptr; - return (mEvent = Event::EndDocument); - } else if (offset + sizeof(ResChunk_header) > mLen) { - mLastError = "chunk is past the end of the document"; - mCurrentChunk = nullptr; - return (mEvent = Event::BadDocument); - } + if (offset == len_) { + current_chunk_ = nullptr; + return (event_ = Event::kEndDocument); + } else if (offset + sizeof(ResChunk_header) > len_) { + error_ = "chunk is past the end of the document"; + current_chunk_ = nullptr; + return (event_ = Event::kBadDocument); + } - if (util::deviceToHost16(mCurrentChunk->headerSize) < sizeof(ResChunk_header)) { - mLastError = "chunk has too small header"; - mCurrentChunk = nullptr; - return (mEvent = Event::BadDocument); - } else if (util::deviceToHost32(mCurrentChunk->size) < - util::deviceToHost16(mCurrentChunk->headerSize)) { - mLastError = "chunk's total size is smaller than header"; - mCurrentChunk = nullptr; - return (mEvent = Event::BadDocument); - } else if (offset + util::deviceToHost32(mCurrentChunk->size) > mLen) { - mLastError = "chunk's data extends past the end of the document"; - mCurrentChunk = nullptr; - return (mEvent = Event::BadDocument); - } - return (mEvent = Event::Chunk); + if (util::DeviceToHost16(current_chunk_->headerSize) < + sizeof(ResChunk_header)) { + error_ = "chunk has too small header"; + current_chunk_ = nullptr; + return (event_ = Event::kBadDocument); + } else if (util::DeviceToHost32(current_chunk_->size) < + util::DeviceToHost16(current_chunk_->headerSize)) { + error_ = "chunk's total size is smaller than header"; + current_chunk_ = nullptr; + return (event_ = Event::kBadDocument); + } else if (offset + util::DeviceToHost32(current_chunk_->size) > len_) { + error_ = "chunk's data extends past the end of the document"; + current_chunk_ = nullptr; + return (event_ = Event::kBadDocument); + } + return (event_ = Event::kChunk); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/unflatten/ResChunkPullParser.h b/tools/aapt2/unflatten/ResChunkPullParser.h index a51d5bfdc9b3..437fc5c8a9b6 100644 --- a/tools/aapt2/unflatten/ResChunkPullParser.h +++ b/tools/aapt2/unflatten/ResChunkPullParser.h @@ -17,11 +17,13 @@ #ifndef AAPT_RES_CHUNK_PULL_PARSER_H #define AAPT_RES_CHUNK_PULL_PARSER_H -#include "util/Util.h" - -#include <androidfw/ResourceTypes.h> #include <string> +#include "android-base/macros.h" +#include "androidfw/ResourceTypes.h" + +#include "util/Util.h" + namespace aapt { /** @@ -37,88 +39,88 @@ namespace aapt { * pointing to the data portion of a chunk. */ class ResChunkPullParser { -public: - enum class Event { - StartDocument, - EndDocument, - BadDocument, - - Chunk, - }; - - /** - * Returns false if the event is EndDocument or BadDocument. - */ - static bool isGoodEvent(Event event); - - /** - * Create a ResChunkPullParser to read android::ResChunk_headers - * from the memory pointed to by data, of len bytes. - */ - ResChunkPullParser(const void* data, size_t len); - - ResChunkPullParser(const ResChunkPullParser&) = delete; - - Event getEvent() const; - const std::string& getLastError() const; - const android::ResChunk_header* getChunk() const; - - /** - * Move to the next android::ResChunk_header. - */ - Event next(); - -private: - Event mEvent; - const android::ResChunk_header* mData; - size_t mLen; - const android::ResChunk_header* mCurrentChunk; - std::string mLastError; + public: + enum class Event { + kStartDocument, + kEndDocument, + kBadDocument, + + kChunk, + }; + + /** + * Returns false if the event is EndDocument or BadDocument. + */ + static bool IsGoodEvent(Event event); + + /** + * Create a ResChunkPullParser to read android::ResChunk_headers + * from the memory pointed to by data, of len bytes. + */ + ResChunkPullParser(const void* data, size_t len); + + Event event() const; + const std::string& error() const; + const android::ResChunk_header* chunk() const; + + /** + * Move to the next android::ResChunk_header. + */ + Event Next(); + + private: + DISALLOW_COPY_AND_ASSIGN(ResChunkPullParser); + + Event event_; + const android::ResChunk_header* data_; + size_t len_; + const android::ResChunk_header* current_chunk_; + std::string error_; }; template <typename T> -inline static const T* convertTo(const android::ResChunk_header* chunk) { - if (util::deviceToHost16(chunk->headerSize) < sizeof(T)) { - return nullptr; - } - return reinterpret_cast<const T*>(chunk); +inline static const T* ConvertTo(const android::ResChunk_header* chunk) { + if (util::DeviceToHost16(chunk->headerSize) < sizeof(T)) { + return nullptr; + } + return reinterpret_cast<const T*>(chunk); } -inline static const uint8_t* getChunkData(const android::ResChunk_header* chunk) { - return reinterpret_cast<const uint8_t*>(chunk) + util::deviceToHost16(chunk->headerSize); +inline static const uint8_t* GetChunkData( + const android::ResChunk_header* chunk) { + return reinterpret_cast<const uint8_t*>(chunk) + + util::DeviceToHost16(chunk->headerSize); } -inline static uint32_t getChunkDataLen(const android::ResChunk_header* chunk) { - return util::deviceToHost32(chunk->size) - util::deviceToHost16(chunk->headerSize); +inline static uint32_t GetChunkDataLen(const android::ResChunk_header* chunk) { + return util::DeviceToHost32(chunk->size) - + util::DeviceToHost16(chunk->headerSize); } // // Implementation // -inline bool ResChunkPullParser::isGoodEvent(ResChunkPullParser::Event event) { - return event != Event::EndDocument && event != Event::BadDocument; +inline bool ResChunkPullParser::IsGoodEvent(ResChunkPullParser::Event event) { + return event != Event::kEndDocument && event != Event::kBadDocument; } -inline ResChunkPullParser::ResChunkPullParser(const void* data, size_t len) : - mEvent(Event::StartDocument), - mData(reinterpret_cast<const android::ResChunk_header*>(data)), - mLen(len), - mCurrentChunk(nullptr) { -} +inline ResChunkPullParser::ResChunkPullParser(const void* data, size_t len) + : event_(Event::kStartDocument), + data_(reinterpret_cast<const android::ResChunk_header*>(data)), + len_(len), + current_chunk_(nullptr) {} -inline ResChunkPullParser::Event ResChunkPullParser::getEvent() const { - return mEvent; +inline ResChunkPullParser::Event ResChunkPullParser::event() const { + return event_; } -inline const std::string& ResChunkPullParser::getLastError() const { - return mLastError; -} +inline const std::string& ResChunkPullParser::error() const { return error_; } -inline const android::ResChunk_header* ResChunkPullParser::getChunk() const { - return mCurrentChunk; +inline const android::ResChunk_header* ResChunkPullParser::chunk() const { + return current_chunk_; } -} // namespace aapt +} // namespace aapt -#endif // AAPT_RES_CHUNK_PULL_PARSER_H +#endif // AAPT_RES_CHUNK_PULL_PARSER_H diff --git a/tools/aapt2/util/BigBuffer.cpp b/tools/aapt2/util/BigBuffer.cpp index c88e3c102415..ef99dca286a4 100644 --- a/tools/aapt2/util/BigBuffer.cpp +++ b/tools/aapt2/util/BigBuffer.cpp @@ -20,33 +20,60 @@ #include <memory> #include <vector> +#include "android-base/logging.h" + namespace aapt { -void* BigBuffer::nextBlockImpl(size_t size) { - if (!mBlocks.empty()) { - Block& block = mBlocks.back(); - if (block.mBlockSize - block.size >= size) { - void* outBuffer = block.buffer.get() + block.size; - block.size += size; - mSize += size; - return outBuffer; - } +void* BigBuffer::NextBlockImpl(size_t size) { + if (!blocks_.empty()) { + Block& block = blocks_.back(); + if (block.block_size_ - block.size >= size) { + void* out_buffer = block.buffer.get() + block.size; + block.size += size; + size_ += size; + return out_buffer; } + } + + const size_t actual_size = std::max(block_size_, size); - const size_t actualSize = std::max(mBlockSize, size); + Block block = {}; - Block block = {}; + // Zero-allocate the block's buffer. + block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[actual_size]()); + CHECK(block.buffer); - // Zero-allocate the block's buffer. - block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[actualSize]()); - assert(block.buffer); + block.size = size; + block.block_size_ = actual_size; - block.size = size; - block.mBlockSize = actualSize; + blocks_.push_back(std::move(block)); + size_ += size; + return blocks_.back().buffer.get(); +} + +void* BigBuffer::NextBlock(size_t* out_size) { + if (!blocks_.empty()) { + Block& block = blocks_.back(); + if (block.size != block.block_size_) { + void* out_buffer = block.buffer.get() + block.size; + size_t size = block.block_size_ - block.size; + block.size = block.block_size_; + size_ += size; + *out_size = size; + return out_buffer; + } + } - mBlocks.push_back(std::move(block)); - mSize += size; - return mBlocks.back().buffer.get(); + // Zero-allocate the block's buffer. + Block block = {}; + block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[block_size_]()); + CHECK(block.buffer); + block.size = block_size_; + block.block_size_ = block_size_; + blocks_.push_back(std::move(block)); + size_ += block_size_; + *out_size = block_size_; + return blocks_.back().buffer.get(); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/util/BigBuffer.h b/tools/aapt2/util/BigBuffer.h index cad2a2e63be1..d23c41d4d6f0 100644 --- a/tools/aapt2/util/BigBuffer.h +++ b/tools/aapt2/util/BigBuffer.h @@ -17,12 +17,14 @@ #ifndef AAPT_BIG_BUFFER_H #define AAPT_BIG_BUFFER_H -#include <cassert> #include <cstring> #include <memory> #include <type_traits> #include <vector> +#include "android-base/logging.h" +#include "android-base/macros.h" + namespace aapt { /** @@ -32,130 +34,153 @@ namespace aapt { * block is allocated and appended to the end of the list. */ class BigBuffer { -public: - /** - * A contiguous block of allocated memory. - */ - struct Block { - /** - * Pointer to the memory. - */ - std::unique_ptr<uint8_t[]> buffer; - - /** - * Size of memory that is currently occupied. The actual - * allocation may be larger. - */ - size_t size; - - private: - friend class BigBuffer; - - /** - * The size of the memory block allocation. - */ - size_t mBlockSize; - }; - - typedef std::vector<Block>::const_iterator const_iterator; - - /** - * Create a BigBuffer with block allocation sizes - * of blockSize. - */ - BigBuffer(size_t blockSize); - - BigBuffer(const BigBuffer&) = delete; // No copying. - - BigBuffer(BigBuffer&& rhs); - - /** - * Number of occupied bytes in all the allocated blocks. - */ - size_t size() const; - - /** - * Returns a pointer to an array of T, where T is - * a POD type. The elements are zero-initialized. - */ - template <typename T> - T* nextBlock(size_t count = 1); - - /** - * Moves the specified BigBuffer into this one. When this method - * returns, buffer is empty. - */ - void appendBuffer(BigBuffer&& buffer); - + public: + /** + * A contiguous block of allocated memory. + */ + struct Block { /** - * Pads the block with 'bytes' bytes of zero values. + * Pointer to the memory. */ - void pad(size_t bytes); + std::unique_ptr<uint8_t[]> buffer; /** - * Pads the block so that it aligns on a 4 byte boundary. + * Size of memory that is currently occupied. The actual + * allocation may be larger. */ - void align4(); + size_t size; - const_iterator begin() const; - const_iterator end() const; + private: + friend class BigBuffer; -private: /** - * Returns a pointer to a buffer of the requested size. - * The buffer is zero-initialized. + * The size of the memory block allocation. */ - void* nextBlockImpl(size_t size); - - size_t mBlockSize; - size_t mSize; - std::vector<Block> mBlocks; + size_t block_size_; + }; + + typedef std::vector<Block>::const_iterator const_iterator; + + /** + * Create a BigBuffer with block allocation sizes + * of block_size. + */ + explicit BigBuffer(size_t block_size); + + BigBuffer(BigBuffer&& rhs); + + /** + * Number of occupied bytes in all the allocated blocks. + */ + size_t size() const; + + /** + * Returns a pointer to an array of T, where T is + * a POD type. The elements are zero-initialized. + */ + template <typename T> + T* NextBlock(size_t count = 1); + + /** + * Returns the next block available and puts the size in out_count. + * This is useful for grabbing blocks where the size doesn't matter. + * Use BackUp() to give back any bytes that were not used. + */ + void* NextBlock(size_t* out_count); + + /** + * Backs up count bytes. This must only be called after NextBlock() + * and can not be larger than sizeof(T) * count of the last NextBlock() + * call. + */ + void BackUp(size_t count); + + /** + * Moves the specified BigBuffer into this one. When this method + * returns, buffer is empty. + */ + void AppendBuffer(BigBuffer&& buffer); + + /** + * Pads the block with 'bytes' bytes of zero values. + */ + void Pad(size_t bytes); + + /** + * Pads the block so that it aligns on a 4 byte boundary. + */ + void Align4(); + + size_t block_size() const; + + const_iterator begin() const; + const_iterator end() const; + + private: + DISALLOW_COPY_AND_ASSIGN(BigBuffer); + + /** + * Returns a pointer to a buffer of the requested size. + * The buffer is zero-initialized. + */ + void* NextBlockImpl(size_t size); + + size_t block_size_; + size_t size_; + std::vector<Block> blocks_; }; -inline BigBuffer::BigBuffer(size_t blockSize) : mBlockSize(blockSize), mSize(0) { -} +inline BigBuffer::BigBuffer(size_t block_size) + : block_size_(block_size), size_(0) {} -inline BigBuffer::BigBuffer(BigBuffer&& rhs) : - mBlockSize(rhs.mBlockSize), mSize(rhs.mSize), mBlocks(std::move(rhs.mBlocks)) { -} +inline BigBuffer::BigBuffer(BigBuffer&& rhs) + : block_size_(rhs.block_size_), + size_(rhs.size_), + blocks_(std::move(rhs.blocks_)) {} -inline size_t BigBuffer::size() const { - return mSize; -} +inline size_t BigBuffer::size() const { return size_; } + +inline size_t BigBuffer::block_size() const { return block_size_; } template <typename T> -inline T* BigBuffer::nextBlock(size_t count) { - static_assert(std::is_standard_layout<T>::value, "T must be standard_layout type"); - assert(count != 0); - return reinterpret_cast<T*>(nextBlockImpl(sizeof(T) * count)); +inline T* BigBuffer::NextBlock(size_t count) { + static_assert(std::is_standard_layout<T>::value, + "T must be standard_layout type"); + CHECK(count != 0); + return reinterpret_cast<T*>(NextBlockImpl(sizeof(T) * count)); } -inline void BigBuffer::appendBuffer(BigBuffer&& buffer) { - std::move(buffer.mBlocks.begin(), buffer.mBlocks.end(), std::back_inserter(mBlocks)); - mSize += buffer.mSize; - buffer.mBlocks.clear(); - buffer.mSize = 0; +inline void BigBuffer::BackUp(size_t count) { + Block& block = blocks_.back(); + block.size -= count; + size_ -= count; } -inline void BigBuffer::pad(size_t bytes) { - nextBlock<char>(bytes); +inline void BigBuffer::AppendBuffer(BigBuffer&& buffer) { + std::move(buffer.blocks_.begin(), buffer.blocks_.end(), + std::back_inserter(blocks_)); + size_ += buffer.size_; + buffer.blocks_.clear(); + buffer.size_ = 0; } -inline void BigBuffer::align4() { - const size_t unaligned = mSize % 4; - if (unaligned != 0) { - pad(4 - unaligned); - } +inline void BigBuffer::Pad(size_t bytes) { NextBlock<char>(bytes); } + +inline void BigBuffer::Align4() { + const size_t unaligned = size_ % 4; + if (unaligned != 0) { + Pad(4 - unaligned); + } } inline BigBuffer::const_iterator BigBuffer::begin() const { - return mBlocks.begin(); + return blocks_.begin(); } inline BigBuffer::const_iterator BigBuffer::end() const { - return mBlocks.end(); + return blocks_.end(); } -} // namespace aapt +} // namespace aapt -#endif // AAPT_BIG_BUFFER_H +#endif // AAPT_BIG_BUFFER_H diff --git a/tools/aapt2/util/BigBuffer_test.cpp b/tools/aapt2/util/BigBuffer_test.cpp index 2a24f123e18e..12c0b3ee3214 100644 --- a/tools/aapt2/util/BigBuffer_test.cpp +++ b/tools/aapt2/util/BigBuffer_test.cpp @@ -16,83 +16,83 @@ #include "util/BigBuffer.h" -#include <gtest/gtest.h> +#include "test/Test.h" namespace aapt { TEST(BigBufferTest, AllocateSingleBlock) { - BigBuffer buffer(4); + BigBuffer buffer(4); - EXPECT_NE(nullptr, buffer.nextBlock<char>(2)); - EXPECT_EQ(2u, buffer.size()); + EXPECT_NE(nullptr, buffer.NextBlock<char>(2)); + EXPECT_EQ(2u, buffer.size()); } TEST(BigBufferTest, ReturnSameBlockIfNextAllocationFits) { - BigBuffer buffer(16); + BigBuffer buffer(16); - char* b1 = buffer.nextBlock<char>(8); - EXPECT_NE(nullptr, b1); + char* b1 = buffer.NextBlock<char>(8); + EXPECT_NE(nullptr, b1); - char* b2 = buffer.nextBlock<char>(4); - EXPECT_NE(nullptr, b2); + char* b2 = buffer.NextBlock<char>(4); + EXPECT_NE(nullptr, b2); - EXPECT_EQ(b1 + 8, b2); + EXPECT_EQ(b1 + 8, b2); } TEST(BigBufferTest, AllocateExactSizeBlockIfLargerThanBlockSize) { - BigBuffer buffer(16); + BigBuffer buffer(16); - EXPECT_NE(nullptr, buffer.nextBlock<char>(32)); - EXPECT_EQ(32u, buffer.size()); + EXPECT_NE(nullptr, buffer.NextBlock<char>(32)); + EXPECT_EQ(32u, buffer.size()); } TEST(BigBufferTest, AppendAndMoveBlock) { - BigBuffer buffer(16); + BigBuffer buffer(16); - uint32_t* b1 = buffer.nextBlock<uint32_t>(); - ASSERT_NE(nullptr, b1); - *b1 = 33; + uint32_t* b1 = buffer.NextBlock<uint32_t>(); + ASSERT_NE(nullptr, b1); + *b1 = 33; - { - BigBuffer buffer2(16); - b1 = buffer2.nextBlock<uint32_t>(); - ASSERT_NE(nullptr, b1); - *b1 = 44; + { + BigBuffer buffer2(16); + b1 = buffer2.NextBlock<uint32_t>(); + ASSERT_NE(nullptr, b1); + *b1 = 44; - buffer.appendBuffer(std::move(buffer2)); - EXPECT_EQ(0u, buffer2.size()); - EXPECT_EQ(buffer2.begin(), buffer2.end()); - } + buffer.AppendBuffer(std::move(buffer2)); + EXPECT_EQ(0u, buffer2.size()); + EXPECT_EQ(buffer2.begin(), buffer2.end()); + } - EXPECT_EQ(2 * sizeof(uint32_t), buffer.size()); + EXPECT_EQ(2 * sizeof(uint32_t), buffer.size()); - auto b = buffer.begin(); - ASSERT_NE(b, buffer.end()); - ASSERT_EQ(sizeof(uint32_t), b->size); - ASSERT_EQ(33u, *reinterpret_cast<uint32_t*>(b->buffer.get())); - ++b; + auto b = buffer.begin(); + ASSERT_NE(b, buffer.end()); + ASSERT_EQ(sizeof(uint32_t), b->size); + ASSERT_EQ(33u, *reinterpret_cast<uint32_t*>(b->buffer.get())); + ++b; - ASSERT_NE(b, buffer.end()); - ASSERT_EQ(sizeof(uint32_t), b->size); - ASSERT_EQ(44u, *reinterpret_cast<uint32_t*>(b->buffer.get())); - ++b; + ASSERT_NE(b, buffer.end()); + ASSERT_EQ(sizeof(uint32_t), b->size); + ASSERT_EQ(44u, *reinterpret_cast<uint32_t*>(b->buffer.get())); + ++b; - ASSERT_EQ(b, buffer.end()); + ASSERT_EQ(b, buffer.end()); } TEST(BigBufferTest, PadAndAlignProperly) { - BigBuffer buffer(16); - - ASSERT_NE(buffer.nextBlock<char>(2), nullptr); - ASSERT_EQ(2u, buffer.size()); - buffer.pad(2); - ASSERT_EQ(4u, buffer.size()); - buffer.align4(); - ASSERT_EQ(4u, buffer.size()); - buffer.pad(2); - ASSERT_EQ(6u, buffer.size()); - buffer.align4(); - ASSERT_EQ(8u, buffer.size()); + BigBuffer buffer(16); + + ASSERT_NE(buffer.NextBlock<char>(2), nullptr); + ASSERT_EQ(2u, buffer.size()); + buffer.Pad(2); + ASSERT_EQ(4u, buffer.size()); + buffer.Align4(); + ASSERT_EQ(4u, buffer.size()); + buffer.Pad(2); + ASSERT_EQ(6u, buffer.size()); + buffer.Align4(); + ASSERT_EQ(8u, buffer.size()); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp index 042ff0e6c1b9..f034607ea8f5 100644 --- a/tools/aapt2/util/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -15,15 +15,20 @@ */ #include "util/Files.h" -#include "util/Util.h" + +#include <dirent.h> +#include <sys/stat.h> #include <algorithm> -#include <android-base/file.h> #include <cerrno> #include <cstdio> -#include <dirent.h> #include <string> -#include <sys/stat.h> + +#include "android-base/errors.h" +#include "android-base/file.h" +#include "android-base/logging.h" + +#include "util/Util.h" #ifdef _WIN32 // Windows includes. @@ -33,244 +38,230 @@ namespace aapt { namespace file { -FileType getFileType(const StringPiece& path) { - struct stat sb; - if (stat(path.data(), &sb) < 0) { - if (errno == ENOENT || errno == ENOTDIR) { - return FileType::kNonexistant; - } - return FileType::kUnknown; +FileType GetFileType(const StringPiece& path) { + struct stat sb; + if (stat(path.data(), &sb) < 0) { + if (errno == ENOENT || errno == ENOTDIR) { + return FileType::kNonexistant; } + return FileType::kUnknown; + } - if (S_ISREG(sb.st_mode)) { - return FileType::kRegular; - } else if (S_ISDIR(sb.st_mode)) { - return FileType::kDirectory; - } else if (S_ISCHR(sb.st_mode)) { - return FileType::kCharDev; - } else if (S_ISBLK(sb.st_mode)) { - return FileType::kBlockDev; - } else if (S_ISFIFO(sb.st_mode)) { - return FileType::kFifo; + if (S_ISREG(sb.st_mode)) { + return FileType::kRegular; + } else if (S_ISDIR(sb.st_mode)) { + return FileType::kDirectory; + } else if (S_ISCHR(sb.st_mode)) { + return FileType::kCharDev; + } else if (S_ISBLK(sb.st_mode)) { + return FileType::kBlockDev; + } else if (S_ISFIFO(sb.st_mode)) { + return FileType::kFifo; #if defined(S_ISLNK) - } else if (S_ISLNK(sb.st_mode)) { - return FileType::kSymlink; + } else if (S_ISLNK(sb.st_mode)) { + return FileType::kSymlink; #endif #if defined(S_ISSOCK) - } else if (S_ISSOCK(sb.st_mode)) { - return FileType::kSocket; + } else if (S_ISSOCK(sb.st_mode)) { + return FileType::kSocket; #endif - } else { - return FileType::kUnknown; - } -} - -std::vector<std::string> listFiles(const StringPiece& root, std::string* outError) { - DIR* dir = opendir(root.data()); - if (dir == nullptr) { - if (outError) { - std::stringstream errorStr; - errorStr << "unable to open file: " << strerror(errno); - *outError = errorStr.str(); - } - return {}; - } - - std::vector<std::string> files; - dirent* entry; - while ((entry = readdir(dir))) { - files.emplace_back(entry->d_name); - } - - closedir(dir); - return files; + } else { + return FileType::kUnknown; + } } -inline static int mkdirImpl(const StringPiece& path) { +inline static int MkdirImpl(const StringPiece& path) { #ifdef _WIN32 - return _mkdir(path.toString().c_str()); + return _mkdir(path.ToString().c_str()); #else - return mkdir(path.toString().c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP); + return mkdir(path.ToString().c_str(), + S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP); #endif } bool mkdirs(const StringPiece& path) { - const char* start = path.begin(); - const char* end = path.end(); - for (const char* current = start; current != end; ++current) { - if (*current == sDirSep && current != start) { - StringPiece parentPath(start, current - start); - int result = mkdirImpl(parentPath); - if (result < 0 && errno != EEXIST) { - return false; - } - } + const char* start = path.begin(); + const char* end = path.end(); + for (const char* current = start; current != end; ++current) { + if (*current == sDirSep && current != start) { + StringPiece parent_path(start, current - start); + int result = MkdirImpl(parent_path); + if (result < 0 && errno != EEXIST) { + return false; + } } - return mkdirImpl(path) == 0 || errno == EEXIST; + } + return MkdirImpl(path) == 0 || errno == EEXIST; } -StringPiece getStem(const StringPiece& path) { - const char* start = path.begin(); - const char* end = path.end(); - for (const char* current = end - 1; current != start - 1; --current) { - if (*current == sDirSep) { - return StringPiece(start, current - start); - } +StringPiece GetStem(const StringPiece& path) { + const char* start = path.begin(); + const char* end = path.end(); + for (const char* current = end - 1; current != start - 1; --current) { + if (*current == sDirSep) { + return StringPiece(start, current - start); } - return {}; + } + return {}; } -StringPiece getFilename(const StringPiece& path) { - const char* end = path.end(); - const char* lastDirSep = path.begin(); - for (const char* c = path.begin(); c != end; ++c) { - if (*c == sDirSep) { - lastDirSep = c + 1; - } +StringPiece GetFilename(const StringPiece& path) { + const char* end = path.end(); + const char* last_dir_sep = path.begin(); + for (const char* c = path.begin(); c != end; ++c) { + if (*c == sDirSep) { + last_dir_sep = c + 1; } - return StringPiece(lastDirSep, end - lastDirSep); + } + return StringPiece(last_dir_sep, end - last_dir_sep); } -StringPiece getExtension(const StringPiece& path) { - StringPiece filename = getFilename(path); - const char* const end = filename.end(); - const char* c = std::find(filename.begin(), end, '.'); - if (c != end) { - return StringPiece(c, end - c); - } - return {}; +StringPiece GetExtension(const StringPiece& path) { + StringPiece filename = GetFilename(path); + const char* const end = filename.end(); + const char* c = std::find(filename.begin(), end, '.'); + if (c != end) { + return StringPiece(c, end - c); + } + return {}; } -void appendPath(std::string* base, StringPiece part) { - assert(base); - const bool baseHasTrailingSep = (!base->empty() && *(base->end() - 1) == sDirSep); - const bool partHasLeadingSep = (!part.empty() && *(part.begin()) == sDirSep); - if (baseHasTrailingSep && partHasLeadingSep) { - // Remove the part's leading sep - part = part.substr(1, part.size() - 1); - } else if (!baseHasTrailingSep && !partHasLeadingSep) { - // None of the pieces has a separator. - *base += sDirSep; - } - base->append(part.data(), part.size()); +void AppendPath(std::string* base, StringPiece part) { + CHECK(base != nullptr); + const bool base_has_trailing_sep = + (!base->empty() && *(base->end() - 1) == sDirSep); + const bool part_has_leading_sep = + (!part.empty() && *(part.begin()) == sDirSep); + if (base_has_trailing_sep && part_has_leading_sep) { + // Remove the part's leading sep + part = part.substr(1, part.size() - 1); + } else if (!base_has_trailing_sep && !part_has_leading_sep) { + // None of the pieces has a separator. + *base += sDirSep; + } + base->append(part.data(), part.size()); } -std::string packageToPath(const StringPiece& package) { - std::string outPath; - for (StringPiece part : util::tokenize<char>(package, '.')) { - appendPath(&outPath, part); - } - return outPath; +std::string PackageToPath(const StringPiece& package) { + std::string out_path; + for (StringPiece part : util::Tokenize(package, '.')) { + AppendPath(&out_path, part); + } + return out_path; } -Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError) { - std::unique_ptr<FILE, decltype(fclose)*> f = { fopen(path.data(), "rb"), fclose }; - if (!f) { - if (outError) *outError = strerror(errno); - return {}; - } +Maybe<android::FileMap> MmapPath(const StringPiece& path, + std::string* out_error) { + std::unique_ptr<FILE, decltype(fclose)*> f = {fopen(path.data(), "rb"), + fclose}; + if (!f) { + if (out_error) *out_error = android::base::SystemErrorCodeToString(errno); + return {}; + } - int fd = fileno(f.get()); + int fd = fileno(f.get()); - struct stat fileStats = {}; - if (fstat(fd, &fileStats) != 0) { - if (outError) *outError = strerror(errno); - return {}; - } + struct stat filestats = {}; + if (fstat(fd, &filestats) != 0) { + if (out_error) *out_error = android::base::SystemErrorCodeToString(errno); + return {}; + } - android::FileMap fileMap; - if (fileStats.st_size == 0) { - // mmap doesn't like a length of 0. Instead we return an empty FileMap. - return std::move(fileMap); - } + android::FileMap filemap; + if (filestats.st_size == 0) { + // mmap doesn't like a length of 0. Instead we return an empty FileMap. + return std::move(filemap); + } - if (!fileMap.create(path.data(), fd, 0, fileStats.st_size, true)) { - if (outError) *outError = strerror(errno); - return {}; - } - return std::move(fileMap); + if (!filemap.create(path.data(), fd, 0, filestats.st_size, true)) { + if (out_error) *out_error = android::base::SystemErrorCodeToString(errno); + return {}; + } + return std::move(filemap); } -bool appendArgsFromFile(const StringPiece& path, std::vector<std::string>* outArgList, - std::string* outError) { - std::string contents; - if (!android::base::ReadFileToString(path.toString(), &contents)) { - if (outError) *outError = "failed to read argument-list file"; - return false; - } +bool AppendArgsFromFile(const StringPiece& path, + std::vector<std::string>* out_arglist, + std::string* out_error) { + std::string contents; + if (!android::base::ReadFileToString(path.ToString(), &contents)) { + if (out_error) *out_error = "failed to read argument-list file"; + return false; + } - for (StringPiece line : util::tokenize<char>(contents, ' ')) { - line = util::trimWhitespace(line); - if (!line.empty()) { - outArgList->push_back(line.toString()); - } + for (StringPiece line : util::Tokenize(contents, ' ')) { + line = util::TrimWhitespace(line); + if (!line.empty()) { + out_arglist->push_back(line.ToString()); } - return true; + } + return true; } -bool FileFilter::setPattern(const StringPiece& pattern) { - mPatternTokens = util::splitAndLowercase(pattern, ':'); - return true; +bool FileFilter::SetPattern(const StringPiece& pattern) { + pattern_tokens_ = util::SplitAndLowercase(pattern, ':'); + return true; } bool FileFilter::operator()(const std::string& filename, FileType type) const { - if (filename == "." || filename == "..") { - return false; - } + if (filename == "." || filename == "..") { + return false; + } - const char kDir[] = "dir"; - const char kFile[] = "file"; - const size_t filenameLen = filename.length(); - bool chatty = true; - for (const std::string& token : mPatternTokens) { - const char* tokenStr = token.c_str(); - if (*tokenStr == '!') { - chatty = false; - tokenStr++; - } + const char kDir[] = "dir"; + const char kFile[] = "file"; + const size_t filename_len = filename.length(); + bool chatty = true; + for (const std::string& token : pattern_tokens_) { + const char* token_str = token.c_str(); + if (*token_str == '!') { + chatty = false; + token_str++; + } - if (strncasecmp(tokenStr, kDir, sizeof(kDir)) == 0) { - if (type != FileType::kDirectory) { - continue; - } - tokenStr += sizeof(kDir); - } + if (strncasecmp(token_str, kDir, sizeof(kDir)) == 0) { + if (type != FileType::kDirectory) { + continue; + } + token_str += sizeof(kDir); + } - if (strncasecmp(tokenStr, kFile, sizeof(kFile)) == 0) { - if (type != FileType::kRegular) { - continue; - } - tokenStr += sizeof(kFile); - } + if (strncasecmp(token_str, kFile, sizeof(kFile)) == 0) { + if (type != FileType::kRegular) { + continue; + } + token_str += sizeof(kFile); + } - bool ignore = false; - size_t n = strlen(tokenStr); - if (*tokenStr == '*') { - // Math suffix. - tokenStr++; - n--; - if (n <= filenameLen) { - ignore = strncasecmp(tokenStr, filename.c_str() + filenameLen - n, n) == 0; - } - } else if (n > 1 && tokenStr[n - 1] == '*') { - // Match prefix. - ignore = strncasecmp(tokenStr, filename.c_str(), n - 1) == 0; - } else { - ignore = strcasecmp(tokenStr, filename.c_str()) == 0; - } + bool ignore = false; + size_t n = strlen(token_str); + if (*token_str == '*') { + // Math suffix. + token_str++; + n--; + if (n <= filename_len) { + ignore = + strncasecmp(token_str, filename.c_str() + filename_len - n, n) == 0; + } + } else if (n > 1 && token_str[n - 1] == '*') { + // Match prefix. + ignore = strncasecmp(token_str, filename.c_str(), n - 1) == 0; + } else { + ignore = strcasecmp(token_str, filename.c_str()) == 0; + } - if (ignore) { - if (chatty) { - mDiag->warn(DiagMessage() << "skipping " - << (type == FileType::kDirectory ? "dir '" : "file '") - << filename << "' due to ignore pattern '" - << token << "'"); - } - return false; - } + if (ignore) { + if (chatty) { + diag_->Warn(DiagMessage() + << "skipping " + << (type == FileType::kDirectory ? "dir '" : "file '") + << filename << "' due to ignore pattern '" << token << "'"); + } + return false; } - return true; + } + return true; } -} // namespace file -} // namespace aapt +} // namespace file +} // namespace aapt diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h index 4d8a1feb63b1..a157dbd80893 100644 --- a/tools/aapt2/util/Files.h +++ b/tools/aapt2/util/Files.h @@ -17,18 +17,18 @@ #ifndef AAPT_FILES_H #define AAPT_FILES_H +#include <memory> +#include <string> +#include <vector> + +#include "android-base/macros.h" +#include "utils/FileMap.h" + #include "Diagnostics.h" #include "Maybe.h" #include "Source.h" - #include "util/StringPiece.h" -#include <utils/FileMap.h> -#include <cassert> -#include <memory> -#include <string> -#include <vector> - namespace aapt { namespace file { @@ -39,29 +39,23 @@ constexpr const char sDirSep = '/'; #endif enum class FileType { - kUnknown = 0, - kNonexistant, - kRegular, - kDirectory, - kCharDev, - kBlockDev, - kFifo, - kSymlink, - kSocket, + kUnknown = 0, + kNonexistant, + kRegular, + kDirectory, + kCharDev, + kBlockDev, + kFifo, + kSymlink, + kSocket, }; -FileType getFileType(const StringPiece& path); - -/* - * Lists files under the directory `root`. Files are listed - * with just their leaf (filename) names. - */ -std::vector<std::string> listFiles(const StringPiece& root); +FileType GetFileType(const StringPiece& path); /* * Appends a path to `base`, separated by the directory separator. */ -void appendPath(std::string* base, StringPiece part); +void AppendPath(std::string* base, StringPiece part); /* * Makes all the directories in `path`. The last element in the path @@ -72,73 +66,75 @@ bool mkdirs(const StringPiece& path); /** * Returns all but the last part of the path. */ -StringPiece getStem(const StringPiece& path); +StringPiece GetStem(const StringPiece& path); /** * Returns the last part of the path with extension. */ -StringPiece getFilename(const StringPiece& path); +StringPiece GetFilename(const StringPiece& path); /** * Returns the extension of the path. This is the entire string after * the first '.' of the last part of the path. */ -StringPiece getExtension(const StringPiece& path); +StringPiece GetExtension(const StringPiece& path); /** * Converts a package name (com.android.app) to a path: com/android/app */ -std::string packageToPath(const StringPiece& package); +std::string PackageToPath(const StringPiece& package); /** * Creates a FileMap for the file at path. */ -Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError); +Maybe<android::FileMap> MmapPath(const StringPiece& path, + std::string* out_error); /** * Reads the file at path and appends each line to the outArgList vector. */ -bool appendArgsFromFile(const StringPiece& path, std::vector<std::string>* outArgList, - std::string* outError); +bool AppendArgsFromFile(const StringPiece& path, + std::vector<std::string>* out_arglist, + std::string* out_error); /* * Filter that determines which resource files/directories are * processed by AAPT. Takes a pattern string supplied by the user. - * Pattern format is specified in the - * FileFilter::setPattern(const std::string&) method. + * Pattern format is specified in the FileFilter::SetPattern() method. */ class FileFilter { -public: - FileFilter(IDiagnostics* diag) : mDiag(diag) { - } - - /* - * Patterns syntax: - * - Delimiter is : - * - Entry can start with the flag ! to avoid printing a warning - * about the file being ignored. - * - Entry can have the flag "<dir>" to match only directories - * or <file> to match only files. Default is to match both. - * - Entry can be a simplified glob "<prefix>*" or "*<suffix>" - * where prefix/suffix must have at least 1 character (so that - * we don't match a '*' catch-all pattern.) - * - The special filenames "." and ".." are always ignored. - * - Otherwise the full string is matched. - * - match is not case-sensitive. - */ - bool setPattern(const StringPiece& pattern); - - /** - * Applies the filter, returning true for pass, false for fail. - */ - bool operator()(const std::string& filename, FileType type) const; - -private: - IDiagnostics* mDiag; - std::vector<std::string> mPatternTokens; + public: + explicit FileFilter(IDiagnostics* diag) : diag_(diag) {} + + /* + * Patterns syntax: + * - Delimiter is : + * - Entry can start with the flag ! to avoid printing a warning + * about the file being ignored. + * - Entry can have the flag "<dir>" to match only directories + * or <file> to match only files. Default is to match both. + * - Entry can be a simplified glob "<prefix>*" or "*<suffix>" + * where prefix/suffix must have at least 1 character (so that + * we don't match a '*' catch-all pattern.) + * - The special filenames "." and ".." are always ignored. + * - Otherwise the full string is matched. + * - match is not case-sensitive. + */ + bool SetPattern(const StringPiece& pattern); + + /** + * Applies the filter, returning true for pass, false for fail. + */ + bool operator()(const std::string& filename, FileType type) const; + + private: + DISALLOW_COPY_AND_ASSIGN(FileFilter); + + IDiagnostics* diag_; + std::vector<std::string> pattern_tokens_; }; -} // namespace file -} // namespace aapt +} // namespace file +} // namespace aapt -#endif // AAPT_FILES_H +#endif // AAPT_FILES_H diff --git a/tools/aapt2/util/Files_test.cpp b/tools/aapt2/util/Files_test.cpp index efb04593ff82..219c18397358 100644 --- a/tools/aapt2/util/Files_test.cpp +++ b/tools/aapt2/util/Files_test.cpp @@ -14,45 +14,46 @@ * limitations under the License. */ -#include "test/Test.h" #include "util/Files.h" #include <sstream> +#include "test/Test.h" + namespace aapt { namespace file { class FilesTest : public ::testing::Test { -public: - void SetUp() override { - std::stringstream builder; - builder << "hello" << sDirSep << "there"; - mExpectedPath = builder.str(); - } - -protected: - std::string mExpectedPath; + public: + void SetUp() override { + std::stringstream builder; + builder << "hello" << sDirSep << "there"; + expected_path_ = builder.str(); + } + + protected: + std::string expected_path_; }; -TEST_F(FilesTest, appendPath) { - std::string base = "hello"; - appendPath(&base, "there"); - EXPECT_EQ(mExpectedPath, base); +TEST_F(FilesTest, AppendPath) { + std::string base = "hello"; + AppendPath(&base, "there"); + EXPECT_EQ(expected_path_, base); } -TEST_F(FilesTest, appendPathWithLeadingOrTrailingSeparators) { - std::string base = "hello/"; - appendPath(&base, "there"); - EXPECT_EQ(mExpectedPath, base); +TEST_F(FilesTest, AppendPathWithLeadingOrTrailingSeparators) { + std::string base = "hello/"; + AppendPath(&base, "there"); + EXPECT_EQ(expected_path_, base); - base = "hello"; - appendPath(&base, "/there"); - EXPECT_EQ(mExpectedPath, base); + base = "hello"; + AppendPath(&base, "/there"); + EXPECT_EQ(expected_path_, base); - base = "hello/"; - appendPath(&base, "/there"); - EXPECT_EQ(mExpectedPath, base); + base = "hello/"; + AppendPath(&base, "/there"); + EXPECT_EQ(expected_path_, base); } -} // namespace files -} // namespace aapt +} // namespace files +} // namespace aapt diff --git a/tools/aapt2/util/ImmutableMap.h b/tools/aapt2/util/ImmutableMap.h index b1f9e9d2fb57..59858e492c4c 100644 --- a/tools/aapt2/util/ImmutableMap.h +++ b/tools/aapt2/util/ImmutableMap.h @@ -17,68 +17,66 @@ #ifndef AAPT_UTIL_IMMUTABLEMAP_H #define AAPT_UTIL_IMMUTABLEMAP_H -#include "util/TypeTraits.h" - #include <utility> #include <vector> +#include "util/TypeTraits.h" + namespace aapt { template <typename TKey, typename TValue> class ImmutableMap { - static_assert(is_comparable<TKey, TKey>::value, "key is not comparable"); - -private: - std::vector<std::pair<TKey, TValue>> mData; - - explicit ImmutableMap(std::vector<std::pair<TKey, TValue>> data) : mData(std::move(data)) { + static_assert(is_comparable<TKey, TKey>::value, "key is not comparable"); + + public: + using const_iterator = + typename std::vector<std::pair<TKey, TValue>>::const_iterator; + + ImmutableMap(ImmutableMap&&) = default; + ImmutableMap& operator=(ImmutableMap&&) = default; + + static ImmutableMap<TKey, TValue> CreatePreSorted( + std::initializer_list<std::pair<TKey, TValue>> list) { + return ImmutableMap( + std::vector<std::pair<TKey, TValue>>(list.begin(), list.end())); + } + + static ImmutableMap<TKey, TValue> CreateAndSort( + std::initializer_list<std::pair<TKey, TValue>> list) { + std::vector<std::pair<TKey, TValue>> data(list.begin(), list.end()); + std::sort(data.begin(), data.end()); + return ImmutableMap(std::move(data)); + } + + template <typename TKey2, typename = typename std::enable_if< + is_comparable<TKey, TKey2>::value>::type> + const_iterator find(const TKey2& key) const { + auto cmp = [](const std::pair<TKey, TValue>& candidate, + const TKey2& target) -> bool { + return candidate.first < target; + }; + + const_iterator end_iter = end(); + auto iter = std::lower_bound(data_.begin(), end_iter, key, cmp); + if (iter == end_iter || iter->first == key) { + return iter; } + return end_iter; + } -public: - using const_iterator = typename decltype(mData)::const_iterator; - - ImmutableMap(ImmutableMap&&) = default; - ImmutableMap& operator=(ImmutableMap&&) = default; + const_iterator begin() const { return data_.begin(); } - ImmutableMap(const ImmutableMap&) = delete; - ImmutableMap& operator=(const ImmutableMap&) = delete; + const_iterator end() const { return data_.end(); } - static ImmutableMap<TKey, TValue> createPreSorted( - std::initializer_list<std::pair<TKey, TValue>> list) { - return ImmutableMap(std::vector<std::pair<TKey, TValue>>(list.begin(), list.end())); - } - - static ImmutableMap<TKey, TValue> createAndSort( - std::initializer_list<std::pair<TKey, TValue>> list) { - std::vector<std::pair<TKey, TValue>> data(list.begin(), list.end()); - std::sort(data.begin(), data.end()); - return ImmutableMap(std::move(data)); - } + private: + DISALLOW_COPY_AND_ASSIGN(ImmutableMap); - template <typename TKey2, - typename = typename std::enable_if<is_comparable<TKey, TKey2>::value>::type> - const_iterator find(const TKey2& key) const { - auto cmp = [](const std::pair<TKey, TValue>& candidate, const TKey2& target) -> bool { - return candidate.first < target; - }; - - const_iterator endIter = end(); - auto iter = std::lower_bound(mData.begin(), endIter, key, cmp); - if (iter == endIter || iter->first == key) { - return iter; - } - return endIter; - } + explicit ImmutableMap(std::vector<std::pair<TKey, TValue>> data) + : data_(std::move(data)) {} - const_iterator begin() const { - return mData.begin(); - } - - const_iterator end() const { - return mData.end(); - } + std::vector<std::pair<TKey, TValue>> data_; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_UTIL_IMMUTABLEMAP_H */ diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h index 595db960d5e5..b43f8e87fd68 100644 --- a/tools/aapt2/util/Maybe.h +++ b/tools/aapt2/util/Maybe.h @@ -17,12 +17,13 @@ #ifndef AAPT_MAYBE_H #define AAPT_MAYBE_H -#include "util/TypeTraits.h" - -#include <cassert> #include <type_traits> #include <utility> +#include "android-base/logging.h" + +#include "util/TypeTraits.h" + namespace aapt { /** @@ -32,280 +33,292 @@ namespace aapt { */ template <typename T> class Maybe { -public: - /** - * Construct Nothing. - */ - Maybe(); + public: + /** + * Construct Nothing. + */ + Maybe(); + + ~Maybe(); - ~Maybe(); + Maybe(const Maybe& rhs); - Maybe(const Maybe& rhs); + template <typename U> + Maybe(const Maybe<U>& rhs); // NOLINT(implicit) - template <typename U> - Maybe(const Maybe<U>& rhs); + Maybe(Maybe&& rhs); - Maybe(Maybe&& rhs); + template <typename U> + Maybe(Maybe<U>&& rhs); // NOLINT(implicit) - template <typename U> - Maybe(Maybe<U>&& rhs); + Maybe& operator=(const Maybe& rhs); - Maybe& operator=(const Maybe& rhs); + template <typename U> + Maybe& operator=(const Maybe<U>& rhs); - template <typename U> - Maybe& operator=(const Maybe<U>& rhs); + Maybe& operator=(Maybe&& rhs); - Maybe& operator=(Maybe&& rhs); + template <typename U> + Maybe& operator=(Maybe<U>&& rhs); - template <typename U> - Maybe& operator=(Maybe<U>&& rhs); + /** + * Construct a Maybe holding a value. + */ + Maybe(const T& value); // NOLINT(implicit) - /** - * Construct a Maybe holding a value. - */ - Maybe(const T& value); + /** + * Construct a Maybe holding a value. + */ + Maybe(T&& value); // NOLINT(implicit) - /** - * Construct a Maybe holding a value. - */ - Maybe(T&& value); + /** + * True if this holds a value, false if + * it holds Nothing. + */ + explicit operator bool() const; - /** - * True if this holds a value, false if - * it holds Nothing. - */ - explicit operator bool() const; + /** + * Gets the value if one exists, or else + * panics. + */ + T& value(); - /** - * Gets the value if one exists, or else - * panics. - */ - T& value(); + /** + * Gets the value if one exists, or else + * panics. + */ + const T& value() const; - /** - * Gets the value if one exists, or else - * panics. - */ - const T& value() const; + T value_or_default(const T& def) const; -private: - template <typename U> - friend class Maybe; + private: + template <typename U> + friend class Maybe; - template <typename U> - Maybe& copy(const Maybe<U>& rhs); + template <typename U> + Maybe& copy(const Maybe<U>& rhs); - template <typename U> - Maybe& move(Maybe<U>&& rhs); + template <typename U> + Maybe& move(Maybe<U>&& rhs); - void destroy(); + void destroy(); - bool mNothing; + bool nothing_; - typename std::aligned_storage<sizeof(T), alignof(T)>::type mStorage; + typename std::aligned_storage<sizeof(T), alignof(T)>::type storage_; }; template <typename T> -Maybe<T>::Maybe() -: mNothing(true) { -} +Maybe<T>::Maybe() : nothing_(true) {} template <typename T> Maybe<T>::~Maybe() { - if (!mNothing) { - destroy(); - } + if (!nothing_) { + destroy(); + } } template <typename T> -Maybe<T>::Maybe(const Maybe& rhs) -: mNothing(rhs.mNothing) { - if (!rhs.mNothing) { - new (&mStorage) T(reinterpret_cast<const T&>(rhs.mStorage)); - } +Maybe<T>::Maybe(const Maybe& rhs) : nothing_(rhs.nothing_) { + if (!rhs.nothing_) { + new (&storage_) T(reinterpret_cast<const T&>(rhs.storage_)); + } } template <typename T> template <typename U> -Maybe<T>::Maybe(const Maybe<U>& rhs) -: mNothing(rhs.mNothing) { - if (!rhs.mNothing) { - new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage)); - } +Maybe<T>::Maybe(const Maybe<U>& rhs) : nothing_(rhs.nothing_) { + if (!rhs.nothing_) { + new (&storage_) T(reinterpret_cast<const U&>(rhs.storage_)); + } } template <typename T> -Maybe<T>::Maybe(Maybe&& rhs) -: mNothing(rhs.mNothing) { - if (!rhs.mNothing) { - rhs.mNothing = true; - - // Move the value from rhs. - new (&mStorage) T(std::move(reinterpret_cast<T&>(rhs.mStorage))); - rhs.destroy(); - } +Maybe<T>::Maybe(Maybe&& rhs) : nothing_(rhs.nothing_) { + if (!rhs.nothing_) { + rhs.nothing_ = true; + + // Move the value from rhs. + new (&storage_) T(std::move(reinterpret_cast<T&>(rhs.storage_))); + rhs.destroy(); + } } template <typename T> template <typename U> -Maybe<T>::Maybe(Maybe<U>&& rhs) -: mNothing(rhs.mNothing) { - if (!rhs.mNothing) { - rhs.mNothing = true; - - // Move the value from rhs. - new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage))); - rhs.destroy(); - } +Maybe<T>::Maybe(Maybe<U>&& rhs) : nothing_(rhs.nothing_) { + if (!rhs.nothing_) { + rhs.nothing_ = true; + + // Move the value from rhs. + new (&storage_) T(std::move(reinterpret_cast<U&>(rhs.storage_))); + rhs.destroy(); + } } template <typename T> inline Maybe<T>& Maybe<T>::operator=(const Maybe& rhs) { - // Delegate to the actual assignment. - return copy(rhs); + // Delegate to the actual assignment. + return copy(rhs); } template <typename T> template <typename U> inline Maybe<T>& Maybe<T>::operator=(const Maybe<U>& rhs) { - return copy(rhs); + return copy(rhs); } template <typename T> template <typename U> Maybe<T>& Maybe<T>::copy(const Maybe<U>& rhs) { - if (mNothing && rhs.mNothing) { - // Both are nothing, nothing to do. - return *this; - } else if (!mNothing && !rhs.mNothing) { - // We both are something, so assign rhs to us. - reinterpret_cast<T&>(mStorage) = reinterpret_cast<const U&>(rhs.mStorage); - } else if (mNothing) { - // We are nothing but rhs is something. - mNothing = rhs.mNothing; - - // Copy the value from rhs. - new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage)); - } else { - // We are something but rhs is nothing, so destroy our value. - mNothing = rhs.mNothing; - destroy(); - } + if (nothing_ && rhs.nothing_) { + // Both are nothing, nothing to do. return *this; + } else if (!nothing_ && !rhs.nothing_) { + // We both are something, so assign rhs to us. + reinterpret_cast<T&>(storage_) = reinterpret_cast<const U&>(rhs.storage_); + } else if (nothing_) { + // We are nothing but rhs is something. + nothing_ = rhs.nothing_; + + // Copy the value from rhs. + new (&storage_) T(reinterpret_cast<const U&>(rhs.storage_)); + } else { + // We are something but rhs is nothing, so destroy our value. + nothing_ = rhs.nothing_; + destroy(); + } + return *this; } template <typename T> inline Maybe<T>& Maybe<T>::operator=(Maybe&& rhs) { - // Delegate to the actual assignment. - return move(std::forward<Maybe<T>>(rhs)); + // Delegate to the actual assignment. + return move(std::forward<Maybe<T>>(rhs)); } template <typename T> template <typename U> inline Maybe<T>& Maybe<T>::operator=(Maybe<U>&& rhs) { - return move(std::forward<Maybe<U>>(rhs)); + return move(std::forward<Maybe<U>>(rhs)); } template <typename T> template <typename U> Maybe<T>& Maybe<T>::move(Maybe<U>&& rhs) { - if (mNothing && rhs.mNothing) { - // Both are nothing, nothing to do. - return *this; - } else if (!mNothing && !rhs.mNothing) { - // We both are something, so move assign rhs to us. - rhs.mNothing = true; - reinterpret_cast<T&>(mStorage) = std::move(reinterpret_cast<U&>(rhs.mStorage)); - rhs.destroy(); - } else if (mNothing) { - // We are nothing but rhs is something. - mNothing = false; - rhs.mNothing = true; - - // Move the value from rhs. - new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage))); - rhs.destroy(); - } else { - // We are something but rhs is nothing, so destroy our value. - mNothing = true; - destroy(); - } + if (nothing_ && rhs.nothing_) { + // Both are nothing, nothing to do. return *this; + } else if (!nothing_ && !rhs.nothing_) { + // We both are something, so move assign rhs to us. + rhs.nothing_ = true; + reinterpret_cast<T&>(storage_) = + std::move(reinterpret_cast<U&>(rhs.storage_)); + rhs.destroy(); + } else if (nothing_) { + // We are nothing but rhs is something. + nothing_ = false; + rhs.nothing_ = true; + + // Move the value from rhs. + new (&storage_) T(std::move(reinterpret_cast<U&>(rhs.storage_))); + rhs.destroy(); + } else { + // We are something but rhs is nothing, so destroy our value. + nothing_ = true; + destroy(); + } + return *this; } template <typename T> -Maybe<T>::Maybe(const T& value) -: mNothing(false) { - new (&mStorage) T(value); +Maybe<T>::Maybe(const T& value) : nothing_(false) { + new (&storage_) T(value); } template <typename T> -Maybe<T>::Maybe(T&& value) -: mNothing(false) { - new (&mStorage) T(std::forward<T>(value)); +Maybe<T>::Maybe(T&& value) : nothing_(false) { + new (&storage_) T(std::forward<T>(value)); } template <typename T> Maybe<T>::operator bool() const { - return !mNothing; + return !nothing_; } template <typename T> T& Maybe<T>::value() { - assert(!mNothing && "Maybe<T>::value() called on Nothing"); - return reinterpret_cast<T&>(mStorage); + CHECK(!nothing_) << "Maybe<T>::value() called on Nothing"; + return reinterpret_cast<T&>(storage_); } template <typename T> const T& Maybe<T>::value() const { - assert(!mNothing && "Maybe<T>::value() called on Nothing"); - return reinterpret_cast<const T&>(mStorage); + CHECK(!nothing_) << "Maybe<T>::value() called on Nothing"; + return reinterpret_cast<const T&>(storage_); +} + +template <typename T> +T Maybe<T>::value_or_default(const T& def) const { + if (nothing_) { + return def; + } + return reinterpret_cast<const T&>(storage_); } template <typename T> void Maybe<T>::destroy() { - reinterpret_cast<T&>(mStorage).~T(); + reinterpret_cast<T&>(storage_).~T(); } template <typename T> inline Maybe<typename std::remove_reference<T>::type> make_value(T&& value) { - return Maybe<typename std::remove_reference<T>::type>(std::forward<T>(value)); + return Maybe<typename std::remove_reference<T>::type>(std::forward<T>(value)); } template <typename T> inline Maybe<T> make_nothing() { - return Maybe<T>(); + return Maybe<T>(); } /** - * Define the == operator between Maybe<T> and Maybe<U> only if the operator T == U is defined. - * That way the compiler will show an error at the callsite when comparing two Maybe<> objects + * Define the == operator between Maybe<T> and Maybe<U> only if the operator T + * == U is defined. + * That way the compiler will show an error at the callsite when comparing two + * Maybe<> objects * whose inner types can't be compared. */ template <typename T, typename U> -typename std::enable_if< - has_eq_op<T, U>::value, - bool ->::type operator==(const Maybe<T>& a, const Maybe<U>& b) { - if (a && b) { - return a.value() == b.value(); - } else if (!a && !b) { - return true; - } - return false; +typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==( + const Maybe<T>& a, const Maybe<U>& b) { + if (a && b) { + return a.value() == b.value(); + } else if (!a && !b) { + return true; + } + return false; } /** * Same as operator== but negated. */ template <typename T, typename U> -typename std::enable_if< - has_eq_op<T, U>::value, - bool ->::type operator!=(const Maybe<T>& a, const Maybe<U>& b) { - return !(a == b); +typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator!=( + const Maybe<T>& a, const Maybe<U>& b) { + return !(a == b); +} + +template <typename T, typename U> +typename std::enable_if<has_lt_op<T, U>::value, bool>::type operator<( + const Maybe<T>& a, const Maybe<U>& b) { + if (a && b) { + return a.value() < b.value(); + } else if (!a && !b) { + return false; + } + return !a; } -} // namespace aapt +} // namespace aapt -#endif // AAPT_MAYBE_H +#endif // AAPT_MAYBE_H diff --git a/tools/aapt2/util/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp index 5d42dc3ac3ab..ca14793dd5c3 100644 --- a/tools/aapt2/util/Maybe_test.cpp +++ b/tools/aapt2/util/Maybe_test.cpp @@ -14,122 +14,116 @@ * limitations under the License. */ -#include "test/Common.h" #include "util/Maybe.h" -#include <gtest/gtest.h> #include <string> +#include "test/Test.h" + namespace aapt { struct Dummy { - Dummy() { - data = new int; - *data = 1; - std::cerr << "Construct Dummy{0x" << (void *) this - << "} with data=0x" << (void*) data - << std::endl; - } - - Dummy(const Dummy& rhs) { - data = nullptr; - if (rhs.data) { - data = new int; - *data = *rhs.data; - } - std::cerr << "CopyConstruct Dummy{0x" << (void *) this - << "} from Dummy{0x" << (const void*) &rhs - << "}" << std::endl; - } - - Dummy(Dummy&& rhs) { - data = rhs.data; - rhs.data = nullptr; - std::cerr << "MoveConstruct Dummy{0x" << (void *) this - << "} from Dummy{0x" << (const void*) &rhs - << "}" << std::endl; - } - - Dummy& operator=(const Dummy& rhs) { - delete data; - data = nullptr; - - if (rhs.data) { - data = new int; - *data = *rhs.data; - } - std::cerr << "CopyAssign Dummy{0x" << (void *) this - << "} from Dummy{0x" << (const void*) &rhs - << "}" << std::endl; - return *this; - } - - Dummy& operator=(Dummy&& rhs) { - delete data; - data = rhs.data; - rhs.data = nullptr; - std::cerr << "MoveAssign Dummy{0x" << (void *) this - << "} from Dummy{0x" << (const void*) &rhs - << "}" << std::endl; - return *this; + Dummy() { + data = new int; + *data = 1; + std::cerr << "Construct Dummy{0x" << (void*)this << "} with data=0x" + << (void*)data << std::endl; + } + + Dummy(const Dummy& rhs) { + data = nullptr; + if (rhs.data) { + data = new int; + *data = *rhs.data; } - - ~Dummy() { - std::cerr << "Destruct Dummy{0x" << (void *) this - << "} with data=0x" << (void*) data - << std::endl; - delete data; + std::cerr << "CopyConstruct Dummy{0x" << (void*)this << "} from Dummy{0x" + << (const void*)&rhs << "}" << std::endl; + } + + Dummy(Dummy&& rhs) { + data = rhs.data; + rhs.data = nullptr; + std::cerr << "MoveConstruct Dummy{0x" << (void*)this << "} from Dummy{0x" + << (const void*)&rhs << "}" << std::endl; + } + + Dummy& operator=(const Dummy& rhs) { + delete data; + data = nullptr; + + if (rhs.data) { + data = new int; + *data = *rhs.data; } - - int* data; + std::cerr << "CopyAssign Dummy{0x" << (void*)this << "} from Dummy{0x" + << (const void*)&rhs << "}" << std::endl; + return *this; + } + + Dummy& operator=(Dummy&& rhs) { + delete data; + data = rhs.data; + rhs.data = nullptr; + std::cerr << "MoveAssign Dummy{0x" << (void*)this << "} from Dummy{0x" + << (const void*)&rhs << "}" << std::endl; + return *this; + } + + ~Dummy() { + std::cerr << "Destruct Dummy{0x" << (void*)this << "} with data=0x" + << (void*)data << std::endl; + delete data; + } + + int* data; }; TEST(MaybeTest, MakeNothing) { - Maybe<int> val = make_nothing<int>(); - AAPT_EXPECT_FALSE(val); + Maybe<int> val = make_nothing<int>(); + AAPT_EXPECT_FALSE(val); - Maybe<std::string> val2 = make_nothing<std::string>(); - AAPT_EXPECT_FALSE(val2); + Maybe<std::string> val2 = make_nothing<std::string>(); + AAPT_EXPECT_FALSE(val2); - val2 = make_nothing<std::string>(); - AAPT_EXPECT_FALSE(val2); + val2 = make_nothing<std::string>(); + AAPT_EXPECT_FALSE(val2); } TEST(MaybeTest, MakeSomething) { - Maybe<int> val = make_value(23); - AAPT_ASSERT_TRUE(val); - EXPECT_EQ(23, val.value()); + Maybe<int> val = make_value(23); + AAPT_ASSERT_TRUE(val); + EXPECT_EQ(23, val.value()); - Maybe<std::string> val2 = make_value(std::string("hey")); - AAPT_ASSERT_TRUE(val2); - EXPECT_EQ(std::string("hey"), val2.value()); + Maybe<std::string> val2 = make_value(std::string("hey")); + AAPT_ASSERT_TRUE(val2); + EXPECT_EQ(std::string("hey"), val2.value()); } TEST(MaybeTest, Lifecycle) { - Maybe<Dummy> val = make_nothing<Dummy>(); + Maybe<Dummy> val = make_nothing<Dummy>(); - Maybe<Dummy> val2 = make_value(Dummy()); + Maybe<Dummy> val2 = make_value(Dummy()); } TEST(MaybeTest, MoveAssign) { - Maybe<Dummy> val; - { - Maybe<Dummy> val2 = Dummy(); - val = std::move(val2); - } + Maybe<Dummy> val; + { + Maybe<Dummy> val2 = Dummy(); + val = std::move(val2); + } } TEST(MaybeTest, Equality) { - Maybe<int> a = 1; - Maybe<int> b = 1; - Maybe<int> c; + Maybe<int> a = 1; + Maybe<int> b = 1; + Maybe<int> c; - Maybe<int> emptyA, emptyB; + Maybe<int> emptyA, emptyB; - EXPECT_EQ(a, b); - EXPECT_EQ(b, a); - EXPECT_NE(a, c); - EXPECT_EQ(emptyA, emptyB); + EXPECT_EQ(a, b); + EXPECT_EQ(b, a); + EXPECT_NE(a, c); + EXPECT_EQ(emptyA, emptyB); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/util/StringPiece.h b/tools/aapt2/util/StringPiece.h index f91bccc93019..5144b1f1fd6a 100644 --- a/tools/aapt2/util/StringPiece.h +++ b/tools/aapt2/util/StringPiece.h @@ -19,8 +19,10 @@ #include <ostream> #include <string> -#include <utils/String8.h> -#include <utils/Unicode.h> + +#include "utils/JenkinsHash.h" +#include "utils/String8.h" +#include "utils/Unicode.h" namespace aapt { @@ -34,42 +36,46 @@ namespace aapt { */ template <typename TChar> class BasicStringPiece { -public: - using const_iterator = const TChar*; - using difference_type = size_t; - - BasicStringPiece(); - BasicStringPiece(const BasicStringPiece<TChar>& str); - BasicStringPiece(const std::basic_string<TChar>& str); - BasicStringPiece(const TChar* str); - BasicStringPiece(const TChar* str, size_t len); - - BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs); - BasicStringPiece<TChar>& assign(const TChar* str, size_t len); - - BasicStringPiece<TChar> substr(size_t start, size_t len) const; - BasicStringPiece<TChar> substr(BasicStringPiece<TChar>::const_iterator begin, - BasicStringPiece<TChar>::const_iterator end) const; - - const TChar* data() const; - size_t length() const; - size_t size() const; - bool empty() const; - std::basic_string<TChar> toString() const; - - bool contains(const BasicStringPiece<TChar>& rhs) const; - int compare(const BasicStringPiece<TChar>& rhs) const; - bool operator<(const BasicStringPiece<TChar>& rhs) const; - bool operator>(const BasicStringPiece<TChar>& rhs) const; - bool operator==(const BasicStringPiece<TChar>& rhs) const; - bool operator!=(const BasicStringPiece<TChar>& rhs) const; - - const_iterator begin() const; - const_iterator end() const; - -private: - const TChar* mData; - size_t mLength; + public: + using const_iterator = const TChar*; + using difference_type = size_t; + + // End of string marker. + constexpr static const size_t npos = static_cast<size_t>(-1); + + BasicStringPiece(); + BasicStringPiece(const BasicStringPiece<TChar>& str); + BasicStringPiece(const std::basic_string<TChar>& str); // NOLINT(implicit) + BasicStringPiece(const TChar* str); // NOLINT(implicit) + BasicStringPiece(const TChar* str, size_t len); + + BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs); + BasicStringPiece<TChar>& assign(const TChar* str, size_t len); + + BasicStringPiece<TChar> substr(size_t start, size_t len = npos) const; + BasicStringPiece<TChar> substr( + BasicStringPiece<TChar>::const_iterator begin, + BasicStringPiece<TChar>::const_iterator end) const; + + const TChar* data() const; + size_t length() const; + size_t size() const; + bool empty() const; + std::basic_string<TChar> ToString() const; + + bool contains(const BasicStringPiece<TChar>& rhs) const; + int compare(const BasicStringPiece<TChar>& rhs) const; + bool operator<(const BasicStringPiece<TChar>& rhs) const; + bool operator>(const BasicStringPiece<TChar>& rhs) const; + bool operator==(const BasicStringPiece<TChar>& rhs) const; + bool operator!=(const BasicStringPiece<TChar>& rhs) const; + + const_iterator begin() const; + const_iterator end() const; + + private: + const TChar* data_; + size_t length_; }; using StringPiece = BasicStringPiece<char>; @@ -80,181 +86,213 @@ using StringPiece16 = BasicStringPiece<char16_t>; // template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece() : mData(nullptr) , mLength(0) { -} +constexpr const size_t BasicStringPiece<TChar>::npos; template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str) : - mData(str.mData), mLength(str.mLength) { -} +inline BasicStringPiece<TChar>::BasicStringPiece() + : data_(nullptr), length_(0) {} template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str) : - mData(str.data()), mLength(str.length()) { -} +inline BasicStringPiece<TChar>::BasicStringPiece( + const BasicStringPiece<TChar>& str) + : data_(str.data_), length_(str.length_) {} + +template <typename TChar> +inline BasicStringPiece<TChar>::BasicStringPiece( + const std::basic_string<TChar>& str) + : data_(str.data()), length_(str.length()) {} template <> -inline BasicStringPiece<char>::BasicStringPiece(const char* str) : - mData(str), mLength(str != nullptr ? strlen(str) : 0) { -} +inline BasicStringPiece<char>::BasicStringPiece(const char* str) + : data_(str), length_(str != nullptr ? strlen(str) : 0) {} template <> -inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str) : - mData(str), mLength(str != nullptr ? strlen16(str) : 0) { -} +inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str) + : data_(str), length_(str != nullptr ? strlen16(str) : 0) {} template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len) : - mData(str), mLength(len) { -} +inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len) + : data_(str), length_(len) {} template <typename TChar> inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::operator=( - const BasicStringPiece<TChar>& rhs) { - mData = rhs.mData; - mLength = rhs.mLength; - return *this; + const BasicStringPiece<TChar>& rhs) { + data_ = rhs.data_; + length_ = rhs.length_; + return *this; } template <typename TChar> -inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(const TChar* str, size_t len) { - mData = str; - mLength = len; - return *this; +inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign( + const TChar* str, size_t len) { + data_ = str; + length_ = len; + return *this; } - template <typename TChar> -inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(size_t start, size_t len) const { - if (start + len > mLength) { - return BasicStringPiece<TChar>(); - } - return BasicStringPiece<TChar>(mData + start, len); +inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr( + size_t start, size_t len) const { + if (len == npos) { + len = length_ - start; + } + + if (start > length_ || start + len > length_) { + return BasicStringPiece<TChar>(); + } + return BasicStringPiece<TChar>(data_ + start, len); } template <typename TChar> inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr( - BasicStringPiece<TChar>::const_iterator begin, - BasicStringPiece<TChar>::const_iterator end) const { - return BasicStringPiece<TChar>(begin, end - begin); + BasicStringPiece<TChar>::const_iterator begin, + BasicStringPiece<TChar>::const_iterator end) const { + return BasicStringPiece<TChar>(begin, end - begin); } template <typename TChar> inline const TChar* BasicStringPiece<TChar>::data() const { - return mData; + return data_; } template <typename TChar> inline size_t BasicStringPiece<TChar>::length() const { - return mLength; + return length_; } template <typename TChar> inline size_t BasicStringPiece<TChar>::size() const { - return mLength; + return length_; } template <typename TChar> inline bool BasicStringPiece<TChar>::empty() const { - return mLength == 0; + return length_ == 0; } template <typename TChar> -inline std::basic_string<TChar> BasicStringPiece<TChar>::toString() const { - return std::basic_string<TChar>(mData, mLength); +inline std::basic_string<TChar> BasicStringPiece<TChar>::ToString() const { + return std::basic_string<TChar>(data_, length_); } template <> -inline bool BasicStringPiece<char>::contains(const BasicStringPiece<char>& rhs) const { - if (!mData || !rhs.mData) { - return false; - } - if (rhs.mLength > mLength) { - return false; - } - return strstr(mData, rhs.mData) != nullptr; +inline bool BasicStringPiece<char>::contains( + const BasicStringPiece<char>& rhs) const { + if (!data_ || !rhs.data_) { + return false; + } + if (rhs.length_ > length_) { + return false; + } + return strstr(data_, rhs.data_) != nullptr; } template <> -inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const { - const char nullStr = '\0'; - const char* b1 = mData != nullptr ? mData : &nullStr; - const char* e1 = b1 + mLength; - const char* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr; - const char* e2 = b2 + rhs.mLength; - - while (b1 < e1 && b2 < e2) { - const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++); - if (d) { - return d; - } +inline int BasicStringPiece<char>::compare( + const BasicStringPiece<char>& rhs) const { + const char nullStr = '\0'; + const char* b1 = data_ != nullptr ? data_ : &nullStr; + const char* e1 = b1 + length_; + const char* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr; + const char* e2 = b2 + rhs.length_; + + while (b1 < e1 && b2 < e2) { + const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++); + if (d) { + return d; } - return static_cast<int>(mLength - rhs.mLength); + } + return static_cast<int>(length_ - rhs.length_); } -inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char16_t>& str) { - android::String8 utf8(str.data(), str.size()); - return out.write(utf8.string(), utf8.size()); +inline ::std::ostream& operator<<(::std::ostream& out, + const BasicStringPiece<char16_t>& str) { + android::String8 utf8(str.data(), str.size()); + return out.write(utf8.string(), utf8.size()); } template <> -inline bool BasicStringPiece<char16_t>::contains(const BasicStringPiece<char16_t>& rhs) const { - if (!mData || !rhs.mData) { - return false; - } - if (rhs.mLength > mLength) { - return false; - } - return strstr16(mData, rhs.mData) != nullptr; +inline bool BasicStringPiece<char16_t>::contains( + const BasicStringPiece<char16_t>& rhs) const { + if (!data_ || !rhs.data_) { + return false; + } + if (rhs.length_ > length_) { + return false; + } + return strstr16(data_, rhs.data_) != nullptr; } template <> -inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const { - const char16_t nullStr = u'\0'; - const char16_t* b1 = mData != nullptr ? mData : &nullStr; - const char16_t* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr; - return strzcmp16(b1, mLength, b2, rhs.mLength); +inline int BasicStringPiece<char16_t>::compare( + const BasicStringPiece<char16_t>& rhs) const { + const char16_t nullStr = u'\0'; + const char16_t* b1 = data_ != nullptr ? data_ : &nullStr; + const char16_t* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr; + return strzcmp16(b1, length_, b2, rhs.length_); } template <typename TChar> -inline bool BasicStringPiece<TChar>::operator<(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) < 0; +inline bool BasicStringPiece<TChar>::operator<( + const BasicStringPiece<TChar>& rhs) const { + return compare(rhs) < 0; } template <typename TChar> -inline bool BasicStringPiece<TChar>::operator>(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) > 0; +inline bool BasicStringPiece<TChar>::operator>( + const BasicStringPiece<TChar>& rhs) const { + return compare(rhs) > 0; } template <typename TChar> -inline bool BasicStringPiece<TChar>::operator==(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) == 0; +inline bool BasicStringPiece<TChar>::operator==( + const BasicStringPiece<TChar>& rhs) const { + return compare(rhs) == 0; } template <typename TChar> -inline bool BasicStringPiece<TChar>::operator!=(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) != 0; +inline bool BasicStringPiece<TChar>::operator!=( + const BasicStringPiece<TChar>& rhs) const { + return compare(rhs) != 0; } template <typename TChar> -inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::begin() const { - return mData; +inline typename BasicStringPiece<TChar>::const_iterator +BasicStringPiece<TChar>::begin() const { + return data_; } template <typename TChar> -inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::end() const { - return mData + mLength; +inline typename BasicStringPiece<TChar>::const_iterator +BasicStringPiece<TChar>::end() const { + return data_ + length_; } -inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) { - return out.write(str.data(), str.size()); +inline ::std::ostream& operator<<(::std::ostream& out, + const BasicStringPiece<char>& str) { + return out.write(str.data(), str.size()); } -} // namespace aapt +} // namespace aapt -inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) { - android::String8 utf8(str.data(), str.size()); - return out.write(utf8.string(), utf8.size()); +inline ::std::ostream& operator<<(::std::ostream& out, + const std::u16string& str) { + android::String8 utf8(str.data(), str.size()); + return out.write(utf8.string(), utf8.size()); } -#endif // AAPT_STRING_PIECE_H +namespace std { + +template <typename TChar> +struct hash<aapt::BasicStringPiece<TChar>> { + size_t operator()(const aapt::BasicStringPiece<TChar>& str) const { + uint32_t hashCode = android::JenkinsHashMixBytes( + 0, reinterpret_cast<const uint8_t*>(str.data()), + sizeof(TChar) * str.size()); + return static_cast<size_t>(hashCode); + } +}; + +} // namespace std + +#endif // AAPT_STRING_PIECE_H diff --git a/tools/aapt2/util/StringPiece_test.cpp b/tools/aapt2/util/StringPiece_test.cpp index 853a9a46fde8..048961d49584 100644 --- a/tools/aapt2/util/StringPiece_test.cpp +++ b/tools/aapt2/util/StringPiece_test.cpp @@ -14,81 +14,82 @@ * limitations under the License. */ +#include "util/StringPiece.h" + #include <algorithm> -#include <gtest/gtest.h> #include <string> #include <vector> -#include "util/StringPiece.h" +#include "test/Test.h" namespace aapt { TEST(StringPieceTest, CompareNonNullTerminatedPiece) { - StringPiece a("hello world", 5); - StringPiece b("hello moon", 5); - EXPECT_EQ(a, b); + StringPiece a("hello world", 5); + StringPiece b("hello moon", 5); + EXPECT_EQ(a, b); - StringPiece16 a16(u"hello world", 5); - StringPiece16 b16(u"hello moon", 5); - EXPECT_EQ(a16, b16); + StringPiece16 a16(u"hello world", 5); + StringPiece16 b16(u"hello moon", 5); + EXPECT_EQ(a16, b16); } TEST(StringPieceTest, PiecesHaveCorrectSortOrder) { - std::u16string testing(u"testing"); - std::u16string banana(u"banana"); - std::u16string car(u"car"); + std::string testing("testing"); + std::string banana("banana"); + std::string car("car"); - EXPECT_TRUE(StringPiece16(testing) > banana); - EXPECT_TRUE(StringPiece16(testing) > car); - EXPECT_TRUE(StringPiece16(banana) < testing); - EXPECT_TRUE(StringPiece16(banana) < car); - EXPECT_TRUE(StringPiece16(car) < testing); - EXPECT_TRUE(StringPiece16(car) > banana); + EXPECT_TRUE(StringPiece(testing) > banana); + EXPECT_TRUE(StringPiece(testing) > car); + EXPECT_TRUE(StringPiece(banana) < testing); + EXPECT_TRUE(StringPiece(banana) < car); + EXPECT_TRUE(StringPiece(car) < testing); + EXPECT_TRUE(StringPiece(car) > banana); } TEST(StringPieceTest, PiecesHaveCorrectSortOrderUtf8) { - std::string testing("testing"); - std::string banana("banana"); - std::string car("car"); + std::string testing("testing"); + std::string banana("banana"); + std::string car("car"); - EXPECT_TRUE(StringPiece(testing) > banana); - EXPECT_TRUE(StringPiece(testing) > car); - EXPECT_TRUE(StringPiece(banana) < testing); - EXPECT_TRUE(StringPiece(banana) < car); - EXPECT_TRUE(StringPiece(car) < testing); - EXPECT_TRUE(StringPiece(car) > banana); + EXPECT_TRUE(StringPiece(testing) > banana); + EXPECT_TRUE(StringPiece(testing) > car); + EXPECT_TRUE(StringPiece(banana) < testing); + EXPECT_TRUE(StringPiece(banana) < car); + EXPECT_TRUE(StringPiece(car) < testing); + EXPECT_TRUE(StringPiece(car) > banana); } TEST(StringPieceTest, ContainsOtherStringPiece) { - StringPiece text("I am a leaf on the wind."); - StringPiece startNeedle("I am"); - StringPiece endNeedle("wind."); - StringPiece middleNeedle("leaf"); - StringPiece emptyNeedle(""); - StringPiece missingNeedle("soar"); - StringPiece longNeedle("This string is longer than the text."); + StringPiece text("I am a leaf on the wind."); + StringPiece start_needle("I am"); + StringPiece end_needle("wind."); + StringPiece middle_needle("leaf"); + StringPiece empty_needle(""); + StringPiece missing_needle("soar"); + StringPiece long_needle("This string is longer than the text."); - EXPECT_TRUE(text.contains(startNeedle)); - EXPECT_TRUE(text.contains(endNeedle)); - EXPECT_TRUE(text.contains(middleNeedle)); - EXPECT_TRUE(text.contains(emptyNeedle)); - EXPECT_FALSE(text.contains(missingNeedle)); - EXPECT_FALSE(text.contains(longNeedle)); + EXPECT_TRUE(text.contains(start_needle)); + EXPECT_TRUE(text.contains(end_needle)); + EXPECT_TRUE(text.contains(middle_needle)); + EXPECT_TRUE(text.contains(empty_needle)); + EXPECT_FALSE(text.contains(missing_needle)); + EXPECT_FALSE(text.contains(long_needle)); - StringPiece16 text16(u"I am a leaf on the wind."); - StringPiece16 startNeedle16(u"I am"); - StringPiece16 endNeedle16(u"wind."); - StringPiece16 middleNeedle16(u"leaf"); - StringPiece16 emptyNeedle16(u""); - StringPiece16 missingNeedle16(u"soar"); - StringPiece16 longNeedle16(u"This string is longer than the text."); + StringPiece16 text16(u"I am a leaf on the wind."); + StringPiece16 start_needle16(u"I am"); + StringPiece16 end_needle16(u"wind."); + StringPiece16 middle_needle16(u"leaf"); + StringPiece16 empty_needle16(u""); + StringPiece16 missing_needle16(u"soar"); + StringPiece16 long_needle16(u"This string is longer than the text."); - EXPECT_TRUE(text16.contains(startNeedle16)); - EXPECT_TRUE(text16.contains(endNeedle16)); - EXPECT_TRUE(text16.contains(middleNeedle16)); - EXPECT_TRUE(text16.contains(emptyNeedle16)); - EXPECT_FALSE(text16.contains(missingNeedle16)); - EXPECT_FALSE(text16.contains(longNeedle16)); + EXPECT_TRUE(text16.contains(start_needle16)); + EXPECT_TRUE(text16.contains(end_needle16)); + EXPECT_TRUE(text16.contains(middle_needle16)); + EXPECT_TRUE(text16.contains(empty_needle16)); + EXPECT_FALSE(text16.contains(missing_needle16)); + EXPECT_FALSE(text16.contains(long_needle16)); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/util/TypeTraits.h b/tools/aapt2/util/TypeTraits.h index 76c13d615e41..b6539edce6d9 100644 --- a/tools/aapt2/util/TypeTraits.h +++ b/tools/aapt2/util/TypeTraits.h @@ -21,19 +21,20 @@ namespace aapt { -#define DEFINE_HAS_BINARY_OP_TRAIT(name, op) \ - template <typename T, typename U> \ - struct name { \ - template <typename V, typename W> \ - static constexpr decltype(std::declval<V>() op std::declval<W>(), bool()) test(int) { \ - return true; \ - } \ - template <typename V, typename W> \ - static constexpr bool test(...) { \ - return false; \ - } \ - static constexpr bool value = test<T, U>(int()); \ -} +#define DEFINE_HAS_BINARY_OP_TRAIT(name, op) \ + template <typename T, typename U> \ + struct name { \ + template <typename V, typename W> \ + static constexpr decltype(std::declval<V>() op std::declval<W>(), bool()) \ + test(int) { \ + return true; \ + } \ + template <typename V, typename W> \ + static constexpr bool test(...) { \ + return false; \ + } \ + static constexpr bool value = test<T, U>(int()); \ + } DEFINE_HAS_BINARY_OP_TRAIT(has_eq_op, ==); DEFINE_HAS_BINARY_OP_TRAIT(has_lt_op, <); @@ -43,9 +44,10 @@ DEFINE_HAS_BINARY_OP_TRAIT(has_lt_op, <); */ template <typename T, typename U> struct is_comparable { - static constexpr bool value = has_eq_op<T, U>::value && has_lt_op<T, U>::value; + static constexpr bool value = + has_eq_op<T, U>::value && has_lt_op<T, U>::value; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_UTIL_TYPETRAITS_H */ diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index e07c88ec9579..d5c0c8a7a5fe 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -14,486 +14,558 @@ * limitations under the License. */ +#include "util/Util.h" #include "util/BigBuffer.h" #include "util/Maybe.h" #include "util/StringPiece.h" -#include "util/Util.h" +#include <utils/Unicode.h> #include <algorithm> #include <ostream> #include <string> -#include <utils/Unicode.h> #include <vector> namespace aapt { namespace util { -static std::vector<std::string> splitAndTransform(const StringPiece& str, char sep, - const std::function<char(char)>& f) { - std::vector<std::string> parts; - const StringPiece::const_iterator end = std::end(str); - StringPiece::const_iterator start = std::begin(str); - StringPiece::const_iterator current; - do { - current = std::find(start, end, sep); - parts.emplace_back(str.substr(start, current).toString()); - if (f) { - std::string& part = parts.back(); - std::transform(part.begin(), part.end(), part.begin(), f); - } - start = current + 1; - } while (current != end); - return parts; +static std::vector<std::string> SplitAndTransform( + const StringPiece& str, char sep, const std::function<char(char)>& f) { + std::vector<std::string> parts; + const StringPiece::const_iterator end = std::end(str); + StringPiece::const_iterator start = std::begin(str); + StringPiece::const_iterator current; + do { + current = std::find(start, end, sep); + parts.emplace_back(str.substr(start, current).ToString()); + if (f) { + std::string& part = parts.back(); + std::transform(part.begin(), part.end(), part.begin(), f); + } + start = current + 1; + } while (current != end); + return parts; } -std::vector<std::string> split(const StringPiece& str, char sep) { - return splitAndTransform(str, sep, nullptr); +std::vector<std::string> Split(const StringPiece& str, char sep) { + return SplitAndTransform(str, sep, nullptr); } -std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep) { - return splitAndTransform(str, sep, ::tolower); +std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) { + return SplitAndTransform(str, sep, ::tolower); } -StringPiece16 trimWhitespace(const StringPiece16& str) { - if (str.size() == 0 || str.data() == nullptr) { - return str; - } - - const char16_t* start = str.data(); - const char16_t* end = str.data() + str.length(); - - while (start != end && util::isspace16(*start)) { - start++; - } - - while (end != start && util::isspace16(*(end - 1))) { - end--; - } +bool StartsWith(const StringPiece& str, const StringPiece& prefix) { + if (str.size() < prefix.size()) { + return false; + } + return str.substr(0, prefix.size()) == prefix; +} - return StringPiece16(start, end - start); +bool EndsWith(const StringPiece& str, const StringPiece& suffix) { + if (str.size() < suffix.size()) { + return false; + } + return str.substr(str.size() - suffix.size(), suffix.size()) == suffix; } -StringPiece trimWhitespace(const StringPiece& str) { - if (str.size() == 0 || str.data() == nullptr) { - return str; - } +StringPiece TrimWhitespace(const StringPiece& str) { + if (str.size() == 0 || str.data() == nullptr) { + return str; + } - const char* start = str.data(); - const char* end = str.data() + str.length(); + const char* start = str.data(); + const char* end = str.data() + str.length(); - while (start != end && isspace(*start)) { - start++; - } + while (start != end && isspace(*start)) { + start++; + } - while (end != start && isspace(*(end - 1))) { - end--; - } + while (end != start && isspace(*(end - 1))) { + end--; + } - return StringPiece(start, end - start); + return StringPiece(start, end - start); } -StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16& str, - const StringPiece16& allowedChars) { - const auto endIter = str.end(); - for (auto iter = str.begin(); iter != endIter; ++iter) { - char16_t c = *iter; - if ((c >= u'a' && c <= u'z') || - (c >= u'A' && c <= u'Z') || - (c >= u'0' && c <= u'9')) { - continue; - } - - bool match = false; - for (char16_t i : allowedChars) { - if (c == i) { - match = true; - break; - } - } - - if (!match) { - return iter; - } +StringPiece::const_iterator FindNonAlphaNumericAndNotInSet( + const StringPiece& str, const StringPiece& allowed_chars) { + const auto end_iter = str.end(); + for (auto iter = str.begin(); iter != end_iter; ++iter) { + char c = *iter; + if ((c >= u'a' && c <= u'z') || (c >= u'A' && c <= u'Z') || + (c >= u'0' && c <= u'9')) { + continue; } - return endIter; -} - -bool isJavaClassName(const StringPiece16& str) { - size_t pieces = 0; - for (const StringPiece16& piece : tokenize(str, u'.')) { - pieces++; - if (piece.empty()) { - return false; - } - // Can't have starting or trailing $ character. - if (piece.data()[0] == u'$' || piece.data()[piece.size() - 1] == u'$') { - return false; - } + bool match = false; + for (char i : allowed_chars) { + if (c == i) { + match = true; + break; + } + } - if (findNonAlphaNumericAndNotInSet(piece, u"$_") != piece.end()) { - return false; - } + if (!match) { + return iter; } - return pieces >= 2; + } + return end_iter; } -bool isJavaPackageName(const StringPiece16& str) { - if (str.empty()) { - return false; +bool IsJavaClassName(const StringPiece& str) { + size_t pieces = 0; + for (const StringPiece& piece : Tokenize(str, '.')) { + pieces++; + if (piece.empty()) { + return false; } - size_t pieces = 0; - for (const StringPiece16& piece : tokenize(str, u'.')) { - pieces++; - if (piece.empty()) { - return false; - } - - if (piece.data()[0] == u'_' || piece.data()[piece.size() - 1] == u'_') { - return false; - } + // Can't have starting or trailing $ character. + if (piece.data()[0] == '$' || piece.data()[piece.size() - 1] == '$') { + return false; + } - if (findNonAlphaNumericAndNotInSet(piece, u"_") != piece.end()) { - return false; - } + if (FindNonAlphaNumericAndNotInSet(piece, "$_") != piece.end()) { + return false; } - return pieces >= 1; + } + return pieces >= 2; } -Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package, - const StringPiece16& className) { - if (className.empty()) { - return {}; - } +bool IsJavaPackageName(const StringPiece& str) { + if (str.empty()) { + return false; + } - if (util::isJavaClassName(className)) { - return className.toString(); + size_t pieces = 0; + for (const StringPiece& piece : Tokenize(str, '.')) { + pieces++; + if (piece.empty()) { + return false; } - if (package.empty()) { - return {}; + if (piece.data()[0] == '_' || piece.data()[piece.size() - 1] == '_') { + return false; } - if (className.data()[0] != u'.') { - return {}; + if (FindNonAlphaNumericAndNotInSet(piece, "_") != piece.end()) { + return false; } + } + return pieces >= 1; +} - std::u16string result(package.data(), package.size()); - result.append(className.data(), className.size()); - if (!isJavaClassName(result)) { - return {}; - } - return result; +Maybe<std::string> GetFullyQualifiedClassName(const StringPiece& package, + const StringPiece& classname) { + if (classname.empty()) { + return {}; + } + + if (util::IsJavaClassName(classname)) { + return classname.ToString(); + } + + if (package.empty()) { + return {}; + } + + std::string result(package.data(), package.size()); + if (classname.data()[0] != '.') { + result += '.'; + } + + result.append(classname.data(), classname.size()); + if (!IsJavaClassName(result)) { + return {}; + } + return result; } -static size_t consumeDigits(const char16_t* start, const char16_t* end) { - const char16_t* c = start; - for (; c != end && *c >= u'0' && *c <= u'9'; c++) {} - return static_cast<size_t>(c - start); +static size_t ConsumeDigits(const char* start, const char* end) { + const char* c = start; + for (; c != end && *c >= '0' && *c <= '9'; c++) { + } + return static_cast<size_t>(c - start); } -bool verifyJavaStringFormat(const StringPiece16& str) { - const char16_t* c = str.begin(); - const char16_t* const end = str.end(); - - size_t argCount = 0; - bool nonpositional = false; - while (c != end) { - if (*c == u'%' && c + 1 < end) { - c++; - - if (*c == u'%') { - c++; - continue; - } - - argCount++; - - size_t numDigits = consumeDigits(c, end); - if (numDigits > 0) { - c += numDigits; - if (c != end && *c != u'$') { - // The digits were a size, but not a positional argument. - nonpositional = true; - } - } else if (*c == u'<') { - // Reusing last argument, bad idea since positions can be moved around - // during translation. - nonpositional = true; - - c++; - - // Optionally we can have a $ after - if (c != end && *c == u'$') { - c++; - } - } else { - nonpositional = true; - } - - // Ignore size, width, flags, etc. - while (c != end && (*c == u'-' || - *c == u'#' || - *c == u'+' || - *c == u' ' || - *c == u',' || - *c == u'(' || - (*c >= u'0' && *c <= '9'))) { - c++; - } - - /* - * This is a shortcut to detect strings that are going to Time.format() - * instead of String.format() - * - * Comparison of String.format() and Time.format() args: - * - * String: ABC E GH ST X abcdefgh nost x - * Time: DEFGHKMS W Za d hkm s w yz - * - * Therefore we know it's definitely Time if we have: - * DFKMWZkmwyz - */ - if (c != end) { - switch (*c) { - case 'D': - case 'F': - case 'K': - case 'M': - case 'W': - case 'Z': - case 'k': - case 'm': - case 'w': - case 'y': - case 'z': - return true; - } - } +bool VerifyJavaStringFormat(const StringPiece& str) { + const char* c = str.begin(); + const char* const end = str.end(); + + size_t arg_count = 0; + bool nonpositional = false; + while (c != end) { + if (*c == '%' && c + 1 < end) { + c++; + + if (*c == '%') { + c++; + continue; + } + + arg_count++; + + size_t num_digits = ConsumeDigits(c, end); + if (num_digits > 0) { + c += num_digits; + if (c != end && *c != '$') { + // The digits were a size, but not a positional argument. + nonpositional = true; } + } else if (*c == '<') { + // Reusing last argument, bad idea since positions can be moved around + // during translation. + nonpositional = true; - if (c != end) { - c++; + c++; + + // Optionally we can have a $ after + if (c != end && *c == '$') { + c++; + } + } else { + nonpositional = true; + } + + // Ignore size, width, flags, etc. + while (c != end && (*c == '-' || *c == '#' || *c == '+' || *c == ' ' || + *c == ',' || *c == '(' || (*c >= '0' && *c <= '9'))) { + c++; + } + + /* + * This is a shortcut to detect strings that are going to Time.format() + * instead of String.format() + * + * Comparison of String.format() and Time.format() args: + * + * String: ABC E GH ST X abcdefgh nost x + * Time: DEFGHKMS W Za d hkm s w yz + * + * Therefore we know it's definitely Time if we have: + * DFKMWZkmwyz + */ + if (c != end) { + switch (*c) { + case 'D': + case 'F': + case 'K': + case 'M': + case 'W': + case 'Z': + case 'k': + case 'm': + case 'w': + case 'y': + case 'z': + return true; } + } } - if (argCount > 1 && nonpositional) { - // Multiple arguments were specified, but some or all were non positional. Translated - // strings may rearrange the order of the arguments, which will break the string. - return false; + if (c != end) { + c++; } - return true; + } + + if (arg_count > 1 && nonpositional) { + // Multiple arguments were specified, but some or all were non positional. + // Translated + // strings may rearrange the order of the arguments, which will break the + // string. + return false; + } + return true; } -static Maybe<char16_t> parseUnicodeCodepoint(const char16_t** start, const char16_t* end) { - char16_t code = 0; - for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) { - char16_t c = **start; - int a; - if (c >= '0' && c <= '9') { - a = c - '0'; - } else if (c >= 'a' && c <= 'f') { - a = c - 'a' + 10; - } else if (c >= 'A' && c <= 'F') { - a = c - 'A' + 10; - } else { - return make_nothing<char16_t>(); - } - code = (code << 4) | a; +static Maybe<std::string> ParseUnicodeCodepoint(const char** start, + const char* end) { + char32_t code = 0; + for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) { + char c = **start; + char32_t a; + if (c >= '0' && c <= '9') { + a = c - '0'; + } else if (c >= 'a' && c <= 'f') { + a = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + a = c - 'A' + 10; + } else { + return {}; } - return make_value(code); + code = (code << 4) | a; + } + + ssize_t len = utf32_to_utf8_length(&code, 1); + if (len < 0) { + return {}; + } + + std::string result_utf8; + result_utf8.resize(len); + utf32_to_utf8(&code, 1, &*result_utf8.begin(), len + 1); + return result_utf8; } -StringBuilder& StringBuilder::append(const StringPiece16& str) { - if (!mError.empty()) { - return *this; - } - - const char16_t* const end = str.end(); - const char16_t* start = str.begin(); - const char16_t* current = start; - while (current != end) { - if (mLastCharWasEscape) { - switch (*current) { - case u't': - mStr += u'\t'; - break; - case u'n': - mStr += u'\n'; - break; - case u'#': - mStr += u'#'; - break; - case u'@': - mStr += u'@'; - break; - case u'?': - mStr += u'?'; - break; - case u'"': - mStr += u'"'; - break; - case u'\'': - mStr += u'\''; - break; - case u'\\': - mStr += u'\\'; - break; - case u'u': { - current++; - Maybe<char16_t> c = parseUnicodeCodepoint(¤t, end); - if (!c) { - mError = "invalid unicode escape sequence"; - return *this; - } - mStr += c.value(); - current -= 1; - break; - } - - default: - // Ignore. - break; - } - mLastCharWasEscape = false; - start = current + 1; - } else if (*current == u'"') { - if (!mQuote && mTrailingSpace) { - // We found an opening quote, and we have - // trailing space, so we should append that - // space now. - if (mTrailingSpace) { - // We had trailing whitespace, so - // replace with a single space. - if (!mStr.empty()) { - mStr += u' '; - } - mTrailingSpace = false; - } - } - mQuote = !mQuote; - mStr.append(start, current - start); - start = current + 1; - } else if (*current == u'\'' && !mQuote) { - // This should be escaped. - mError = "unescaped apostrophe"; +StringBuilder& StringBuilder::Append(const StringPiece& str) { + if (!error_.empty()) { + return *this; + } + + // Where the new data will be appended to. + size_t new_data_index = str_.size(); + + const char* const end = str.end(); + const char* start = str.begin(); + const char* current = start; + while (current != end) { + if (last_char_was_escape_) { + switch (*current) { + case 't': + str_ += '\t'; + break; + case 'n': + str_ += '\n'; + break; + case '#': + str_ += '#'; + break; + case '@': + str_ += '@'; + break; + case '?': + str_ += '?'; + break; + case '"': + str_ += '"'; + break; + case '\'': + str_ += '\''; + break; + case '\\': + str_ += '\\'; + break; + case 'u': { + current++; + Maybe<std::string> c = ParseUnicodeCodepoint(¤t, end); + if (!c) { + error_ = "invalid unicode escape sequence"; return *this; - } else if (*current == u'\\') { - // This is an escape sequence, convert to the real value. - if (!mQuote && mTrailingSpace) { - // We had trailing whitespace, so - // replace with a single space. - if (!mStr.empty()) { - mStr += u' '; - } - mTrailingSpace = false; - } - mStr.append(start, current - start); - start = current + 1; - mLastCharWasEscape = true; - } else if (!mQuote) { - // This is not quoted text, so look for whitespace. - if (isspace16(*current)) { - // We found whitespace, see if we have seen some - // before. - if (!mTrailingSpace) { - // We didn't see a previous adjacent space, - // so mark that we did. - mTrailingSpace = true; - mStr.append(start, current - start); - } - - // Keep skipping whitespace. - start = current + 1; - } else if (mTrailingSpace) { - // We saw trailing space before, so replace all - // that trailing space with one space. - if (!mStr.empty()) { - mStr += u' '; - } - mTrailingSpace = false; - } + } + str_ += c.value(); + current -= 1; + break; + } + + default: + // Ignore. + break; + } + last_char_was_escape_ = false; + start = current + 1; + } else if (*current == '"') { + if (!quote_ && trailing_space_) { + // We found an opening quote, and we have + // trailing space, so we should append that + // space now. + if (trailing_space_) { + // We had trailing whitespace, so + // replace with a single space. + if (!str_.empty()) { + str_ += ' '; + } + trailing_space_ = false; + } + } + quote_ = !quote_; + str_.append(start, current - start); + start = current + 1; + } else if (*current == '\'' && !quote_) { + // This should be escaped. + error_ = "unescaped apostrophe"; + return *this; + } else if (*current == '\\') { + // This is an escape sequence, convert to the real value. + if (!quote_ && trailing_space_) { + // We had trailing whitespace, so + // replace with a single space. + if (!str_.empty()) { + str_ += ' '; + } + trailing_space_ = false; + } + str_.append(start, current - start); + start = current + 1; + last_char_was_escape_ = true; + } else if (!quote_) { + // This is not quoted text, so look for whitespace. + if (isspace(*current)) { + // We found whitespace, see if we have seen some + // before. + if (!trailing_space_) { + // We didn't see a previous adjacent space, + // so mark that we did. + trailing_space_ = true; + str_.append(start, current - start); + } + + // Keep skipping whitespace. + start = current + 1; + } else if (trailing_space_) { + // We saw trailing space before, so replace all + // that trailing space with one space. + if (!str_.empty()) { + str_ += ' '; } - current++; + trailing_space_ = false; + } } - mStr.append(start, end - start); + current++; + } + str_.append(start, end - start); + + // Accumulate the added string's UTF-16 length. + ssize_t len = utf8_to_utf16_length( + reinterpret_cast<const uint8_t*>(str_.data()) + new_data_index, + str_.size() - new_data_index); + if (len < 0) { + error_ = "invalid unicode code point"; return *this; + } + utf16_len_ += len; + return *this; } -std::u16string utf8ToUtf16(const StringPiece& utf8) { - ssize_t utf16Length = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()), - utf8.length()); - if (utf16Length <= 0) { - return {}; - } +std::u16string Utf8ToUtf16(const StringPiece& utf8) { + ssize_t utf16_length = utf8_to_utf16_length( + reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length()); + if (utf16_length <= 0) { + return {}; + } + + std::u16string utf16; + utf16.resize(utf16_length); + utf8_to_utf16(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length(), + &*utf16.begin(), utf16_length + 1); + return utf16; +} + +std::string Utf16ToUtf8(const StringPiece16& utf16) { + ssize_t utf8_length = utf16_to_utf8_length(utf16.data(), utf16.length()); + if (utf8_length <= 0) { + return {}; + } - std::u16string utf16; - utf16.resize(utf16Length); - utf8_to_utf16( - reinterpret_cast<const uint8_t*>(utf8.data()), - utf8.length(), - &*utf16.begin(), - (size_t) utf16Length + 1); - return utf16; + std::string utf8; + utf8.resize(utf8_length); + utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8_length + 1); + return utf8; } -std::string utf16ToUtf8(const StringPiece16& utf16) { - ssize_t utf8Length = utf16_to_utf8_length(utf16.data(), utf16.length()); - if (utf8Length <= 0) { - return {}; +bool WriteAll(std::ostream& out, const BigBuffer& buffer) { + for (const auto& b : buffer) { + if (!out.write(reinterpret_cast<const char*>(b.buffer.get()), b.size)) { + return false; } + } + return true; +} - std::string utf8; - // Make room for '\0' explicitly. - utf8.resize(utf8Length + 1); - utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8Length + 1); - utf8.resize(utf8Length); - return utf8; +std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer) { + std::unique_ptr<uint8_t[]> data = + std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]); + uint8_t* p = data.get(); + for (const auto& block : buffer) { + memcpy(p, block.buffer.get(), block.size); + p += block.size; + } + return data; } -bool writeAll(std::ostream& out, const BigBuffer& buffer) { - for (const auto& b : buffer) { - if (!out.write(reinterpret_cast<const char*>(b.buffer.get()), b.size)) { - return false; - } +typename Tokenizer::iterator& Tokenizer::iterator::operator++() { + const char* start = token_.end(); + const char* end = str_.end(); + if (start == end) { + end_ = true; + token_.assign(token_.end(), 0); + return *this; + } + + start += 1; + const char* current = start; + while (current != end) { + if (*current == separator_) { + token_.assign(start, current - start); + return *this; } - return true; + ++current; + } + token_.assign(start, end - start); + return *this; } -std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer) { - std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]); - uint8_t* p = data.get(); - for (const auto& block : buffer) { - memcpy(p, block.buffer.get(), block.size); - p += block.size; - } - return data; +bool Tokenizer::iterator::operator==(const iterator& rhs) const { + // We check equality here a bit differently. + // We need to know that the addresses are the same. + return token_.begin() == rhs.token_.begin() && + token_.end() == rhs.token_.end() && end_ == rhs.end_; } -bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix, - StringPiece16* outEntry, StringPiece16* outSuffix) { - if (!stringStartsWith<char16_t>(path, u"res/")) { - return false; - } +bool Tokenizer::iterator::operator!=(const iterator& rhs) const { + return !(*this == rhs); +} - StringPiece16::const_iterator lastOccurence = path.end(); - for (auto iter = path.begin() + StringPiece16(u"res/").size(); iter != path.end(); ++iter) { - if (*iter == u'/') { - lastOccurence = iter; - } +Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok, + bool end) + : str_(s), separator_(sep), token_(tok), end_(end) {} + +Tokenizer::Tokenizer(StringPiece str, char sep) + : begin_(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)), + end_(str, sep, StringPiece(str.end(), 0), true) {} + +bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix, + StringPiece* out_entry, StringPiece* out_suffix) { + const StringPiece res_prefix("res/"); + if (!StartsWith(path, res_prefix)) { + return false; + } + + StringPiece::const_iterator last_occurence = path.end(); + for (auto iter = path.begin() + res_prefix.size(); iter != path.end(); + ++iter) { + if (*iter == '/') { + last_occurence = iter; } + } - if (lastOccurence == path.end()) { - return false; - } + if (last_occurence == path.end()) { + return false; + } + + auto iter = std::find(last_occurence, path.end(), '.'); + *out_suffix = StringPiece(iter, path.end() - iter); + *out_entry = StringPiece(last_occurence + 1, iter - last_occurence - 1); + *out_prefix = StringPiece(path.begin(), last_occurence - path.begin() + 1); + return true; +} + +StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx) { + size_t len; + const char16_t* str = pool.stringAt(idx, &len); + if (str != nullptr) { + return StringPiece16(str, len); + } + return StringPiece16(); +} - auto iter = std::find(lastOccurence, path.end(), u'.'); - *outSuffix = StringPiece16(iter, path.end() - iter); - *outEntry = StringPiece16(lastOccurence + 1, iter - lastOccurence - 1); - *outPrefix = StringPiece16(path.begin(), lastOccurence - path.begin() + 1); - return true; +std::string GetString(const android::ResStringPool& pool, size_t idx) { + size_t len; + const char* str = pool.string8At(idx, &len); + if (str != nullptr) { + return std::string(str, len); + } + return Utf16ToUtf8(GetString16(pool, idx)); } -} // namespace util -} // namespace aapt +} // namespace util +} // namespace aapt diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index 0dacbd773488..05e9cc5d28ca 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -17,327 +17,246 @@ #ifndef AAPT_UTIL_H #define AAPT_UTIL_H -#include "util/BigBuffer.h" -#include "util/Maybe.h" -#include "util/StringPiece.h" - -#include <androidfw/ResourceTypes.h> #include <functional> #include <memory> #include <ostream> #include <string> #include <vector> +#include "androidfw/ResourceTypes.h" +#include "utils/ByteOrder.h" + +#include "util/BigBuffer.h" +#include "util/Maybe.h" +#include "util/StringPiece.h" + +#ifdef _WIN32 +// TODO(adamlesinski): remove once http://b/32447322 is resolved. +// utils/ByteOrder.h includes winsock2.h on WIN32, +// which will pull in the ERROR definition. This conflicts +// with android-base/logging.h, which takes care of undefining +// ERROR, but it gets included too early (before winsock2.h). +#ifdef ERROR +#undef ERROR +#endif +#endif + namespace aapt { namespace util { -std::vector<std::string> split(const StringPiece& str, char sep); -std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep); +std::vector<std::string> Split(const StringPiece& str, char sep); +std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep); /** * Returns true if the string starts with prefix. */ -template <typename T> -bool stringStartsWith(const BasicStringPiece<T>& str, const BasicStringPiece<T>& prefix) { - if (str.size() < prefix.size()) { - return false; - } - return str.substr(0, prefix.size()) == prefix; -} +bool StartsWith(const StringPiece& str, const StringPiece& prefix); /** * Returns true if the string ends with suffix. */ -template <typename T> -bool stringEndsWith(const BasicStringPiece<T>& str, const BasicStringPiece<T>& suffix) { - if (str.size() < suffix.size()) { - return false; - } - return str.substr(str.size() - suffix.size(), suffix.size()) == suffix; -} +bool EndsWith(const StringPiece& str, const StringPiece& suffix); /** * Creates a new StringPiece16 that points to a substring * of the original string without leading or trailing whitespace. */ -StringPiece16 trimWhitespace(const StringPiece16& str); +StringPiece TrimWhitespace(const StringPiece& str); -StringPiece trimWhitespace(const StringPiece& str); +StringPiece TrimWhitespace(const StringPiece& str); /** * UTF-16 isspace(). It basically checks for lower range characters that are * whitespace. */ -inline bool isspace16(char16_t c) { - return c < 0x0080 && isspace(c); -} +inline bool isspace16(char16_t c) { return c < 0x0080 && isspace(c); } /** * Returns an iterator to the first character that is not alpha-numeric and that * is not in the allowedChars set. */ -StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16& str, - const StringPiece16& allowedChars); +StringPiece::const_iterator FindNonAlphaNumericAndNotInSet( + const StringPiece& str, const StringPiece& allowed_chars); /** * Tests that the string is a valid Java class name. */ -bool isJavaClassName(const StringPiece16& str); +bool IsJavaClassName(const StringPiece& str); /** * Tests that the string is a valid Java package name. */ -bool isJavaPackageName(const StringPiece16& str); +bool IsJavaPackageName(const StringPiece& str); /** - * Converts the class name to a fully qualified class name from the given `package`. Ex: + * Converts the class name to a fully qualified class name from the given + * `package`. Ex: * * asdf --> package.asdf * .asdf --> package.asdf * .a.b --> package.a.b * asdf.adsf --> asdf.adsf */ -Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package, - const StringPiece16& className); - +Maybe<std::string> GetFullyQualifiedClassName(const StringPiece& package, + const StringPiece& class_name); /** - * Makes a std::unique_ptr<> with the template parameter inferred by the compiler. + * Makes a std::unique_ptr<> with the template parameter inferred by the + * compiler. * This will be present in C++14 and can be removed then. */ template <typename T, class... Args> std::unique_ptr<T> make_unique(Args&&... args) { - return std::unique_ptr<T>(new T{std::forward<Args>(args)...}); + return std::unique_ptr<T>(new T{std::forward<Args>(args)...}); } /** - * Writes a set of items to the std::ostream, joining the times with the provided + * Writes a set of items to the std::ostream, joining the times with the + * provided * separator. */ -template <typename Iterator> -::std::function<::std::ostream&(::std::ostream&)> joiner(Iterator begin, Iterator end, - const char* sep) { - return [begin, end, sep](::std::ostream& out) -> ::std::ostream& { - for (auto iter = begin; iter != end; ++iter) { - if (iter != begin) { - out << sep; - } - out << *iter; - } - return out; - }; -} - -inline ::std::function<::std::ostream&(::std::ostream&)> formatSize(size_t size) { - return [size](::std::ostream& out) -> ::std::ostream& { - constexpr size_t K = 1024u; - constexpr size_t M = K * K; - constexpr size_t G = M * K; - if (size < K) { - out << size << "B"; - } else if (size < M) { - out << (double(size) / K) << " KiB"; - } else if (size < G) { - out << (double(size) / M) << " MiB"; - } else { - out << (double(size) / G) << " GiB"; - } - return out; - }; +template <typename Container> +::std::function<::std::ostream&(::std::ostream&)> Joiner( + const Container& container, const char* sep) { + using std::begin; + using std::end; + const auto begin_iter = begin(container); + const auto end_iter = end(container); + return [begin_iter, end_iter, sep](::std::ostream& out) -> ::std::ostream& { + for (auto iter = begin_iter; iter != end_iter; ++iter) { + if (iter != begin_iter) { + out << sep; + } + out << *iter; + } + return out; + }; } /** - * Helper method to extract a string from a StringPool. + * Helper method to extract a UTF-16 string from a StringPool. If the string is + * stored as UTF-8, + * the conversion to UTF-16 happens within ResStringPool. */ -inline StringPiece16 getString(const android::ResStringPool& pool, size_t idx) { - size_t len; - const char16_t* str = pool.stringAt(idx, &len); - if (str != nullptr) { - return StringPiece16(str, len); - } - return StringPiece16(); -} +StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx); -inline StringPiece getString8(const android::ResStringPool& pool, size_t idx) { - size_t len; - const char* str = pool.string8At(idx, &len); - if (str != nullptr) { - return StringPiece(str, len); - } - return StringPiece(); -} +/** + * Helper method to extract a UTF-8 string from a StringPool. If the string is + * stored as UTF-16, + * the conversion from UTF-16 to UTF-8 does not happen in ResStringPool and is + * done by this method, + * which maintains no state or cache. This means we must return an std::string + * copy. + */ +std::string GetString(const android::ResStringPool& pool, size_t idx); /** - * Checks that the Java string format contains no non-positional arguments (arguments without - * explicitly specifying an index) when there are more than one argument. This is an error - * because translations may rearrange the order of the arguments in the string, which will + * Checks that the Java string format contains no non-positional arguments + * (arguments without + * explicitly specifying an index) when there are more than one argument. This + * is an error + * because translations may rearrange the order of the arguments in the string, + * which will * break the string interpolation. */ -bool verifyJavaStringFormat(const StringPiece16& str); +bool VerifyJavaStringFormat(const StringPiece& str); class StringBuilder { -public: - StringBuilder& append(const StringPiece16& str); - const std::u16string& str() const; - const std::string& error() const; - operator bool() const; - -private: - std::u16string mStr; - bool mQuote = false; - bool mTrailingSpace = false; - bool mLastCharWasEscape = false; - std::string mError; + public: + StringBuilder& Append(const StringPiece& str); + const std::string& ToString() const; + const std::string& Error() const; + + // When building StyledStrings, we need UTF-16 indices into the string, + // which is what the Java layer expects when dealing with java + // String.charAt(). + size_t Utf16Len() const; + + explicit operator bool() const; + + private: + std::string str_; + size_t utf16_len_ = 0; + bool quote_ = false; + bool trailing_space_ = false; + bool last_char_was_escape_ = false; + std::string error_; }; -inline const std::u16string& StringBuilder::str() const { - return mStr; -} +inline const std::string& StringBuilder::ToString() const { return str_; } -inline const std::string& StringBuilder::error() const { - return mError; -} +inline const std::string& StringBuilder::Error() const { return error_; } -inline StringBuilder::operator bool() const { - return mError.empty(); -} +inline size_t StringBuilder::Utf16Len() const { return utf16_len_; } + +inline StringBuilder::operator bool() const { return error_.empty(); } /** * Converts a UTF8 string to a UTF16 string. */ -std::u16string utf8ToUtf16(const StringPiece& utf8); -std::string utf16ToUtf8(const StringPiece16& utf8); +std::u16string Utf8ToUtf16(const StringPiece& utf8); +std::string Utf16ToUtf8(const StringPiece16& utf16); /** * Writes the entire BigBuffer to the output stream. */ -bool writeAll(std::ostream& out, const BigBuffer& buffer); +bool WriteAll(std::ostream& out, const BigBuffer& buffer); /* * Copies the entire BigBuffer into a single buffer. */ -std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer); +std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer); /** * A Tokenizer implemented as an iterable collection. It does not allocate * any memory on the heap nor use standard containers. */ -template <typename Char> class Tokenizer { -public: - class iterator { - public: - iterator(const iterator&) = default; - iterator& operator=(const iterator&) = default; - - iterator& operator++(); - BasicStringPiece<Char> operator*(); - bool operator==(const iterator& rhs) const; - bool operator!=(const iterator& rhs) const; - - private: - friend class Tokenizer<Char>; - - iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok, bool end); - - BasicStringPiece<Char> mStr; - Char mSeparator; - BasicStringPiece<Char> mToken; - bool mEnd; - }; - - Tokenizer(BasicStringPiece<Char> str, Char sep); - iterator begin(); - iterator end(); - -private: - const iterator mBegin; - const iterator mEnd; -}; + public: + class iterator { + public: + iterator(const iterator&) = default; + iterator& operator=(const iterator&) = default; -template <typename Char> -inline Tokenizer<Char> tokenize(BasicStringPiece<Char> str, Char sep) { - return Tokenizer<Char>(str, sep); -} + iterator& operator++(); -template <typename Char> -typename Tokenizer<Char>::iterator& Tokenizer<Char>::iterator::operator++() { - const Char* start = mToken.end(); - const Char* end = mStr.end(); - if (start == end) { - mEnd = true; - mToken.assign(mToken.end(), 0); - return *this; - } + StringPiece operator*() { return token_; } + bool operator==(const iterator& rhs) const; + bool operator!=(const iterator& rhs) const; - start += 1; - const Char* current = start; - while (current != end) { - if (*current == mSeparator) { - mToken.assign(start, current - start); - return *this; - } - ++current; - } - mToken.assign(start, end - start); - return *this; -} + private: + friend class Tokenizer; -template <typename Char> -inline BasicStringPiece<Char> Tokenizer<Char>::iterator::operator*() { - return mToken; -} + iterator(StringPiece s, char sep, StringPiece tok, bool end); -template <typename Char> -inline bool Tokenizer<Char>::iterator::operator==(const iterator& rhs) const { - // We check equality here a bit differently. - // We need to know that the addresses are the same. - return mToken.begin() == rhs.mToken.begin() && mToken.end() == rhs.mToken.end() && - mEnd == rhs.mEnd; -} + StringPiece str_; + char separator_; + StringPiece token_; + bool end_; + }; -template <typename Char> -inline bool Tokenizer<Char>::iterator::operator!=(const iterator& rhs) const { - return !(*this == rhs); -} + Tokenizer(StringPiece str, char sep); -template <typename Char> -inline Tokenizer<Char>::iterator::iterator(BasicStringPiece<Char> s, Char sep, - BasicStringPiece<Char> tok, bool end) : - mStr(s), mSeparator(sep), mToken(tok), mEnd(end) { -} + iterator begin() { return begin_; } -template <typename Char> -inline typename Tokenizer<Char>::iterator Tokenizer<Char>::begin() { - return mBegin; -} + iterator end() { return end_; } -template <typename Char> -inline typename Tokenizer<Char>::iterator Tokenizer<Char>::end() { - return mEnd; -} + private: + const iterator begin_; + const iterator end_; +}; -template <typename Char> -inline Tokenizer<Char>::Tokenizer(BasicStringPiece<Char> str, Char sep) : - mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0), false)), - mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0), true) { +inline Tokenizer Tokenize(const StringPiece& str, char sep) { + return Tokenizer(str, sep); } -inline uint16_t hostToDevice16(uint16_t value) { - return htods(value); -} +inline uint16_t HostToDevice16(uint16_t value) { return htods(value); } -inline uint32_t hostToDevice32(uint32_t value) { - return htodl(value); -} +inline uint32_t HostToDevice32(uint32_t value) { return htodl(value); } -inline uint16_t deviceToHost16(uint16_t value) { - return dtohs(value); -} +inline uint16_t DeviceToHost16(uint16_t value) { return dtohs(value); } -inline uint32_t deviceToHost32(uint32_t value) { - return dtohl(value); -} +inline uint32_t DeviceToHost32(uint32_t value) { return dtohl(value); } /** * Given a path like: res/xml-sw600dp/foo.xml @@ -348,20 +267,22 @@ inline uint32_t deviceToHost32(uint32_t value) { * * Returns true if successful. */ -bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix, - StringPiece16* outEntry, StringPiece16* outSuffix); +bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix, + StringPiece* out_entry, StringPiece* out_suffix); -} // namespace util +} // namespace util /** - * Stream operator for functions. Calls the function with the stream as an argument. + * Stream operator for functions. Calls the function with the stream as an + * argument. * In the aapt namespace for lookup. */ -inline ::std::ostream& operator<<(::std::ostream& out, - ::std::function<::std::ostream&(::std::ostream&)> f) { - return f(out); +inline ::std::ostream& operator<<( + ::std::ostream& out, + const ::std::function<::std::ostream&(::std::ostream&)>& f) { + return f(out); } -} // namespace aapt +} // namespace aapt -#endif // AAPT_UTIL_H +#endif // AAPT_UTIL_H diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp index 1e0c7fa9152d..cac3de4696ab 100644 --- a/tools/aapt2/util/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -14,191 +14,196 @@ * limitations under the License. */ -#include "test/Common.h" -#include "util/StringPiece.h" #include "util/Util.h" -#include <gtest/gtest.h> #include <string> +#include "test/Test.h" + namespace aapt { TEST(UtilTest, TrimOnlyWhitespace) { - const std::u16string full = u"\n "; + const std::string full = "\n "; - StringPiece16 trimmed = util::trimWhitespace(full); - EXPECT_TRUE(trimmed.empty()); - EXPECT_EQ(0u, trimmed.size()); + StringPiece trimmed = util::TrimWhitespace(full); + EXPECT_TRUE(trimmed.empty()); + EXPECT_EQ(0u, trimmed.size()); } TEST(UtilTest, StringEndsWith) { - EXPECT_TRUE(util::stringEndsWith<char>("hello.xml", ".xml")); + EXPECT_TRUE(util::EndsWith("hello.xml", ".xml")); } TEST(UtilTest, StringStartsWith) { - EXPECT_TRUE(util::stringStartsWith<char>("hello.xml", "he")); + EXPECT_TRUE(util::StartsWith("hello.xml", "he")); } TEST(UtilTest, StringBuilderSplitEscapeSequence) { - EXPECT_EQ(StringPiece16(u"this is a new\nline."), - util::StringBuilder().append(u"this is a new\\") - .append(u"nline.") - .str()); + EXPECT_EQ(StringPiece("this is a new\nline."), util::StringBuilder() + .Append("this is a new\\") + .Append("nline.") + .ToString()); } TEST(UtilTest, StringBuilderWhitespaceRemoval) { - EXPECT_EQ(StringPiece16(u"hey guys this is so cool"), - util::StringBuilder().append(u" hey guys ") - .append(u" this is so cool ") - .str()); + EXPECT_EQ(StringPiece("hey guys this is so cool"), + util::StringBuilder() + .Append(" hey guys ") + .Append(" this is so cool ") + .ToString()); - EXPECT_EQ(StringPiece16(u" wow, so many \t spaces. what?"), - util::StringBuilder().append(u" \" wow, so many \t ") - .append(u"spaces. \"what? ") - .str()); + EXPECT_EQ(StringPiece(" wow, so many \t spaces. what?"), + util::StringBuilder() + .Append(" \" wow, so many \t ") + .Append("spaces. \"what? ") + .ToString()); - EXPECT_EQ(StringPiece16(u"where is the pie?"), - util::StringBuilder().append(u" where \t ") - .append(u" \nis the "" pie?") - .str()); + EXPECT_EQ(StringPiece("where is the pie?"), util::StringBuilder() + .Append(" where \t ") + .Append(" \nis the " + " pie?") + .ToString()); } TEST(UtilTest, StringBuilderEscaping) { - EXPECT_EQ(StringPiece16(u"hey guys\n this \t is so\\ cool"), - util::StringBuilder().append(u" hey guys\\n ") - .append(u" this \\t is so\\\\ cool ") - .str()); + EXPECT_EQ(StringPiece("hey guys\n this \t is so\\ cool"), + util::StringBuilder() + .Append(" hey guys\\n ") + .Append(" this \\t is so\\\\ cool ") + .ToString()); - EXPECT_EQ(StringPiece16(u"@?#\\\'"), - util::StringBuilder().append(u"\\@\\?\\#\\\\\\'") - .str()); + EXPECT_EQ(StringPiece("@?#\\\'"), + util::StringBuilder().Append("\\@\\?\\#\\\\\\'").ToString()); } TEST(UtilTest, StringBuilderMisplacedQuote) { - util::StringBuilder builder{}; - EXPECT_FALSE(builder.append(u"they're coming!")); + util::StringBuilder builder{}; + EXPECT_FALSE(builder.Append("they're coming!")); } TEST(UtilTest, StringBuilderUnicodeCodes) { - EXPECT_EQ(StringPiece16(u"\u00AF\u0AF0 woah"), - util::StringBuilder().append(u"\\u00AF\\u0AF0 woah") - .str()); + EXPECT_EQ(std::string("\u00AF\u0AF0 woah"), + util::StringBuilder().Append("\\u00AF\\u0AF0 woah").ToString()); - EXPECT_FALSE(util::StringBuilder().append(u"\\u00 yo")); + EXPECT_FALSE(util::StringBuilder().Append("\\u00 yo")); } TEST(UtilTest, TokenizeInput) { - auto tokenizer = util::tokenize(StringPiece16(u"this| is|the|end"), u'|'); - auto iter = tokenizer.begin(); - ASSERT_EQ(*iter, StringPiece16(u"this")); - ++iter; - ASSERT_EQ(*iter, StringPiece16(u" is")); - ++iter; - ASSERT_EQ(*iter, StringPiece16(u"the")); - ++iter; - ASSERT_EQ(*iter, StringPiece16(u"end")); - ++iter; - ASSERT_EQ(tokenizer.end(), iter); + auto tokenizer = util::Tokenize(StringPiece("this| is|the|end"), '|'); + auto iter = tokenizer.begin(); + ASSERT_EQ(*iter, StringPiece("this")); + ++iter; + ASSERT_EQ(*iter, StringPiece(" is")); + ++iter; + ASSERT_EQ(*iter, StringPiece("the")); + ++iter; + ASSERT_EQ(*iter, StringPiece("end")); + ++iter; + ASSERT_EQ(tokenizer.end(), iter); } TEST(UtilTest, TokenizeEmptyString) { - auto tokenizer = util::tokenize(StringPiece16(u""), u'|'); - auto iter = tokenizer.begin(); - ASSERT_NE(tokenizer.end(), iter); - ASSERT_EQ(StringPiece16(), *iter); - ++iter; - ASSERT_EQ(tokenizer.end(), iter); + auto tokenizer = util::Tokenize(StringPiece(""), '|'); + auto iter = tokenizer.begin(); + ASSERT_NE(tokenizer.end(), iter); + ASSERT_EQ(StringPiece(), *iter); + ++iter; + ASSERT_EQ(tokenizer.end(), iter); } TEST(UtilTest, TokenizeAtEnd) { - auto tokenizer = util::tokenize(StringPiece16(u"one."), u'.'); - auto iter = tokenizer.begin(); - ASSERT_EQ(*iter, StringPiece16(u"one")); - ++iter; - ASSERT_NE(iter, tokenizer.end()); - ASSERT_EQ(*iter, StringPiece16()); + auto tokenizer = util::Tokenize(StringPiece("one."), '.'); + auto iter = tokenizer.begin(); + ASSERT_EQ(*iter, StringPiece("one")); + ++iter; + ASSERT_NE(iter, tokenizer.end()); + ASSERT_EQ(*iter, StringPiece()); } TEST(UtilTest, IsJavaClassName) { - EXPECT_TRUE(util::isJavaClassName(u"android.test.Class")); - EXPECT_TRUE(util::isJavaClassName(u"android.test.Class$Inner")); - EXPECT_TRUE(util::isJavaClassName(u"android_test.test.Class")); - EXPECT_TRUE(util::isJavaClassName(u"_android_.test._Class_")); - EXPECT_FALSE(util::isJavaClassName(u"android.test.$Inner")); - EXPECT_FALSE(util::isJavaClassName(u"android.test.Inner$")); - EXPECT_FALSE(util::isJavaClassName(u".test.Class")); - EXPECT_FALSE(util::isJavaClassName(u"android")); + EXPECT_TRUE(util::IsJavaClassName("android.test.Class")); + EXPECT_TRUE(util::IsJavaClassName("android.test.Class$Inner")); + EXPECT_TRUE(util::IsJavaClassName("android_test.test.Class")); + EXPECT_TRUE(util::IsJavaClassName("_android_.test._Class_")); + EXPECT_FALSE(util::IsJavaClassName("android.test.$Inner")); + EXPECT_FALSE(util::IsJavaClassName("android.test.Inner$")); + EXPECT_FALSE(util::IsJavaClassName(".test.Class")); + EXPECT_FALSE(util::IsJavaClassName("android")); } TEST(UtilTest, IsJavaPackageName) { - EXPECT_TRUE(util::isJavaPackageName(u"android")); - EXPECT_TRUE(util::isJavaPackageName(u"android.test")); - EXPECT_TRUE(util::isJavaPackageName(u"android.test_thing")); - EXPECT_FALSE(util::isJavaPackageName(u"_android")); - EXPECT_FALSE(util::isJavaPackageName(u"android_")); - EXPECT_FALSE(util::isJavaPackageName(u"android.")); - EXPECT_FALSE(util::isJavaPackageName(u".android")); - EXPECT_FALSE(util::isJavaPackageName(u"android._test")); - EXPECT_FALSE(util::isJavaPackageName(u"..")); + EXPECT_TRUE(util::IsJavaPackageName("android")); + EXPECT_TRUE(util::IsJavaPackageName("android.test")); + EXPECT_TRUE(util::IsJavaPackageName("android.test_thing")); + EXPECT_FALSE(util::IsJavaPackageName("_android")); + EXPECT_FALSE(util::IsJavaPackageName("android_")); + EXPECT_FALSE(util::IsJavaPackageName("android.")); + EXPECT_FALSE(util::IsJavaPackageName(".android")); + EXPECT_FALSE(util::IsJavaPackageName("android._test")); + EXPECT_FALSE(util::IsJavaPackageName("..")); } TEST(UtilTest, FullyQualifiedClassName) { - Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf"); - AAPT_ASSERT_FALSE(res); + Maybe<std::string> res = util::GetFullyQualifiedClassName("android", ".asdf"); + AAPT_ASSERT_TRUE(res); + EXPECT_EQ(res.value(), "android.asdf"); - res = util::getFullyQualifiedClassName(u"android", u".asdf"); - AAPT_ASSERT_TRUE(res); - EXPECT_EQ(res.value(), u"android.asdf"); + res = util::GetFullyQualifiedClassName("android", ".a.b"); + AAPT_ASSERT_TRUE(res); + EXPECT_EQ(res.value(), "android.a.b"); - res = util::getFullyQualifiedClassName(u"android", u".a.b"); - AAPT_ASSERT_TRUE(res); - EXPECT_EQ(res.value(), u"android.a.b"); + res = util::GetFullyQualifiedClassName("android", "a.b"); + AAPT_ASSERT_TRUE(res); + EXPECT_EQ(res.value(), "a.b"); - res = util::getFullyQualifiedClassName(u"android", u"a.b"); - AAPT_ASSERT_TRUE(res); - EXPECT_EQ(res.value(), u"a.b"); + res = util::GetFullyQualifiedClassName("", "a.b"); + AAPT_ASSERT_TRUE(res); + EXPECT_EQ(res.value(), "a.b"); - res = util::getFullyQualifiedClassName(u"", u"a.b"); - AAPT_ASSERT_TRUE(res); - EXPECT_EQ(res.value(), u"a.b"); + res = util::GetFullyQualifiedClassName("android", "Class"); + AAPT_ASSERT_TRUE(res); + EXPECT_EQ(res.value(), "android.Class"); - res = util::getFullyQualifiedClassName(u"", u""); - AAPT_ASSERT_FALSE(res); + res = util::GetFullyQualifiedClassName("", ""); + AAPT_ASSERT_FALSE(res); - res = util::getFullyQualifiedClassName(u"android", u"./Apple"); - AAPT_ASSERT_FALSE(res); + res = util::GetFullyQualifiedClassName("android", "./Apple"); + AAPT_ASSERT_FALSE(res); } TEST(UtilTest, ExtractResourcePathComponents) { - StringPiece16 prefix, entry, suffix; - ASSERT_TRUE(util::extractResFilePathParts(u"res/xml-sw600dp/entry.xml", &prefix, &entry, - &suffix)); - EXPECT_EQ(prefix, u"res/xml-sw600dp/"); - EXPECT_EQ(entry, u"entry"); - EXPECT_EQ(suffix, u".xml"); + StringPiece prefix, entry, suffix; + ASSERT_TRUE(util::ExtractResFilePathParts("res/xml-sw600dp/entry.xml", + &prefix, &entry, &suffix)); + EXPECT_EQ(prefix, "res/xml-sw600dp/"); + EXPECT_EQ(entry, "entry"); + EXPECT_EQ(suffix, ".xml"); - ASSERT_TRUE(util::extractResFilePathParts(u"res/xml-sw600dp/entry.9.png", &prefix, &entry, - &suffix)); + ASSERT_TRUE(util::ExtractResFilePathParts("res/xml-sw600dp/entry.9.png", + &prefix, &entry, &suffix)); - EXPECT_EQ(prefix, u"res/xml-sw600dp/"); - EXPECT_EQ(entry, u"entry"); - EXPECT_EQ(suffix, u".9.png"); + EXPECT_EQ(prefix, "res/xml-sw600dp/"); + EXPECT_EQ(entry, "entry"); + EXPECT_EQ(suffix, ".9.png"); - EXPECT_FALSE(util::extractResFilePathParts(u"AndroidManifest.xml", &prefix, &entry, &suffix)); - EXPECT_FALSE(util::extractResFilePathParts(u"res/.xml", &prefix, &entry, &suffix)); + EXPECT_FALSE(util::ExtractResFilePathParts("AndroidManifest.xml", &prefix, + &entry, &suffix)); + EXPECT_FALSE( + util::ExtractResFilePathParts("res/.xml", &prefix, &entry, &suffix)); - ASSERT_TRUE(util::extractResFilePathParts(u"res//.", &prefix, &entry, &suffix)); - EXPECT_EQ(prefix, u"res//"); - EXPECT_EQ(entry, u""); - EXPECT_EQ(suffix, u"."); + ASSERT_TRUE( + util::ExtractResFilePathParts("res//.", &prefix, &entry, &suffix)); + EXPECT_EQ(prefix, "res//"); + EXPECT_EQ(entry, ""); + EXPECT_EQ(suffix, "."); } TEST(UtilTest, VerifyJavaStringFormat) { - ASSERT_TRUE(util::verifyJavaStringFormat(u"%09.34f")); - ASSERT_TRUE(util::verifyJavaStringFormat(u"%9$.34f %8$")); - ASSERT_TRUE(util::verifyJavaStringFormat(u"%% %%")); - ASSERT_FALSE(util::verifyJavaStringFormat(u"%09$f %f")); - ASSERT_FALSE(util::verifyJavaStringFormat(u"%09f %08s")); + ASSERT_TRUE(util::VerifyJavaStringFormat("%09.34f")); + ASSERT_TRUE(util::VerifyJavaStringFormat("%9$.34f %8$")); + ASSERT_TRUE(util::VerifyJavaStringFormat("%% %%")); + ASSERT_FALSE(util::VerifyJavaStringFormat("%09$f %f")); + ASSERT_FALSE(util::VerifyJavaStringFormat("%09f %08s")); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp index 0ef67eaf3dc5..7580b469a98e 100644 --- a/tools/aapt2/xml/XmlActionExecutor.cpp +++ b/tools/aapt2/xml/XmlActionExecutor.cpp @@ -19,94 +19,94 @@ namespace aapt { namespace xml { -static bool wrapperOne(XmlNodeAction::ActionFunc& f, Element* el, SourcePathDiagnostics*) { - return f(el); +static bool wrapper_one(XmlNodeAction::ActionFunc& f, Element* el, + SourcePathDiagnostics*) { + return f(el); } -static bool wrapperTwo(XmlNodeAction::ActionFuncWithDiag& f, Element* el, - SourcePathDiagnostics* diag) { - return f(el, diag); +static bool wrapper_two(XmlNodeAction::ActionFuncWithDiag& f, Element* el, + SourcePathDiagnostics* diag) { + return f(el, diag); } -void XmlNodeAction::action(XmlNodeAction::ActionFunc f) { - mActions.emplace_back(std::bind(wrapperOne, std::move(f), - std::placeholders::_1, - std::placeholders::_2)); +void XmlNodeAction::Action(XmlNodeAction::ActionFunc f) { + actions_.emplace_back(std::bind( + wrapper_one, std::move(f), std::placeholders::_1, std::placeholders::_2)); } -void XmlNodeAction::action(XmlNodeAction::ActionFuncWithDiag f) { - mActions.emplace_back(std::bind(wrapperTwo, std::move(f), - std::placeholders::_1, - std::placeholders::_2)); +void XmlNodeAction::Action(XmlNodeAction::ActionFuncWithDiag f) { + actions_.emplace_back(std::bind( + wrapper_two, std::move(f), std::placeholders::_1, std::placeholders::_2)); } -static void printElementToDiagMessage(const Element* el, DiagMessage* msg) { - *msg << "<"; - if (!el->namespaceUri.empty()) { - *msg << el->namespaceUri << ":"; - } - *msg << el->name << ">"; +static void PrintElementToDiagMessage(const Element* el, DiagMessage* msg) { + *msg << "<"; + if (!el->namespace_uri.empty()) { + *msg << el->namespace_uri << ":"; + } + *msg << el->name << ">"; } -bool XmlNodeAction::execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag, - Element* el) const { - bool error = false; - for (const ActionFuncWithDiag& action : mActions) { - error |= !action(el, diag); - } +bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, + SourcePathDiagnostics* diag, Element* el) const { + bool error = false; + for (const ActionFuncWithDiag& action : actions_) { + error |= !action(el, diag); + } - for (Element* childEl : el->getChildElements()) { - if (childEl->namespaceUri.empty()) { - std::map<std::u16string, XmlNodeAction>::const_iterator iter = - mMap.find(childEl->name); - if (iter != mMap.end()) { - error |= !iter->second.execute(policy, diag, childEl); - continue; - } - } + for (Element* child_el : el->GetChildElements()) { + if (child_el->namespace_uri.empty()) { + std::map<std::string, XmlNodeAction>::const_iterator iter = + map_.find(child_el->name); + if (iter != map_.end()) { + error |= !iter->second.Execute(policy, diag, child_el); + continue; + } + } - if (policy == XmlActionExecutorPolicy::Whitelist) { - DiagMessage errorMsg(childEl->lineNumber); - errorMsg << "unknown element "; - printElementToDiagMessage(childEl, &errorMsg); - errorMsg << " found"; - diag->error(errorMsg); - error = true; - } + if (policy == XmlActionExecutorPolicy::kWhitelist) { + DiagMessage error_msg(child_el->line_number); + error_msg << "unknown element "; + PrintElementToDiagMessage(child_el, &error_msg); + error_msg << " found"; + diag->Error(error_msg); + error = true; } - return !error; + } + return !error; } -bool XmlActionExecutor::execute(XmlActionExecutorPolicy policy, IDiagnostics* diag, - XmlResource* doc) const { - SourcePathDiagnostics sourceDiag(doc->file.source, diag); +bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy, + IDiagnostics* diag, XmlResource* doc) const { + SourcePathDiagnostics source_diag(doc->file.source, diag); - Element* el = findRootElement(doc); - if (!el) { - if (policy == XmlActionExecutorPolicy::Whitelist) { - sourceDiag.error(DiagMessage() << "no root XML tag found"); - return false; - } - return true; + Element* el = FindRootElement(doc); + if (!el) { + if (policy == XmlActionExecutorPolicy::kWhitelist) { + source_diag.Error(DiagMessage() << "no root XML tag found"); + return false; } + return true; + } - if (el->namespaceUri.empty()) { - std::map<std::u16string, XmlNodeAction>::const_iterator iter = mMap.find(el->name); - if (iter != mMap.end()) { - return iter->second.execute(policy, &sourceDiag, el); - } + if (el->namespace_uri.empty()) { + std::map<std::string, XmlNodeAction>::const_iterator iter = + map_.find(el->name); + if (iter != map_.end()) { + return iter->second.Execute(policy, &source_diag, el); } + } - if (policy == XmlActionExecutorPolicy::Whitelist) { - DiagMessage errorMsg(el->lineNumber); - errorMsg << "unknown element "; - printElementToDiagMessage(el, &errorMsg); - errorMsg << " found"; - sourceDiag.error(errorMsg); - return false; - } - return true; + if (policy == XmlActionExecutorPolicy::kWhitelist) { + DiagMessage error_msg(el->line_number); + error_msg << "unknown element "; + PrintElementToDiagMessage(el, &error_msg); + error_msg << " found"; + source_diag.Error(error_msg); + return false; + } + return true; } -} // namespace xml -} // namespace aapt +} // namespace xml +} // namespace aapt diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h index 36b94dbfde05..68e35631988e 100644 --- a/tools/aapt2/xml/XmlActionExecutor.h +++ b/tools/aapt2/xml/XmlActionExecutor.h @@ -17,92 +17,97 @@ #ifndef AAPT_XML_XMLPATTERN_H #define AAPT_XML_XMLPATTERN_H -#include "Diagnostics.h" -#include "xml/XmlDom.h" - -#include <android-base/macros.h> #include <functional> #include <map> #include <string> #include <vector> +#include "android-base/macros.h" + +#include "Diagnostics.h" +#include "xml/XmlDom.h" + namespace aapt { namespace xml { enum class XmlActionExecutorPolicy { - /** - * Actions on run if elements are matched, errors occur only when actions return false. - */ - None, - - /** - * The actions defined must match and run. If an element is found that does not match - * an action, an error occurs. - */ - Whitelist, + /** + * Actions on run if elements are matched, errors occur only when actions + * return false. + */ + kNone, + + /** + * The actions defined must match and run. If an element is found that does + * not match + * an action, an error occurs. + */ + kWhitelist, }; /** - * Contains the actions to perform at this XML node. This is a recursive data structure that + * Contains the actions to perform at this XML node. This is a recursive data + * structure that * holds XmlNodeActions for child XML nodes. */ class XmlNodeAction { -public: - using ActionFuncWithDiag = std::function<bool(Element*, SourcePathDiagnostics*)>; - using ActionFunc = std::function<bool(Element*)>; - - /** - * Find or create a child XmlNodeAction that will be performed for the child element - * with the name `name`. - */ - XmlNodeAction& operator[](const std::u16string& name) { - return mMap[name]; - } - - /** - * Add an action to be performed at this XmlNodeAction. - */ - void action(ActionFunc f); - void action(ActionFuncWithDiag); - -private: - friend class XmlActionExecutor; - - bool execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag, Element* el) const; - - std::map<std::u16string, XmlNodeAction> mMap; - std::vector<ActionFuncWithDiag> mActions; + public: + using ActionFuncWithDiag = + std::function<bool(Element*, SourcePathDiagnostics*)>; + using ActionFunc = std::function<bool(Element*)>; + + /** + * Find or create a child XmlNodeAction that will be performed for the child + * element + * with the name `name`. + */ + XmlNodeAction& operator[](const std::string& name) { return map_[name]; } + + /** + * Add an action to be performed at this XmlNodeAction. + */ + void Action(ActionFunc f); + void Action(ActionFuncWithDiag); + + private: + friend class XmlActionExecutor; + + bool Execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag, + Element* el) const; + + std::map<std::string, XmlNodeAction> map_; + std::vector<ActionFuncWithDiag> actions_; }; /** - * Allows the definition of actions to execute at specific XML elements defined by their + * Allows the definition of actions to execute at specific XML elements defined + * by their * hierarchy. */ class XmlActionExecutor { -public: - XmlActionExecutor() = default; - - /** - * Find or create a root XmlNodeAction that will be performed for the root XML element - * with the name `name`. - */ - XmlNodeAction& operator[](const std::u16string& name) { - return mMap[name]; - } - - /** - * Execute the defined actions for this XmlResource. - * Returns true if all actions return true, otherwise returns false. - */ - bool execute(XmlActionExecutorPolicy policy, IDiagnostics* diag, XmlResource* doc) const; - -private: - std::map<std::u16string, XmlNodeAction> mMap; - - DISALLOW_COPY_AND_ASSIGN(XmlActionExecutor); + public: + XmlActionExecutor() = default; + + /** + * Find or create a root XmlNodeAction that will be performed for the root XML + * element with the name `name`. + */ + XmlNodeAction& operator[](const std::string& name) { return map_[name]; } + + /** + * Execute the defined actions for this XmlResource. + * Returns true if all actions return true, otherwise returns false. + */ + bool Execute(XmlActionExecutorPolicy policy, IDiagnostics* diag, + XmlResource* doc) const; + + private: + std::map<std::string, XmlNodeAction> map_; + + DISALLOW_COPY_AND_ASSIGN(XmlActionExecutor); }; -} // namespace xml -} // namespace aapt +} // namespace xml +} // namespace aapt #endif /* AAPT_XML_XMLPATTERN_H */ diff --git a/tools/aapt2/xml/XmlActionExecutor_test.cpp b/tools/aapt2/xml/XmlActionExecutor_test.cpp index ebf287a251f2..7110c90fa3a9 100644 --- a/tools/aapt2/xml/XmlActionExecutor_test.cpp +++ b/tools/aapt2/xml/XmlActionExecutor_test.cpp @@ -14,49 +14,53 @@ * limitations under the License. */ -#include "test/Test.h" #include "xml/XmlActionExecutor.h" +#include "test/Test.h" + namespace aapt { namespace xml { TEST(XmlActionExecutorTest, BuildsAccessibleNestedPattern) { - XmlActionExecutor executor; - XmlNodeAction& manifestAction = executor[u"manifest"]; - XmlNodeAction& applicationAction = manifestAction[u"application"]; - - Element* manifestEl = nullptr; - manifestAction.action([&](Element* manifest) -> bool { - manifestEl = manifest; - return true; - }); - - Element* applicationEl = nullptr; - applicationAction.action([&](Element* application) -> bool { - applicationEl = application; - return true; - }); - - std::unique_ptr<XmlResource> doc = test::buildXmlDom("<manifest><application /></manifest>"); - - StdErrDiagnostics diag; - ASSERT_TRUE(executor.execute(XmlActionExecutorPolicy::None, &diag, doc.get())); - ASSERT_NE(nullptr, manifestEl); - EXPECT_EQ(std::u16string(u"manifest"), manifestEl->name); - - ASSERT_NE(nullptr, applicationEl); - EXPECT_EQ(std::u16string(u"application"), applicationEl->name); + XmlActionExecutor executor; + XmlNodeAction& manifest_action = executor["manifest"]; + XmlNodeAction& application_action = manifest_action["application"]; + + Element* manifest_el = nullptr; + manifest_action.Action([&](Element* manifest) -> bool { + manifest_el = manifest; + return true; + }); + + Element* application_el = nullptr; + application_action.Action([&](Element* application) -> bool { + application_el = application; + return true; + }); + + std::unique_ptr<XmlResource> doc = + test::BuildXmlDom("<manifest><application /></manifest>"); + + StdErrDiagnostics diag; + ASSERT_TRUE( + executor.Execute(XmlActionExecutorPolicy::kNone, &diag, doc.get())); + ASSERT_NE(nullptr, manifest_el); + EXPECT_EQ(std::string("manifest"), manifest_el->name); + + ASSERT_NE(nullptr, application_el); + EXPECT_EQ(std::string("application"), application_el->name); } TEST(XmlActionExecutorTest, FailsWhenUndefinedHierarchyExists) { - XmlActionExecutor executor; - executor[u"manifest"][u"application"]; + XmlActionExecutor executor; + executor["manifest"]["application"]; - std::unique_ptr<XmlResource> doc = test::buildXmlDom( - "<manifest><application /><activity /></manifest>"); - StdErrDiagnostics diag; - ASSERT_FALSE(executor.execute(XmlActionExecutorPolicy::Whitelist, &diag, doc.get())); + std::unique_ptr<XmlResource> doc = + test::BuildXmlDom("<manifest><application /><activity /></manifest>"); + StdErrDiagnostics diag; + ASSERT_FALSE( + executor.Execute(XmlActionExecutorPolicy::kWhitelist, &diag, doc.get())); } -} // namespace xml -} // namespace aapt +} // namespace xml +} // namespace aapt diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp index 0ce333af3115..960d3614305e 100644 --- a/tools/aapt2/xml/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -15,430 +15,501 @@ */ #include "XmlDom.h" -#include "XmlPullParser.h" -#include "util/Util.h" -#include <cassert> #include <expat.h> + +#include <cassert> #include <memory> #include <stack> #include <string> #include <tuple> +#include "android-base/logging.h" + +#include "XmlPullParser.h" +#include "util/Util.h" + namespace aapt { namespace xml { constexpr char kXmlNamespaceSep = 1; struct Stack { - std::unique_ptr<xml::Node> root; - std::stack<xml::Node*> nodeStack; - std::u16string pendingComment; + std::unique_ptr<xml::Node> root; + std::stack<xml::Node*> node_stack; + std::string pending_comment; }; /** * Extracts the namespace and name of an expanded element or attribute name. */ -static void splitName(const char* name, std::u16string* outNs, std::u16string* outName) { - const char* p = name; - while (*p != 0 && *p != kXmlNamespaceSep) { - p++; - } - - if (*p == 0) { - outNs->clear(); - *outName = util::utf8ToUtf16(name); - } else { - *outNs = util::utf8ToUtf16(StringPiece(name, (p - name))); - *outName = util::utf8ToUtf16(p + 1); - } +static void SplitName(const char* name, std::string* out_ns, + std::string* out_name) { + const char* p = name; + while (*p != 0 && *p != kXmlNamespaceSep) { + p++; + } + + if (*p == 0) { + out_ns->clear(); + *out_name = StringPiece(name).ToString(); + } else { + *out_ns = StringPiece(name, (p - name)).ToString(); + *out_name = StringPiece(p + 1).ToString(); + } } -static void addToStack(Stack* stack, XML_Parser parser, std::unique_ptr<Node> node) { - node->lineNumber = XML_GetCurrentLineNumber(parser); - node->columnNumber = XML_GetCurrentColumnNumber(parser); - - Node* thisNode = node.get(); - if (!stack->nodeStack.empty()) { - stack->nodeStack.top()->addChild(std::move(node)); - } else { - stack->root = std::move(node); - } - - if (!nodeCast<Text>(thisNode)) { - stack->nodeStack.push(thisNode); - } +static void AddToStack(Stack* stack, XML_Parser parser, + std::unique_ptr<Node> node) { + node->line_number = XML_GetCurrentLineNumber(parser); + node->column_number = XML_GetCurrentColumnNumber(parser); + + Node* this_node = node.get(); + if (!stack->node_stack.empty()) { + stack->node_stack.top()->AppendChild(std::move(node)); + } else { + stack->root = std::move(node); + } + + if (!NodeCast<Text>(this_node)) { + stack->node_stack.push(this_node); + } } -static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri) { - XML_Parser parser = reinterpret_cast<XML_Parser>(userData); - Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); +static void XMLCALL StartNamespaceHandler(void* user_data, const char* prefix, + const char* uri) { + XML_Parser parser = reinterpret_cast<XML_Parser>(user_data); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); - std::unique_ptr<Namespace> ns = util::make_unique<Namespace>(); - if (prefix) { - ns->namespacePrefix = util::utf8ToUtf16(prefix); - } + std::unique_ptr<Namespace> ns = util::make_unique<Namespace>(); + if (prefix) { + ns->namespace_prefix = StringPiece(prefix).ToString(); + } - if (uri) { - ns->namespaceUri = util::utf8ToUtf16(uri); - } + if (uri) { + ns->namespace_uri = StringPiece(uri).ToString(); + } - addToStack(stack, parser, std::move(ns)); + AddToStack(stack, parser, std::move(ns)); } -static void XMLCALL endNamespaceHandler(void* userData, const char* prefix) { - XML_Parser parser = reinterpret_cast<XML_Parser>(userData); - Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); +static void XMLCALL EndNamespaceHandler(void* user_data, const char* prefix) { + XML_Parser parser = reinterpret_cast<XML_Parser>(user_data); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); - assert(!stack->nodeStack.empty()); - stack->nodeStack.pop(); + CHECK(!stack->node_stack.empty()); + stack->node_stack.pop(); } -static bool lessAttribute(const Attribute& lhs, const Attribute& rhs) { - return std::tie(lhs.namespaceUri, lhs.name, lhs.value) < - std::tie(rhs.namespaceUri, rhs.name, rhs.value); +static bool less_attribute(const Attribute& lhs, const Attribute& rhs) { + return std::tie(lhs.namespace_uri, lhs.name, lhs.value) < + std::tie(rhs.namespace_uri, rhs.name, rhs.value); } -static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs) { - XML_Parser parser = reinterpret_cast<XML_Parser>(userData); - Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); +static void XMLCALL StartElementHandler(void* user_data, const char* name, + const char** attrs) { + XML_Parser parser = reinterpret_cast<XML_Parser>(user_data); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); - std::unique_ptr<Element> el = util::make_unique<Element>(); - splitName(name, &el->namespaceUri, &el->name); + std::unique_ptr<Element> el = util::make_unique<Element>(); + SplitName(name, &el->namespace_uri, &el->name); - while (*attrs) { - Attribute attribute; - splitName(*attrs++, &attribute.namespaceUri, &attribute.name); - attribute.value = util::utf8ToUtf16(*attrs++); + while (*attrs) { + Attribute attribute; + SplitName(*attrs++, &attribute.namespace_uri, &attribute.name); + attribute.value = StringPiece(*attrs++).ToString(); - // Insert in sorted order. - auto iter = std::lower_bound(el->attributes.begin(), el->attributes.end(), attribute, - lessAttribute); - el->attributes.insert(iter, std::move(attribute)); - } + // Insert in sorted order. + auto iter = std::lower_bound(el->attributes.begin(), el->attributes.end(), + attribute, less_attribute); + el->attributes.insert(iter, std::move(attribute)); + } - el->comment = std::move(stack->pendingComment); - addToStack(stack, parser, std::move(el)); + el->comment = std::move(stack->pending_comment); + AddToStack(stack, parser, std::move(el)); } -static void XMLCALL endElementHandler(void* userData, const char* name) { - XML_Parser parser = reinterpret_cast<XML_Parser>(userData); - Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); +static void XMLCALL EndElementHandler(void* user_data, const char* name) { + XML_Parser parser = reinterpret_cast<XML_Parser>(user_data); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); - assert(!stack->nodeStack.empty()); - //stack->nodeStack.top()->comment = std::move(stack->pendingComment); - stack->nodeStack.pop(); + CHECK(!stack->node_stack.empty()); + // stack->nodeStack.top()->comment = std::move(stack->pendingComment); + stack->node_stack.pop(); } -static void XMLCALL characterDataHandler(void* userData, const char* s, int len) { - XML_Parser parser = reinterpret_cast<XML_Parser>(userData); - Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); - - if (!s || len <= 0) { +static void XMLCALL CharacterDataHandler(void* user_data, const char* s, + int len) { + XML_Parser parser = reinterpret_cast<XML_Parser>(user_data); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); + + if (!s || len <= 0) { + return; + } + + // See if we can just append the text to a previous text node. + if (!stack->node_stack.empty()) { + Node* currentParent = stack->node_stack.top(); + if (!currentParent->children.empty()) { + Node* last_child = currentParent->children.back().get(); + if (Text* text = NodeCast<Text>(last_child)) { + text->text += StringPiece(s, len).ToString(); return; + } } + } - // See if we can just append the text to a previous text node. - if (!stack->nodeStack.empty()) { - Node* currentParent = stack->nodeStack.top(); - if (!currentParent->children.empty()) { - Node* lastChild = currentParent->children.back().get(); - if (Text* text = nodeCast<Text>(lastChild)) { - text->text += util::utf8ToUtf16(StringPiece(s, len)); - return; - } - } - } - - std::unique_ptr<Text> text = util::make_unique<Text>(); - text->text = util::utf8ToUtf16(StringPiece(s, len)); - addToStack(stack, parser, std::move(text)); + std::unique_ptr<Text> text = util::make_unique<Text>(); + text->text = StringPiece(s, len).ToString(); + AddToStack(stack, parser, std::move(text)); } -static void XMLCALL commentDataHandler(void* userData, const char* comment) { - XML_Parser parser = reinterpret_cast<XML_Parser>(userData); - Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); +static void XMLCALL CommentDataHandler(void* user_data, const char* comment) { + XML_Parser parser = reinterpret_cast<XML_Parser>(user_data); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); - if (!stack->pendingComment.empty()) { - stack->pendingComment += '\n'; - } - stack->pendingComment += util::utf8ToUtf16(comment); + if (!stack->pending_comment.empty()) { + stack->pending_comment += '\n'; + } + stack->pending_comment += comment; } -std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source) { - Stack stack; - - XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep); - XML_SetUserData(parser, &stack); - XML_UseParserAsHandlerArg(parser); - XML_SetElementHandler(parser, startElementHandler, endElementHandler); - XML_SetNamespaceDeclHandler(parser, startNamespaceHandler, endNamespaceHandler); - XML_SetCharacterDataHandler(parser, characterDataHandler); - XML_SetCommentHandler(parser, commentDataHandler); - - char buffer[1024]; - while (!in->eof()) { - in->read(buffer, sizeof(buffer) / sizeof(buffer[0])); - if (in->bad() && !in->eof()) { - stack.root = {}; - diag->error(DiagMessage(source) << strerror(errno)); - break; - } - - if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == XML_STATUS_ERROR) { - stack.root = {}; - diag->error(DiagMessage(source.withLine(XML_GetCurrentLineNumber(parser))) - << XML_ErrorString(XML_GetErrorCode(parser))); - break; - } +std::unique_ptr<XmlResource> Inflate(std::istream* in, IDiagnostics* diag, + const Source& source) { + Stack stack; + + XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep); + XML_SetUserData(parser, &stack); + XML_UseParserAsHandlerArg(parser); + XML_SetElementHandler(parser, StartElementHandler, EndElementHandler); + XML_SetNamespaceDeclHandler(parser, StartNamespaceHandler, + EndNamespaceHandler); + XML_SetCharacterDataHandler(parser, CharacterDataHandler); + XML_SetCommentHandler(parser, CommentDataHandler); + + char buffer[1024]; + while (!in->eof()) { + in->read(buffer, sizeof(buffer) / sizeof(buffer[0])); + if (in->bad() && !in->eof()) { + stack.root = {}; + diag->Error(DiagMessage(source) << strerror(errno)); + break; } - XML_ParserFree(parser); - if (stack.root) { - return util::make_unique<XmlResource>(ResourceFile{ {}, {}, source }, std::move(stack.root)); + if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == + XML_STATUS_ERROR) { + stack.root = {}; + diag->Error(DiagMessage(source.WithLine(XML_GetCurrentLineNumber(parser))) + << XML_ErrorString(XML_GetErrorCode(parser))); + break; } - return {}; + } + + XML_ParserFree(parser); + if (stack.root) { + return util::make_unique<XmlResource>(ResourceFile{{}, {}, source}, + std::move(stack.root)); + } + return {}; } -static void copyAttributes(Element* el, android::ResXMLParser* parser) { - const size_t attrCount = parser->getAttributeCount(); - if (attrCount > 0) { - el->attributes.reserve(attrCount); - for (size_t i = 0; i < attrCount; i++) { - Attribute attr; - size_t len; - const char16_t* str16 = parser->getAttributeNamespace(i, &len); - if (str16) { - attr.namespaceUri.assign(str16, len); - } - - str16 = parser->getAttributeName(i, &len); - if (str16) { - attr.name.assign(str16, len); - } - - str16 = parser->getAttributeStringValue(i, &len); - if (str16) { - attr.value.assign(str16, len); - } - el->attributes.push_back(std::move(attr)); - } +static void CopyAttributes(Element* el, android::ResXMLParser* parser) { + const size_t attr_count = parser->getAttributeCount(); + if (attr_count > 0) { + el->attributes.reserve(attr_count); + for (size_t i = 0; i < attr_count; i++) { + Attribute attr; + size_t len; + const char16_t* str16 = parser->getAttributeNamespace(i, &len); + if (str16) { + attr.namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len)); + } + + str16 = parser->getAttributeName(i, &len); + if (str16) { + attr.name = util::Utf16ToUtf8(StringPiece16(str16, len)); + } + + str16 = parser->getAttributeStringValue(i, &len); + if (str16) { + attr.value = util::Utf16ToUtf8(StringPiece16(str16, len)); + } + el->attributes.push_back(std::move(attr)); } + } } -std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag, - const Source& source) { - // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which - // causes errors when qualifying it with android:: - using namespace android; +std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, + IDiagnostics* diag, const Source& source) { + // We import the android namespace because on Windows NO_ERROR is a macro, not + // an enum, which + // causes errors when qualifying it with android:: + using namespace android; - std::unique_ptr<Node> root; - std::stack<Node*> nodeStack; + std::unique_ptr<Node> root; + std::stack<Node*> node_stack; - ResXMLTree tree; - if (tree.setTo(data, dataLen) != NO_ERROR) { - return {}; - } + ResXMLTree tree; + if (tree.setTo(data, data_len) != NO_ERROR) { + return {}; + } + + ResXMLParser::event_code_t code; + while ((code = tree.next()) != ResXMLParser::BAD_DOCUMENT && + code != ResXMLParser::END_DOCUMENT) { + std::unique_ptr<Node> new_node; + switch (code) { + case ResXMLParser::START_NAMESPACE: { + std::unique_ptr<Namespace> node = util::make_unique<Namespace>(); + size_t len; + const char16_t* str16 = tree.getNamespacePrefix(&len); + if (str16) { + node->namespace_prefix = util::Utf16ToUtf8(StringPiece16(str16, len)); + } - ResXMLParser::event_code_t code; - while ((code = tree.next()) != ResXMLParser::BAD_DOCUMENT && - code != ResXMLParser::END_DOCUMENT) { - std::unique_ptr<Node> newNode; - switch (code) { - case ResXMLParser::START_NAMESPACE: { - std::unique_ptr<Namespace> node = util::make_unique<Namespace>(); - size_t len; - const char16_t* str16 = tree.getNamespacePrefix(&len); - if (str16) { - node->namespacePrefix.assign(str16, len); - } - - str16 = tree.getNamespaceUri(&len); - if (str16) { - node->namespaceUri.assign(str16, len); - } - newNode = std::move(node); - break; - } - - case ResXMLParser::START_TAG: { - std::unique_ptr<Element> node = util::make_unique<Element>(); - size_t len; - const char16_t* str16 = tree.getElementNamespace(&len); - if (str16) { - node->namespaceUri.assign(str16, len); - } - - str16 = tree.getElementName(&len); - if (str16) { - node->name.assign(str16, len); - } - - copyAttributes(node.get(), &tree); - - newNode = std::move(node); - break; - } - - case ResXMLParser::TEXT: { - std::unique_ptr<Text> node = util::make_unique<Text>(); - size_t len; - const char16_t* str16 = tree.getText(&len); - if (str16) { - node->text.assign(str16, len); - } - newNode = std::move(node); - break; - } - - case ResXMLParser::END_NAMESPACE: - case ResXMLParser::END_TAG: - assert(!nodeStack.empty()); - nodeStack.pop(); - break; - - default: - assert(false); - break; + str16 = tree.getNamespaceUri(&len); + if (str16) { + node->namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len)); + } + new_node = std::move(node); + break; + } + + case ResXMLParser::START_TAG: { + std::unique_ptr<Element> node = util::make_unique<Element>(); + size_t len; + const char16_t* str16 = tree.getElementNamespace(&len); + if (str16) { + node->namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len)); } - if (newNode) { - newNode->lineNumber = tree.getLineNumber(); - - Node* thisNode = newNode.get(); - if (!root) { - assert(nodeStack.empty()); - root = std::move(newNode); - } else { - assert(!nodeStack.empty()); - nodeStack.top()->addChild(std::move(newNode)); - } - - if (!nodeCast<Text>(thisNode)) { - nodeStack.push(thisNode); - } + str16 = tree.getElementName(&len); + if (str16) { + node->name = util::Utf16ToUtf8(StringPiece16(str16, len)); } + + CopyAttributes(node.get(), &tree); + + new_node = std::move(node); + break; + } + + case ResXMLParser::TEXT: { + std::unique_ptr<Text> node = util::make_unique<Text>(); + size_t len; + const char16_t* str16 = tree.getText(&len); + if (str16) { + node->text = util::Utf16ToUtf8(StringPiece16(str16, len)); + } + new_node = std::move(node); + break; + } + + case ResXMLParser::END_NAMESPACE: + case ResXMLParser::END_TAG: + CHECK(!node_stack.empty()); + node_stack.pop(); + break; + + default: + LOG(FATAL) << "unhandled XML chunk type"; + break; + } + + if (new_node) { + new_node->line_number = tree.getLineNumber(); + + Node* this_node = new_node.get(); + if (!root) { + CHECK(node_stack.empty()) << "node stack should be empty"; + root = std::move(new_node); + } else { + CHECK(!node_stack.empty()) << "node stack should not be empty"; + node_stack.top()->AppendChild(std::move(new_node)); + } + + if (!NodeCast<Text>(this_node)) { + node_stack.push(this_node); + } } - return util::make_unique<XmlResource>(ResourceFile{}, std::move(root)); + } + return util::make_unique<XmlResource>(ResourceFile{}, std::move(root)); } -Element* findRootElement(XmlResource* doc) { - return findRootElement(doc->root.get()); +std::unique_ptr<Node> Namespace::Clone() { + auto ns = util::make_unique<Namespace>(); + ns->comment = comment; + ns->line_number = line_number; + ns->column_number = column_number; + ns->namespace_prefix = namespace_prefix; + ns->namespace_uri = namespace_uri; + + ns->children.reserve(children.size()); + for (const std::unique_ptr<xml::Node>& child : children) { + ns->AppendChild(child->Clone()); + } + return std::move(ns); } -Element* findRootElement(Node* node) { - if (!node) { - return nullptr; - } +Element* FindRootElement(XmlResource* doc) { + return FindRootElement(doc->root.get()); +} - Element* el = nullptr; - while ((el = nodeCast<Element>(node)) == nullptr) { - if (node->children.empty()) { - return nullptr; - } - // We are looking for the first element, and namespaces can only have one child. - node = node->children.front().get(); +Element* FindRootElement(Node* node) { + if (!node) { + return nullptr; + } + + Element* el = nullptr; + while ((el = NodeCast<Element>(node)) == nullptr) { + if (node->children.empty()) { + return nullptr; } - return el; + // We are looking for the first element, and namespaces can only have one + // child. + node = node->children.front().get(); + } + return el; } -void Node::addChild(std::unique_ptr<Node> child) { - child->parent = this; - children.push_back(std::move(child)); +void Node::AppendChild(std::unique_ptr<Node> child) { + child->parent = this; + children.push_back(std::move(child)); } -Attribute* Element::findAttribute(const StringPiece16& ns, const StringPiece16& name) { - for (auto& attr : attributes) { - if (ns == attr.namespaceUri && name == attr.name) { - return &attr; - } +void Node::InsertChild(size_t index, std::unique_ptr<Node> child) { + child->parent = this; + children.insert(children.begin() + index, std::move(child)); +} + +Attribute* Element::FindAttribute(const StringPiece& ns, + const StringPiece& name) { + for (auto& attr : attributes) { + if (ns == attr.namespace_uri && name == attr.name) { + return &attr; } - return nullptr; + } + return nullptr; } -Element* Element::findChild(const StringPiece16& ns, const StringPiece16& name) { - return findChildWithAttribute(ns, name, {}, {}, {}); +Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) { + return FindChildWithAttribute(ns, name, {}, {}, {}); } -Element* Element::findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name, - const StringPiece16& attrNs, const StringPiece16& attrName, - const StringPiece16& attrValue) { - for (auto& childNode : children) { - Node* child = childNode.get(); - while (nodeCast<Namespace>(child)) { - if (child->children.empty()) { - break; - } - child = child->children[0].get(); +Element* Element::FindChildWithAttribute(const StringPiece& ns, + const StringPiece& name, + const StringPiece& attr_ns, + const StringPiece& attr_name, + const StringPiece& attr_value) { + for (auto& child_node : children) { + Node* child = child_node.get(); + while (NodeCast<Namespace>(child)) { + if (child->children.empty()) { + break; + } + child = child->children[0].get(); + } + + if (Element* el = NodeCast<Element>(child)) { + if (ns == el->namespace_uri && name == el->name) { + if (attr_ns.empty() && attr_name.empty()) { + return el; } - if (Element* el = nodeCast<Element>(child)) { - if (ns == el->namespaceUri && name == el->name) { - if (attrNs.empty() && attrName.empty()) { - return el; - } - - Attribute* attr = el->findAttribute(attrNs, attrName); - if (attr && attrValue == attr->value) { - return el; - } - } + Attribute* attr = el->FindAttribute(attr_ns, attr_name); + if (attr && attr_value == attr->value) { + return el; } + } } - return nullptr; + } + return nullptr; } -std::vector<Element*> Element::getChildElements() { - std::vector<Element*> elements; - for (auto& childNode : children) { - Node* child = childNode.get(); - while (nodeCast<Namespace>(child)) { - if (child->children.empty()) { - break; - } - child = child->children[0].get(); - } +std::vector<Element*> Element::GetChildElements() { + std::vector<Element*> elements; + for (auto& child_node : children) { + Node* child = child_node.get(); + while (NodeCast<Namespace>(child)) { + if (child->children.empty()) { + break; + } + child = child->children[0].get(); + } - if (Element* el = nodeCast<Element>(child)) { - elements.push_back(el); - } + if (Element* el = NodeCast<Element>(child)) { + elements.push_back(el); } - return elements; + } + return elements; } -void PackageAwareVisitor::visit(Namespace* ns) { - bool added = false; - if (Maybe<ExtractedPackage> maybePackage = extractPackageFromNamespace(ns->namespaceUri)) { - ExtractedPackage& package = maybePackage.value(); - mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, std::move(package) }); - added = true; - } +std::unique_ptr<Node> Element::Clone() { + auto el = util::make_unique<Element>(); + el->comment = comment; + el->line_number = line_number; + el->column_number = column_number; + el->name = name; + el->namespace_uri = namespace_uri; + + el->attributes.reserve(attributes.size()); + for (xml::Attribute& attr : attributes) { + // Don't copy compiled values or attributes. + el->attributes.push_back( + xml::Attribute{attr.namespace_uri, attr.name, attr.value}); + } + + el->children.reserve(children.size()); + for (const std::unique_ptr<xml::Node>& child : children) { + el->AppendChild(child->Clone()); + } + return std::move(el); +} - Visitor::visit(ns); +std::unique_ptr<Node> Text::Clone() { + auto t = util::make_unique<Text>(); + t->comment = comment; + t->line_number = line_number; + t->column_number = column_number; + t->text = text; + return std::move(t); +} - if (added) { - mPackageDecls.pop_back(); - } +void PackageAwareVisitor::Visit(Namespace* ns) { + bool added = false; + if (Maybe<ExtractedPackage> maybe_package = + ExtractPackageFromNamespace(ns->namespace_uri)) { + ExtractedPackage& package = maybe_package.value(); + package_decls_.push_back( + PackageDecl{ns->namespace_prefix, std::move(package)}); + added = true; + } + + Visitor::Visit(ns); + + if (added) { + package_decls_.pop_back(); + } } -Maybe<ExtractedPackage> PackageAwareVisitor::transformPackageAlias( - const StringPiece16& alias, const StringPiece16& localPackage) const { - if (alias.empty()) { - return ExtractedPackage{ localPackage.toString(), false /* private */ }; - } - - const auto rend = mPackageDecls.rend(); - for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) { - if (alias == iter->prefix) { - if (iter->package.package.empty()) { - return ExtractedPackage{ localPackage.toString(), - iter->package.privateNamespace }; - } - return iter->package; - } - } - return {}; +Maybe<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias( + const StringPiece& alias, const StringPiece& local_package) const { + if (alias.empty()) { + return ExtractedPackage{local_package.ToString(), false /* private */}; + } + + const auto rend = package_decls_.rend(); + for (auto iter = package_decls_.rbegin(); iter != rend; ++iter) { + if (alias == iter->prefix) { + if (iter->package.package.empty()) { + return ExtractedPackage{local_package.ToString(), + iter->package.private_namespace}; + } + return iter->package; + } + } + return {}; } -} // namespace xml -} // namespace aapt +} // namespace xml +} // namespace aapt diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h index b374d20039a5..720fe357bfa1 100644 --- a/tools/aapt2/xml/XmlDom.h +++ b/tools/aapt2/xml/XmlDom.h @@ -17,6 +17,11 @@ #ifndef AAPT_XML_DOM_H #define AAPT_XML_DOM_H +#include <istream> +#include <memory> +#include <string> +#include <vector> + #include "Diagnostics.h" #include "Resource.h" #include "ResourceValues.h" @@ -24,30 +29,28 @@ #include "util/Util.h" #include "xml/XmlUtil.h" -#include <istream> -#include <memory> -#include <string> -#include <vector> - namespace aapt { namespace xml { -struct RawVisitor; +class RawVisitor; /** * Base class for all XML nodes. */ -struct Node { - Node* parent = nullptr; - size_t lineNumber = 0; - size_t columnNumber = 0; - std::u16string comment; - std::vector<std::unique_ptr<Node>> children; - - virtual ~Node() = default; - - void addChild(std::unique_ptr<Node> child); - virtual void accept(RawVisitor* visitor) = 0; +class Node { + public: + Node* parent = nullptr; + size_t line_number = 0; + size_t column_number = 0; + std::string comment; + std::vector<std::unique_ptr<Node>> children; + + virtual ~Node() = default; + + void AppendChild(std::unique_ptr<Node> child); + void InsertChild(size_t index, std::unique_ptr<Node> child); + virtual void Accept(RawVisitor* visitor) = 0; + virtual std::unique_ptr<Node> Clone() = 0; }; /** @@ -55,166 +58,175 @@ struct Node { * subclass of Node. */ template <typename Derived> -struct BaseNode : public Node { - virtual void accept(RawVisitor* visitor) override; +class BaseNode : public Node { + public: + virtual void Accept(RawVisitor* visitor) override; }; /** * A Namespace XML node. Can only have one child. */ -struct Namespace : public BaseNode<Namespace> { - std::u16string namespacePrefix; - std::u16string namespaceUri; +class Namespace : public BaseNode<Namespace> { + public: + std::string namespace_prefix; + std::string namespace_uri; + + std::unique_ptr<Node> Clone() override; }; struct AaptAttribute { - Maybe<ResourceId> id; - aapt::Attribute attribute; + Maybe<ResourceId> id; + aapt::Attribute attribute; }; /** * An XML attribute. */ struct Attribute { - std::u16string namespaceUri; - std::u16string name; - std::u16string value; + std::string namespace_uri; + std::string name; + std::string value; - Maybe<AaptAttribute> compiledAttribute; - std::unique_ptr<Item> compiledValue; + Maybe<AaptAttribute> compiled_attribute; + std::unique_ptr<Item> compiled_value; }; /** * An Element XML node. */ -struct Element : public BaseNode<Element> { - std::u16string namespaceUri; - std::u16string name; - std::vector<Attribute> attributes; - - Attribute* findAttribute(const StringPiece16& ns, const StringPiece16& name); - xml::Element* findChild(const StringPiece16& ns, const StringPiece16& name); - xml::Element* findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name, - const StringPiece16& attrNs, - const StringPiece16& attrName, - const StringPiece16& attrValue); - std::vector<xml::Element*> getChildElements(); +class Element : public BaseNode<Element> { + public: + std::string namespace_uri; + std::string name; + std::vector<Attribute> attributes; + + Attribute* FindAttribute(const StringPiece& ns, const StringPiece& name); + xml::Element* FindChild(const StringPiece& ns, const StringPiece& name); + xml::Element* FindChildWithAttribute(const StringPiece& ns, + const StringPiece& name, + const StringPiece& attr_ns, + const StringPiece& attr_name, + const StringPiece& attr_value); + std::vector<xml::Element*> GetChildElements(); + std::unique_ptr<Node> Clone() override; }; /** * A Text (CDATA) XML node. Can not have any children. */ -struct Text : public BaseNode<Text> { - std::u16string text; +class Text : public BaseNode<Text> { + public: + std::string text; + + std::unique_ptr<Node> Clone() override; }; /** * An XML resource with a source, name, and XML tree. */ -struct XmlResource { - ResourceFile file; - std::unique_ptr<xml::Node> root; +class XmlResource { + public: + ResourceFile file; + std::unique_ptr<xml::Node> root; }; /** * Inflates an XML DOM from a text stream, logging errors to the logger. * Returns the root node on success, or nullptr on failure. */ -std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source); +std::unique_ptr<XmlResource> Inflate(std::istream* in, IDiagnostics* diag, + const Source& source); /** * Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger. * Returns the root node on success, or nullptr on failure. */ -std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag, - const Source& source); +std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, + IDiagnostics* diag, const Source& source); -Element* findRootElement(XmlResource* doc); -Element* findRootElement(Node* node); +Element* FindRootElement(XmlResource* doc); +Element* FindRootElement(Node* node); /** - * A visitor interface for the different XML Node subtypes. This will not traverse into + * A visitor interface for the different XML Node subtypes. This will not + * traverse into * children. Use Visitor for that. */ -struct RawVisitor { - virtual ~RawVisitor() = default; +class RawVisitor { + public: + virtual ~RawVisitor() = default; - virtual void visit(Namespace* node) {} - virtual void visit(Element* node) {} - virtual void visit(Text* text) {} + virtual void Visit(Namespace* node) {} + virtual void Visit(Element* node) {} + virtual void Visit(Text* text) {} }; /** * Visitor whose default implementation visits the children nodes of any node. */ -struct Visitor : public RawVisitor { - using RawVisitor::visit; +class Visitor : public RawVisitor { + public: + using RawVisitor::Visit; - void visit(Namespace* node) override { - visitChildren(node); - } + void Visit(Namespace* node) override { VisitChildren(node); } - void visit(Element* node) override { - visitChildren(node); - } + void Visit(Element* node) override { VisitChildren(node); } - void visit(Text* text) override { - visitChildren(text); - } + void Visit(Text* text) override { VisitChildren(text); } - void visitChildren(Node* node) { - for (auto& child : node->children) { - child->accept(this); - } + void VisitChildren(Node* node) { + for (auto& child : node->children) { + child->Accept(this); } + } }; /** * An XML DOM visitor that will record the package name for a namespace prefix. */ class PackageAwareVisitor : public Visitor, public IPackageDeclStack { -private: - struct PackageDecl { - std::u16string prefix; - ExtractedPackage package; - }; + public: + using Visitor::Visit; - std::vector<PackageDecl> mPackageDecls; + void Visit(Namespace* ns) override; + Maybe<ExtractedPackage> TransformPackageAlias( + const StringPiece& alias, + const StringPiece& local_package) const override; -public: - using Visitor::visit; + private: + struct PackageDecl { + std::string prefix; + ExtractedPackage package; + }; - void visit(Namespace* ns) override; - Maybe<ExtractedPackage> transformPackageAlias( - const StringPiece16& alias, const StringPiece16& localPackage) const override; + std::vector<PackageDecl> package_decls_; }; // Implementations template <typename Derived> -void BaseNode<Derived>::accept(RawVisitor* visitor) { - visitor->visit(static_cast<Derived*>(this)); +void BaseNode<Derived>::Accept(RawVisitor* visitor) { + visitor->Visit(static_cast<Derived*>(this)); } template <typename T> -struct NodeCastImpl : public RawVisitor { - using RawVisitor::visit; +class NodeCastImpl : public RawVisitor { + public: + using RawVisitor::Visit; - T* value = nullptr; + T* value = nullptr; - void visit(T* v) override { - value = v; - } + void Visit(T* v) override { value = v; } }; template <typename T> -T* nodeCast(Node* node) { - NodeCastImpl<T> visitor; - node->accept(&visitor); - return visitor.value; +T* NodeCast(Node* node) { + NodeCastImpl<T> visitor; + node->Accept(&visitor); + return visitor.value; } -} // namespace xml -} // namespace aapt +} // namespace xml +} // namespace aapt -#endif // AAPT_XML_DOM_H +#endif // AAPT_XML_DOM_H diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp index 431ee2c8fb46..a414afe92fc0 100644 --- a/tools/aapt2/xml/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -16,17 +16,19 @@ #include "xml/XmlDom.h" -#include <gtest/gtest.h> #include <sstream> #include <string> +#include "test/Test.h" + namespace aapt { -constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; +constexpr const char* kXmlPreamble = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; TEST(XmlDomTest, Inflate) { - std::stringstream in(kXmlPreamble); - in << R"EOF( + std::stringstream in(kXmlPreamble); + in << R"EOF( <Layout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> @@ -36,15 +38,15 @@ TEST(XmlDomTest, Inflate) { </Layout> )EOF"; - const Source source = { "test.xml" }; - StdErrDiagnostics diag; - std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, source); - ASSERT_NE(doc, nullptr); + const Source source = {"test.xml"}; + StdErrDiagnostics diag; + std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, &diag, source); + ASSERT_NE(doc, nullptr); - xml::Namespace* ns = xml::nodeCast<xml::Namespace>(doc->root.get()); - ASSERT_NE(ns, nullptr); - EXPECT_EQ(ns->namespaceUri, u"http://schemas.android.com/apk/res/android"); - EXPECT_EQ(ns->namespacePrefix, u"android"); + xml::Namespace* ns = xml::NodeCast<xml::Namespace>(doc->root.get()); + ASSERT_NE(ns, nullptr); + EXPECT_EQ(ns->namespace_uri, xml::kSchemaAndroid); + EXPECT_EQ(ns->namespace_prefix, "android"); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp index 323ec05b5f2c..e59fa86788cd 100644 --- a/tools/aapt2/xml/XmlPullParser.cpp +++ b/tools/aapt2/xml/XmlPullParser.cpp @@ -14,295 +14,295 @@ * limitations under the License. */ +#include <iostream> +#include <string> + #include "util/Maybe.h" #include "util/Util.h" #include "xml/XmlPullParser.h" #include "xml/XmlUtil.h" -#include <iostream> -#include <string> - namespace aapt { namespace xml { constexpr char kXmlNamespaceSep = 1; -XmlPullParser::XmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) { - mParser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep); - XML_SetUserData(mParser, this); - XML_SetElementHandler(mParser, startElementHandler, endElementHandler); - XML_SetNamespaceDeclHandler(mParser, startNamespaceHandler, endNamespaceHandler); - XML_SetCharacterDataHandler(mParser, characterDataHandler); - XML_SetCommentHandler(mParser, commentDataHandler); - mEventQueue.push(EventData{ Event::kStartDocument, 0, mDepth++ }); +XmlPullParser::XmlPullParser(std::istream& in) : in_(in), empty_(), depth_(0) { + parser_ = XML_ParserCreateNS(nullptr, kXmlNamespaceSep); + XML_SetUserData(parser_, this); + XML_SetElementHandler(parser_, StartElementHandler, EndElementHandler); + XML_SetNamespaceDeclHandler(parser_, StartNamespaceHandler, + EndNamespaceHandler); + XML_SetCharacterDataHandler(parser_, CharacterDataHandler); + XML_SetCommentHandler(parser_, CommentDataHandler); + event_queue_.push(EventData{Event::kStartDocument, 0, depth_++}); } -XmlPullParser::~XmlPullParser() { - XML_ParserFree(mParser); -} +XmlPullParser::~XmlPullParser() { XML_ParserFree(parser_); } + +XmlPullParser::Event XmlPullParser::Next() { + const Event currentEvent = event(); + if (currentEvent == Event::kBadDocument || + currentEvent == Event::kEndDocument) { + return currentEvent; + } -XmlPullParser::Event XmlPullParser::next() { - const Event currentEvent = getEvent(); - if (currentEvent == Event::kBadDocument || currentEvent == Event::kEndDocument) { - return currentEvent; + event_queue_.pop(); + while (event_queue_.empty()) { + in_.read(buffer_, sizeof(buffer_) / sizeof(*buffer_)); + + const bool done = in_.eof(); + if (in_.bad() && !done) { + error_ = strerror(errno); + event_queue_.push(EventData{Event::kBadDocument}); + continue; } - mEventQueue.pop(); - while (mEventQueue.empty()) { - mIn.read(mBuffer, sizeof(mBuffer) / sizeof(*mBuffer)); - - const bool done = mIn.eof(); - if (mIn.bad() && !done) { - mLastError = strerror(errno); - mEventQueue.push(EventData{ Event::kBadDocument }); - continue; - } - - if (XML_Parse(mParser, mBuffer, mIn.gcount(), done) == XML_STATUS_ERROR) { - mLastError = XML_ErrorString(XML_GetErrorCode(mParser)); - mEventQueue.push(EventData{ Event::kBadDocument }); - continue; - } - - if (done) { - mEventQueue.push(EventData{ Event::kEndDocument, 0, 0 }); - } + if (XML_Parse(parser_, buffer_, in_.gcount(), done) == XML_STATUS_ERROR) { + error_ = XML_ErrorString(XML_GetErrorCode(parser_)); + event_queue_.push(EventData{Event::kBadDocument}); + continue; } - Event event = getEvent(); - - // Record namespace prefixes and package names so that we can do our own - // handling of references that use namespace aliases. - if (event == Event::kStartNamespace || event == Event::kEndNamespace) { - Maybe<ExtractedPackage> result = extractPackageFromNamespace(getNamespaceUri()); - if (event == Event::kStartNamespace) { - if (result) { - mPackageAliases.emplace_back( - PackageDecl{ getNamespacePrefix(), std::move(result.value()) }); - } - } else { - if (result) { - mPackageAliases.pop_back(); - } - } + if (done) { + event_queue_.push(EventData{Event::kEndDocument, 0, 0}); + } + } + + Event next_event = event(); + + // Record namespace prefixes and package names so that we can do our own + // handling of references that use namespace aliases. + if (next_event == Event::kStartNamespace || + next_event == Event::kEndNamespace) { + Maybe<ExtractedPackage> result = + ExtractPackageFromNamespace(namespace_uri()); + if (next_event == Event::kStartNamespace) { + if (result) { + package_aliases_.emplace_back( + PackageDecl{namespace_prefix(), std::move(result.value())}); + } + } else { + if (result) { + package_aliases_.pop_back(); + } } + } - return event; + return next_event; } -XmlPullParser::Event XmlPullParser::getEvent() const { - return mEventQueue.front().event; +XmlPullParser::Event XmlPullParser::event() const { + return event_queue_.front().event; } -const std::string& XmlPullParser::getLastError() const { - return mLastError; -} +const std::string& XmlPullParser::error() const { return error_; } -const std::u16string& XmlPullParser::getComment() const { - return mEventQueue.front().data1; +const std::string& XmlPullParser::comment() const { + return event_queue_.front().data1; } -size_t XmlPullParser::getLineNumber() const { - return mEventQueue.front().lineNumber; +size_t XmlPullParser::line_number() const { + return event_queue_.front().line_number; } -size_t XmlPullParser::getDepth() const { - return mEventQueue.front().depth; -} +size_t XmlPullParser::depth() const { return event_queue_.front().depth; } -const std::u16string& XmlPullParser::getText() const { - if (getEvent() != Event::kText) { - return mEmpty; - } - return mEventQueue.front().data1; +const std::string& XmlPullParser::text() const { + if (event() != Event::kText) { + return empty_; + } + return event_queue_.front().data1; } -const std::u16string& XmlPullParser::getNamespacePrefix() const { - const Event currentEvent = getEvent(); - if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) { - return mEmpty; - } - return mEventQueue.front().data1; +const std::string& XmlPullParser::namespace_prefix() const { + const Event current_event = event(); + if (current_event != Event::kStartNamespace && + current_event != Event::kEndNamespace) { + return empty_; + } + return event_queue_.front().data1; } -const std::u16string& XmlPullParser::getNamespaceUri() const { - const Event currentEvent = getEvent(); - if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) { - return mEmpty; - } - return mEventQueue.front().data2; +const std::string& XmlPullParser::namespace_uri() const { + const Event current_event = event(); + if (current_event != Event::kStartNamespace && + current_event != Event::kEndNamespace) { + return empty_; + } + return event_queue_.front().data2; } -Maybe<ExtractedPackage> XmlPullParser::transformPackageAlias( - const StringPiece16& alias, const StringPiece16& localPackage) const { - if (alias.empty()) { - return ExtractedPackage{ localPackage.toString(), false /* private */ }; +Maybe<ExtractedPackage> XmlPullParser::TransformPackageAlias( + const StringPiece& alias, const StringPiece& local_package) const { + if (alias.empty()) { + return ExtractedPackage{local_package.ToString(), false /* private */}; + } + + const auto end_iter = package_aliases_.rend(); + for (auto iter = package_aliases_.rbegin(); iter != end_iter; ++iter) { + if (alias == iter->prefix) { + if (iter->package.package.empty()) { + return ExtractedPackage{local_package.ToString(), + iter->package.private_namespace}; + } + return iter->package; } - - const auto endIter = mPackageAliases.rend(); - for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) { - if (alias == iter->prefix) { - if (iter->package.package.empty()) { - return ExtractedPackage{ localPackage.toString(), - iter->package.privateNamespace }; - } - return iter->package; - } - } - return {}; + } + return {}; } -const std::u16string& XmlPullParser::getElementNamespace() const { - const Event currentEvent = getEvent(); - if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) { - return mEmpty; - } - return mEventQueue.front().data1; +const std::string& XmlPullParser::element_namespace() const { + const Event current_event = event(); + if (current_event != Event::kStartElement && + current_event != Event::kEndElement) { + return empty_; + } + return event_queue_.front().data1; } -const std::u16string& XmlPullParser::getElementName() const { - const Event currentEvent = getEvent(); - if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) { - return mEmpty; - } - return mEventQueue.front().data2; +const std::string& XmlPullParser::element_name() const { + const Event current_event = event(); + if (current_event != Event::kStartElement && + current_event != Event::kEndElement) { + return empty_; + } + return event_queue_.front().data2; } -XmlPullParser::const_iterator XmlPullParser::beginAttributes() const { - return mEventQueue.front().attributes.begin(); +XmlPullParser::const_iterator XmlPullParser::begin_attributes() const { + return event_queue_.front().attributes.begin(); } -XmlPullParser::const_iterator XmlPullParser::endAttributes() const { - return mEventQueue.front().attributes.end(); +XmlPullParser::const_iterator XmlPullParser::end_attributes() const { + return event_queue_.front().attributes.end(); } -size_t XmlPullParser::getAttributeCount() const { - if (getEvent() != Event::kStartElement) { - return 0; - } - return mEventQueue.front().attributes.size(); +size_t XmlPullParser::attribute_count() const { + if (event() != Event::kStartElement) { + return 0; + } + return event_queue_.front().attributes.size(); } /** * Extracts the namespace and name of an expanded element or attribute name. */ -static void splitName(const char* name, std::u16string& outNs, std::u16string& outName) { - const char* p = name; - while (*p != 0 && *p != kXmlNamespaceSep) { - p++; - } - - if (*p == 0) { - outNs = std::u16string(); - outName = util::utf8ToUtf16(name); - } else { - outNs = util::utf8ToUtf16(StringPiece(name, (p - name))); - outName = util::utf8ToUtf16(p + 1); - } +static void SplitName(const char* name, std::string& out_ns, + std::string& out_name) { + const char* p = name; + while (*p != 0 && *p != kXmlNamespaceSep) { + p++; + } + + if (*p == 0) { + out_ns = std::string(); + out_name = name; + } else { + out_ns = StringPiece(name, (p - name)).ToString(); + out_name = p + 1; + } } -void XMLCALL XmlPullParser::startNamespaceHandler(void* userData, const char* prefix, - const char* uri) { - XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); - std::u16string namespaceUri = uri != nullptr ? util::utf8ToUtf16(uri) : std::u16string(); - parser->mNamespaceUris.push(namespaceUri); - parser->mEventQueue.push(EventData{ - Event::kStartNamespace, - XML_GetCurrentLineNumber(parser->mParser), - parser->mDepth++, - prefix != nullptr ? util::utf8ToUtf16(prefix) : std::u16string(), - namespaceUri - }); +void XMLCALL XmlPullParser::StartNamespaceHandler(void* user_data, + const char* prefix, + const char* uri) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data); + std::string namespace_uri = uri != nullptr ? uri : std::string(); + parser->namespace_uris_.push(namespace_uri); + parser->event_queue_.push( + EventData{Event::kStartNamespace, + XML_GetCurrentLineNumber(parser->parser_), parser->depth_++, + prefix != nullptr ? prefix : std::string(), namespace_uri}); } -void XMLCALL XmlPullParser::startElementHandler(void* userData, const char* name, - const char** attrs) { - XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); - - EventData data = { - Event::kStartElement, XML_GetCurrentLineNumber(parser->mParser), parser->mDepth++ - }; - splitName(name, data.data1, data.data2); - - while (*attrs) { - Attribute attribute; - splitName(*attrs++, attribute.namespaceUri, attribute.name); - attribute.value = util::utf8ToUtf16(*attrs++); - - // Insert in sorted order. - auto iter = std::lower_bound(data.attributes.begin(), data.attributes.end(), attribute); - data.attributes.insert(iter, std::move(attribute)); - } - - // Move the structure into the queue (no copy). - parser->mEventQueue.push(std::move(data)); +void XMLCALL XmlPullParser::StartElementHandler(void* user_data, + const char* name, + const char** attrs) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data); + + EventData data = {Event::kStartElement, + XML_GetCurrentLineNumber(parser->parser_), + parser->depth_++}; + SplitName(name, data.data1, data.data2); + + while (*attrs) { + Attribute attribute; + SplitName(*attrs++, attribute.namespace_uri, attribute.name); + attribute.value = *attrs++; + + // Insert in sorted order. + auto iter = std::lower_bound(data.attributes.begin(), data.attributes.end(), + attribute); + data.attributes.insert(iter, std::move(attribute)); + } + + // Move the structure into the queue (no copy). + parser->event_queue_.push(std::move(data)); } -void XMLCALL XmlPullParser::characterDataHandler(void* userData, const char* s, int len) { - XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); +void XMLCALL XmlPullParser::CharacterDataHandler(void* user_data, const char* s, + int len) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data); - parser->mEventQueue.push(EventData{ - Event::kText, - XML_GetCurrentLineNumber(parser->mParser), - parser->mDepth, - util::utf8ToUtf16(StringPiece(s, len)) - }); + parser->event_queue_.push( + EventData{Event::kText, XML_GetCurrentLineNumber(parser->parser_), + parser->depth_, StringPiece(s, len).ToString()}); } -void XMLCALL XmlPullParser::endElementHandler(void* userData, const char* name) { - XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); +void XMLCALL XmlPullParser::EndElementHandler(void* user_data, + const char* name) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data); - EventData data = { - Event::kEndElement, XML_GetCurrentLineNumber(parser->mParser), --(parser->mDepth) - }; - splitName(name, data.data1, data.data2); + EventData data = {Event::kEndElement, + XML_GetCurrentLineNumber(parser->parser_), + --(parser->depth_)}; + SplitName(name, data.data1, data.data2); - // Move the data into the queue (no copy). - parser->mEventQueue.push(std::move(data)); + // Move the data into the queue (no copy). + parser->event_queue_.push(std::move(data)); } -void XMLCALL XmlPullParser::endNamespaceHandler(void* userData, const char* prefix) { - XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); - - parser->mEventQueue.push(EventData{ - Event::kEndNamespace, - XML_GetCurrentLineNumber(parser->mParser), - --(parser->mDepth), - prefix != nullptr ? util::utf8ToUtf16(prefix) : std::u16string(), - parser->mNamespaceUris.top() - }); - parser->mNamespaceUris.pop(); +void XMLCALL XmlPullParser::EndNamespaceHandler(void* user_data, + const char* prefix) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data); + + parser->event_queue_.push( + EventData{Event::kEndNamespace, XML_GetCurrentLineNumber(parser->parser_), + --(parser->depth_), prefix != nullptr ? prefix : std::string(), + parser->namespace_uris_.top()}); + parser->namespace_uris_.pop(); } -void XMLCALL XmlPullParser::commentDataHandler(void* userData, const char* comment) { - XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); +void XMLCALL XmlPullParser::CommentDataHandler(void* user_data, + const char* comment) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data); - parser->mEventQueue.push(EventData{ - Event::kComment, - XML_GetCurrentLineNumber(parser->mParser), - parser->mDepth, - util::utf8ToUtf16(comment) - }); + parser->event_queue_.push(EventData{Event::kComment, + XML_GetCurrentLineNumber(parser->parser_), + parser->depth_, comment}); } -Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name) { - auto iter = parser->findAttribute(u"", name); - if (iter != parser->endAttributes()) { - return StringPiece16(util::trimWhitespace(iter->value)); - } - return {}; +Maybe<StringPiece> FindAttribute(const XmlPullParser* parser, + const StringPiece& name) { + auto iter = parser->FindAttribute("", name); + if (iter != parser->end_attributes()) { + return StringPiece(util::TrimWhitespace(iter->value)); + } + return {}; } -Maybe<StringPiece16> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece16& name) { - auto iter = parser->findAttribute(u"", name); - if (iter != parser->endAttributes()) { - StringPiece16 trimmed = util::trimWhitespace(iter->value); - if (!trimmed.empty()) { - return trimmed; - } +Maybe<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, + const StringPiece& name) { + auto iter = parser->FindAttribute("", name); + if (iter != parser->end_attributes()) { + StringPiece trimmed = util::TrimWhitespace(iter->value); + if (!trimmed.empty()) { + return trimmed; } - return {}; + } + return {}; } -} // namespace xml -} // namespace aapt +} // namespace xml +} // namespace aapt diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h index 7e7070e5e5ea..ff58d604e35e 100644 --- a/tools/aapt2/xml/XmlPullParser.h +++ b/tools/aapt2/xml/XmlPullParser.h @@ -17,14 +17,9 @@ #ifndef AAPT_XML_PULL_PARSER_H #define AAPT_XML_PULL_PARSER_H -#include "Resource.h" -#include "process/IResourceTableConsumer.h" -#include "util/Maybe.h" -#include "util/StringPiece.h" -#include "xml/XmlUtil.h" +#include <expat.h> #include <algorithm> -#include <expat.h> #include <istream> #include <ostream> #include <queue> @@ -32,267 +27,303 @@ #include <string> #include <vector> +#include "android-base/macros.h" + +#include "Resource.h" +#include "process/IResourceTableConsumer.h" +#include "util/Maybe.h" +#include "util/StringPiece.h" +#include "xml/XmlUtil.h" + namespace aapt { namespace xml { class XmlPullParser : public IPackageDeclStack { -public: - enum class Event { - kBadDocument, - kStartDocument, - kEndDocument, - - kStartNamespace, - kEndNamespace, - kStartElement, - kEndElement, - kText, - kComment, - }; - - /** - * Skips to the next direct descendant node of the given startDepth, - * skipping namespace nodes. - * - * When nextChildNode returns true, you can expect Comments, Text, and StartElement events. - */ - static bool nextChildNode(XmlPullParser* parser, size_t startDepth); - static bool skipCurrentElement(XmlPullParser* parser); - static bool isGoodEvent(Event event); - - XmlPullParser(std::istream& in); - ~XmlPullParser(); - - /** - * Returns the current event that is being processed. - */ - Event getEvent() const; - - const std::string& getLastError() const; - - /** - * Note, unlike XmlPullParser, the first call to next() will return - * StartElement of the first element. - */ - Event next(); - - // - // These are available for all nodes. - // - - const std::u16string& getComment() const; - size_t getLineNumber() const; - size_t getDepth() const; - - /** - * Returns the character data for a Text event. - */ - const std::u16string& getText() const; - - // - // Namespace prefix and URI are available for StartNamespace and EndNamespace. - // - - const std::u16string& getNamespacePrefix() const; - const std::u16string& getNamespaceUri() const; - - // - // These are available for StartElement and EndElement. - // - - const std::u16string& getElementNamespace() const; - const std::u16string& getElementName() const; - - /* - * Uses the current stack of namespaces to resolve the package. Eg: - * xmlns:app = "http://schemas.android.com/apk/res/com.android.app" - * ... - * android:text="@app:string/message" - * - * In this case, 'app' will be converted to 'com.android.app'. - * - * If xmlns:app="http://schemas.android.com/apk/res-auto", then - * 'package' will be set to 'defaultPackage'. - */ - Maybe<ExtractedPackage> transformPackageAlias( - const StringPiece16& alias, const StringPiece16& localPackage) const override; - - // - // Remaining methods are for retrieving information about attributes - // associated with a StartElement. - // - // Attributes must be in sorted order (according to the less than operator - // of struct Attribute). - // - - struct Attribute { - std::u16string namespaceUri; - std::u16string name; - std::u16string value; - - int compare(const Attribute& rhs) const; - bool operator<(const Attribute& rhs) const; - bool operator==(const Attribute& rhs) const; - bool operator!=(const Attribute& rhs) const; - }; - - using const_iterator = std::vector<Attribute>::const_iterator; - - const_iterator beginAttributes() const; - const_iterator endAttributes() const; - size_t getAttributeCount() const; - const_iterator findAttribute(StringPiece16 namespaceUri, StringPiece16 name) const; - -private: - static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri); - static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs); - static void XMLCALL characterDataHandler(void* userData, const char* s, int len); - static void XMLCALL endElementHandler(void* userData, const char* name); - static void XMLCALL endNamespaceHandler(void* userData, const char* prefix); - static void XMLCALL commentDataHandler(void* userData, const char* comment); - - struct EventData { - Event event; - size_t lineNumber; - size_t depth; - std::u16string data1; - std::u16string data2; - std::vector<Attribute> attributes; - }; - - std::istream& mIn; - XML_Parser mParser; - char mBuffer[16384]; - std::queue<EventData> mEventQueue; - std::string mLastError; - const std::u16string mEmpty; - size_t mDepth; - std::stack<std::u16string> mNamespaceUris; - - struct PackageDecl { - std::u16string prefix; - ExtractedPackage package; - }; - std::vector<PackageDecl> mPackageAliases; + public: + enum class Event { + kBadDocument, + kStartDocument, + kEndDocument, + + kStartNamespace, + kEndNamespace, + kStartElement, + kEndElement, + kText, + kComment, + }; + + /** + * Skips to the next direct descendant node of the given start_depth, + * skipping namespace nodes. + * + * When NextChildNode() returns true, you can expect Comments, Text, and + * StartElement events. + */ + static bool NextChildNode(XmlPullParser* parser, size_t start_depth); + static bool SkipCurrentElement(XmlPullParser* parser); + static bool IsGoodEvent(Event event); + + explicit XmlPullParser(std::istream& in); + ~XmlPullParser(); + + /** + * Returns the current event that is being processed. + */ + Event event() const; + + const std::string& error() const; + + /** + * Note, unlike XmlPullParser, the first call to next() will return + * StartElement of the first element. + */ + Event Next(); + + // + // These are available for all nodes. + // + + const std::string& comment() const; + size_t line_number() const; + size_t depth() const; + + /** + * Returns the character data for a Text event. + */ + const std::string& text() const; + + // + // Namespace prefix and URI are available for StartNamespace and EndNamespace. + // + + const std::string& namespace_prefix() const; + const std::string& namespace_uri() const; + + // + // These are available for StartElement and EndElement. + // + + const std::string& element_namespace() const; + const std::string& element_name() const; + + /* + * Uses the current stack of namespaces to resolve the package. Eg: + * xmlns:app = "http://schemas.android.com/apk/res/com.android.app" + * ... + * android:text="@app:string/message" + * + * In this case, 'app' will be converted to 'com.android.app'. + * + * If xmlns:app="http://schemas.android.com/apk/res-auto", then + * 'package' will be set to 'defaultPackage'. + */ + Maybe<ExtractedPackage> TransformPackageAlias( + const StringPiece& alias, + const StringPiece& local_package) const override; + + // + // Remaining methods are for retrieving information about attributes + // associated with a StartElement. + // + // Attributes must be in sorted order (according to the less than operator + // of struct Attribute). + // + + struct Attribute { + std::string namespace_uri; + std::string name; + std::string value; + + int compare(const Attribute& rhs) const; + bool operator<(const Attribute& rhs) const; + bool operator==(const Attribute& rhs) const; + bool operator!=(const Attribute& rhs) const; + }; + + using const_iterator = std::vector<Attribute>::const_iterator; + + const_iterator begin_attributes() const; + const_iterator end_attributes() const; + size_t attribute_count() const; + const_iterator FindAttribute(StringPiece namespace_uri, + StringPiece name) const; + + private: + DISALLOW_COPY_AND_ASSIGN(XmlPullParser); + + static void XMLCALL StartNamespaceHandler(void* user_data, const char* prefix, + const char* uri); + static void XMLCALL StartElementHandler(void* user_data, const char* name, + const char** attrs); + static void XMLCALL CharacterDataHandler(void* user_data, const char* s, + int len); + static void XMLCALL EndElementHandler(void* user_data, const char* name); + static void XMLCALL EndNamespaceHandler(void* user_data, const char* prefix); + static void XMLCALL CommentDataHandler(void* user_data, const char* comment); + + struct EventData { + Event event; + size_t line_number; + size_t depth; + std::string data1; + std::string data2; + std::vector<Attribute> attributes; + }; + + std::istream& in_; + XML_Parser parser_; + char buffer_[16384]; + std::queue<EventData> event_queue_; + std::string error_; + const std::string empty_; + size_t depth_; + std::stack<std::string> namespace_uris_; + + struct PackageDecl { + std::string prefix; + ExtractedPackage package; + }; + std::vector<PackageDecl> package_aliases_; }; /** * Finds the attribute in the current element within the global namespace. */ -Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name); +Maybe<StringPiece> FindAttribute(const XmlPullParser* parser, + const StringPiece& name); /** - * Finds the attribute in the current element within the global namespace. The attribute's value + * Finds the attribute in the current element within the global namespace. The + * attribute's value * must not be the empty string. */ -Maybe<StringPiece16> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece16& name); +Maybe<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, + const StringPiece& name); // // Implementation // -inline ::std::ostream& operator<<(::std::ostream& out, XmlPullParser::Event event) { - switch (event) { - case XmlPullParser::Event::kBadDocument: return out << "BadDocument"; - case XmlPullParser::Event::kStartDocument: return out << "StartDocument"; - case XmlPullParser::Event::kEndDocument: return out << "EndDocument"; - case XmlPullParser::Event::kStartNamespace: return out << "StartNamespace"; - case XmlPullParser::Event::kEndNamespace: return out << "EndNamespace"; - case XmlPullParser::Event::kStartElement: return out << "StartElement"; - case XmlPullParser::Event::kEndElement: return out << "EndElement"; - case XmlPullParser::Event::kText: return out << "Text"; - case XmlPullParser::Event::kComment: return out << "Comment"; - } - return out; +inline ::std::ostream& operator<<(::std::ostream& out, + XmlPullParser::Event event) { + switch (event) { + case XmlPullParser::Event::kBadDocument: + return out << "BadDocument"; + case XmlPullParser::Event::kStartDocument: + return out << "StartDocument"; + case XmlPullParser::Event::kEndDocument: + return out << "EndDocument"; + case XmlPullParser::Event::kStartNamespace: + return out << "StartNamespace"; + case XmlPullParser::Event::kEndNamespace: + return out << "EndNamespace"; + case XmlPullParser::Event::kStartElement: + return out << "StartElement"; + case XmlPullParser::Event::kEndElement: + return out << "EndElement"; + case XmlPullParser::Event::kText: + return out << "Text"; + case XmlPullParser::Event::kComment: + return out << "Comment"; + } + return out; } -inline bool XmlPullParser::nextChildNode(XmlPullParser* parser, size_t startDepth) { - Event event; +inline bool XmlPullParser::NextChildNode(XmlPullParser* parser, + size_t start_depth) { + Event event; + + // First get back to the start depth. + while (IsGoodEvent(event = parser->Next()) && + parser->depth() > start_depth + 1) { + } - // First get back to the start depth. - while (isGoodEvent(event = parser->next()) && parser->getDepth() > startDepth + 1) {} - - // Now look for the first good node. - while ((event != Event::kEndElement || parser->getDepth() > startDepth) && isGoodEvent(event)) { - switch (event) { - case Event::kText: - case Event::kComment: - case Event::kStartElement: - return true; - default: - break; - } - event = parser->next(); + // Now look for the first good node. + while ((event != Event::kEndElement || parser->depth() > start_depth) && + IsGoodEvent(event)) { + switch (event) { + case Event::kText: + case Event::kComment: + case Event::kStartElement: + return true; + default: + break; } - return false; + event = parser->Next(); + } + return false; } -inline bool XmlPullParser::skipCurrentElement(XmlPullParser* parser) { - int depth = 1; - while (depth > 0) { - switch (parser->next()) { - case Event::kEndDocument: - return true; - case Event::kBadDocument: - return false; - case Event::kStartElement: - depth++; - break; - case Event::kEndElement: - depth--; - break; - default: - break; - } +inline bool XmlPullParser::SkipCurrentElement(XmlPullParser* parser) { + int depth = 1; + while (depth > 0) { + switch (parser->Next()) { + case Event::kEndDocument: + return true; + case Event::kBadDocument: + return false; + case Event::kStartElement: + depth++; + break; + case Event::kEndElement: + depth--; + break; + default: + break; } - return true; + } + return true; } -inline bool XmlPullParser::isGoodEvent(XmlPullParser::Event event) { - return event != Event::kBadDocument && event != Event::kEndDocument; +inline bool XmlPullParser::IsGoodEvent(XmlPullParser::Event event) { + return event != Event::kBadDocument && event != Event::kEndDocument; } inline int XmlPullParser::Attribute::compare(const Attribute& rhs) const { - int cmp = namespaceUri.compare(rhs.namespaceUri); - if (cmp != 0) return cmp; - return name.compare(rhs.name); + int cmp = namespace_uri.compare(rhs.namespace_uri); + if (cmp != 0) return cmp; + return name.compare(rhs.name); } inline bool XmlPullParser::Attribute::operator<(const Attribute& rhs) const { - return compare(rhs) < 0; + return compare(rhs) < 0; } inline bool XmlPullParser::Attribute::operator==(const Attribute& rhs) const { - return compare(rhs) == 0; + return compare(rhs) == 0; } inline bool XmlPullParser::Attribute::operator!=(const Attribute& rhs) const { - return compare(rhs) != 0; + return compare(rhs) != 0; } -inline XmlPullParser::const_iterator XmlPullParser::findAttribute(StringPiece16 namespaceUri, - StringPiece16 name) const { - const auto endIter = endAttributes(); - const auto iter = std::lower_bound(beginAttributes(), endIter, - std::pair<StringPiece16, StringPiece16>(namespaceUri, name), - [](const Attribute& attr, const std::pair<StringPiece16, StringPiece16>& rhs) -> bool { - int cmp = attr.namespaceUri.compare(0, attr.namespaceUri.size(), - rhs.first.data(), rhs.first.size()); - if (cmp < 0) return true; - if (cmp > 0) return false; - cmp = attr.name.compare(0, attr.name.size(), rhs.second.data(), rhs.second.size()); - if (cmp < 0) return true; - return false; - } - ); - - if (iter != endIter && namespaceUri == iter->namespaceUri && name == iter->name) { - return iter; - } - return endIter; +inline XmlPullParser::const_iterator XmlPullParser::FindAttribute( + StringPiece namespace_uri, StringPiece name) const { + const auto end_iter = end_attributes(); + const auto iter = std::lower_bound( + begin_attributes(), end_iter, + std::pair<StringPiece, StringPiece>(namespace_uri, name), + [](const Attribute& attr, + const std::pair<StringPiece, StringPiece>& rhs) -> bool { + int cmp = attr.namespace_uri.compare( + 0, attr.namespace_uri.size(), rhs.first.data(), rhs.first.size()); + if (cmp < 0) return true; + if (cmp > 0) return false; + cmp = attr.name.compare(0, attr.name.size(), rhs.second.data(), + rhs.second.size()); + if (cmp < 0) return true; + return false; + }); + + if (iter != end_iter && namespace_uri == iter->namespace_uri && + name == iter->name) { + return iter; + } + return end_iter; } -} // namespace xml -} // namespace aapt +} // namespace xml +} // namespace aapt -#endif // AAPT_XML_PULL_PARSER_H +#endif // AAPT_XML_PULL_PARSER_H diff --git a/tools/aapt2/xml/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp index 8fa2c6d274c8..4f18cd218cd9 100644 --- a/tools/aapt2/xml/XmlPullParser_test.cpp +++ b/tools/aapt2/xml/XmlPullParser_test.cpp @@ -14,42 +14,43 @@ * limitations under the License. */ -#include "util/StringPiece.h" #include "xml/XmlPullParser.h" -#include <gtest/gtest.h> #include <sstream> +#include "test/Test.h" +#include "util/StringPiece.h" + namespace aapt { TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) { - std::stringstream str; - str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" - "<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>"; - xml::XmlPullParser parser(str); + std::stringstream str; + str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>"; + xml::XmlPullParser parser(str); - const size_t depthOuter = parser.getDepth(); - ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthOuter)); + const size_t depth_outer = parser.depth(); + ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_outer)); - EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent()); - EXPECT_EQ(StringPiece16(u"a"), StringPiece16(parser.getElementName())); + EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event()); + EXPECT_EQ(StringPiece("a"), StringPiece(parser.element_name())); - const size_t depthA = parser.getDepth(); - ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthA)); - EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent()); - EXPECT_EQ(StringPiece16(u"b"), StringPiece16(parser.getElementName())); + const size_t depth_a = parser.depth(); + ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_a)); + EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event()); + EXPECT_EQ(StringPiece("b"), StringPiece(parser.element_name())); - const size_t depthB = parser.getDepth(); - ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB)); - EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent()); - EXPECT_EQ(StringPiece16(u"c"), StringPiece16(parser.getElementName())); + const size_t depth_b = parser.depth(); + ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b)); + EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event()); + EXPECT_EQ(StringPiece("c"), StringPiece(parser.element_name())); - ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB)); - EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent()); - EXPECT_EQ(StringPiece16(u"e"), StringPiece16(parser.getElementName())); + ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b)); + EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event()); + EXPECT_EQ(StringPiece("e"), StringPiece(parser.element_name())); - ASSERT_FALSE(xml::XmlPullParser::nextChildNode(&parser, depthOuter)); - EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.getEvent()); + ASSERT_FALSE(xml::XmlPullParser::NextChildNode(&parser, depth_outer)); + EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.event()); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp index ab9f544d67ea..d00f7f2fe0aa 100644 --- a/tools/aapt2/xml/XmlUtil.cpp +++ b/tools/aapt2/xml/XmlUtil.cpp @@ -14,54 +14,69 @@ * limitations under the License. */ -#include "util/Maybe.h" -#include "util/Util.h" #include "xml/XmlUtil.h" #include <string> +#include "util/Maybe.h" +#include "util/Util.h" + namespace aapt { namespace xml { -Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri) { - if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPublicPrefix)) { - StringPiece16 schemaPrefix = kSchemaPublicPrefix; - StringPiece16 package = namespaceUri; - package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size()); - if (package.empty()) { - return {}; - } - return ExtractedPackage{ package.toString(), false /* isPrivate */ }; +std::string BuildPackageNamespace(const StringPiece& package, + bool private_reference) { + std::string result = + private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix; + result.append(package.data(), package.size()); + return result; +} - } else if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPrivatePrefix)) { - StringPiece16 schemaPrefix = kSchemaPrivatePrefix; - StringPiece16 package = namespaceUri; - package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size()); - if (package.empty()) { - return {}; - } - return ExtractedPackage{ package.toString(), true /* isPrivate */ }; +Maybe<ExtractedPackage> ExtractPackageFromNamespace( + const std::string& namespace_uri) { + if (util::StartsWith(namespace_uri, kSchemaPublicPrefix)) { + StringPiece schema_prefix = kSchemaPublicPrefix; + StringPiece package = namespace_uri; + package = package.substr(schema_prefix.size(), + package.size() - schema_prefix.size()); + if (package.empty()) { + return {}; + } + return ExtractedPackage{package.ToString(), false /* is_private */}; - } else if (namespaceUri == kSchemaAuto) { - return ExtractedPackage{ std::u16string(), true /* isPrivate */ }; + } else if (util::StartsWith(namespace_uri, kSchemaPrivatePrefix)) { + StringPiece schema_prefix = kSchemaPrivatePrefix; + StringPiece package = namespace_uri; + package = package.substr(schema_prefix.size(), + package.size() - schema_prefix.size()); + if (package.empty()) { + return {}; } - return {}; + return ExtractedPackage{package.ToString(), true /* is_private */}; + + } else if (namespace_uri == kSchemaAuto) { + return ExtractedPackage{std::string(), true /* is_private */}; + } + return {}; } -void transformReferenceFromNamespace(IPackageDeclStack* declStack, - const StringPiece16& localPackage, Reference* inRef) { - if (inRef->name) { - if (Maybe<ExtractedPackage> transformedPackage = - declStack->transformPackageAlias(inRef->name.value().package, localPackage)) { - ExtractedPackage& extractedPackage = transformedPackage.value(); - inRef->name.value().package = std::move(extractedPackage.package); +void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack, + const StringPiece& local_package, + Reference* in_ref) { + if (in_ref->name) { + if (Maybe<ExtractedPackage> transformed_package = + decl_stack->TransformPackageAlias(in_ref->name.value().package, + local_package)) { + ExtractedPackage& extracted_package = transformed_package.value(); + in_ref->name.value().package = std::move(extracted_package.package); - // If the reference was already private (with a * prefix) and the namespace is public, - // we keep the reference private. - inRef->privateReference |= extractedPackage.privateNamespace; - } + // If the reference was already private (with a * prefix) and the + // namespace is public, + // we keep the reference private. + in_ref->private_reference |= extracted_package.private_namespace; } + } } -} // namespace xml -} // namespace aapt +} // namespace xml +} // namespace aapt diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h index 98e5520a6ea2..536540162d07 100644 --- a/tools/aapt2/xml/XmlUtil.h +++ b/tools/aapt2/xml/XmlUtil.h @@ -17,34 +17,41 @@ #ifndef AAPT_XML_XMLUTIL_H #define AAPT_XML_XMLUTIL_H +#include <string> + #include "ResourceValues.h" #include "util/Maybe.h" -#include <string> - namespace aapt { namespace xml { -constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto"; -constexpr const char16_t* kSchemaPublicPrefix = u"http://schemas.android.com/apk/res/"; -constexpr const char16_t* kSchemaPrivatePrefix = u"http://schemas.android.com/apk/prv/res/"; -constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; +constexpr const char* kSchemaAuto = "http://schemas.android.com/apk/res-auto"; +constexpr const char* kSchemaPublicPrefix = + "http://schemas.android.com/apk/res/"; +constexpr const char* kSchemaPrivatePrefix = + "http://schemas.android.com/apk/prv/res/"; +constexpr const char* kSchemaAndroid = + "http://schemas.android.com/apk/res/android"; +constexpr const char* kSchemaTools = "http://schemas.android.com/tools"; +constexpr const char* kSchemaAapt = "http://schemas.android.com/aapt"; /** * Result of extracting a package name from a namespace URI declaration. */ struct ExtractedPackage { - /** - * The name of the package. This can be the empty string, which means that the package - * should be assumed to be the package being compiled. - */ - std::u16string package; + /** + * The name of the package. This can be the empty string, which means that the + * package + * should be assumed to be the package being compiled. + */ + std::string package; - /** - * True if the package's private namespace was declared. This means that private resources - * are made visible. - */ - bool privateNamespace; + /** + * True if the package's private namespace was declared. This means that + * private resources + * are made visible. + */ + bool private_namespace; }; /** @@ -55,31 +62,48 @@ struct ExtractedPackage { * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto, * returns an empty package name. */ -Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri); +Maybe<ExtractedPackage> ExtractPackageFromNamespace( + const std::string& namespace_uri); + +/** + * Returns an XML Android namespace for the given package of the form: + * + * http://schemas.android.com/apk/res/<package> + * + * If privateReference == true, the package will be of the form: + * + * http://schemas.android.com/apk/prv/res/<package> + */ +std::string BuildPackageNamespace(const StringPiece& package, + bool private_reference = false); /** - * Interface representing a stack of XML namespace declarations. When looking up the package + * Interface representing a stack of XML namespace declarations. When looking up + * the package * for a namespace prefix, the stack is checked from top to bottom. */ struct IPackageDeclStack { - virtual ~IPackageDeclStack() = default; + virtual ~IPackageDeclStack() = default; - /** - * Returns an ExtractedPackage struct if the alias given corresponds with a package declaration. - */ - virtual Maybe<ExtractedPackage> transformPackageAlias( - const StringPiece16& alias, const StringPiece16& localPackage) const = 0; + /** + * Returns an ExtractedPackage struct if the alias given corresponds with a + * package declaration. + */ + virtual Maybe<ExtractedPackage> TransformPackageAlias( + const StringPiece& alias, const StringPiece& local_package) const = 0; }; /** - * Helper function for transforming the original Reference inRef to a fully qualified reference - * via the IPackageDeclStack. This will also mark the Reference as private if the namespace of - * the package declaration was private. + * Helper function for transforming the original Reference inRef to a fully + * qualified reference + * via the IPackageDeclStack. This will also mark the Reference as private if + * the namespace of the package declaration was private. */ -void transformReferenceFromNamespace(IPackageDeclStack* declStack, - const StringPiece16& localPackage, Reference* inRef); +void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack, + const StringPiece& local_package, + Reference* in_ref); -} // namespace xml -} // namespace aapt +} // namespace xml +} // namespace aapt #endif /* AAPT_XML_XMLUTIL_H */ diff --git a/tools/aapt2/xml/XmlUtil_test.cpp b/tools/aapt2/xml/XmlUtil_test.cpp index 319e7707d874..5eecc8f5fb20 100644 --- a/tools/aapt2/xml/XmlUtil_test.cpp +++ b/tools/aapt2/xml/XmlUtil_test.cpp @@ -14,41 +14,46 @@ * limitations under the License. */ -#include "test/Common.h" #include "xml/XmlUtil.h" -#include <gtest/gtest.h> +#include "test/Test.h" namespace aapt { TEST(XmlUtilTest, ExtractPackageFromNamespace) { - AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"com.android")); - AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk")); - AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res")); - AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/")); - AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace( - u"http://schemas.android.com/apk/prv/res/")); - - Maybe<xml::ExtractedPackage> p = - xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/a"); - AAPT_ASSERT_TRUE(p); - EXPECT_EQ(std::u16string(u"a"), p.value().package); - EXPECT_FALSE(p.value().privateNamespace); - - p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/android"); - AAPT_ASSERT_TRUE(p); - EXPECT_EQ(std::u16string(u"android"), p.value().package); - EXPECT_TRUE(p.value().privateNamespace); - - p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/com.test"); - AAPT_ASSERT_TRUE(p); - EXPECT_EQ(std::u16string(u"com.test"), p.value().package); - EXPECT_TRUE(p.value().privateNamespace); - - p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res-auto"); - AAPT_ASSERT_TRUE(p); - EXPECT_EQ(std::u16string(), p.value().package); - EXPECT_TRUE(p.value().privateNamespace); + AAPT_ASSERT_FALSE(xml::ExtractPackageFromNamespace("com.android")); + AAPT_ASSERT_FALSE( + xml::ExtractPackageFromNamespace("http://schemas.android.com/apk")); + AAPT_ASSERT_FALSE( + xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res")); + AAPT_ASSERT_FALSE( + xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res/")); + AAPT_ASSERT_FALSE(xml::ExtractPackageFromNamespace( + "http://schemas.android.com/apk/prv/res/")); + + Maybe<xml::ExtractedPackage> p = + xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res/a"); + AAPT_ASSERT_TRUE(p); + EXPECT_EQ(std::string("a"), p.value().package); + EXPECT_FALSE(p.value().private_namespace); + + p = xml::ExtractPackageFromNamespace( + "http://schemas.android.com/apk/prv/res/android"); + AAPT_ASSERT_TRUE(p); + EXPECT_EQ(std::string("android"), p.value().package); + EXPECT_TRUE(p.value().private_namespace); + + p = xml::ExtractPackageFromNamespace( + "http://schemas.android.com/apk/prv/res/com.test"); + AAPT_ASSERT_TRUE(p); + EXPECT_EQ(std::string("com.test"), p.value().package); + EXPECT_TRUE(p.value().private_namespace); + + p = xml::ExtractPackageFromNamespace( + "http://schemas.android.com/apk/res-auto"); + AAPT_ASSERT_TRUE(p); + EXPECT_EQ(std::string(), p.value().package); + EXPECT_TRUE(p.value().private_namespace); } -} // namespace aapt +} // namespace aapt diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py index ca2d2e75ee89..6ca59b0fb93b 100644 --- a/tools/apilint/apilint.py +++ b/tools/apilint/apilint.py @@ -91,7 +91,7 @@ class Method(): while r in raw: raw.remove(r) self.split = list(raw) - for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract"]: + for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default"]: while r in raw: raw.remove(r) self.typ = raw[0] diff --git a/tools/bit/Android.mk b/tools/bit/Android.mk new file mode 100644 index 000000000000..1c1291f07d2e --- /dev/null +++ b/tools/bit/Android.mk @@ -0,0 +1,46 @@ +# +# Copyright (C) 2015 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. +# +LOCAL_PATH:= $(call my-dir) + +# ========================================================== +# Build the host executable: protoc-gen-javastream +# ========================================================== +include $(CLEAR_VARS) + +LOCAL_MODULE := bit + +# This tool doesn't build on darwin. +LOCAL_MODULE_HOST_OS := linux + +LOCAL_SRC_FILES := \ + aapt.cpp \ + adb.cpp \ + command.cpp \ + main.cpp \ + make.cpp \ + print.cpp \ + util.cpp + +LOCAL_STATIC_LIBRARIES := \ + libexpat \ + libinstrumentation \ + libjsoncpp + +LOCAL_SHARED_LIBRARIES := \ + libprotobuf-cpp-full + +include $(BUILD_HOST_EXECUTABLE) + diff --git a/tools/bit/aapt.cpp b/tools/bit/aapt.cpp new file mode 100644 index 000000000000..961b47cdfecd --- /dev/null +++ b/tools/bit/aapt.cpp @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "aapt.h" + +#include "command.h" +#include "print.h" +#include "util.h" + +#include <regex> + +const regex NS_REGEX("( *)N: ([^=]+)=(.*)"); +const regex ELEMENT_REGEX("( *)E: ([^ ]+) \\(line=(\\d+)\\)"); +const regex ATTR_REGEX("( *)A: ([^\\(=]+)[^=]*=\"([^\"]+)\".*"); + +const string ANDROID_NS("http://schemas.android.com/apk/res/android"); + +bool +Apk::HasActivity(const string& className) +{ + string fullClassName = full_class_name(package, className); + const size_t N = activities.size(); + for (size_t i=0; i<N; i++) { + if (activities[i] == fullClassName) { + return true; + } + } + return false; +} + +struct Attribute { + string ns; + string name; + string value; +}; + +struct Element { + Element* parent; + string ns; + string name; + int lineno; + vector<Attribute> attributes; + vector<Element*> children; + + /** + * Indentation in the xmltree dump. Might not be equal to the distance + * from the root because namespace rows (scopes) have their own indentation. + */ + int depth; + + Element(); + ~Element(); + + string GetAttr(const string& ns, const string& name) const; + void FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse); + +}; + +Element::Element() +{ +} + +Element::~Element() +{ + const size_t N = children.size(); + for (size_t i=0; i<N; i++) { + delete children[i]; + } +} + +string +Element::GetAttr(const string& ns, const string& name) const +{ + const size_t N = attributes.size(); + for (size_t i=0; i<N; i++) { + const Attribute& attr = attributes[i]; + if (attr.ns == ns && attr.name == name) { + return attr.value; + } + } + return string(); +} + +void +Element::FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse) +{ + const size_t N = children.size(); + for (size_t i=0; i<N; i++) { + Element* child = children[i]; + if (child->ns == ns && child->name == name) { + result->push_back(child); + } + if (recurse) { + child->FindElements(ns, name, result, recurse); + } + } +} + +struct Scope { + Scope* parent; + int depth; + map<string,string> namespaces; + + Scope(Scope* parent, int depth); +}; + +Scope::Scope(Scope* p, int d) + :parent(p), + depth(d) +{ + if (p != NULL) { + namespaces = p->namespaces; + } +} + + +string +full_class_name(const string& packageName, const string& className) +{ + if (className.length() == 0) { + return ""; + } + if (className[0] == '.') { + return packageName + className; + } + if (className.find('.') == string::npos) { + return packageName + "." + className; + } + return className; +} + +string +pretty_component_name(const string& packageName, const string& className) +{ + if (starts_with(packageName, className)) { + size_t pn = packageName.length(); + size_t cn = className.length(); + if (cn > pn && className[pn] == '.') { + return packageName + "/" + string(className, pn, string::npos); + } + } + return packageName + "/" + className; +} + +int +inspect_apk(Apk* apk, const string& filename) +{ + // Load the manifest xml + Command cmd("aapt"); + cmd.AddArg("dump"); + cmd.AddArg("xmltree"); + cmd.AddArg(filename); + cmd.AddArg("AndroidManifest.xml"); + + int err; + + string output = get_command_output(cmd, &err, false); + check_error(err); + + // Parse the manifest xml + Scope* scope = new Scope(NULL, -1); + Element* root = NULL; + Element* current = NULL; + vector<string> lines; + split_lines(&lines, output); + for (size_t i=0; i<lines.size(); i++) { + const string& line = lines[i]; + smatch match; + if (regex_match(line, match, NS_REGEX)) { + int depth = match[1].length() / 2; + while (depth < scope->depth) { + Scope* tmp = scope; + scope = scope->parent; + delete tmp; + } + scope = new Scope(scope, depth); + scope->namespaces[match[2]] = match[3]; + } else if (regex_match(line, match, ELEMENT_REGEX)) { + Element* element = new Element(); + + string str = match[2]; + size_t colon = str.find(':'); + if (colon == string::npos) { + element->name = str; + } else { + element->ns = scope->namespaces[string(str, 0, colon)]; + element->name.assign(str, colon+1, string::npos); + } + element->lineno = atoi(match[3].str().c_str()); + element->depth = match[1].length() / 2; + + if (root == NULL) { + current = element; + root = element; + } else { + while (element->depth <= current->depth && current->parent != NULL) { + current = current->parent; + } + element->parent = current; + current->children.push_back(element); + current = element; + } + } else if (regex_match(line, match, ATTR_REGEX)) { + if (current != NULL) { + Attribute attr; + string str = match[2]; + size_t colon = str.find(':'); + if (colon == string::npos) { + attr.name = str; + } else { + attr.ns = scope->namespaces[string(str, 0, colon)]; + attr.name.assign(str, colon+1, string::npos); + } + attr.value = match[3]; + current->attributes.push_back(attr); + } + } + } + while (scope != NULL) { + Scope* tmp = scope; + scope = scope->parent; + delete tmp; + } + + // Package name + apk->package = root->GetAttr("", "package"); + if (apk->package.size() == 0) { + print_error("%s:%d: Manifest root element doesn't contain a package attribute", + filename.c_str(), root->lineno); + delete root; + return 1; + } + + // Instrumentation runner + vector<Element*> instrumentation; + root->FindElements("", "instrumentation", &instrumentation, true); + if (instrumentation.size() > 0) { + // TODO: How could we deal with multiple instrumentation tags? + // We'll just pick the first one. + apk->runner = instrumentation[0]->GetAttr(ANDROID_NS, "name"); + } + + // Activities + vector<Element*> activities; + root->FindElements("", "activity", &activities, true); + for (size_t i=0; i<activities.size(); i++) { + string name = activities[i]->GetAttr(ANDROID_NS, "name"); + if (name.size() == 0) { + continue; + } + apk->activities.push_back(full_class_name(apk->package, name)); + } + + delete root; + return 0; +} + diff --git a/tools/bit/aapt.h b/tools/bit/aapt.h new file mode 100644 index 000000000000..6aeb03f18744 --- /dev/null +++ b/tools/bit/aapt.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef AAPT_H +#define AAPT_H + +#include <string> +#include <vector> + +using namespace std; + +struct Apk +{ + string package; + string runner; + vector<string> activities; + + bool HasActivity(const string& className); +}; + +string full_class_name(const string& packageName, const string& className); +string pretty_component_name(const string& packageName, const string& className); + +int inspect_apk(Apk* apk, const string& filename); + +#endif // AAPT_H diff --git a/tools/bit/adb.cpp b/tools/bit/adb.cpp new file mode 100644 index 000000000000..0c8424de566d --- /dev/null +++ b/tools/bit/adb.cpp @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "adb.h" + +#include "command.h" +#include "print.h" +#include "util.h" + +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <limits.h> + +#include <iostream> +#include <istream> +#include <streambuf> + +using namespace std; + +struct Buffer: public streambuf +{ + Buffer(char* begin, size_t size); +}; + +Buffer::Buffer(char* begin, size_t size) +{ + this->setg(begin, begin, begin + size); +} + +int +run_adb(const char* first, ...) +{ + Command cmd("adb"); + + if (first == NULL) { + return 0; + } + + cmd.AddArg(first); + + va_list args; + va_start(args, first); + while (true) { + const char* arg = va_arg(args, char*); + if (arg == NULL) { + break; + } + cmd.AddArg(arg); + } + va_end(args); + + return run_command(cmd); +} + +string +get_system_property(const string& name, int* err) +{ + Command cmd("adb"); + cmd.AddArg("shell"); + cmd.AddArg("getprop"); + cmd.AddArg(name); + + return trim(get_command_output(cmd, err, false)); +} + + +static uint64_t +read_varint(int fd, int* err, bool* done) +{ + uint32_t bits = 0; + uint64_t result = 0; + while (true) { + uint8_t byte; + ssize_t amt = read(fd, &byte, 1); + if (amt == 0) { + *done = true; + return result; + } else if (amt < 0) { + return *err = errno; + } + result |= uint64_t(byte & 0x7F) << bits; + if ((byte & 0x80) == 0) { + return result; + } + bits += 7; + if (bits > 64) { + *err = -1; + return 0; + } + } +} + +static char* +read_sized_buffer(int fd, int* err, size_t* resultSize) +{ + bool done = false; + uint64_t size = read_varint(fd, err, &done); + if (*err != 0 || done) { + return NULL; + } + if (size == 0) { + *resultSize = 0; + return NULL; + } + // 10 MB seems like a reasonable limit. + if (size > 10*1024*1024) { + print_error("result buffer too large: %llu", size); + return NULL; + } + char* buf = (char*)malloc(size); + if (buf == NULL) { + print_error("Can't allocate a buffer of size for test results: %llu", size); + return NULL; + } + int pos = 0; + while (size - pos > 0) { + ssize_t amt = read(fd, buf+pos, size-pos); + if (amt == 0) { + // early end of pipe + print_error("Early end of pipe."); + *err = -1; + free(buf); + return NULL; + } else if (amt < 0) { + // error + *err = errno; + free(buf); + return NULL; + } + pos += amt; + } + *resultSize = (size_t)size; + return buf; +} + +static int +read_sized_proto(int fd, Message* message) +{ + int err = 0; + size_t size; + char* buf = read_sized_buffer(fd, &err, &size); + if (err != 0) { + if (buf != NULL) { + free(buf); + } + return err; + } else if (size == 0) { + if (buf != NULL) { + free(buf); + } + return 0; + } else if (buf == NULL) { + return -1; + } + Buffer buffer(buf, size); + istream in(&buffer); + + err = message->ParseFromIstream(&in) ? 0 : -1; + + free(buf); + return err; +} + +static int +skip_bytes(int fd, ssize_t size, char* scratch, int scratchSize) +{ + while (size > 0) { + ssize_t amt = size < scratchSize ? size : scratchSize; + fprintf(stderr, "skipping %lu/%ld bytes\n", size, amt); + amt = read(fd, scratch, amt); + if (amt == 0) { + // early end of pipe + print_error("Early end of pipe."); + return -1; + } else if (amt < 0) { + // error + return errno; + } + size -= amt; + } + return 0; +} + +static int +skip_unknown_field(int fd, uint64_t tag, char* scratch, int scratchSize) { + bool done; + int err; + uint64_t size; + switch (tag & 0x7) { + case 0: // varint + read_varint(fd, &err, &done); + if (err != 0) { + return err; + } else if (done) { + return -1; + } else { + return 0; + } + case 1: + return skip_bytes(fd, 8, scratch, scratchSize); + case 2: + size = read_varint(fd, &err, &done); + if (err != 0) { + return err; + } else if (done) { + return -1; + } + if (size > INT_MAX) { + // we'll be here a long time but this keeps it from overflowing + return -1; + } + return skip_bytes(fd, (ssize_t)size, scratch, scratchSize); + case 5: + return skip_bytes(fd, 4, scratch, scratchSize); + default: + print_error("bad wire type for tag 0x%lx\n", tag); + return -1; + } +} + +static int +read_instrumentation_results(int fd, char* scratch, int scratchSize, + InstrumentationCallbacks* callbacks) +{ + bool done = false; + int err = 0; + string result; + while (true) { + uint64_t tag = read_varint(fd, &err, &done); + if (done) { + // Done reading input (this is the only place that a stream end isn't an error). + return 0; + } else if (err != 0) { + return err; + } else if (tag == 0xa) { // test_status + TestStatus status; + err = read_sized_proto(fd, &status); + if (err != 0) { + return err; + } + callbacks->OnTestStatus(status); + } else if (tag == 0x12) { // session_status + SessionStatus status; + err = read_sized_proto(fd, &status); + if (err != 0) { + return err; + } + callbacks->OnSessionStatus(status); + } else { + err = skip_unknown_field(fd, tag, scratch, scratchSize); + if (err != 0) { + return err; + } + } + } + return 0; +} + +int +run_instrumentation_test(const string& packageName, const string& runner, const string& className, + InstrumentationCallbacks* callbacks) +{ + Command cmd("adb"); + cmd.AddArg("shell"); + cmd.AddArg("am"); + cmd.AddArg("instrument"); + cmd.AddArg("-w"); + cmd.AddArg("-m"); + if (className.length() > 0) { + cmd.AddArg("-e"); + cmd.AddArg("class"); + cmd.AddArg(className); + } + cmd.AddArg(packageName + "/" + runner); + + print_command(cmd); + + int fds[2]; + pipe(fds); + + pid_t pid = fork(); + + if (pid == -1) { + // fork error + return errno; + } else if (pid == 0) { + // child + while ((dup2(fds[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {} + close(fds[1]); + close(fds[0]); + const char* prog = cmd.GetProg(); + char* const* argv = cmd.GetArgv(); + char* const* env = cmd.GetEnv(); + exec_with_path_search(prog, argv, env); + print_error("Unable to run command: %s", prog); + exit(1); + } else { + // parent + close(fds[1]); + string result; + const int size = 16*1024; + char* buf = (char*)malloc(size); + int err = read_instrumentation_results(fds[0], buf, size, callbacks); + free(buf); + int status; + waitpid(pid, &status, 0); + if (err != 0) { + return err; + } + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } else { + return -1; + } + } +} + +/** + * Get the second to last bundle in the args list. Stores the last name found + * in last. If the path is not found or if the args list is empty, returns NULL. + */ +static const ResultsBundleEntry * +find_penultimate_entry(const ResultsBundle& bundle, va_list args) +{ + const ResultsBundle* b = &bundle; + const char* arg = va_arg(args, char*); + while (arg) { + string last = arg; + arg = va_arg(args, char*); + bool found = false; + for (int i=0; i<b->entries_size(); i++) { + const ResultsBundleEntry& e = b->entries(i); + if (e.key() == last) { + if (arg == NULL) { + return &e; + } else if (e.has_value_bundle()) { + b = &e.value_bundle(); + found = true; + } + } + } + if (!found) { + return NULL; + } + if (arg == NULL) { + return NULL; + } + } + return NULL; +} + +string +get_bundle_string(const ResultsBundle& bundle, bool* found, ...) +{ + va_list args; + va_start(args, found); + const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args); + va_end(args); + if (entry == NULL) { + *found = false; + return string(); + } + if (entry->has_value_string()) { + *found = true; + return entry->value_string(); + } + *found = false; + return string(); +} + +int32_t +get_bundle_int(const ResultsBundle& bundle, bool* found, ...) +{ + va_list args; + va_start(args, found); + const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args); + va_end(args); + if (entry == NULL) { + *found = false; + return 0; + } + if (entry->has_value_int()) { + *found = true; + return entry->value_int(); + } + *found = false; + return 0; +} + +float +get_bundle_float(const ResultsBundle& bundle, bool* found, ...) +{ + va_list args; + va_start(args, found); + const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args); + va_end(args); + if (entry == NULL) { + *found = false; + return 0; + } + if (entry->has_value_float()) { + *found = true; + return entry->value_float(); + } + *found = false; + return 0; +} + +double +get_bundle_double(const ResultsBundle& bundle, bool* found, ...) +{ + va_list args; + va_start(args, found); + const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args); + va_end(args); + if (entry == NULL) { + *found = false; + return 0; + } + if (entry->has_value_double()) { + *found = true; + return entry->value_double(); + } + *found = false; + return 0; +} + +int64_t +get_bundle_long(const ResultsBundle& bundle, bool* found, ...) +{ + va_list args; + va_start(args, found); + const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args); + va_end(args); + if (entry == NULL) { + *found = false; + return 0; + } + if (entry->has_value_long()) { + *found = true; + return entry->value_long(); + } + *found = false; + return 0; +} + diff --git a/tools/bit/adb.h b/tools/bit/adb.h new file mode 100644 index 000000000000..dca80c853b45 --- /dev/null +++ b/tools/bit/adb.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef ADB_H +#define ADB_H + +#include "instrumentation_data.pb.h" + +#include <string> + +using namespace android::am; +using namespace google::protobuf; +using namespace std; + +class InstrumentationCallbacks { +public: + virtual void OnTestStatus(TestStatus& status) = 0; + virtual void OnSessionStatus(SessionStatus& status) = 0; +}; + +int run_adb(const char* first, ...); + +string get_system_property(const string& name, int* err); + +int run_instrumentation_test(const string& packageName, const string& runner, + const string& className, InstrumentationCallbacks* callbacks); + +string get_bundle_string(const ResultsBundle& bundle, bool* found, ...); +int32_t get_bundle_int(const ResultsBundle& bundle, bool* found, ...); +float get_bundle_float(const ResultsBundle& bundle, bool* found, ...); +double get_bundle_double(const ResultsBundle& bundle, bool* found, ...); +int64_t get_bundle_long(const ResultsBundle& bundle, bool* found, ...); + +#endif // ADB_H diff --git a/tools/bit/command.cpp b/tools/bit/command.cpp new file mode 100644 index 000000000000..9a8449bf9356 --- /dev/null +++ b/tools/bit/command.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "command.h" + +#include "print.h" +#include "util.h" + +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <sys/wait.h> + +extern char **environ; + +Command::Command(const string& prog) + :prog(prog) +{ +} + +Command::~Command() +{ +} + +void +Command::AddArg(const string& arg) +{ + args.push_back(arg); +} + +void +Command::AddEnv(const string& name, const string& value) +{ + env[name] = value; +} + +const char* +Command::GetProg() const +{ + return prog.c_str(); +} + +char *const * +Command::GetArgv() const +{ + const int N = args.size(); + char** result = (char**)malloc(sizeof(char*)*(N+2)); + result[0] = strdup(prog.c_str()); + for (int i=0; i<N; i++) { + result[i+1] = strdup(args[i].c_str()); + } + result[N+1] = 0; + return result; +} + +char *const * +Command::GetEnv() const +{ + map<string,string> copy; + for (const char** p=(const char**)environ; *p != NULL; p++) { + char* name = strdup(*p); + char* value = strchr(name, '='); + *value = '\0'; + value++; + copy[name] = value; + free(name); + } + for (map<string,string>::const_iterator it=env.begin(); it!=env.end(); it++) { + copy[it->first] = it->second; + } + char** result = (char**)malloc(sizeof(char*)*(copy.size()+1)); + char** row = result; + for (map<string,string>::const_iterator it=copy.begin(); it!=copy.end(); it++) { + *row = (char*)malloc(it->first.size() + it->second.size() + 2); + strcpy(*row, it->first.c_str()); + strcat(*row, "="); + strcat(*row, it->second.c_str()); + row++; + } + *row = NULL; + return result; +} + +string +get_command_output(const Command& command, int* err, bool quiet) +{ + if (!quiet) { + print_command(command); + } + + int fds[2]; + pipe(fds); + + pid_t pid = fork(); + + if (pid == -1) { + // fork error + *err = errno; + return string(); + } else if (pid == 0) { + // child + while ((dup2(fds[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {} + close(fds[1]); + close(fds[0]); + const char* prog = command.GetProg(); + char* const* argv = command.GetArgv(); + char* const* env = command.GetEnv(); + exec_with_path_search(prog, argv, env); + if (!quiet) { + print_error("Unable to run command: %s", prog); + } + exit(1); + } else { + // parent + close(fds[1]); + string result; + const int size = 16*1024; + char* buf = (char*)malloc(size); + while (true) { + ssize_t amt = read(fds[0], buf, size); + if (amt <= 0) { + break; + } else if (amt > 0) { + result.append(buf, amt); + } + } + free(buf); + int status; + waitpid(pid, &status, 0); + if (WIFEXITED(status)) { + *err = WEXITSTATUS(status); + return result; + } else { + *err = -1; + return string(); + } + } +} + + +int +run_command(const Command& command) +{ + print_command(command); + + pid_t pid = fork(); + + if (pid == -1) { + // fork error + return errno; + } else if (pid == 0) { + // child + const char* prog = command.GetProg(); + char* const* argv = command.GetArgv(); + char* const* env = command.GetEnv(); + exec_with_path_search(prog, argv, env); + print_error("Unable to run command: %s", prog); + exit(1); + } else { + // parent + int status; + waitpid(pid, &status, 0); + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } else { + return -1; + } + } +} + +int +exec_with_path_search(const char* prog, char const* const* argv, char const* const* envp) +{ + if (prog[0] == '/') { + return execve(prog, (char*const*)argv, (char*const*)envp); + } else { + char* pathEnv = strdup(getenv("PATH")); + if (pathEnv == NULL) { + return 1; + } + char* dir = pathEnv; + while (dir) { + char* next = strchr(dir, ':'); + if (next != NULL) { + *next = '\0'; + next++; + } + if (dir[0] == '/') { + struct stat st; + string executable = string(dir) + "/" + prog; + if (stat(executable.c_str(), &st) == 0) { + execve(executable.c_str(), (char*const*)argv, (char*const*)envp); + } + } + dir = next; + } + free(pathEnv); + return 1; + } +} + diff --git a/tools/bit/command.h b/tools/bit/command.h new file mode 100644 index 000000000000..fb44900b0806 --- /dev/null +++ b/tools/bit/command.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef COMMAND_H +#define COMMAND_H + +#include <map> +#include <string> +#include <vector> + +using namespace std; + +struct Command +{ + Command(const string& prog); + ~Command(); + + void AddArg(const string& arg); + void AddEnv(const string& name, const string& value); + + const char* GetProg() const; + char* const* GetArgv() const; + char* const* GetEnv() const; + + string GetCommandline() const; + + string prog; + vector<string> args; + map<string,string> env; +}; + +/** + * Run the command and collect stdout. + * Returns the exit code. + */ +string get_command_output(const Command& command, int* err, bool quiet=false); + +/** + * Run the command. + * Returns the exit code. + */ +int run_command(const Command& command); + +// Mac OS doesn't have execvpe. This is the same as execvpe. +int exec_with_path_search(const char* prog, char const* const* argv, char const* const* envp); + +#endif // COMMAND_H + diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp new file mode 100644 index 000000000000..868d90a1188d --- /dev/null +++ b/tools/bit/main.cpp @@ -0,0 +1,994 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "aapt.h" +#include "adb.h" +#include "make.h" +#include "print.h" +#include "util.h" + +#include <sstream> +#include <string> +#include <vector> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <google/protobuf/stubs/common.h> + +using namespace std; + +/** + * An entry from the command line for something that will be built, installed, + * and/or tested. + */ +struct Target { + bool build; + bool install; + bool test; + string pattern; + string name; + vector<string> actions; + Module module; + + int testActionCount; + + int testPassCount; + int testFailCount; + bool actionsWithNoTests; + + Target(bool b, bool i, bool t, const string& p); +}; + +Target::Target(bool b, bool i, bool t, const string& p) + :build(b), + install(i), + test(t), + pattern(p), + testActionCount(0), + testPassCount(0), + testFailCount(0), + actionsWithNoTests(false) +{ +} + +/** + * Command line options. + */ +struct Options { + // For help + bool runHelp; + + // For tab completion + bool runTab; + string tabPattern; + + // For build/install/test + bool noRestart; + bool reboot; + vector<Target*> targets; + + Options(); + ~Options(); +}; + +Options::Options() + :runHelp(false), + runTab(false), + noRestart(false), + reboot(false), + targets() +{ +} + +Options::~Options() +{ +} + +struct InstallApk +{ + TrackedFile file; + bool alwaysInstall; + bool installed; + + InstallApk(); + InstallApk(const InstallApk& that); + InstallApk(const string& filename, bool always); + ~InstallApk() {}; +}; + +InstallApk::InstallApk() +{ +} + +InstallApk::InstallApk(const InstallApk& that) + :file(that.file), + alwaysInstall(that.alwaysInstall), + installed(that.installed) +{ +} + +InstallApk::InstallApk(const string& filename, bool always) + :file(filename), + alwaysInstall(always), + installed(false) +{ +} + + +/** + * Record for an test that is going to be launched. + */ +struct TestAction { + TestAction(); + + // The package name from the apk + string packageName; + + // The test runner class + string runner; + + // The test class, or none if all tests should be run + string className; + + // The original target that requested this action + Target* target; + + // The number of tests that passed + int passCount; + + // The number of tests that failed + int failCount; +}; + +TestAction::TestAction() + :passCount(0), + failCount(0) +{ +} + +/** + * Record for an activity that is going to be launched. + */ +struct ActivityAction { + // The package name from the apk + string packageName; + + // The test class, or none if all tests should be run + string className; +}; + +/** + * Callback class for the am instrument command. + */ +class TestResults: public InstrumentationCallbacks +{ +public: + virtual void OnTestStatus(TestStatus& status); + virtual void OnSessionStatus(SessionStatus& status); + + /** + * Set the TestAction that the tests are for. + * It will be updated with statistics as the tests run. + */ + void SetCurrentAction(TestAction* action); + +private: + TestAction* m_currentAction; +}; + +void +TestResults::OnTestStatus(TestStatus& status) +{ + bool found; +// printf("OnTestStatus\n"); +// status.PrintDebugString(); + int32_t resultCode = status.has_results() ? status.result_code() : 0; + + if (!status.has_results()) { + return; + } + const ResultsBundle &results = status.results(); + + int32_t currentTestNum = get_bundle_int(results, &found, "current", NULL); + if (!found) { + currentTestNum = -1; + } + + int32_t testCount = get_bundle_int(results, &found, "numtests", NULL); + if (!found) { + testCount = -1; + } + + string className = get_bundle_string(results, &found, "class", NULL); + if (!found) { + return; + } + + string testName = get_bundle_string(results, &found, "test", NULL); + if (!found) { + return; + } + + if (resultCode == 0) { + // test passed + m_currentAction->passCount++; + m_currentAction->target->testPassCount++; + } else if (resultCode == 1) { + // test starting + ostringstream line; + line << "Running"; + if (currentTestNum > 0) { + line << ": " << currentTestNum; + if (testCount > 0) { + line << " of " << testCount; + } + } + line << ": " << m_currentAction->target->name << ':' << className << "\\#" << testName; + print_one_line("%s", line.str().c_str()); + } else if (resultCode == -2) { + // test failed + m_currentAction->failCount++; + m_currentAction->target->testFailCount++; + printf("%s\n%sFailed: %s:%s\\#%s%s\n", g_escapeClearLine, g_escapeRedBold, + m_currentAction->target->name.c_str(), className.c_str(), + testName.c_str(), g_escapeEndColor); + + string stack = get_bundle_string(results, &found, "stack", NULL); + if (found) { + printf("%s\n", stack.c_str()); + } + } +} + +void +TestResults::OnSessionStatus(SessionStatus& /*status*/) +{ + //status.PrintDebugString(); +} + +void +TestResults::SetCurrentAction(TestAction* action) +{ + m_currentAction = action; +} + +/** + * Prints the usage statement / help text. + */ +static void +print_usage(FILE* out) { + fprintf(out, "usage: bit OPTIONS PATTERN\n"); + fprintf(out, "\n"); + fprintf(out, " Build, sync and test android code.\n"); + fprintf(out, "\n"); + fprintf(out, " The -b -i and -t options allow you to specify which phases\n"); + fprintf(out, " you want to run. If none of those options are given, then\n"); + fprintf(out, " all phases are run. If any of these options are provided\n"); + fprintf(out, " then only the listed phases are run.\n"); + fprintf(out, "\n"); + fprintf(out, " OPTIONS\n"); + fprintf(out, " -b Run a build\n"); + fprintf(out, " -i Install the targets\n"); + fprintf(out, " -t Run the tests\n"); + fprintf(out, "\n"); + fprintf(out, " -n Don't reboot or restart\n"); + fprintf(out, " -r If the runtime needs to be restarted, do a full reboot\n"); + fprintf(out, " instead\n"); + fprintf(out, "\n"); + fprintf(out, " PATTERN\n"); + fprintf(out, " One or more targets to build, install and test. The target\n"); + fprintf(out, " names are the names that appear in the LOCAL_MODULE or\n"); + fprintf(out, " LOCAL_PACKAGE_NAME variables in Android.mk or Android.bp files.\n"); + fprintf(out, "\n"); + fprintf(out, " Building and installing\n"); + fprintf(out, " -----------------------\n"); + fprintf(out, " The modules specified will be built and then installed. If the\n"); + fprintf(out, " files are on the system partition, they will be synced and the\n"); + fprintf(out, " attached device rebooted. If they are APKs that aren't on the\n"); + fprintf(out, " system partition they are installed with adb install.\n"); + fprintf(out, "\n"); + fprintf(out, " For example:\n"); + fprintf(out, " bit framework\n"); + fprintf(out, " Builds framework.jar, syncs the system partition and reboots.\n"); + fprintf(out, "\n"); + fprintf(out, " bit SystemUI\n"); + fprintf(out, " Builds SystemUI.apk, syncs the system partition and reboots.\n"); + fprintf(out, "\n"); + fprintf(out, " bit CtsProtoTestCases\n"); + fprintf(out, " Builds this CTS apk, adb installs it, but does not run any\n"); + fprintf(out, " tests.\n"); + fprintf(out, "\n"); + fprintf(out, " Running Unit Tests\n"); + fprintf(out, " ------------------\n"); + fprintf(out, " To run a unit test, list the test class names and optionally the\n"); + fprintf(out, " test method after the module.\n"); + fprintf(out, "\n"); + fprintf(out, " For example:\n"); + fprintf(out, " bit CtsProtoTestCases:*\n"); + fprintf(out, " Builds this CTS apk, adb installs it, and runs all the tests\n"); + fprintf(out, " contained in that apk.\n"); + fprintf(out, "\n"); + fprintf(out, " bit framework CtsProtoTestCases:*\n"); + fprintf(out, " Builds the framework and the apk, syncs and reboots, then\n"); + fprintf(out, " adb installs CtsProtoTestCases.apk, and runs all tests \n"); + fprintf(out, " contained in that apk.\n"); + fprintf(out, "\n"); + fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\n"); + fprintf(out, " bit CtsProtoTestCases:android.util.proto.cts.ProtoOutputStreamBoolTest\n"); + fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs all the\n"); + fprintf(out, " tests in the ProtoOutputStreamBoolTest class.\n"); + fprintf(out, "\n"); + fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\\#testWrite\n"); + fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the testWrite\n"); + fprintf(out, " test method on that class.\n"); + fprintf(out, "\n"); + fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\\#testWrite,.ProtoOutputStreamBoolTest\\#testRepeated\n"); + fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the testWrite\n"); + fprintf(out, " and testRepeated test methods on that class.\n"); + fprintf(out, "\n"); + fprintf(out, " Launching an Activity\n"); + fprintf(out, " ---------------------\n"); + fprintf(out, " To launch an activity, specify the activity class name after\n"); + fprintf(out, " the module name.\n"); + fprintf(out, "\n"); + fprintf(out, " For example:\n"); + fprintf(out, " bit StatusBarTest:NotificationBuilderTest\n"); + fprintf(out, " bit StatusBarTest:.NotificationBuilderTest\n"); + fprintf(out, " bit StatusBarTest:com.android.statusbartest.NotificationBuilderTest\n"); + fprintf(out, " Builds and installs StatusBarTest.apk, launches the\n"); + fprintf(out, " com.android.statusbartest/.NotificationBuilderTest activity.\n"); + fprintf(out, "\n"); + fprintf(out, "\n"); + fprintf(out, "usage: bit --tab ...\n"); + fprintf(out, "\n"); + fprintf(out, " Lists the targets in a format for tab completion. To get tab\n"); + fprintf(out, " completion, add this to your bash environment:\n"); + fprintf(out, "\n"); + fprintf(out, " complete -C \"bit --tab\" bit\n"); + fprintf(out, "\n"); + fprintf(out, " Sourcing android's build/envsetup.sh will do this for you\n"); + fprintf(out, " automatically.\n"); + fprintf(out, "\n"); + fprintf(out, "\n"); + fprintf(out, "usage: bit --help\n"); + fprintf(out, "usage: bit -h\n"); + fprintf(out, "\n"); + fprintf(out, " Print this help message\n"); + fprintf(out, "\n"); +} + + +/** + * Sets the appropriate flag* variables. If there is a problem with the + * commandline arguments, prints the help message and exits with an error. + */ +static void +parse_args(Options* options, int argc, const char** argv) +{ + // Help + if (argc == 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) { + options->runHelp = true; + return; + } + + // Tab + if (argc >= 4 && strcmp(argv[1], "--tab") == 0) { + options->runTab = true; + options->tabPattern = argv[3]; + return; + } + + // Normal usage + bool anyPhases = false; + bool gotPattern = false; + bool flagBuild = false; + bool flagInstall = false; + bool flagTest = false; + for (int i=1; i < argc; i++) { + string arg(argv[i]); + if (arg[0] == '-') { + for (size_t j=1; j<arg.size(); j++) { + switch (arg[j]) { + case '-': + break; + case 'b': + if (gotPattern) { + gotPattern = false; + flagInstall = false; + flagTest = false; + } + flagBuild = true; + anyPhases = true; + break; + case 'i': + if (gotPattern) { + gotPattern = false; + flagBuild = false; + flagTest = false; + } + flagInstall = true; + anyPhases = true; + break; + case 't': + if (gotPattern) { + gotPattern = false; + flagBuild = false; + flagInstall = false; + } + flagTest = true; + anyPhases = true; + break; + case 'n': + options->noRestart = true; + break; + case 'r': + options->reboot = true; + break; + default: + fprintf(stderr, "Unrecognized option '%c'\n", arg[j]); + print_usage(stderr); + exit(1); + break; + } + } + } else { + Target* target = new Target(flagBuild || !anyPhases, flagInstall || !anyPhases, + flagTest || !anyPhases, arg); + size_t colonPos = arg.find(':'); + if (colonPos == 0) { + fprintf(stderr, "Test / activity supplied without a module to build: %s\n", + arg.c_str()); + print_usage(stderr); + exit(1); + } else if (colonPos == string::npos) { + target->name = arg; + } else { + target->name.assign(arg, 0, colonPos); + size_t beginPos = colonPos+1; + size_t commaPos; + while (true) { + commaPos = arg.find(',', beginPos); + if (commaPos == string::npos) { + if (beginPos != arg.size()) { + target->actions.push_back(string(arg, beginPos, commaPos)); + } + break; + } else { + if (commaPos != beginPos) { + target->actions.push_back(string(arg, beginPos, commaPos-beginPos)); + } + beginPos = commaPos+1; + } + } + } + options->targets.push_back(target); + gotPattern = true; + } + } + // If no pattern was supplied, give an error + if (options->targets.size() == 0) { + fprintf(stderr, "No PATTERN supplied.\n\n"); + print_usage(stderr); + exit(1); + } +} + +/** + * Get an environment variable. + * Exits with an error if it is unset or the empty string. + */ +static string +get_required_env(const char* name, bool quiet) +{ + const char* value = getenv(name); + if (value == NULL || value[0] == '\0') { + if (!quiet) { + fprintf(stderr, "%s not set. Did you source build/envsetup.sh," + " run lunch and do a build?\n", name); + } + exit(1); + } + return string(value); +} + +/** + * Get the out directory. + * + * This duplicates the logic in build/make/core/envsetup.mk (which hasn't changed since 2011) + * so that we don't have to wait for get_build_var make invocation. + */ +string +get_out_dir() +{ + const char* out_dir = getenv("OUT_DIR"); + if (out_dir == NULL || out_dir[0] == '\0') { + const char* common_base = getenv("OUT_DIR_COMMON_BASE"); + if (common_base == NULL || common_base[0] == '\0') { + // We don't prefix with buildTop because we cd there and it + // makes all the filenames long when being pretty printed. + return "out"; + } else { + char pwd[PATH_MAX]; + if (getcwd(pwd, PATH_MAX) == NULL) { + fprintf(stderr, "Your pwd is too long.\n"); + exit(1); + } + const char* slash = strrchr(pwd, '/'); + if (slash == NULL) { + slash = ""; + } + string result(common_base); + result += slash; + return result; + } + } + return string(out_dir); +} + +/** + * Check that a system property on the device matches the expected value. + * Exits with an error if they don't. + */ +static void +check_device_property(const string& property, const string& expected) +{ + int err; + string deviceValue = get_system_property(property, &err); + check_error(err); + if (deviceValue != expected) { + print_error("There is a mismatch between the build you just did and the device you"); + print_error("are trying to sync it to in the %s system property", property.c_str()); + print_error(" build: %s", expected.c_str()); + print_error(" device: %s", deviceValue.c_str()); + exit(1); + } +} + +/** + * Run the build, install, and test actions. + */ +void +run_phases(vector<Target*> targets, const Options& options) +{ + int err = 0; + + // + // Initialization + // + + print_status("Initializing"); + + const string buildTop = get_required_env("ANDROID_BUILD_TOP", false); + const string buildProduct = get_required_env("TARGET_PRODUCT", false); + const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false); + const string buildType = get_required_env("TARGET_BUILD_TYPE", false); + const string buildDevice = get_build_var(buildTop, "TARGET_DEVICE", false); + const string buildId = get_build_var(buildTop, "BUILD_ID", false); + const string buildOut = get_out_dir(); + + // TODO: print_command("cd", buildTop.c_str()); + chdir(buildTop.c_str()); + + // Get the modules for the targets + map<string,Module> modules; + read_modules(buildOut, buildDevice, &modules, false); + for (size_t i=0; i<targets.size(); i++) { + Target* target = targets[i]; + map<string,Module>::iterator mod = modules.find(target->name); + if (mod != modules.end()) { + target->module = mod->second; + } else { + print_error("Error: Could not find module: %s", target->name.c_str()); + err = 1; + } + } + if (err != 0) { + exit(1); + } + + // Choose the goals + vector<string> goals; + for (size_t i=0; i<targets.size(); i++) { + Target* target = targets[i]; + if (target->build) { + goals.push_back(target->name); + } + } + + // Figure out whether we need to sync the system and which apks to install + string systemPath = buildOut + "/target/product/" + buildDevice + "/system/"; + string dataPath = buildOut + "/target/product/" + buildDevice + "/data/"; + bool syncSystem = false; + bool alwaysSyncSystem = false; + vector<InstallApk> installApks; + for (size_t i=0; i<targets.size(); i++) { + Target* target = targets[i]; + if (target->install) { + for (size_t j=0; j<target->module.installed.size(); j++) { + const string& file = target->module.installed[j]; + // System partition + if (starts_with(file, systemPath)) { + syncSystem = true; + if (!target->build) { + // If a system partition target didn't get built then + // it won't change we will always need to do adb sync + alwaysSyncSystem = true; + } + continue; + } + // Apk in the data partition + if (starts_with(file, dataPath) && ends_with(file, ".apk")) { + // Always install it if we didn't build it because otherwise + // it will never have changed. + installApks.push_back(InstallApk(file, !target->build)); + continue; + } + } + } + } + map<string,FileInfo> systemFilesBefore; + if (syncSystem && !alwaysSyncSystem) { + get_directory_contents(systemPath, &systemFilesBefore); + } + + // + // Build + // + + // Run the build + if (goals.size() > 0) { + print_status("Building"); + err = build_goals(goals); + check_error(err); + } + + // + // Install + // + + // Sync the system partition and reboot + bool skipSync = false; + if (syncSystem) { + print_status("Syncing /system"); + + if (!alwaysSyncSystem) { + // If nothing changed and we weren't forced to sync, skip the reboot for speed. + map<string,FileInfo> systemFilesAfter; + get_directory_contents(systemPath, &systemFilesAfter); + skipSync = !directory_contents_differ(systemFilesBefore, systemFilesAfter); + } + if (skipSync) { + printf("Skipping sync because no files changed.\n"); + } else { + // Do some sanity checks + check_device_property("ro.build.product", buildProduct); + check_device_property("ro.build.type", buildVariant); + check_device_property("ro.build.id", buildId); + + // Stop & Sync + if (!options.noRestart) { + err = run_adb("shell", "stop", NULL); + check_error(err); + } + err = run_adb("remount", NULL); + check_error(err); + err = run_adb("sync", "system", NULL); + check_error(err); + + if (!options.noRestart) { + if (options.reboot) { + print_status("Rebooting"); + + err = run_adb("reboot", NULL); + check_error(err); + err = run_adb("wait-for-device", NULL); + check_error(err); + } else { + print_status("Restarting the runtime"); + + err = run_adb("shell", "setprop", "sys.boot_completed", "0", NULL); + check_error(err); + err = run_adb("shell", "start", NULL); + check_error(err); + } + + while (true) { + string completed = get_system_property("sys.boot_completed", &err); + check_error(err); + if (completed == "1") { + break; + } + sleep(2); + } + sleep(1); + err = run_adb("shell", "wm", "dismiss-keyguard", NULL); + check_error(err); + } + } + } + + // Install APKs + if (installApks.size() > 0) { + print_status("Installing APKs"); + for (size_t i=0; i<installApks.size(); i++) { + InstallApk& apk = installApks[i]; + if (!apk.file.fileInfo.exists || apk.file.HasChanged()) { + // It didn't exist before or it changed, so int needs install + err = run_adb("install", "-r", apk.file.filename.c_str(), NULL); + check_error(err); + apk.installed = true; + } else { + printf("APK didn't change. Skipping install of %s\n", apk.file.filename.c_str()); + } + } + } + + // + // Actions + // + + // Inspect the apks, and figure out what is an activity and what needs a test runner + bool printedInspecting = false; + vector<TestAction> testActions; + vector<ActivityAction> activityActions; + for (size_t i=0; i<targets.size(); i++) { + Target* target = targets[i]; + if (target->test) { + for (size_t j=0; j<target->module.installed.size(); j++) { + string filename = target->module.installed[j]; + + if (!ends_with(filename, ".apk")) { + continue; + } + + if (!printedInspecting) { + printedInspecting = true; + print_status("Inspecting APKs"); + } + + Apk apk; + err = inspect_apk(&apk, filename); + check_error(err); + + for (size_t k=0; k<target->actions.size(); k++) { + string actionString = target->actions[k]; + if (actionString == "*") { + if (apk.runner.length() == 0) { + print_error("Error: Test requested for apk that doesn't" + " have an <instrumentation> tag: %s\n", + target->module.name.c_str()); + exit(1); + } + TestAction action; + action.packageName = apk.package; + action.runner = apk.runner; + action.target = target; + testActions.push_back(action); + target->testActionCount++; + } else if (apk.HasActivity(actionString)) { + ActivityAction action; + action.packageName = apk.package; + action.className = full_class_name(apk.package, actionString); + activityActions.push_back(action); + } else { + if (apk.runner.length() == 0) { + print_error("Error: Test requested for apk that doesn't" + " have an <instrumentation> tag: %s\n", + target->module.name.c_str()); + exit(1); + } + TestAction action; + action.packageName = apk.package; + action.runner = apk.runner; + action.className = full_class_name(apk.package, actionString); + action.target = target; + testActions.push_back(action); + target->testActionCount++; + } + } + } + } + } + + // Run the instrumentation tests + TestResults testResults; + if (testActions.size() > 0) { + print_status("Running tests"); + for (size_t i=0; i<testActions.size(); i++) { + TestAction& action = testActions[i]; + testResults.SetCurrentAction(&action); + err = run_instrumentation_test(action.packageName, action.runner, action.className, + &testResults); + check_error(err); + if (action.passCount == 0 && action.failCount == 0) { + action.target->actionsWithNoTests = true; + } + int total = action.passCount + action.failCount; + printf("%sRan %d test%s for %s. ", g_escapeClearLine, + total, total > 1 ? "s" : "", action.target->name.c_str()); + if (action.passCount == 0 && action.failCount == 0) { + printf("%s%d passed, %d failed%s\n", g_escapeYellowBold, action.passCount, + action.failCount, g_escapeEndColor); + } else if (action.failCount > 0) { + printf("%d passed, %s%d failed%s\n", action.passCount, g_escapeRedBold, + action.failCount, g_escapeEndColor); + } else { + printf("%s%d passed%s, %d failed\n", g_escapeGreenBold, action.passCount, + g_escapeEndColor, action.failCount); + } + } + } + + // Launch the activity + if (activityActions.size() > 0) { + print_status("Starting activity"); + + if (activityActions.size() > 1) { + print_warning("Multiple activities specified. Will only start the first one:"); + for (size_t i=0; i<activityActions.size(); i++) { + ActivityAction& action = activityActions[i]; + print_warning(" %s", + pretty_component_name(action.packageName, action.className).c_str()); + } + } + + const ActivityAction& action = activityActions[0]; + string componentName = action.packageName + "/" + action.className; + err = run_adb("shell", "am", "start", componentName.c_str(), NULL); + check_error(err); + } + + // + // Print summary + // + + printf("\n%s--------------------------------------------%s\n", g_escapeBold, g_escapeEndColor); + + // Build + if (goals.size() > 0) { + printf("%sBuilt:%s\n", g_escapeBold, g_escapeEndColor); + for (size_t i=0; i<goals.size(); i++) { + printf(" %s\n", goals[i].c_str()); + } + } + + // Install + if (syncSystem) { + if (skipSync) { + printf("%sSkipped syncing /system partition%s\n", g_escapeBold, g_escapeEndColor); + } else { + printf("%sSynced /system partition%s\n", g_escapeBold, g_escapeEndColor); + } + } + if (installApks.size() > 0) { + bool printedTitle = false; + for (size_t i=0; i<installApks.size(); i++) { + const InstallApk& apk = installApks[i]; + if (apk.installed) { + if (!printedTitle) { + printf("%sInstalled:%s\n", g_escapeBold, g_escapeEndColor); + printedTitle = true; + } + printf(" %s\n", apk.file.filename.c_str()); + } + } + printedTitle = false; + for (size_t i=0; i<installApks.size(); i++) { + const InstallApk& apk = installApks[i]; + if (!apk.installed) { + if (!printedTitle) { + printf("%sSkipped install:%s\n", g_escapeBold, g_escapeEndColor); + printedTitle = true; + } + printf(" %s\n", apk.file.filename.c_str()); + } + } + } + + // Tests + if (testActions.size() > 0) { + printf("%sRan tests:%s\n", g_escapeBold, g_escapeEndColor); + size_t maxNameLength = 0; + for (size_t i=0; i<targets.size(); i++) { + Target* target = targets[i]; + if (target->test) { + size_t len = target->name.length(); + if (len > maxNameLength) { + maxNameLength = len; + } + } + } + string padding(maxNameLength, ' '); + for (size_t i=0; i<targets.size(); i++) { + Target* target = targets[i]; + if (target->testActionCount > 0) { + printf(" %s%s", target->name.c_str(), padding.c_str() + target->name.length()); + if (target->actionsWithNoTests) { + printf(" %s%d passed, %d failed%s\n", g_escapeYellowBold, + target->testPassCount, target->testFailCount, g_escapeEndColor); + } else if (target->testFailCount > 0) { + printf(" %d passed, %s%d failed%s\n", target->testPassCount, + g_escapeRedBold, target->testFailCount, g_escapeEndColor); + } else { + printf(" %s%d passed%s, %d failed\n", g_escapeGreenBold, + target->testPassCount, g_escapeEndColor, target->testFailCount); + } + } + } + } + if (activityActions.size() > 1) { + printf("%sStarted Activity:%s\n", g_escapeBold, g_escapeEndColor); + const ActivityAction& action = activityActions[0]; + printf(" %s\n", pretty_component_name(action.packageName, action.className).c_str()); + } + + printf("%s--------------------------------------------%s\n", g_escapeBold, g_escapeEndColor); +} + +/** + * Implement tab completion of the target names from the all modules file. + */ +void +run_tab_completion(const string& word) +{ + const string buildTop = get_required_env("ANDROID_BUILD_TOP", true); + const string buildProduct = get_required_env("TARGET_PRODUCT", false); + const string buildOut = get_out_dir(); + + chdir(buildTop.c_str()); + + string buildDevice = sniff_device_name(buildOut, buildProduct); + + map<string,Module> modules; + read_modules(buildOut, buildDevice, &modules, true); + + for (map<string,Module>::const_iterator it = modules.begin(); it != modules.end(); it++) { + if (starts_with(it->first, word)) { + printf("%s\n", it->first.c_str()); + } + } +} + +/** + * Main entry point. + */ +int +main(int argc, const char** argv) +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + init_print(); + + Options options; + parse_args(&options, argc, argv); + + if (options.runHelp) { + // Help + print_usage(stdout); + exit(0); + } else if (options.runTab) { + run_tab_completion(options.tabPattern); + exit(0); + } else { + // Normal run + run_phases(options.targets, options); + } + + return 0; +} + diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp new file mode 100644 index 000000000000..60b5687bb313 --- /dev/null +++ b/tools/bit/make.cpp @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "make.h" + +#include "command.h" +#include "print.h" +#include "util.h" + +#include <json/reader.h> +#include <json/value.h> + +#include <fstream> +#include <string> +#include <map> + +#include <sys/types.h> +#include <dirent.h> +#include <string.h> + +using namespace std; + +map<string,string> g_buildVars; + +string +get_build_var(const string& buildTop, const string& name, bool quiet) +{ + int err; + + map<string,string>::iterator it = g_buildVars.find(name); + if (it == g_buildVars.end()) { + Command cmd("make"); + cmd.AddArg("--no-print-directory"); + cmd.AddArg("-C"); + cmd.AddArg(buildTop); + cmd.AddArg("-f"); + cmd.AddArg("build/core/config.mk"); + cmd.AddArg(string("dumpvar-") + name); + cmd.AddEnv("CALLED_FROM_SETUP", "true"); + cmd.AddEnv("BUILD_SYSTEM", "build/core"); + + string output = trim(get_command_output(cmd, &err, quiet)); + if (err == 0) { + g_buildVars[name] = output; + return output; + } else { + return string(); + } + } else { + return it->second; + } +} + +string +sniff_device_name(const string& buildOut, const string& product) +{ + string match("ro.build.product=" + product); + + string base(buildOut + "/target/product"); + DIR* dir = opendir(base.c_str()); + if (dir == NULL) { + return string(); + } + + dirent* entry; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + if (entry->d_type == DT_DIR) { + string filename(base + "/" + entry->d_name + "/system/build.prop"); + vector<string> lines; + split_lines(&lines, read_file(filename)); + for (size_t i=0; i<lines.size(); i++) { + if (lines[i] == match) { + return entry->d_name; + } + } + } + } + + closedir(dir); + return string(); +} + +void +json_error(const string& filename, const char* error, bool quiet) +{ + if (!quiet) { + print_error("Unable to parse module info file (%s): %s", error, filename.c_str()); + print_error("Have you done a full build?"); + } + exit(1); +} + +static void +get_values(const Json::Value& json, const string& name, vector<string>* result) +{ + Json::Value nullValue; + + const Json::Value& value = json.get(name, nullValue); + if (!value.isArray()) { + return; + } + + const int N = value.size(); + for (int i=0; i<N; i++) { + const Json::Value& child = value[i]; + if (child.isString()) { + result->push_back(child.asString()); + } + } +} + +void +read_modules(const string& buildOut, const string& device, map<string,Module>* result, bool quiet) +{ + string filename(string(buildOut + "/target/product/") + device + "/module-info.json"); + std::ifstream stream(filename, std::ifstream::binary); + + if (stream.fail()) { + if (!quiet) { + print_error("Unable to open module info file: %s", filename.c_str()); + print_error("Have you done a full build?"); + } + exit(1); + } + + Json::Value json; + Json::Reader reader; + if (!reader.parse(stream, json)) { + json_error(filename, "can't parse json format", quiet); + return; + } + + if (!json.isObject()) { + json_error(filename, "root element not an object", quiet); + return; + } + + vector<string> names = json.getMemberNames(); + const int N = names.size(); + for (int i=0; i<N; i++) { + const string& name = names[i]; + + const Json::Value& value = json[name]; + if (!value.isObject()) { + continue; + } + + Module module; + + module.name = name; + get_values(value, "class", &module.classes); + get_values(value, "path", &module.paths); + get_values(value, "installed", &module.installed); + + // Only keep classes we can handle + for (ssize_t i = module.classes.size() - 1; i >= 0; i--) { + string cl = module.classes[i]; + if (!(cl == "JAVA_LIBRARIES" || cl == "EXECUTABLES" || cl == "SHARED_LIBRARIES" + || cl == "APPS")) { + module.classes.erase(module.classes.begin() + i); + } + } + if (module.classes.size() == 0) { + continue; + } + + // Only target modules (not host) + for (ssize_t i = module.installed.size() - 1; i >= 0; i--) { + string fn = module.installed[i]; + if (!starts_with(fn, buildOut + "/target/")) { + module.installed.erase(module.installed.begin() + i); + } + } + if (module.installed.size() == 0) { + continue; + } + + (*result)[name] = module; + } +} + +int +build_goals(const vector<string>& goals) +{ + Command cmd("make"); + cmd.AddArg("-f"); + cmd.AddArg("build/core/main.mk"); + for (size_t i=0; i<goals.size(); i++) { + cmd.AddArg(goals[i]); + } + + return run_command(cmd); +} + diff --git a/tools/bit/make.h b/tools/bit/make.h new file mode 100644 index 000000000000..bb83c6e14226 --- /dev/null +++ b/tools/bit/make.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef MAKE_H +#define MAKE_H + +#include <map> +#include <string> +#include <vector> + +using namespace std; + +struct Module +{ + string name; + vector<string> classes; + vector<string> paths; + vector<string> installed; +}; + +string get_build_var(const string& buildTop, const string& name, bool quiet); + +/** + * Poke around in the out directory and try to find a device name that matches + * our product. This is faster than running get_build_var and good enough for + * tab completion. + * + * Returns the empty string if we can't find one. + */ +string sniff_device_name(const string& buildOut, const string& product); + +void read_modules(const string& buildOut, const string& buildDevice, + map<string,Module>* modules, bool quiet); + +int build_goals(const vector<string>& goals); + +#endif // MAKE_H diff --git a/tools/bit/print.cpp b/tools/bit/print.cpp new file mode 100644 index 000000000000..790e0b4b227e --- /dev/null +++ b/tools/bit/print.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "print.h" + +#include <sys/ioctl.h> +#include <stdio.h> +#include <unistd.h> + +#include "util.h" + +bool g_stdoutIsTty; +char const* g_escapeBold; +char const* g_escapeRedBold; +char const* g_escapeGreenBold; +char const* g_escapeYellowBold; +char const* g_escapeUnderline; +char const* g_escapeEndColor; +char const* g_escapeClearLine; + +void +init_print() +{ + if (isatty(fileno(stdout))) { + g_stdoutIsTty = true; + g_escapeBold = "\033[1m"; + g_escapeRedBold = "\033[91m\033[1m"; + g_escapeGreenBold = "\033[92m\033[1m"; + g_escapeYellowBold = "\033[93m\033[1m"; + g_escapeUnderline = "\033[4m"; + g_escapeEndColor = "\033[0m"; + g_escapeClearLine = "\033[K"; + } else { + g_stdoutIsTty = false; + g_escapeBold = ""; + g_escapeRedBold = ""; + g_escapeGreenBold = ""; + g_escapeYellowBold = ""; + g_escapeUnderline = ""; + g_escapeEndColor = ""; + g_escapeClearLine = ""; + } +} + +void +print_status(const char* format, ...) +{ + printf("\n%s%s", g_escapeBold, g_escapeUnderline); + + va_list args; + va_start(args, format); + vfprintf(stdout, format, args); + va_end(args); + + printf("%s\n", g_escapeEndColor); +} + +void +print_command(const Command& command) +{ + fputs(g_escapeBold, stdout); + for (map<string,string>::const_iterator it=command.env.begin(); it!=command.env.end(); it++) { + fputs(it->first.c_str(), stdout); + fputc('=', stdout); + fputs(escape_for_commandline(it->second.c_str()).c_str(), stdout); + putc(' ', stdout); + } + fputs(command.prog.c_str(), stdout); + for (vector<string>::const_iterator it=command.args.begin(); it!=command.args.end(); it++) { + putc(' ', stdout); + fputs(escape_for_commandline(it->c_str()).c_str(), stdout); + } + fputs(g_escapeEndColor, stdout); + fputc('\n', stdout); +} + +void +print_error(const char* format, ...) +{ + fputs(g_escapeRedBold, stderr); + + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + + fputs(g_escapeEndColor, stderr); + fputc('\n', stderr); +} + +void +print_warning(const char* format, ...) +{ + fputs(g_escapeYellowBold, stderr); + + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + + fputs(g_escapeEndColor, stderr); + fputc('\n', stderr); +} + +void +print_one_line(const char* format, ...) +{ + if (g_stdoutIsTty) { + struct winsize ws; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); + int size = ws.ws_col + 1; + char* buf = (char*)malloc(size); + + va_list args; + va_start(args, format); + vsnprintf(buf, size, format, args); + va_end(args); + + printf("%s%s\r", buf, g_escapeClearLine); + free(buf); + + fflush(stdout); + } else { + va_list args; + va_start(args, format); + vfprintf(stdout, format, args); + va_end(args); + printf("\n"); + } +} + +void +check_error(int err) +{ + if (err != 0) { + fputc('\n', stderr); + print_error("Stopping due to errors."); + exit(1); + } +} + + diff --git a/tools/bit/print.h b/tools/bit/print.h new file mode 100644 index 000000000000..b6c3e9aa27fa --- /dev/null +++ b/tools/bit/print.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef PRINT_H +#define PRINT_H + +#include "command.h" + +extern bool g_stdoutIsTty; +extern char const* g_escapeBold; +extern char const* g_escapeRedBold; +extern char const* g_escapeGreenBold; +extern char const* g_escapeYellowBold; +extern char const* g_escapeUnderline; +extern char const* g_escapeEndColor; +extern char const* g_escapeClearLine; + +void init_print(); +void print_status(const char* format, ...); +void print_command(const Command& command); +void print_error(const char* format, ...); +void print_warning(const char* format, ...); +void print_one_line(const char* format, ...); +void check_error(int err); + +#endif // PRINT_H diff --git a/tools/bit/util.cpp b/tools/bit/util.cpp new file mode 100644 index 000000000000..fc93bcb8c935 --- /dev/null +++ b/tools/bit/util.cpp @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "util.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <string.h> +#include <unistd.h> + + +FileInfo::FileInfo() +{ + memset(this, 0, sizeof(FileInfo)); +} + +FileInfo::FileInfo(const FileInfo& that) +{ + memcpy(this, &that, sizeof(FileInfo)); +} + +FileInfo::FileInfo(const string& filename) +{ + struct stat st; + int err = stat(filename.c_str(), &st); + if (err != 0) { + memset(this, 0, sizeof(FileInfo)); + } else { + exists = true; + mtime = st.st_mtime; + ctime = st.st_ctime; + size = st.st_size; + } +} + +bool +FileInfo::operator==(const FileInfo& that) const +{ + return exists == that.exists + && mtime == that.mtime + && ctime == that.ctime + && size == that.size; +} + +bool +FileInfo::operator!=(const FileInfo& that) const +{ + return exists != that.exists + || mtime != that.mtime + || ctime != that.ctime + || size != that.size; +} + +FileInfo::~FileInfo() +{ +} + +TrackedFile::TrackedFile() + :filename(), + fileInfo() +{ +} + +TrackedFile::TrackedFile(const TrackedFile& that) +{ + filename = that.filename; + fileInfo = that.fileInfo; +} + +TrackedFile::TrackedFile(const string& file) + :filename(file), + fileInfo(file) +{ +} + +TrackedFile::~TrackedFile() +{ +} + +bool +TrackedFile::HasChanged() const +{ + FileInfo updated(filename); + return !updated.exists || fileInfo != updated; +} + +void +get_directory_contents(const string& name, map<string,FileInfo>* results) +{ + int err; + DIR* dir = opendir(name.c_str()); + if (dir == NULL) { + return; + } + + dirent* entry; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + if (entry->d_type == DT_DIR) { + string subdir = name + "/" + entry->d_name; + get_directory_contents(subdir, results); + } else if (entry->d_type == DT_LNK || entry->d_type == DT_REG) { + string filename(name + "/" + entry->d_name); + (*results)[filename] = FileInfo(filename); + } + } + + closedir(dir); +} + +bool +directory_contents_differ(const map<string,FileInfo>& before, const map<string,FileInfo>& after) +{ + if (before.size() != after.size()) { + return true; + } + map<string,FileInfo>::const_iterator b = before.begin(); + map<string,FileInfo>::const_iterator a = after.begin(); + while (b != before.end() && a != after.end()) { + if (b->first != a->first) { + return true; + } + if (a->second != b->second) { + return true; + } + a++; + b++; + } + return false; +} + +string +escape_quotes(const char* str) +{ + string result; + while (*str) { + if (*str == '"') { + result += '\\'; + result += '"'; + } else { + result += *str; + } + } + return result; +} + +string +escape_for_commandline(const char* str) +{ + if (strchr(str, '"') != NULL || strchr(str, ' ') != NULL + || strchr(str, '\t') != NULL) { + return escape_quotes(str); + } else { + return str; + } +} + +static bool +spacechr(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +string +trim(const string& str) +{ + const ssize_t N = (ssize_t)str.size(); + ssize_t begin = 0; + while (begin < N && spacechr(str[begin])) { + begin++; + } + ssize_t end = N - 1; + while (end >= begin && spacechr(str[end])) { + end--; + } + return string(str, begin, end-begin+1); +} + +bool +starts_with(const string& str, const string& prefix) +{ + return str.compare(0, prefix.length(), prefix) == 0; +} + +bool +ends_with(const string& str, const string& suffix) +{ + if (str.length() < suffix.length()) { + return false; + } else { + return str.compare(str.length()-suffix.length(), suffix.length(), suffix) == 0; + } +} + +void +split_lines(vector<string>* result, const string& str) +{ + const int N = str.length(); + int begin = 0; + int end = 0; + for (; end < N; end++) { + const char c = str[end]; + if (c == '\r' || c == '\n') { + if (begin != end) { + result->push_back(string(str, begin, end-begin)); + } + begin = end+1; + } + } + if (begin != end) { + result->push_back(string(str, begin, end-begin)); + } +} + +string +read_file(const string& filename) +{ + FILE* file = fopen(filename.c_str(), "r"); + if (file == NULL) { + return string(); + } + + fseek(file, 0, SEEK_END); + int size = ftell(file); + fseek(file, 0, SEEK_SET); + + char* buf = (char*)malloc(size); + fread(buf, 1, size, file); + + string result(buf, size); + + free(buf); + fclose(file); + + return result; +} + + diff --git a/tools/bit/util.h b/tools/bit/util.h new file mode 100644 index 000000000000..718f1474a969 --- /dev/null +++ b/tools/bit/util.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef UTIL_H +#define UTIL_H + +#include <map> +#include <string> +#include <vector> + +using namespace std; + +struct FileInfo +{ + bool exists; + time_t mtime; + time_t ctime; + off_t size; + + FileInfo(); + FileInfo(const FileInfo& that); + explicit FileInfo(const string& filename); + ~FileInfo(); + + bool operator==(const FileInfo& that) const; + bool operator!=(const FileInfo& that) const; +}; + + +/** + * Record for a file that we are watching + */ +struct TrackedFile { + string filename; + FileInfo fileInfo; + + TrackedFile(); + TrackedFile(const TrackedFile& that); + explicit TrackedFile(const string& filename); + ~TrackedFile(); + + // Returns if the file has changed. If it doesn't currently exist, returns true. + bool HasChanged() const; +}; + +/** + * Get FileInfo structures recursively for all the files and symlinks in a directory. + * Does not traverse symlinks, but it does record them. + */ +void get_directory_contents(const string& dir, map<string,FileInfo>* results); + +bool directory_contents_differ(const map<string,FileInfo>& before, + const map<string,FileInfo>& after); + +string escape_quotes(const char* str); + +string escape_for_commandline(const char* str); + +string trim(const string& trim); + +bool starts_with(const string& str, const string& prefix); + +bool ends_with(const string& str, const string& suffix); + +void split_lines(vector<string>* result, const string& str); + +string read_file(const string& filename); + +#endif // UTIL_H + diff --git a/tools/fonts/fontchain_lint.py b/tools/fonts/fontchain_lint.py index 7ec46a3ee86b..219fa2de7e50 100755 --- a/tools/fonts/fontchain_lint.py +++ b/tools/fonts/fontchain_lint.py @@ -314,8 +314,11 @@ def check_emoji_defaults(default_emoji): continue # For later fonts, we only check them if they have a script # defined, since the defined script may get them to a higher - # score even if they appear after the emoji font. - if emoji_font_seen and not record.scripts: + # score even if they appear after the emoji font. However, + # we should skip checking the text symbols font, since + # symbol fonts should be able to override the emoji display + # style when 'Zsym' is explicitly specified by the user. + if emoji_font_seen and (not record.scripts or 'Zsym' in record.scripts): continue # Check default emoji-style characters @@ -588,6 +591,19 @@ def compute_expected_emoji(): return all_emoji, default_emoji, equivalent_emoji +def check_vertical_metrics(): + for record in _fallback_chain: + if record.name in ['sans-serif', 'sans-serif-condensed']: + font = open_font(record.font) + assert font['head'].yMax == 2163 and font['head'].yMin == -555, ( + 'yMax and yMin of %s do not match expected values.' % (record.font,)) + + if record.name in ['sans-serif', 'sans-serif-condensed', 'serif', 'monospace']: + font = open_font(record.font) + assert font['hhea'].ascent == 1900 and font['hhea'].descent == -500, ( + 'ascent and descent of %s do not match expected values.' % (record.font,)) + + def main(): global _fonts_dir target_out = sys.argv[1] @@ -596,6 +612,8 @@ def main(): fonts_xml_path = path.join(target_out, 'etc', 'fonts.xml') parse_fonts_xml(fonts_xml_path) + check_vertical_metrics() + hyphens_dir = path.join(target_out, 'usr', 'hyphen-data') check_hyphens(hyphens_dir) diff --git a/tools/layoutlib/.gitignore b/tools/layoutlib/.gitignore index eb52b64fd32c..819103db9d99 100644 --- a/tools/layoutlib/.gitignore +++ b/tools/layoutlib/.gitignore @@ -1,3 +1,4 @@ bin /.idea/workspace.xml /out +/bridge/out diff --git a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml index 3681f2aaf3f1..74fa549f66d4 100644 --- a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml +++ b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml @@ -11,7 +11,6 @@ <option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" /> <option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" /> </inspection_tool> - <inspection_tool class="ToArrayCallWithZeroLengthArrayArgument" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="WeakerAccess" enabled="true" level="WARNING" enabled_by_default="true"> <option name="SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS" value="false" /> <option name="SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES" value="false" /> diff --git a/tools/layoutlib/bridge/bridge.iml b/tools/layoutlib/bridge/bridge.iml index 57d08cb22c7e..fbaed520fff9 100644 --- a/tools/layoutlib/bridge/bridge.iml +++ b/tools/layoutlib/bridge/bridge.iml @@ -7,6 +7,7 @@ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/tests/res" type="java-test-resource" /> <sourceFolder url="file://$MODULE_DIR$/tests/src" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/src/main/myapplication.widgets" isTestSource="true" /> <excludeFolder url="file://$MODULE_DIR$/.settings" /> <excludeFolder url="file://$MODULE_DIR$/bin" /> <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/.gradle" /> diff --git a/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java index e0d3b8cd4de4..b4d5288bc925 100644 --- a/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java +++ b/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java @@ -18,6 +18,8 @@ package android.content.res; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import android.util.SparseArray; + /** * Delegate used to provide implementation of a select few native methods of {@link AssetManager} * <p/> @@ -38,4 +40,8 @@ public class AssetManager_Delegate { Resources_Theme_Delegate.getDelegateManager().removeJavaReferenceFor(theme); } + @LayoutlibDelegate + /*package*/ static SparseArray<String> getAssignedPackageIdentifiers(AssetManager manager) { + return new SparseArray<>(); + } } diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java index d0e431acadff..9da65a60a319 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java @@ -584,6 +584,7 @@ public final class BridgeTypedArray extends TypedArray { if (value == null) { return defValue; } + value = value.trim(); // if the value is just an integer, return it. try { @@ -595,6 +596,11 @@ public final class BridgeTypedArray extends TypedArray { // pass } + if (value.startsWith("#")) { + // this looks like a color, do not try to parse it + return defValue; + } + // Handle the @id/<name>, @+id/<name> and @android:id/<name> // We need to return the exact value that was compiled (from the various R classes), // as these values can be reused internally with calls to findViewById(). @@ -632,7 +638,15 @@ public final class BridgeTypedArray extends TypedArray { } } - // not a direct id valid reference? resolve it + // not a direct id valid reference. First check if it's an enum (this is a corner case + // for attributes that have a reference|enum type), then fallback to resolve + // as an ID without prefix. + Integer enumValue = resolveEnumAttribute(index); + if (enumValue != null) { + return enumValue; + } + + // Ok, not an enum, resolve as an ID Integer idValue; if (resValue.isFramework()) { diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java index ea320c701c24..c3d4cef61b35 100644 --- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java +++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java @@ -45,6 +45,7 @@ import android.content.res.Resources.Theme; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.util.LruCache; import android.util.TypedValue; import android.view.ViewGroup.LayoutParams; @@ -58,6 +59,9 @@ import java.util.Iterator; public class Resources_Delegate { private static boolean[] mPlatformResourceFlag = new boolean[1]; + // TODO: This cache is cleared every time a render session is disposed. Look into making this + // more long lived. + private static LruCache<String, Drawable.ConstantState> sDrawableCache = new LruCache<>(50); public static Resources initSystem(BridgeContext context, AssetManager assets, @@ -75,6 +79,7 @@ public class Resources_Delegate { * would prevent us from unloading the library. */ public static void disposeSystem() { + sDrawableCache.evictAll(); Resources.mSystem.mContext = null; Resources.mSystem.mLayoutlibCallback = null; Resources.mSystem = null; @@ -137,9 +142,23 @@ public class Resources_Delegate { @LayoutlibDelegate static Drawable getDrawable(Resources resources, int id, Theme theme) { Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); - if (value != null) { - return ResourceHelper.getDrawable(value.getSecond(), resources.mContext, theme); + String key = value.getSecond().getValue(); + + Drawable.ConstantState constantState = key != null ? sDrawableCache.get(key) : null; + Drawable drawable; + if (constantState != null) { + drawable = constantState.newDrawable(resources, theme); + } else { + drawable = + ResourceHelper.getDrawable(value.getSecond(), resources.mContext, theme); + + if (key != null) { + sDrawableCache.put(key, drawable.getConstantState()); + } + } + + return drawable; } // id was not found or not resolved. Throw a NotFoundException. diff --git a/tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java new file mode 100644 index 000000000000..df858067ba04 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java @@ -0,0 +1,727 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.layoutlib.bridge.impl.GcSnapshot; +import com.android.layoutlib.bridge.impl.PorterDuffUtility; +import com.android.ninepatch.NinePatchChunk; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.text.TextUtils; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; + +public class BaseCanvas_Delegate { + // ---- delegate manager ---- + protected static DelegateManager<BaseCanvas_Delegate> sManager = + new DelegateManager<>(BaseCanvas_Delegate.class); + + // ---- delegate helper data ---- + private final static boolean[] sBoolOut = new boolean[1]; + + + // ---- delegate data ---- + protected Bitmap_Delegate mBitmap; + protected GcSnapshot mSnapshot; + + // ---- Public Helper methods ---- + + protected BaseCanvas_Delegate(Bitmap_Delegate bitmap) { + mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap); + } + + protected BaseCanvas_Delegate() { + mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/); + } + + /** + * Disposes of the {@link Graphics2D} stack. + */ + protected void dispose() { + mSnapshot.dispose(); + } + + /** + * Returns the current {@link Graphics2D} used to draw. + */ + public GcSnapshot getSnapshot() { + return mSnapshot; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top, + long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity) { + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + BufferedImage image = bitmapDelegate.getImage(); + float right = left + image.getWidth(); + float bottom = top + image.getHeight(); + + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + 0, 0, image.getWidth(), image.getHeight(), + (int)left, (int)top, (int)right, (int)bottom); + } + + @LayoutlibDelegate + /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float srcLeft, float srcTop, + float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight, + float dstBottom, long nativePaintOrZero, int screenDensity, int bitmapDensity) { + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, (int) srcLeft, (int) srcTop, + (int) srcRight, (int) srcBottom, (int) dstLeft, (int) dstTop, (int) dstRight, + (int) dstBottom); + } + + @LayoutlibDelegate + /*package*/ static void nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride, + final float x, final float y, int width, int height, boolean hasAlpha, + long nativePaintOrZero) { + // create a temp BufferedImage containing the content. + final BufferedImage image = new BufferedImage(width, height, + hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + image.setRGB(0, 0, width, height, colors, offset, stride); + + draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paint) -> { + if (paint != null && paint.isFilterBitmap()) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + graphics.drawImage(image, (int) x, (int) y, null); + }); + } + + @LayoutlibDelegate + /*package*/ static void nDrawColor(long nativeCanvas, final int color, final int mode) { + // get the delegate from the native int. + BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + final int w = canvasDelegate.mBitmap.getImage().getWidth(); + final int h = canvasDelegate.mBitmap.getImage().getHeight(); + draw(nativeCanvas, (graphics, paint) -> { + // reset its transform just in case + graphics.setTransform(new AffineTransform()); + + // set the color + graphics.setColor(new java.awt.Color(color, true /*alpha*/)); + + Composite composite = PorterDuffUtility.getComposite( + PorterDuffUtility.getPorterDuffMode(mode), 0xFF); + if (composite != null) { + graphics.setComposite(composite); + } + + graphics.fillRect(0, 0, w, h); + }); + } + + @LayoutlibDelegate + /*package*/ static void nDrawPaint(long nativeCanvas, long paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPaint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nDrawPoint(long nativeCanvas, float x, float y, + long nativePaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPoint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nDrawPoints(long nativeCanvas, float[] pts, int offset, int count, + long nativePaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPoint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nDrawLine(long nativeCanvas, + final float startX, final float startY, final float stopX, final float stopY, + long paint) { + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paintDelegate) -> graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY)); + } + + @LayoutlibDelegate + /*package*/ static void nDrawLines(long nativeCanvas, + final float[] pts, final int offset, final int count, + long nativePaint) { + draw(nativeCanvas, nativePaint, false /*compositeOnly*/, + false /*forceSrcMode*/, (graphics, paintDelegate) -> { + for (int i = 0; i < count; i += 4) { + graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1], + (int) pts[i + offset + 2], (int) pts[i + offset + 3]); + } + }); + } + + @LayoutlibDelegate + /*package*/ static void nDrawRect(long nativeCanvas, + final float left, final float top, final float right, final float bottom, long paint) { + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paintDelegate) -> { + int style = paintDelegate.getStyle(); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fillRect((int)left, (int)top, + (int)(right-left), (int)(bottom-top)); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.drawRect((int)left, (int)top, + (int)(right-left), (int)(bottom-top)); + } + }); + } + + @LayoutlibDelegate + /*package*/ static void nDrawOval(long nativeCanvas, final float left, + final float top, final float right, final float bottom, long paint) { + if (right > left && bottom > top) { + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paintDelegate) -> { + int style = paintDelegate.getStyle(); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fillOval((int)left, (int)top, + (int)(right - left), (int)(bottom - top)); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.drawOval((int)left, (int)top, + (int)(right - left), (int)(bottom - top)); + } + }); + } + } + + @LayoutlibDelegate + /*package*/ static void nDrawCircle(long nativeCanvas, + float cx, float cy, float radius, long paint) { + nDrawOval(nativeCanvas, + cx - radius, cy - radius, cx + radius, cy + radius, + paint); + } + + @LayoutlibDelegate + /*package*/ static void nDrawArc(long nativeCanvas, + final float left, final float top, final float right, final float bottom, + final float startAngle, final float sweep, + final boolean useCenter, long paint) { + if (right > left && bottom > top) { + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paintDelegate) -> { + int style = paintDelegate.getStyle(); + + Arc2D.Float arc = new Arc2D.Float( + left, top, right - left, bottom - top, + -startAngle, -sweep, + useCenter ? Arc2D.PIE : Arc2D.OPEN); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fill(arc); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.draw(arc); + } + }); + } + } + + @LayoutlibDelegate + /*package*/ static void nDrawRoundRect(long nativeCanvas, + final float left, final float top, final float right, final float bottom, + final float rx, final float ry, long paint) { + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paintDelegate) -> { + int style = paintDelegate.getStyle(); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fillRoundRect( + (int)left, (int)top, + (int)(right - left), (int)(bottom - top), + 2 * (int)rx, 2 * (int)ry); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.drawRoundRect( + (int)left, (int)top, + (int)(right - left), (int)(bottom - top), + 2 * (int)rx, 2 * (int)ry); + } + }); + } + + @LayoutlibDelegate + public static void nDrawPath(long nativeCanvas, long path, long paint) { + final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path); + if (pathDelegate == null) { + return; + } + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paintDelegate) -> { + Shape shape = pathDelegate.getJavaShape(); + Rectangle2D bounds = shape.getBounds2D(); + if (bounds.isEmpty()) { + // Apple JRE 1.6 doesn't like drawing empty shapes. + // http://b.android.com/178278 + + if (pathDelegate.isEmpty()) { + // This means that the path doesn't have any lines or curves so + // nothing to draw. + return; + } + + // The stroke width is not consider for the size of the bounds so, + // for example, a horizontal line, would be considered as an empty + // rectangle. + // If the strokeWidth is not 0, we use it to consider the size of the + // path as well. + float strokeWidth = paintDelegate.getStrokeWidth(); + if (strokeWidth <= 0.0f) { + return; + } + bounds.setRect(bounds.getX(), bounds.getY(), + Math.max(strokeWidth, bounds.getWidth()), + Math.max(strokeWidth, bounds.getHeight())); + } + + int style = paintDelegate.getStyle(); + + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fill(shape); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.draw(shape); + } + }); + } + + @LayoutlibDelegate + /*package*/ static void nDrawRegion(long nativeCanvas, long nativeRegion, + long nativePaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Some canvas paths may not be drawn", null, null); + } + + @LayoutlibDelegate + /*package*/ static void nDrawNinePatch(long nativeCanvas, long nativeBitmap, long ninePatch, + final float dstLeft, final float dstTop, final float dstRight, final float dstBottom, + long nativePaintOrZero, final int screenDensity, final int bitmapDensity) { + + // get the delegate from the native int. + final Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap); + if (bitmapDelegate == null) { + return; + } + + byte[] c = NinePatch_Delegate.getChunk(ninePatch); + if (c == null) { + // not a 9-patch? + BufferedImage image = bitmapDelegate.getImage(); + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(), + image.getHeight(), (int) dstLeft, (int) dstTop, (int) dstRight, + (int) dstBottom); + return; + } + + final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c); + assert chunkObject != null; + if (chunkObject == null) { + return; + } + + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + // this one can be null + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero); + + canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() { + @Override + public void draw(Graphics2D graphics, Paint_Delegate paint) { + chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop, + (int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity, + bitmapDensity); + } + }, paintDelegate, true, false); + + } + + @LayoutlibDelegate + /*package*/ static void nDrawBitmapMatrix(long nCanvas, Bitmap bitmap, + long nMatrix, long nPaint) { + // get the delegate from the native int. + BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + // get the delegate from the native int, which can be null + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); + + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut); + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix); + if (matrixDelegate == null) { + return; + } + + final AffineTransform mtx = matrixDelegate.getAffineTransform(); + + canvasDelegate.getSnapshot().draw((graphics, paint) -> { + if (paint != null && paint.isFilterBitmap()) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + //FIXME add support for canvas, screen and bitmap densities. + graphics.drawImage(image, mtx, null); + }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/); + } + + @LayoutlibDelegate + /*package*/ static void nDrawBitmapMesh(long nCanvas, Bitmap bitmap, + int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, + int colorOffset, long nPaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawBitmapMesh is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nDrawVertices(long nCanvas, int mode, int n, + float[] verts, int vertOffset, + float[] texs, int texOffset, + int[] colors, int colorOffset, + short[] indices, int indexOffset, + int indexCount, long nPaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawVertices is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nDrawText(long nativeCanvas, char[] text, int index, int count, + float startX, float startY, int flags, long paint, long typeface) { + drawText(nativeCanvas, text, index, count, startX, startY, (flags & 1) != 0, + paint, typeface); + } + + @LayoutlibDelegate + /*package*/ static void nDrawText(long nativeCanvas, String text, + int start, int end, float x, float y, final int flags, long paint, + long typeface) { + int count = end - start; + char[] buffer = TemporaryBuffer.obtain(count); + TextUtils.getChars(text, start, end, buffer, 0); + + nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface); + } + + @LayoutlibDelegate + /*package*/ static void nDrawTextRun(long nativeCanvas, String text, + int start, int end, int contextStart, int contextEnd, + float x, float y, boolean isRtl, long paint, long typeface) { + int count = end - start; + char[] buffer = TemporaryBuffer.obtain(count); + TextUtils.getChars(text, start, end, buffer, 0); + + drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface); + } + + @LayoutlibDelegate + /*package*/ static void nDrawTextRun(long nativeCanvas, char[] text, + int start, int count, int contextStart, int contextCount, + float x, float y, boolean isRtl, long paint, long typeface) { + drawText(nativeCanvas, text, start, count, x, y, isRtl, paint, typeface); + } + + @LayoutlibDelegate + /*package*/ static void nDrawTextOnPath(long nativeCanvas, + char[] text, int index, + int count, long path, + float hOffset, + float vOffset, int bidiFlags, + long paint, long typeface) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawTextOnPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nDrawTextOnPath(long nativeCanvas, + String text, long path, + float hOffset, + float vOffset, + int bidiFlags, long paint, + long typeface) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawTextOnPath is not supported.", null, null /*data*/); + } + + // ---- Private delegate/helper methods ---- + + /** + * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint. + * <p>Note that the drawable may actually be executed several times if there are + * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. + */ + private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode, + GcSnapshot.Drawable drawable) { + // get the delegate from the native int. + BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + // get the paint which can be null if nPaint is 0; + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); + + canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode); + } + + /** + * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided + * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}. + * <p>Note that the drawable may actually be executed several times if there are + * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. + */ + private static void draw(long nCanvas, GcSnapshot.Drawable drawable) { + // get the delegate from the native int. + BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.mSnapshot.draw(drawable); + } + + private static void drawText(long nativeCanvas, final char[] text, final int index, + final int count, final float startX, final float startY, final boolean isRtl, + long paint, final long typeface) { + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paintDelegate) -> { + // WARNING: the logic in this method is similar to Paint_Delegate.measureText. + // Any change to this method should be reflected in Paint.measureText + + // assert that the typeface passed is actually the one stored in paint. + assert (typeface == paintDelegate.mNativeTypeface); + + // Paint.TextAlign indicates how the text is positioned relative to X. + // LEFT is the default and there's nothing to do. + float x = startX; + int limit = index + count; + if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) { + RectF bounds = + paintDelegate.measureText(text, index, count, null, 0, isRtl); + float m = bounds.right - bounds.left; + if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) { + x -= m / 2; + } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) { + x -= m; + } + } + + new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x, + startY).renderText(index, limit, isRtl, null, 0, true); + }); + } + + private static void drawBitmap(long nativeCanvas, Bitmap_Delegate bitmap, + long nativePaintOrZero, final int sleft, final int stop, final int sright, + final int sbottom, final int dleft, final int dtop, final int dright, + final int dbottom) { + // get the delegate from the native int. + BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + // get the paint, which could be null if the int is 0 + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero); + + final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut); + + draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0], + (graphics, paint) -> { + if (paint != null && paint.isFilterBitmap()) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + //FIXME add support for canvas, screen and bitmap densities. + graphics.drawImage(image, dleft, dtop, dright, dbottom, sleft, stop, sright, + sbottom, null); + }); + } + + /** + * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate. + * The image returns, through a 1-size boolean array, whether the drawing code should + * use a SRC composite no matter what the paint says. + * + * @param bitmap the bitmap + * @param paint the paint that will be used to draw + * @param forceSrcMode whether the composite will have to be SRC + * @return the image to draw + */ + private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint, + boolean[] forceSrcMode) { + BufferedImage image = bitmap.getImage(); + forceSrcMode[0] = false; + + // if the bitmap config is alpha_8, then we erase all color value from it + // before drawing it. + if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) { + fixAlpha8Bitmap(image); + } else if (!bitmap.hasAlpha()) { + // hasAlpha is merely a rendering hint. There can in fact be alpha values + // in the bitmap but it should be ignored at drawing time. + // There is two ways to do this: + // - override the composite to be SRC. This can only be used if the composite + // was going to be SRC or SRC_OVER in the first place + // - Create a different bitmap to draw in which all the alpha channel values is set + // to 0xFF. + if (paint != null) { + PorterDuff.Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode()); + + forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER || mode == PorterDuff.Mode.SRC; + } + + // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB + if (!forceSrcMode[0]) { + image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF); + } + } + + return image; + } + + private static void fixAlpha8Bitmap(final BufferedImage image) { + int w = image.getWidth(); + int h = image.getHeight(); + int[] argb = new int[w * h]; + image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); + + final int length = argb.length; + for (int i = 0 ; i < length; i++) { + argb[i] &= 0xFF000000; + } + image.setRGB(0, 0, w, h, argb, 0, w); + } + + protected int save(int saveFlags) { + // get the current save count + int count = mSnapshot.size(); + + mSnapshot = mSnapshot.save(saveFlags); + + // return the old save count + return count; + } + + protected int saveLayerAlpha(RectF rect, int alpha, int saveFlags) { + Paint_Delegate paint = new Paint_Delegate(); + paint.setAlpha(alpha); + return saveLayer(rect, paint, saveFlags); + } + + protected int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) { + // get the current save count + int count = mSnapshot.size(); + + mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags); + + // return the old save count + return count; + } + + /** + * Restores the {@link GcSnapshot} to <var>saveCount</var> + * @param saveCount the saveCount + */ + protected void restoreTo(int saveCount) { + mSnapshot = mSnapshot.restoreTo(saveCount); + } + + /** + * Restores the top {@link GcSnapshot} + */ + protected void restore() { + mSnapshot = mSnapshot.restore(); + } + + protected boolean clipRect(float left, float top, float right, float bottom, int regionOp) { + return mSnapshot.clipRect(left, top, right, bottom, regionOp); + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java index f1da3a266448..2ae46540674f 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java @@ -92,8 +92,7 @@ public final class Bitmap_Delegate { @Nullable public static Bitmap_Delegate getDelegate(@Nullable Bitmap bitmap) { - // refSkPixelRef is a hack to get the native pointer: see #nativeRefPixelRef() - return bitmap == null ? null : getDelegate(bitmap.refSkPixelRef()); + return bitmap == null ? null : getDelegate(bitmap.getNativeInstance()); } /** @@ -327,7 +326,7 @@ public final class Bitmap_Delegate { @LayoutlibDelegate /*package*/ static void nativeReconfigure(long nativeBitmap, int width, int height, - int config, int allocSize, boolean isPremultiplied) { + int config, boolean isPremultiplied) { Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Bitmap.reconfigure() is not supported", null /*data*/); } @@ -601,12 +600,20 @@ public final class Bitmap_Delegate { return Arrays.equals(argb1, argb2); } - // Only used by AssetAtlasService, which we don't care about. @LayoutlibDelegate - /*package*/ static long nativeRefPixelRef(long nativeBitmap) { - // Hack: This is called by Bitmap.refSkPixelRef() and LayoutLib uses that method to get - // the native pointer from a Bitmap. So, we return nativeBitmap here. - return nativeBitmap; + /*package*/ static int nativeGetAllocationByteCount(long nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + return nativeRowBytes(nativeBitmap) * delegate.mImage.getHeight(); + + } + + @LayoutlibDelegate + /*package*/ static void nativePrepareToDraw(long nativeBitmap) { + // do nothing as Bitmap_Delegate does not have caches } // ---- Private delegate/helper methods ---- @@ -627,7 +634,7 @@ public final class Bitmap_Delegate { boolean isPremultiplied = createFlags.contains(BitmapCreateFlags.PREMULTIPLIED); // and create/return a new Bitmap with it - return new Bitmap(nativeInt, null /* buffer */, width, height, density, isMutable, + return new Bitmap(nativeInt, width, height, density, isMutable, isPremultiplied, null /*ninePatchChunk*/, null /* layoutBounds */); } diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index fa880f0710c4..43a0ff5a23dc 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -55,40 +55,27 @@ import libcore.util.NativeAllocationRegistry_Delegate; * @see DelegateManager * */ -public final class Canvas_Delegate { +public final class Canvas_Delegate extends BaseCanvas_Delegate { // ---- delegate manager ---- - private static final DelegateManager<Canvas_Delegate> sManager = - new DelegateManager<Canvas_Delegate>(Canvas_Delegate.class); private static long sFinalizer = -1; - - // ---- delegate helper data ---- - - private final static boolean[] sBoolOut = new boolean[1]; - - - // ---- delegate data ---- - private Bitmap_Delegate mBitmap; - private GcSnapshot mSnapshot; - private DrawFilter_Delegate mDrawFilter = null; - // ---- Public Helper methods ---- /** * Returns the native delegate associated to a given {@link Canvas} object. */ public static Canvas_Delegate getDelegate(Canvas canvas) { - return sManager.getDelegate(canvas.getNativeCanvasWrapper()); + return (Canvas_Delegate) sManager.getDelegate(canvas.getNativeCanvasWrapper()); } /** * Returns the native delegate associated to a given an int referencing a {@link Canvas} object. */ public static Canvas_Delegate getDelegate(long native_canvas) { - return sManager.getDelegate(native_canvas); + return (Canvas_Delegate) sManager.getDelegate(native_canvas); } /** @@ -110,20 +97,20 @@ public final class Canvas_Delegate { // ---- native methods ---- @LayoutlibDelegate - /*package*/ static void freeCaches() { + /*package*/ static void nFreeCaches() { // nothing to be done here. } @LayoutlibDelegate - /*package*/ static void freeTextLayoutCaches() { + /*package*/ static void nFreeTextLayoutCaches() { // nothing to be done here yet. } @LayoutlibDelegate - /*package*/ static long initRaster(@Nullable Bitmap bitmap) { + /*package*/ static long nInitRaster(@Nullable Bitmap bitmap) { long nativeBitmapOrZero = 0; if (bitmap != null) { - nativeBitmapOrZero = bitmap.refSkPixelRef(); + nativeBitmapOrZero = bitmap.getNativeInstance(); } if (nativeBitmapOrZero > 0) { // get the Bitmap from the int @@ -142,8 +129,8 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static void native_setBitmap(long canvas, Bitmap bitmap) { - Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas); + public static void nSetBitmap(long canvas, Bitmap bitmap) { + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas); Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); if (canvasDelegate == null || bitmapDelegate==null) { return; @@ -153,9 +140,9 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static boolean native_isOpaque(long nativeCanvas) { + public static boolean nIsOpaque(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return false; } @@ -164,12 +151,12 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static void native_setHighContrastText(long nativeCanvas, boolean highContrastText){} + public static void nSetHighContrastText(long nativeCanvas, boolean highContrastText){} @LayoutlibDelegate - public static int native_getWidth(long nativeCanvas) { + public static int nGetWidth(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } @@ -178,9 +165,9 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static int native_getHeight(long nativeCanvas) { + public static int nGetHeight(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } @@ -189,9 +176,9 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static int native_save(long nativeCanvas, int saveFlags) { + public static int nSave(long nativeCanvas, int saveFlags) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } @@ -200,11 +187,11 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static int native_saveLayer(long nativeCanvas, float l, + public static int nSaveLayer(long nativeCanvas, float l, float t, float r, float b, long paint, int layerFlags) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } @@ -219,11 +206,11 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static int native_saveLayerAlpha(long nativeCanvas, float l, + public static int nSaveLayerAlpha(long nativeCanvas, float l, float t, float r, float b, int alpha, int layerFlags) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } @@ -232,10 +219,10 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static void native_restore(long nativeCanvas, boolean throwOnUnderflow) { + public static void nRestore(long nativeCanvas, boolean throwOnUnderflow) { // FIXME: implement throwOnUnderflow. // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } @@ -244,11 +231,11 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static void native_restoreToCount(long nativeCanvas, int saveCount, + public static void nRestoreToCount(long nativeCanvas, int saveCount, boolean throwOnUnderflow) { // FIXME: implement throwOnUnderflow. // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } @@ -257,9 +244,9 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static int native_getSaveCount(long nativeCanvas) { + public static int nGetSaveCount(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } @@ -268,9 +255,9 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static void native_translate(long nativeCanvas, float dx, float dy) { + public static void nTranslate(long nativeCanvas, float dx, float dy) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } @@ -279,9 +266,9 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static void native_scale(long nativeCanvas, float sx, float sy) { + public static void nScale(long nativeCanvas, float sx, float sy) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } @@ -290,9 +277,9 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static void native_rotate(long nativeCanvas, float degrees) { + public static void nRotate(long nativeCanvas, float degrees) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } @@ -301,9 +288,9 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static void native_skew(long nativeCanvas, float kx, float ky) { + public static void nSkew(long nativeCanvas, float kx, float ky) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } @@ -325,9 +312,9 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static void native_concat(long nCanvas, long nMatrix) { + public static void nConcat(long nCanvas, long nMatrix) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas); if (canvasDelegate == null) { return; } @@ -353,9 +340,9 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static void native_setMatrix(long nCanvas, long nMatrix) { + public static void nSetMatrix(long nCanvas, long nMatrix) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas); if (canvasDelegate == null) { return; } @@ -383,12 +370,12 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static boolean native_clipRect(long nCanvas, + public static boolean nClipRect(long nCanvas, float left, float top, float right, float bottom, int regionOp) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas); if (canvasDelegate == null) { return false; } @@ -397,10 +384,10 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static boolean native_clipPath(long nativeCanvas, + public static boolean nClipPath(long nativeCanvas, long nativePath, int regionOp) { - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return true; } @@ -414,10 +401,10 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static boolean native_clipRegion(long nativeCanvas, + public static boolean nClipRegion(long nativeCanvas, long nativeRegion, int regionOp) { - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return true; } @@ -431,8 +418,8 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static void nativeSetDrawFilter(long nativeCanvas, long nativeFilter) { - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + public static void nSetDrawFilter(long nativeCanvas, long nativeFilter) { + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } @@ -446,10 +433,10 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static boolean native_getClipBounds(long nativeCanvas, + public static boolean nGetClipBounds(long nativeCanvas, Rect bounds) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return false; } @@ -467,9 +454,9 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static void native_getCTM(long canvas, long matrix) { + public static void nGetCTM(long canvas, long matrix) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas); if (canvasDelegate == null) { return; } @@ -484,13 +471,13 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static boolean native_quickReject(long nativeCanvas, long path) { + public static boolean nQuickReject(long nativeCanvas, long path) { // FIXME properly implement quickReject return false; } @LayoutlibDelegate - public static boolean native_quickReject(long nativeCanvas, + public static boolean nQuickReject(long nativeCanvas, float left, float top, float right, float bottom) { // FIXME properly implement quickReject @@ -498,509 +485,11 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static void native_drawColor(long nativeCanvas, final int color, final int mode) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); - if (canvasDelegate == null) { - return; - } - - final int w = canvasDelegate.mBitmap.getImage().getWidth(); - final int h = canvasDelegate.mBitmap.getImage().getHeight(); - draw(nativeCanvas, new GcSnapshot.Drawable() { - - @Override - public void draw(Graphics2D graphics, Paint_Delegate paint) { - // reset its transform just in case - graphics.setTransform(new AffineTransform()); - - // set the color - graphics.setColor(new Color(color, true /*alpha*/)); - - Composite composite = PorterDuffUtility.getComposite( - PorterDuffUtility.getPorterDuffMode(mode), 0xFF); - if (composite != null) { - graphics.setComposite(composite); - } - - graphics.fillRect(0, 0, w, h); - } - }); - } - - @LayoutlibDelegate - public static void native_drawPaint(long nativeCanvas, long paint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawPaint is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - public static void native_drawPoint(long nativeCanvas, float x, float y, - long nativePaint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawPoint is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - public static void native_drawPoints(long nativeCanvas, float[] pts, int offset, int count, - long nativePaint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawPoint is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - public static void native_drawLine(long nativeCanvas, - final float startX, final float startY, final float stopX, final float stopY, - long paint) { - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY); - } - }); - } - - @LayoutlibDelegate - public static void native_drawLines(long nativeCanvas, - final float[] pts, final int offset, final int count, - long nativePaint) { - draw(nativeCanvas, nativePaint, false /*compositeOnly*/, - false /*forceSrcMode*/, new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - for (int i = 0; i < count; i += 4) { - graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1], - (int) pts[i + offset + 2], (int) pts[i + offset + 3]); - } - } - }); - } - - @LayoutlibDelegate - public static void native_drawRect(long nativeCanvas, - final float left, final float top, final float right, final float bottom, long paint) { - - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - int style = paintDelegate.getStyle(); - - // draw - if (style == Paint.Style.FILL.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.fillRect((int)left, (int)top, - (int)(right-left), (int)(bottom-top)); - } - - if (style == Paint.Style.STROKE.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.drawRect((int)left, (int)top, - (int)(right-left), (int)(bottom-top)); - } - } - }); - } - - @LayoutlibDelegate - public static void native_drawOval(long nativeCanvas, final float left, - final float top, final float right, final float bottom, long paint) { - if (right > left && bottom > top) { - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - int style = paintDelegate.getStyle(); - - // draw - if (style == Paint.Style.FILL.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.fillOval((int)left, (int)top, - (int)(right - left), (int)(bottom - top)); - } - - if (style == Paint.Style.STROKE.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.drawOval((int)left, (int)top, - (int)(right - left), (int)(bottom - top)); - } - } - }); - } - } - - @LayoutlibDelegate - public static void native_drawCircle(long nativeCanvas, - float cx, float cy, float radius, long paint) { - native_drawOval(nativeCanvas, - cx - radius, cy - radius, cx + radius, cy + radius, - paint); - } - - @LayoutlibDelegate - public static void native_drawArc(long nativeCanvas, - final float left, final float top, final float right, final float bottom, - final float startAngle, final float sweep, - final boolean useCenter, long paint) { - if (right > left && bottom > top) { - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - int style = paintDelegate.getStyle(); - - Arc2D.Float arc = new Arc2D.Float( - left, top, right - left, bottom - top, - -startAngle, -sweep, - useCenter ? Arc2D.PIE : Arc2D.OPEN); - - // draw - if (style == Paint.Style.FILL.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.fill(arc); - } - - if (style == Paint.Style.STROKE.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.draw(arc); - } - } - }); - } - } - - @LayoutlibDelegate - public static void native_drawRoundRect(long nativeCanvas, - final float left, final float top, final float right, final float bottom, - final float rx, final float ry, long paint) { - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - int style = paintDelegate.getStyle(); - - // draw - if (style == Paint.Style.FILL.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.fillRoundRect( - (int)left, (int)top, - (int)(right - left), (int)(bottom - top), - 2 * (int)rx, 2 * (int)ry); - } - - if (style == Paint.Style.STROKE.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.drawRoundRect( - (int)left, (int)top, - (int)(right - left), (int)(bottom - top), - 2 * (int)rx, 2 * (int)ry); - } - } - }); - } - - @LayoutlibDelegate - public static void native_drawPath(long nativeCanvas, long path, long paint) { - final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path); - if (pathDelegate == null) { - return; - } - - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - Shape shape = pathDelegate.getJavaShape(); - Rectangle2D bounds = shape.getBounds2D(); - if (bounds.isEmpty()) { - // Apple JRE 1.6 doesn't like drawing empty shapes. - // http://b.android.com/178278 - - if (pathDelegate.isEmpty()) { - // This means that the path doesn't have any lines or curves so - // nothing to draw. - return; - } - - // The stroke width is not consider for the size of the bounds so, - // for example, a horizontal line, would be considered as an empty - // rectangle. - // If the strokeWidth is not 0, we use it to consider the size of the - // path as well. - float strokeWidth = paintDelegate.getStrokeWidth(); - if (strokeWidth <= 0.0f) { - return; - } - bounds.setRect(bounds.getX(), bounds.getY(), - Math.max(strokeWidth, bounds.getWidth()), - Math.max(strokeWidth, bounds.getHeight())); - } - - int style = paintDelegate.getStyle(); - - if (style == Paint.Style.FILL.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.fill(shape); - } - - if (style == Paint.Style.STROKE.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.draw(shape); - } - } - }); - } - - @LayoutlibDelegate - public static void native_drawRegion(long nativeCanvas, long nativeRegion, - long nativePaint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Some canvas paths may not be drawn", null, null); - } - - @LayoutlibDelegate - public static void native_drawNinePatch(Canvas thisCanvas, long nativeCanvas, - long nativeBitmap, long ninePatch, final float dstLeft, final float dstTop, - final float dstRight, final float dstBottom, long nativePaintOrZero, - final int screenDensity, final int bitmapDensity) { - - // get the delegate from the native int. - final Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap); - if (bitmapDelegate == null) { - return; - } - - byte[] c = NinePatch_Delegate.getChunk(ninePatch); - if (c == null) { - // not a 9-patch? - BufferedImage image = bitmapDelegate.getImage(); - drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(), - image.getHeight(), (int) dstLeft, (int) dstTop, (int) dstRight, - (int) dstBottom); - return; - } - - final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c); - assert chunkObject != null; - if (chunkObject == null) { - return; - } - - Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); - if (canvasDelegate == null) { - return; - } - - // this one can be null - Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero); - - canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paint) { - chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop, - (int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity, - bitmapDensity); - } - }, paintDelegate, true, false); - - } - - @LayoutlibDelegate - public static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap, - float left, float top, - long nativePaintOrZero, - int canvasDensity, - int screenDensity, - int bitmapDensity) { - // get the delegate from the native int. - Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); - if (bitmapDelegate == null) { - return; - } - - BufferedImage image = bitmapDelegate.getImage(); - float right = left + image.getWidth(); - float bottom = top + image.getHeight(); - - drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, - 0, 0, image.getWidth(), image.getHeight(), - (int)left, (int)top, (int)right, (int)bottom); - } - - @LayoutlibDelegate - public static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap, - float srcLeft, float srcTop, float srcRight, float srcBottom, - float dstLeft, float dstTop, float dstRight, float dstBottom, - long nativePaintOrZero, int screenDensity, int bitmapDensity) { - // get the delegate from the native int. - Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); - if (bitmapDelegate == null) { - return; - } - - drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, - (int)srcLeft, (int)srcTop, (int)srcRight, (int)srcBottom, - (int)dstLeft, (int)dstTop, (int)dstRight, (int)dstBottom); - } - - @LayoutlibDelegate - public static void native_drawBitmap(long nativeCanvas, int[] colors, - int offset, int stride, final float x, - final float y, int width, int height, - boolean hasAlpha, - long nativePaintOrZero) { - // create a temp BufferedImage containing the content. - final BufferedImage image = new BufferedImage(width, height, - hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); - image.setRGB(0, 0, width, height, colors, offset, stride); - - draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paint) { - if (paint != null && paint.isFilterBitmap()) { - graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_BILINEAR); - } - - graphics.drawImage(image, (int) x, (int) y, null); - } - }); - } - - @LayoutlibDelegate - public static void nativeDrawBitmapMatrix(long nCanvas, Bitmap bitmap, - long nMatrix, long nPaint) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); - if (canvasDelegate == null) { - return; - } - - // get the delegate from the native int, which can be null - Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); - - // get the delegate from the native int. - Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); - if (bitmapDelegate == null) { - return; - } - - final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut); - - Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix); - if (matrixDelegate == null) { - return; - } - - final AffineTransform mtx = matrixDelegate.getAffineTransform(); - - canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paint) { - if (paint != null && paint.isFilterBitmap()) { - graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_BILINEAR); - } - - //FIXME add support for canvas, screen and bitmap densities. - graphics.drawImage(image, mtx, null); - } - }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/); - } - - @LayoutlibDelegate - public static void nativeDrawBitmapMesh(long nCanvas, Bitmap bitmap, - int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, - int colorOffset, long nPaint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawBitmapMesh is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - public static void nativeDrawVertices(long nCanvas, int mode, int n, - float[] verts, int vertOffset, - float[] texs, int texOffset, - int[] colors, int colorOffset, - short[] indices, int indexOffset, - int indexCount, long nPaint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawVertices is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - public static void native_drawText(long nativeCanvas, char[] text, int index, int count, - float startX, float startY, int flags, long paint, long typeface) { - drawText(nativeCanvas, text, index, count, startX, startY, (flags & 1) != 0, - paint, typeface); - } - - @LayoutlibDelegate - public static void native_drawText(long nativeCanvas, String text, - int start, int end, float x, float y, final int flags, long paint, - long typeface) { - int count = end - start; - char[] buffer = TemporaryBuffer.obtain(count); - TextUtils.getChars(text, start, end, buffer, 0); - - native_drawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface); - } - - @LayoutlibDelegate - public static void native_drawTextRun(long nativeCanvas, String text, - int start, int end, int contextStart, int contextEnd, - float x, float y, boolean isRtl, long paint, long typeface) { - int count = end - start; - char[] buffer = TemporaryBuffer.obtain(count); - TextUtils.getChars(text, start, end, buffer, 0); - - drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface); - } - - @LayoutlibDelegate - public static void native_drawTextRun(long nativeCanvas, char[] text, - int start, int count, int contextStart, int contextCount, - float x, float y, boolean isRtl, long paint, long typeface) { - drawText(nativeCanvas, text, start, count, x, y, isRtl, paint, typeface); - } - - @LayoutlibDelegate - public static void native_drawTextOnPath(long nativeCanvas, - char[] text, int index, - int count, long path, - float hOffset, - float vOffset, int bidiFlags, - long paint, long typeface) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawTextOnPath is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - public static void native_drawTextOnPath(long nativeCanvas, - String text, long path, - float hOffset, - float vOffset, - int bidiFlags, long paint, - long typeface) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawTextOnPath is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - /*package*/ static long getNativeFinalizer() { + /*package*/ static long nGetNativeFinalizer() { synchronized (Canvas_Delegate.class) { if (sFinalizer == -1) { sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(nativePtr -> { - Canvas_Delegate delegate = sManager.getDelegate(nativePtr); + Canvas_Delegate delegate = Canvas_Delegate.getDelegate(nativePtr); if (delegate != null) { delegate.dispose(); } @@ -1011,230 +500,12 @@ public final class Canvas_Delegate { return sFinalizer; } - // ---- Private delegate/helper methods ---- - - /** - * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint. - * <p>Note that the drawable may actually be executed several times if there are - * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. - */ - private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode, - GcSnapshot.Drawable drawable) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); - if (canvasDelegate == null) { - return; - } - - // get the paint which can be null if nPaint is 0; - Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); - - canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode); - } - - /** - * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided - * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}. - * <p>Note that the drawable may actually be executed several times if there are - * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. - */ - private static void draw(long nCanvas, GcSnapshot.Drawable drawable) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); - if (canvasDelegate == null) { - return; - } - - canvasDelegate.mSnapshot.draw(drawable); - } - - private static void drawText(long nativeCanvas, final char[] text, final int index, - final int count, final float startX, final float startY, final boolean isRtl, - long paint, final long typeface) { - - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - // WARNING: the logic in this method is similar to Paint_Delegate.measureText. - // Any change to this method should be reflected in Paint.measureText - - // assert that the typeface passed is actually the one stored in paint. - assert (typeface == paintDelegate.mNativeTypeface); - - // Paint.TextAlign indicates how the text is positioned relative to X. - // LEFT is the default and there's nothing to do. - float x = startX; - int limit = index + count; - if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) { - RectF bounds = paintDelegate.measureText(text, index, count, null, 0, - isRtl); - float m = bounds.right - bounds.left; - if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) { - x -= m / 2; - } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) { - x -= m; - } - } - - new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x, startY) - .renderText(index, limit, isRtl, null, 0, true); - } - }); - } - private Canvas_Delegate(Bitmap_Delegate bitmap) { - mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap); + super(bitmap); } private Canvas_Delegate() { - mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/); - } - - /** - * Disposes of the {@link Graphics2D} stack. - */ - private void dispose() { - mSnapshot.dispose(); - } - - private int save(int saveFlags) { - // get the current save count - int count = mSnapshot.size(); - - mSnapshot = mSnapshot.save(saveFlags); - - // return the old save count - return count; - } - - private int saveLayerAlpha(RectF rect, int alpha, int saveFlags) { - Paint_Delegate paint = new Paint_Delegate(); - paint.setAlpha(alpha); - return saveLayer(rect, paint, saveFlags); - } - - private int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) { - // get the current save count - int count = mSnapshot.size(); - - mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags); - - // return the old save count - return count; - } - - /** - * Restores the {@link GcSnapshot} to <var>saveCount</var> - * @param saveCount the saveCount - */ - private void restoreTo(int saveCount) { - mSnapshot = mSnapshot.restoreTo(saveCount); - } - - /** - * Restores the top {@link GcSnapshot} - */ - private void restore() { - mSnapshot = mSnapshot.restore(); - } - - private boolean clipRect(float left, float top, float right, float bottom, int regionOp) { - return mSnapshot.clipRect(left, top, right, bottom, regionOp); - } - - private static void drawBitmap( - long nativeCanvas, - Bitmap_Delegate bitmap, - long nativePaintOrZero, - final int sleft, final int stop, final int sright, final int sbottom, - final int dleft, final int dtop, final int dright, final int dbottom) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); - if (canvasDelegate == null) { - return; - } - - // get the paint, which could be null if the int is 0 - Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero); - - final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut); - - draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0], - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paint) { - if (paint != null && paint.isFilterBitmap()) { - graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_BILINEAR); - } - - //FIXME add support for canvas, screen and bitmap densities. - graphics.drawImage(image, dleft, dtop, dright, dbottom, - sleft, stop, sright, sbottom, null); - } - }); - } - - - /** - * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate. - * The image returns, through a 1-size boolean array, whether the drawing code should - * use a SRC composite no matter what the paint says. - * - * @param bitmap the bitmap - * @param paint the paint that will be used to draw - * @param forceSrcMode whether the composite will have to be SRC - * @return the image to draw - */ - private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint, - boolean[] forceSrcMode) { - BufferedImage image = bitmap.getImage(); - forceSrcMode[0] = false; - - // if the bitmap config is alpha_8, then we erase all color value from it - // before drawing it. - if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) { - fixAlpha8Bitmap(image); - } else if (!bitmap.hasAlpha()) { - // hasAlpha is merely a rendering hint. There can in fact be alpha values - // in the bitmap but it should be ignored at drawing time. - // There is two ways to do this: - // - override the composite to be SRC. This can only be used if the composite - // was going to be SRC or SRC_OVER in the first place - // - Create a different bitmap to draw in which all the alpha channel values is set - // to 0xFF. - if (paint != null) { - Xfermode_Delegate xfermodeDelegate = paint.getXfermode(); - if (xfermodeDelegate instanceof PorterDuffXfermode_Delegate) { - PorterDuff.Mode mode = - ((PorterDuffXfermode_Delegate)xfermodeDelegate).getMode(); - - forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER || - mode == PorterDuff.Mode.SRC; - } - } - - // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB - if (!forceSrcMode[0]) { - image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF); - } - } - - return image; - } - - private static void fixAlpha8Bitmap(final BufferedImage image) { - int w = image.getWidth(); - int h = image.getHeight(); - int[] argb = new int[w * h]; - image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); - - final int length = argb.length; - for (int i = 0 ; i < length; i++) { - argb[i] &= 0xFF000000; - } - image.setRGB(0, 0, w, h, argb, 0, w); + super(); } } diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java index 59ddcc6a6ca8..a459734e805f 100644 --- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java @@ -63,16 +63,8 @@ public class ComposeShader_Delegate extends Shader_Delegate { // ---- native methods ---- @LayoutlibDelegate - /*package*/ static long nativeCreate1(long native_shaderA, long native_shaderB, - long native_mode) { - // FIXME not supported yet. - ComposeShader_Delegate newDelegate = new ComposeShader_Delegate(); - return sManager.addNewDelegate(newDelegate); - } - - @LayoutlibDelegate - /*package*/ static long nativeCreate2(long native_shaderA, long native_shaderB, - int porterDuffMode) { + /*package*/ static long nativeCreate(long native_shaderA, long native_shaderB, + int native_mode) { // FIXME not supported yet. ComposeShader_Delegate newDelegate = new ComposeShader_Delegate(); return sManager.addNewDelegate(newDelegate); diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java index a503e50407ed..354f9191beda 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java @@ -27,6 +27,8 @@ import android.graphics.Matrix.ScaleToFit; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; +import libcore.util.NativeAllocationRegistry_Delegate; + /** * Delegate implementing the native methods of android.graphics.Matrix * @@ -47,6 +49,7 @@ public final class Matrix_Delegate { // ---- delegate manager ---- private static final DelegateManager<Matrix_Delegate> sManager = new DelegateManager<Matrix_Delegate>(Matrix_Delegate.class); + private static long sFinalizer = -1; // ---- delegate data ---- private float mValues[] = new float[MATRIX_SIZE]; @@ -174,7 +177,7 @@ public final class Matrix_Delegate { // ---- native methods ---- @LayoutlibDelegate - /*package*/ static long native_create(long native_src_or_zero) { + /*package*/ static long nCreate(long native_src_or_zero) { // create the delegate Matrix_Delegate newDelegate = new Matrix_Delegate(); @@ -193,7 +196,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_isIdentity(long native_object) { + /*package*/ static boolean nIsIdentity(long native_object) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return false; @@ -203,7 +206,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_isAffine(long native_object) { + /*package*/ static boolean nIsAffine(long native_object) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return true; @@ -213,7 +216,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_rectStaysRect(long native_object) { + /*package*/ static boolean nRectStaysRect(long native_object) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return true; @@ -223,7 +226,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_reset(long native_object) { + /*package*/ static void nReset(long native_object) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return; @@ -233,7 +236,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_set(long native_object, long other) { + /*package*/ static void nSet(long native_object, long other) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return; @@ -248,7 +251,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setTranslate(long native_object, float dx, float dy) { + /*package*/ static void nSetTranslate(long native_object, float dx, float dy) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return; @@ -258,7 +261,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setScale(long native_object, float sx, float sy, + /*package*/ static void nSetScale(long native_object, float sx, float sy, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { @@ -269,7 +272,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setScale(long native_object, float sx, float sy) { + /*package*/ static void nSetScale(long native_object, float sx, float sy) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return; @@ -287,7 +290,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setRotate(long native_object, float degrees, float px, float py) { + /*package*/ static void nSetRotate(long native_object, float degrees, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return; @@ -297,7 +300,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setRotate(long native_object, float degrees) { + /*package*/ static void nSetRotate(long native_object, float degrees) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return; @@ -307,7 +310,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setSinCos(long native_object, float sinValue, float cosValue, + /*package*/ static void nSetSinCos(long native_object, float sinValue, float cosValue, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { @@ -326,7 +329,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setSinCos(long native_object, float sinValue, float cosValue) { + /*package*/ static void nSetSinCos(long native_object, float sinValue, float cosValue) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return; @@ -336,7 +339,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setSkew(long native_object, float kx, float ky, + /*package*/ static void nSetSkew(long native_object, float kx, float ky, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { @@ -347,7 +350,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setSkew(long native_object, float kx, float ky) { + /*package*/ static void nSetSkew(long native_object, float kx, float ky) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return; @@ -365,12 +368,12 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setConcat(long native_object, long a, long b) { + /*package*/ static void nSetConcat(long native_object, long a, long b) { if (a == native_object) { - native_preConcat(native_object, b); + nPreConcat(native_object, b); return; } else if (b == native_object) { - native_postConcat(native_object, a); + nPostConcat(native_object, a); return; } @@ -383,7 +386,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_preTranslate(long native_object, float dx, float dy) { + /*package*/ static void nPreTranslate(long native_object, float dx, float dy) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d != null) { d.preTransform(getTranslate(dx, dy)); @@ -391,7 +394,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_preScale(long native_object, float sx, float sy, + /*package*/ static void nPreScale(long native_object, float sx, float sy, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d != null) { @@ -400,7 +403,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_preScale(long native_object, float sx, float sy) { + /*package*/ static void nPreScale(long native_object, float sx, float sy) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d != null) { d.preTransform(getScale(sx, sy)); @@ -408,7 +411,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_preRotate(long native_object, float degrees, + /*package*/ static void nPreRotate(long native_object, float degrees, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d != null) { @@ -417,7 +420,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_preRotate(long native_object, float degrees) { + /*package*/ static void nPreRotate(long native_object, float degrees) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d != null) { @@ -430,7 +433,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_preSkew(long native_object, float kx, float ky, + /*package*/ static void nPreSkew(long native_object, float kx, float ky, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d != null) { @@ -439,7 +442,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_preSkew(long native_object, float kx, float ky) { + /*package*/ static void nPreSkew(long native_object, float kx, float ky) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d != null) { d.preTransform(getSkew(kx, ky)); @@ -447,7 +450,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_preConcat(long native_object, long other_matrix) { + /*package*/ static void nPreConcat(long native_object, long other_matrix) { Matrix_Delegate d = sManager.getDelegate(native_object); Matrix_Delegate other = sManager.getDelegate(other_matrix); if (d != null && other != null) { @@ -456,7 +459,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_postTranslate(long native_object, float dx, float dy) { + /*package*/ static void nPostTranslate(long native_object, float dx, float dy) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d != null) { d.postTransform(getTranslate(dx, dy)); @@ -464,7 +467,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_postScale(long native_object, float sx, float sy, + /*package*/ static void nPostScale(long native_object, float sx, float sy, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d != null) { @@ -473,7 +476,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_postScale(long native_object, float sx, float sy) { + /*package*/ static void nPostScale(long native_object, float sx, float sy) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d != null) { d.postTransform(getScale(sx, sy)); @@ -481,7 +484,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_postRotate(long native_object, float degrees, + /*package*/ static void nPostRotate(long native_object, float degrees, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d != null) { @@ -490,7 +493,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_postRotate(long native_object, float degrees) { + /*package*/ static void nPostRotate(long native_object, float degrees) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d != null) { d.postTransform(getRotate(degrees)); @@ -498,7 +501,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_postSkew(long native_object, float kx, float ky, + /*package*/ static void nPostSkew(long native_object, float kx, float ky, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d != null) { @@ -507,7 +510,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_postSkew(long native_object, float kx, float ky) { + /*package*/ static void nPostSkew(long native_object, float kx, float ky) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d != null) { d.postTransform(getSkew(kx, ky)); @@ -515,7 +518,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_postConcat(long native_object, long other_matrix) { + /*package*/ static void nPostConcat(long native_object, long other_matrix) { Matrix_Delegate d = sManager.getDelegate(native_object); Matrix_Delegate other = sManager.getDelegate(other_matrix); if (d != null && other != null) { @@ -524,7 +527,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_setRectToRect(long native_object, RectF src, + /*package*/ static boolean nSetRectToRect(long native_object, RectF src, RectF dst, int stf) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { @@ -589,7 +592,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_setPolyToPoly(long native_object, float[] src, int srcIndex, + /*package*/ static boolean nSetPolyToPoly(long native_object, float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -599,7 +602,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_invert(long native_object, long inverse) { + /*package*/ static boolean nInvert(long native_object, long inverse) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return false; @@ -627,7 +630,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_mapPoints(long native_object, float[] dst, int dstIndex, + /*package*/ static void nMapPoints(long native_object, float[] dst, int dstIndex, float[] src, int srcIndex, int ptCount, boolean isPts) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { @@ -642,7 +645,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_mapRect(long native_object, RectF dst, RectF src) { + /*package*/ static boolean nMapRect(long native_object, RectF dst, RectF src) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return false; @@ -652,7 +655,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static float native_mapRadius(long native_object, float radius) { + /*package*/ static float nMapRadius(long native_object, float radius) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return 0.f; @@ -667,7 +670,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_getValues(long native_object, float[] values) { + /*package*/ static void nGetValues(long native_object, float[] values) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return; @@ -677,7 +680,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setValues(long native_object, float[] values) { + /*package*/ static void nSetValues(long native_object, float[] values) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { return; @@ -687,7 +690,7 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_equals(long native_a, long native_b) { + /*package*/ static boolean nEquals(long native_a, long native_b) { Matrix_Delegate a = sManager.getDelegate(native_a); if (a == null) { return false; @@ -708,8 +711,13 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static void finalizer(long native_instance) { - sManager.removeJavaReferenceFor(native_instance); + /*package*/ static long nGetNativeFinalizer() { + synchronized (Matrix_Delegate.class) { + if (sFinalizer == -1) { + sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor); + } + } + return sFinalizer; } // ---- Private helper methods ---- diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java index 33296e1abdc9..e68d8b3e9f93 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -90,10 +90,11 @@ public class Paint_Delegate { private int mHintingMode = Paint.HINTING_ON; private int mHyphenEdit; private float mLetterSpacing; // not used in actual text rendering. + private float mWordSpacing; // not used in actual text rendering. // Variant of the font. A paint's variant can only be compact or elegant. private FontVariant mFontVariant = FontVariant.COMPACT; - private Xfermode_Delegate mXfermode; + private int mPorterDuffMode = Xfermode.DEFAULT; private ColorFilter_Delegate mColorFilter; private Shader_Delegate mShader; private PathEffect_Delegate mPathEffect; @@ -206,12 +207,10 @@ public class Paint_Delegate { } /** - * Returns the {@link Xfermode} delegate or null if none have been set - * - * @return the delegate or null. + * Returns the {@link PorterDuff.Mode} as an int */ - public Xfermode_Delegate getXfermode() { - return mXfermode; + public int getPorterDuffMode() { + return mPorterDuffMode; } /** @@ -261,7 +260,7 @@ public class Paint_Delegate { // ---- native methods ---- @LayoutlibDelegate - /*package*/ static int nGetFlags(Paint thisPaint, long nativePaint) { + /*package*/ static int nGetFlags(long nativePaint) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -274,7 +273,7 @@ public class Paint_Delegate { @LayoutlibDelegate - /*package*/ static void nSetFlags(Paint thisPaint, long nativePaint, int flags) { + /*package*/ static void nSetFlags(long nativePaint, int flags) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -285,12 +284,12 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void nSetFilterBitmap(Paint thisPaint, long nativePaint, boolean filter) { + /*package*/ static void nSetFilterBitmap(long nativePaint, boolean filter) { setFlag(nativePaint, Paint.FILTER_BITMAP_FLAG, filter); } @LayoutlibDelegate - /*package*/ static int nGetHinting(Paint thisPaint, long nativePaint) { + /*package*/ static int nGetHinting(long nativePaint) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -301,7 +300,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void nSetHinting(Paint thisPaint, long nativePaint, int mode) { + /*package*/ static void nSetHinting(long nativePaint, int mode) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -312,46 +311,46 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void nSetAntiAlias(Paint thisPaint, long nativePaint, boolean aa) { + /*package*/ static void nSetAntiAlias(long nativePaint, boolean aa) { setFlag(nativePaint, Paint.ANTI_ALIAS_FLAG, aa); } @LayoutlibDelegate - /*package*/ static void nSetSubpixelText(Paint thisPaint, long nativePaint, + /*package*/ static void nSetSubpixelText(long nativePaint, boolean subpixelText) { setFlag(nativePaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText); } @LayoutlibDelegate - /*package*/ static void nSetUnderlineText(Paint thisPaint, long nativePaint, + /*package*/ static void nSetUnderlineText(long nativePaint, boolean underlineText) { setFlag(nativePaint, Paint.UNDERLINE_TEXT_FLAG, underlineText); } @LayoutlibDelegate - /*package*/ static void nSetStrikeThruText(Paint thisPaint, long nativePaint, + /*package*/ static void nSetStrikeThruText(long nativePaint, boolean strikeThruText) { setFlag(nativePaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText); } @LayoutlibDelegate - /*package*/ static void nSetFakeBoldText(Paint thisPaint, long nativePaint, + /*package*/ static void nSetFakeBoldText(long nativePaint, boolean fakeBoldText) { setFlag(nativePaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText); } @LayoutlibDelegate - /*package*/ static void nSetDither(Paint thisPaint, long nativePaint, boolean dither) { + /*package*/ static void nSetDither(long nativePaint, boolean dither) { setFlag(nativePaint, Paint.DITHER_FLAG, dither); } @LayoutlibDelegate - /*package*/ static void nSetLinearText(Paint thisPaint, long nativePaint, boolean linearText) { + /*package*/ static void nSetLinearText(long nativePaint, boolean linearText) { setFlag(nativePaint, Paint.LINEAR_TEXT_FLAG, linearText); } @LayoutlibDelegate - /*package*/ static int nGetColor(Paint thisPaint, long nativePaint) { + /*package*/ static int nGetColor(long nativePaint) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -362,7 +361,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void nSetColor(Paint thisPaint, long nativePaint, int color) { + /*package*/ static void nSetColor(long nativePaint, int color) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -373,7 +372,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int nGetAlpha(Paint thisPaint, long nativePaint) { + /*package*/ static int nGetAlpha(long nativePaint) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -384,7 +383,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void nSetAlpha(Paint thisPaint, long nativePaint, int a) { + /*package*/ static void nSetAlpha(long nativePaint, int a) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -395,7 +394,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float nGetStrokeWidth(Paint thisPaint, long nativePaint) { + /*package*/ static float nGetStrokeWidth(long nativePaint) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -406,7 +405,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void nSetStrokeWidth(Paint thisPaint, long nativePaint, float width) { + /*package*/ static void nSetStrokeWidth(long nativePaint, float width) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -417,7 +416,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float nGetStrokeMiter(Paint thisPaint, long nativePaint) { + /*package*/ static float nGetStrokeMiter(long nativePaint) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -428,7 +427,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void nSetStrokeMiter(Paint thisPaint, long nativePaint, float miter) { + /*package*/ static void nSetStrokeMiter(long nativePaint, float miter) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -455,14 +454,14 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static boolean nIsElegantTextHeight(Paint thisPaint, long nativePaint) { + /*package*/ static boolean nIsElegantTextHeight(long nativePaint) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); return delegate != null && delegate.mFontVariant == FontVariant.ELEGANT; } @LayoutlibDelegate - /*package*/ static void nSetElegantTextHeight(Paint thisPaint, long nativePaint, + /*package*/ static void nSetElegantTextHeight(long nativePaint, boolean elegant) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); @@ -474,7 +473,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float nGetTextSize(Paint thisPaint, long nativePaint) { + /*package*/ static float nGetTextSize(long nativePaint) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -485,7 +484,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void nSetTextSize(Paint thisPaint, long nativePaint, float textSize) { + /*package*/ static void nSetTextSize(long nativePaint, float textSize) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -499,7 +498,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float nGetTextScaleX(Paint thisPaint, long nativePaint) { + /*package*/ static float nGetTextScaleX(long nativePaint) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -510,7 +509,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void nSetTextScaleX(Paint thisPaint, long nativePaint, float scaleX) { + /*package*/ static void nSetTextScaleX(long nativePaint, float scaleX) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -524,7 +523,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float nGetTextSkewX(Paint thisPaint, long nativePaint) { + /*package*/ static float nGetTextSkewX(long nativePaint) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -535,7 +534,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void nSetTextSkewX(Paint thisPaint, long nativePaint, float skewX) { + /*package*/ static void nSetTextSkewX(long nativePaint, float skewX) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -549,7 +548,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float nAscent(Paint thisPaint, long nativePaint, long nativeTypeface) { + /*package*/ static float nAscent(long nativePaint, long nativeTypeface) { // get the delegate Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -566,7 +565,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float nDescent(Paint thisPaint, long nativePaint, long nativeTypeface) { + /*package*/ static float nDescent(long nativePaint, long nativeTypeface) { // get the delegate Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -583,7 +582,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float nGetFontMetrics(Paint thisPaint, long nativePaint, long nativeTypeface, + /*package*/ static float nGetFontMetrics(long nativePaint, long nativeTypeface, FontMetrics metrics) { // get the delegate Paint_Delegate delegate = sManager.getDelegate(nativePaint); @@ -595,7 +594,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int nGetFontMetricsInt(Paint thisPaint, long nativePaint, + /*package*/ static int nGetFontMetricsInt(long nativePaint, long nativeTypeface, FontMetricsInt fmi) { // get the delegate Paint_Delegate delegate = sManager.getDelegate(nativePaint); @@ -841,16 +840,12 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long nSetXfermode(long native_object, long xfermode) { - // get the delegate from the native int. + /*package*/ static void nSetXfermode(long native_object, int xfermode) { Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { - return xfermode; + return; } - - delegate.mXfermode = Xfermode_Delegate.getDelegate(xfermode); - - return xfermode; + delegate.mPorterDuffMode = xfermode; } @LayoutlibDelegate @@ -998,7 +993,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int nGetTextRunCursor(Paint thisPaint, long native_object, char[] text, + /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, char[] text, int contextStart, int contextLength, int flags, int offset, int cursorOpt) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -1007,7 +1002,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int nGetTextRunCursor(Paint thisPaint, long native_object, String text, + /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, String text, int contextStart, int contextEnd, int flags, int offset, int cursorOpt) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -1086,6 +1081,26 @@ public class Paint_Delegate { } @LayoutlibDelegate + /*package*/ static float nGetWordSpacing(long nativePaint) { + Paint_Delegate delegate = sManager.getDelegate(nativePaint); + if (delegate == null) { + return 0; + } + return delegate.mWordSpacing; + } + + @LayoutlibDelegate + /*package*/ static void nSetWordSpacing(long nativePaint, float wordSpacing) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING, + "Paint.setWordSpacing() not supported.", null, null); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); + if (delegate == null) { + return; + } + delegate.mWordSpacing = wordSpacing; + } + + @LayoutlibDelegate /*package*/ static void nSetFontFeatureSettings(long nativePaint, String settings) { Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING, "Paint.setFontFeatureSettings() not supported.", null, null); @@ -1215,7 +1230,7 @@ public class Paint_Delegate { mStrokeWidth = paint.mStrokeWidth; mStrokeMiter = paint.mStrokeMiter; - mXfermode = paint.mXfermode; + mPorterDuffMode = paint.mPorterDuffMode; mColorFilter = paint.mColorFilter; mShader = paint.mShader; mPathEffect = paint.mPathEffect; @@ -1242,7 +1257,7 @@ public class Paint_Delegate { mTextSize = 20.f; mTextScaleX = 1.f; mTextSkewX = 0.f; - mXfermode = null; + mPorterDuffMode = Xfermode.DEFAULT; mColorFilter = null; mShader = null; mPathEffect = null; diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java index 265ebd1755e3..219c487cac53 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java @@ -86,6 +86,8 @@ public final class Path_Delegate { public void reset() { mPath.reset(); + mLastX = 0; + mLastY = 0; } public void setPathIterator(PathIterator iterator) { @@ -124,7 +126,7 @@ public final class Path_Delegate { return; } - pathDelegate.mPath.reset(); + pathDelegate.reset(); } @LayoutlibDelegate diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java deleted file mode 100644 index 8825f84995c8..000000000000 --- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.graphics; - -import com.android.layoutlib.bridge.impl.DelegateManager; -import com.android.layoutlib.bridge.impl.PorterDuffUtility; -import com.android.tools.layoutlib.annotations.LayoutlibDelegate; - -import android.graphics.PorterDuff.Mode; - -import java.awt.Composite; - -import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode; - -/** - * Delegate implementing the native methods of android.graphics.PorterDuffXfermode - * - * Through the layoutlib_create tool, the original native methods of PorterDuffXfermode have been - * replaced by calls to methods of the same name in this delegate class. - * - * This class behaves like the original native implementation, but in Java, keeping previously - * native data into its own objects and mapping them to int that are sent back and forth between - * it and the original PorterDuffXfermode class. - * - * Because this extends {@link Xfermode_Delegate}, there's no need to use a - * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by - * {@link Xfermode_Delegate}. - * - */ -public class PorterDuffXfermode_Delegate extends Xfermode_Delegate { - - // ---- delegate data ---- - - private final Mode mMode; - - // ---- Public Helper methods ---- - - public Mode getMode() { - return mMode; - } - - @Override - public Composite getComposite(int alpha) { - return PorterDuffUtility.getComposite(mMode, alpha); - } - - @Override - public boolean isSupported() { - return true; - } - - @Override - public String getSupportMessage() { - // no message since isSupported returns true; - return null; - } - - - // ---- native methods ---- - - @LayoutlibDelegate - /*package*/ static long nativeCreateXfermode(int mode) { - PorterDuffXfermode_Delegate newDelegate = new PorterDuffXfermode_Delegate(mode); - return sManager.addNewDelegate(newDelegate); - } - - // ---- Private delegate/helper methods ---- - - private PorterDuffXfermode_Delegate(int mode) { - mMode = getPorterDuffMode(mode); - } - -} diff --git a/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java deleted file mode 100644 index 94a6d76fe8dc..000000000000 --- a/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.graphics; - -import com.android.layoutlib.bridge.impl.DelegateManager; -import com.android.tools.layoutlib.annotations.LayoutlibDelegate; - -import java.awt.Composite; - -/** - * Delegate implementing the native methods of android.graphics.Xfermode - * - * Through the layoutlib_create tool, the original native methods of Xfermode have been replaced - * by calls to methods of the same name in this delegate class. - * - * This class behaves like the original native implementation, but in Java, keeping previously - * native data into its own objects and mapping them to int that are sent back and forth between - * it and the original Xfermode class. - * - * This also serve as a base class for all Xfermode delegate classes. - * - * @see DelegateManager - * - */ -public abstract class Xfermode_Delegate { - - // ---- delegate manager ---- - protected static final DelegateManager<Xfermode_Delegate> sManager = - new DelegateManager<Xfermode_Delegate>(Xfermode_Delegate.class); - - // ---- delegate helper data ---- - - // ---- delegate data ---- - - // ---- Public Helper methods ---- - - public static Xfermode_Delegate getDelegate(long native_instance) { - return sManager.getDelegate(native_instance); - } - - public abstract Composite getComposite(int alpha); - public abstract boolean isSupported(); - public abstract String getSupportMessage(); - - - // ---- native methods ---- - - @LayoutlibDelegate - /*package*/ static void finalizer(long native_instance) { - sManager.removeJavaReferenceFor(native_instance); - } - - // ---- Private delegate/helper methods ---- - -} diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java index 200fe3b1d192..ad2c5647eef2 100644 --- a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java @@ -58,8 +58,13 @@ public class AnimatedVectorDrawable_Delegate { } @LayoutlibDelegate + /*package*/ static void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr) { + // TODO: implement + } + @LayoutlibDelegate /*package*/ static void nAddAnimator(long setPtr, long propertyValuesHolder, - long nativeInterpolator, long startDelay, long duration, int repeatCount) { + long nativeInterpolator, long startDelay, long duration, int repeatCount, + int repeatMode) { PropertySetter holder = sHolders.getDelegate(propertyValuesHolder); if (holder == null || holder.getValues() == null) { return; @@ -72,6 +77,7 @@ public class AnimatedVectorDrawable_Delegate { animator.setStartDelay(startDelay); animator.setDuration(duration); animator.setRepeatCount(repeatCount); + animator.setRepeatMode(repeatMode); animator.setTarget(holder); animator.setPropertyName(holder.getValues().getPropertyName()); @@ -137,6 +143,14 @@ public class AnimatedVectorDrawable_Delegate { } @LayoutlibDelegate + /*package*/ static void nSetPropertyHolderData(long nativePtr, int[] data, int length) { + PropertySetter setter = sHolders.getDelegate(nativePtr); + assert setter != null; + + setter.setValues(data); + } + + @LayoutlibDelegate /*package*/ static void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) { AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr); assert animatorSet != null; diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java index 3d78931a152c..fc848d9af956 100644 --- a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java @@ -18,6 +18,7 @@ package android.graphics.drawable; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import android.graphics.Canvas; import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT; public class AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate { @@ -25,4 +26,9 @@ public class AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate { /*package*/ static boolean useLastSeenTarget(VectorDrawableAnimatorRT thisDrawableAnimator) { return true; } + + @LayoutlibDelegate + /*package*/ static void onDraw(VectorDrawableAnimatorRT thisDrawableAnimator, Canvas canvas) { + // Do not attempt to record as we are not using a DisplayListCanvas + } } diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java index 49f8691986be..cee679a1d469 100644 --- a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.BaseCanvas_Delegate; import android.graphics.Canvas_Delegate; import android.graphics.Color; import android.graphics.Matrix; @@ -70,6 +71,13 @@ public class VectorDrawable_Delegate { private static final DelegateManager<VNativeObject> sPathManager = new DelegateManager<>(VNativeObject.class); + private static long addNativeObject(VNativeObject object) { + long ptr = sPathManager.addNewDelegate(object); + object.setNativePtr(ptr); + + return ptr; + } + /** * Obtains styled attributes from the theme, if available, or unstyled resources if the theme is * null. @@ -91,8 +99,14 @@ public class VectorDrawable_Delegate { @LayoutlibDelegate static long nCreateTree(long rootGroupPtr) { - VGroup_Delegate rootGroup = VNativeObject.getDelegate(rootGroupPtr); - return sPathManager.addNewDelegate(new VPathRenderer_Delegate(rootGroup)); + return addNativeObject(new VPathRenderer_Delegate(rootGroupPtr)); + } + + @LayoutlibDelegate + static long nCreateTreeFromCopy(long rendererToCopyPtr, long rootGroupPtr) { + VPathRenderer_Delegate rendererToCopy = VNativeObject.getDelegate(rendererToCopyPtr); + return addNativeObject(new VPathRenderer_Delegate(rendererToCopy, + rootGroupPtr)); } @LayoutlibDelegate @@ -128,12 +142,12 @@ public class VectorDrawable_Delegate { long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache) { VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr); - Canvas_Delegate.native_save(canvasWrapperPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG); - Canvas_Delegate.native_translate(canvasWrapperPtr, bounds.left, bounds.top); + Canvas_Delegate.nSave(canvasWrapperPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG); + Canvas_Delegate.nTranslate(canvasWrapperPtr, bounds.left, bounds.top); if (needsMirroring) { - Canvas_Delegate.native_translate(canvasWrapperPtr, bounds.width(), 0); - Canvas_Delegate.native_scale(canvasWrapperPtr, -1.0f, 1.0f); + Canvas_Delegate.nTranslate(canvasWrapperPtr, bounds.width(), 0); + Canvas_Delegate.nScale(canvasWrapperPtr, -1.0f, 1.0f); } // At this point, canvas has been translated to the right position. @@ -142,21 +156,20 @@ public class VectorDrawable_Delegate { bounds.offsetTo(0, 0); nativePathRenderer.draw(canvasWrapperPtr, colorFilterPtr, bounds.width(), bounds.height()); - Canvas_Delegate.native_restore(canvasWrapperPtr, true); + Canvas_Delegate.nRestore(canvasWrapperPtr, true); return bounds.width() * bounds.height(); } @LayoutlibDelegate static long nCreateFullPath() { - return sPathManager.addNewDelegate(new VFullPath_Delegate()); + return addNativeObject(new VFullPath_Delegate()); } @LayoutlibDelegate static long nCreateFullPath(long nativeFullPathPtr) { VFullPath_Delegate original = VNativeObject.getDelegate(nativeFullPathPtr); - - return sPathManager.addNewDelegate(new VFullPath_Delegate(original)); + return addNativeObject(new VFullPath_Delegate(original)); } @LayoutlibDelegate @@ -222,25 +235,24 @@ public class VectorDrawable_Delegate { @LayoutlibDelegate static long nCreateClipPath() { - return sPathManager.addNewDelegate(new VClipPath_Delegate()); + return addNativeObject(new VClipPath_Delegate()); } @LayoutlibDelegate static long nCreateClipPath(long clipPathPtr) { VClipPath_Delegate original = VNativeObject.getDelegate(clipPathPtr); - return sPathManager.addNewDelegate(new VClipPath_Delegate(original)); + return addNativeObject(new VClipPath_Delegate(original)); } @LayoutlibDelegate static long nCreateGroup() { - return sPathManager.addNewDelegate(new VGroup_Delegate()); + return addNativeObject(new VGroup_Delegate()); } @LayoutlibDelegate static long nCreateGroup(long groupPtr) { VGroup_Delegate original = VNativeObject.getDelegate(groupPtr); - return sPathManager.addNewDelegate( - new VGroup_Delegate(original, new ArrayMap<String, Object>())); + return addNativeObject(new VGroup_Delegate(original, new ArrayMap<>())); } @LayoutlibDelegate @@ -493,7 +505,9 @@ public class VectorDrawable_Delegate { * not need it * </ol> */ - interface VNativeObject { + abstract static class VNativeObject { + long mNativePtr = 0; + @NonNull static <T> T getDelegate(long nativePtr) { //noinspection unchecked @@ -503,7 +517,17 @@ public class VectorDrawable_Delegate { return vNativeObject; } - void setName(String name); + abstract void setName(String name); + + void setNativePtr(long nativePtr) { + mNativePtr = nativePtr; + } + + /** + * Method to explicitly dispose native objects + */ + void dispose() { + } } private static class VClipPath_Delegate extends VPath_Delegate { @@ -773,7 +797,7 @@ public class VectorDrawable_Delegate { } } - static class VGroup_Delegate implements VNativeObject { + static class VGroup_Delegate extends VNativeObject { // This constants need to be kept in sync with their definitions in VectorDrawable.Group private static final int ROTATE_INDEX = 0; private static final int PIVOT_X_INDEX = 1; @@ -959,9 +983,28 @@ public class VectorDrawable_Delegate { public void setName(String name) { mGroupName = name; } + + @Override + protected void dispose() { + mChildren.stream().filter(child -> child instanceof VNativeObject).forEach(child + -> { + VNativeObject nativeObject = (VNativeObject) child; + if (nativeObject.mNativePtr != 0) { + sPathManager.removeJavaReferenceFor(nativeObject.mNativePtr); + nativeObject.mNativePtr = 0; + } + nativeObject.dispose(); + }); + mChildren.clear(); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + } } - public static class VPath_Delegate implements VNativeObject { + public static class VPath_Delegate extends VNativeObject { protected PathParser_Delegate.PathDataNode[] mNodes = null; String mPathName; int mChangingConfigurations; @@ -973,7 +1016,7 @@ public class VectorDrawable_Delegate { public VPath_Delegate(VPath_Delegate copy) { mPathName = copy.mPathName; mChangingConfigurations = copy.mChangingConfigurations; - mNodes = PathParser_Delegate.deepCopyNodes(copy.mNodes); + mNodes = copy.mNodes != null ? PathParser_Delegate.deepCopyNodes(copy.mNodes) : null; } public void toPath(Path path) { @@ -1001,9 +1044,14 @@ public class VectorDrawable_Delegate { PathParser_Delegate.updateNodes(mNodes, nodes); } } + + @Override + void dispose() { + mNodes = null; + } } - static class VPathRenderer_Delegate implements VNativeObject { + static class VPathRenderer_Delegate extends VNativeObject { /* Right now the internal data structure is organized as a tree. * Each node can be a group node, or a path. * A group node can have groups or paths as children, but a path node has @@ -1021,7 +1069,7 @@ public class VectorDrawable_Delegate { private final Path mPath; private final Path mRenderPath; private final Matrix mFinalPathMatrix = new Matrix(); - private final VGroup_Delegate mRootGroup; + private final long mRootGroupPtr; private float mViewportWidth = 0; private float mViewportHeight = 0; private float mRootAlpha = 1.0f; @@ -1029,12 +1077,20 @@ public class VectorDrawable_Delegate { private Paint mFillPaint; private PathMeasure mPathMeasure; - private VPathRenderer_Delegate(VGroup_Delegate rootGroup) { - mRootGroup = rootGroup; + private VPathRenderer_Delegate(long rootGroupPtr) { + mRootGroupPtr = rootGroupPtr; mPath = new Path(); mRenderPath = new Path(); } + private VPathRenderer_Delegate(VPathRenderer_Delegate rendererToCopy, + long rootGroupPtr) { + this(rootGroupPtr); + mViewportWidth = rendererToCopy.mViewportWidth; + mViewportHeight = rendererToCopy.mViewportHeight; + mRootAlpha = rendererToCopy.mRootAlpha; + } + private float getRootAlpha() { return mRootAlpha; } @@ -1053,7 +1109,7 @@ public class VectorDrawable_Delegate { currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix); // Save the current clip information, which is local to this group. - Canvas_Delegate.native_save(canvasPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG); + Canvas_Delegate.nSave(canvasPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG); // Draw the group tree in the same order as the XML file. for (int i = 0; i < currentGroup.mChildren.size(); i++) { Object child = currentGroup.mChildren.get(i); @@ -1066,12 +1122,12 @@ public class VectorDrawable_Delegate { drawPath(currentGroup, childPath, canvasPtr, w, h, filterPtr); } } - Canvas_Delegate.native_restore(canvasPtr, true); + Canvas_Delegate.nRestore(canvasPtr, true); } public void draw(long canvasPtr, long filterPtr, int w, int h) { // Traverse the tree in pre-order to draw. - drawGroupTree(mRootGroup, Matrix.IDENTITY_MATRIX, canvasPtr, w, h, filterPtr); + drawGroupTree(VNativeObject.getDelegate(mRootGroupPtr), Matrix.IDENTITY_MATRIX, canvasPtr, w, h, filterPtr); } private void drawPath(VGroup_Delegate VGroup, VPath_Delegate VPath, long canvasPtr, @@ -1098,7 +1154,7 @@ public class VectorDrawable_Delegate { if (VPath.isClipPath()) { mRenderPath.addPath(path, mFinalPathMatrix); - Canvas_Delegate.native_clipPath(canvasPtr, mRenderPath.mNativePath, Op + Canvas_Delegate.nClipPath(canvasPtr, mRenderPath.mNativePath, Op .INTERSECT.nativeInt); } else { VFullPath_Delegate fullPath = (VFullPath_Delegate) VPath; @@ -1133,7 +1189,8 @@ public class VectorDrawable_Delegate { } final Paint fillPaint = mFillPaint; - fillPaint.setColor(applyAlpha(fullPath.mFillColor, fullPath.mFillAlpha)); + fillPaint.setColor(applyAlpha(applyAlpha(fullPath.mFillColor, fullPath + .mFillAlpha), getRootAlpha())); Paint_Delegate fillPaintDelegate = Paint_Delegate.getDelegate(fillPaint .getNativeInstance()); // mFillPaint can not be null at this point so we will have a delegate @@ -1141,7 +1198,7 @@ public class VectorDrawable_Delegate { fillPaintDelegate.setColorFilter(filterPtr); fillPaintDelegate.setShader(fullPath.mFillGradient); Path_Delegate.native_setFillType(mRenderPath.mNativePath, fullPath.mFillType); - Canvas_Delegate.native_drawPath(canvasPtr, mRenderPath.mNativePath, fillPaint + BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, fillPaint .getNativeInstance()); } @@ -1162,7 +1219,8 @@ public class VectorDrawable_Delegate { } strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit); - strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha)); + strokePaint.setColor(applyAlpha(applyAlpha(fullPath.mStrokeColor, fullPath + .mStrokeAlpha), getRootAlpha())); Paint_Delegate strokePaintDelegate = Paint_Delegate.getDelegate(strokePaint .getNativeInstance()); // mStrokePaint can not be null at this point so we will have a delegate @@ -1171,7 +1229,7 @@ public class VectorDrawable_Delegate { final float finalStrokeScale = minScale * matrixScale; strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale); strokePaintDelegate.setShader(fullPath.mStrokeGradient); - Canvas_Delegate.native_drawPath(canvasPtr, mRenderPath.mNativePath, strokePaint + BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, strokePaint .getNativeInstance()); } } @@ -1209,5 +1267,17 @@ public class VectorDrawable_Delegate { @Override public void setName(String name) { } + + @Override + protected void finalize() throws Throwable { + // The mRootGroupPtr is not explicitly freed by anything in the VectorDrawable so we + // need to free it here. + VNativeObject nativeObject = sPathManager.getDelegate(mRootGroupPtr); + sPathManager.removeJavaReferenceFor(mRootGroupPtr); + assert nativeObject != null; + nativeObject.dispose(); + + super.finalize(); + } } } diff --git a/tools/layoutlib/bridge/src/android/os/ServiceManager.java b/tools/layoutlib/bridge/src/android/os/ServiceManager.java index 549074d15757..34c78455f85d 100644 --- a/tools/layoutlib/bridge/src/android/os/ServiceManager.java +++ b/tools/layoutlib/bridge/src/android/os/ServiceManager.java @@ -31,6 +31,13 @@ public final class ServiceManager { } /** + * Is not supposed to return null, but that is fine for layoutlib. + */ + public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException { + throw new ServiceNotFoundException(name); + } + + /** * Place a new @a service called @a name into the service * manager. * @@ -71,4 +78,18 @@ public final class ServiceManager { public static void initServiceCache(Map<String, IBinder> cache) { // pass } + + /** + * Exception thrown when no service published for given name. This might be + * thrown early during boot before certain services have published + * themselves. + * + * @hide + */ + public static class ServiceNotFoundException extends Exception { + // identical to the original implementation + public ServiceNotFoundException(String name) { + super("No service published for: " + name); + } + } } diff --git a/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java index af0c4569b08b..d299add05eab 100644 --- a/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java +++ b/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java @@ -102,4 +102,9 @@ public class SystemProperties_Delegate { /*package*/ static void native_add_change_callback() { // pass. } + + @LayoutlibDelegate + /*package*/ static void native_report_sysprop_change() { + // pass. + } } diff --git a/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java b/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java index 4f00b5da08a8..aa393a976d12 100644 --- a/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java +++ b/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java @@ -21,6 +21,7 @@ import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import android.content.Context; import android.util.AttributeSet; +import android.view.InflateException; public class BridgePreferenceInflater extends PreferenceInflater { @@ -42,7 +43,15 @@ public class BridgePreferenceInflater extends PreferenceInflater { viewKey = ((BridgeXmlBlockParser) attrs).getViewCookie(); } - Preference preference = super.onCreateItem(name, attrs); + Preference preference = null; + try { + preference = super.onCreateItem(name, attrs); + } catch (ClassNotFoundException | InflateException exception) { + // name is probably not a valid preference type + if ("SwitchPreferenceCompat".equals(name)) { + preference = super.onCreateItem("SwitchPreference", attrs); + } + } if (viewKey != null && bc != null) { bc.addCookie(preference, viewKey); diff --git a/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java b/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java index 94f3f546d0c3..85584d3ed2cc 100644 --- a/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java +++ b/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java @@ -34,7 +34,7 @@ public class AttachInfo_Accessor { Display display = wm.getDefaultDisplay(); ViewRootImpl root = new ViewRootImpl(context, display); AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(), - display, root, new Handler(), null); + display, root, new Handler(), null, context); info.mHasWindowFocus = true; info.mWindowVisibility = View.VISIBLE; info.mInTouchMode = false; // this is so that we can display selections. diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java index bdddfd8d8ba3..b19cb5860a63 100644 --- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java @@ -39,11 +39,28 @@ import android.annotation.NonNull; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; +import android.widget.NumberPicker; import java.io.File; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; - +import java.util.Set; + +import static com.android.SdkConstants.AUTO_COMPLETE_TEXT_VIEW; +import static com.android.SdkConstants.BUTTON; +import static com.android.SdkConstants.CHECKED_TEXT_VIEW; +import static com.android.SdkConstants.CHECK_BOX; +import static com.android.SdkConstants.EDIT_TEXT; +import static com.android.SdkConstants.IMAGE_BUTTON; +import static com.android.SdkConstants.IMAGE_VIEW; +import static com.android.SdkConstants.MULTI_AUTO_COMPLETE_TEXT_VIEW; +import static com.android.SdkConstants.RADIO_BUTTON; +import static com.android.SdkConstants.SEEK_BAR; +import static com.android.SdkConstants.SPINNER; +import static com.android.SdkConstants.TEXT_VIEW; import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext; /** @@ -52,6 +69,21 @@ import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext; public final class BridgeInflater extends LayoutInflater { private final LayoutlibCallback mLayoutlibCallback; + /** + * If true, the inflater will try to replace the framework widgets with the AppCompat versions. + * Ideally, this should be based on the activity being an AppCompat activity but since that is + * not trivial to check from layoutlib, we currently base the decision on the current theme + * being an AppCompat theme. + */ + private boolean mLoadAppCompatViews; + /** + * This set contains the framework views that have an AppCompat version but failed to load. + * This might happen because not all widgets are contained in all versions of the support + * library. + * This will help us to avoid trying to load the AppCompat version multiple times if it + * doesn't exist. + */ + private Set<String> mFailedAppCompatViews = new HashSet<>(); private boolean mIsInMerge = false; private ResourceReference mResourceReference; private Map<View, String> mOpenDrawerLayouts; @@ -59,6 +91,15 @@ public final class BridgeInflater extends LayoutInflater { // Keep in sync with the same value in LayoutInflater. private static final int[] ATTRS_THEME = new int[] {com.android.internal.R.attr.theme }; + private static final String APPCOMPAT_WIDGET_PREFIX = "android.support.v7.widget.AppCompat"; + /** List of platform widgets that have an AppCompat version */ + private static final Set<String> APPCOMPAT_VIEWS = Collections.unmodifiableSet( + new HashSet<>( + Arrays.asList(TEXT_VIEW, IMAGE_VIEW, BUTTON, EDIT_TEXT, SPINNER, + IMAGE_BUTTON, CHECK_BOX, RADIO_BUTTON, CHECKED_TEXT_VIEW, + AUTO_COMPLETE_TEXT_VIEW, MULTI_AUTO_COMPLETE_TEXT_VIEW, "RatingBar", + SEEK_BAR))); + /** * List of class prefixes which are tried first by default. * <p/> @@ -74,13 +115,15 @@ public final class BridgeInflater extends LayoutInflater { return sClassPrefixList; } - protected BridgeInflater(LayoutInflater original, Context newContext) { + private BridgeInflater(LayoutInflater original, Context newContext) { super(original, newContext); newContext = getBaseContext(newContext); if (newContext instanceof BridgeContext) { mLayoutlibCallback = ((BridgeContext) newContext).getLayoutlibCallback(); + mLoadAppCompatViews = ((BridgeContext) newContext).isAppCompatTheme(); } else { mLayoutlibCallback = null; + mLoadAppCompatViews = false; } } @@ -90,10 +133,11 @@ public final class BridgeInflater extends LayoutInflater { * @param context The Android application context. * @param layoutlibCallback the {@link LayoutlibCallback} object. */ - public BridgeInflater(Context context, LayoutlibCallback layoutlibCallback) { + public BridgeInflater(BridgeContext context, LayoutlibCallback layoutlibCallback) { super(context); mLayoutlibCallback = layoutlibCallback; mConstructorArgs[0] = context; + mLoadAppCompatViews = context.isAppCompatTheme(); } @Override @@ -101,26 +145,39 @@ public final class BridgeInflater extends LayoutInflater { View view = null; try { - // First try to find a class using the default Android prefixes - for (String prefix : sClassPrefixList) { - try { - view = createView(name, prefix, attrs); - if (view != null) { - break; - } - } catch (ClassNotFoundException e) { - // Ignore. We'll try again using the base class below. + if (mLoadAppCompatViews + && APPCOMPAT_VIEWS.contains(name) + && !mFailedAppCompatViews.contains(name)) { + // We are using an AppCompat theme so try to load the appcompat views + view = loadCustomView(APPCOMPAT_WIDGET_PREFIX + name, attrs, true); + + if (view == null) { + mFailedAppCompatViews.add(name); // Do not try this one anymore } } - // Next try using the parent loader. This will most likely only work for - // fully-qualified class names. - try { - if (view == null) { - view = super.onCreateView(name, attrs); + if (view == null) { + // First try to find a class using the default Android prefixes + for (String prefix : sClassPrefixList) { + try { + view = createView(name, prefix, attrs); + if (view != null) { + break; + } + } catch (ClassNotFoundException e) { + // Ignore. We'll try again using the base class below. + } + } + + // Next try using the parent loader. This will most likely only work for + // fully-qualified class names. + try { + if (view == null) { + view = super.onCreateView(name, attrs); + } + } catch (ClassNotFoundException e) { + // Ignore. We'll try again using the custom view loader below. } - } catch (ClassNotFoundException e) { - // Ignore. We'll try again using the custom view loader below. } // Finally try again using the custom view loader @@ -144,9 +201,26 @@ public final class BridgeInflater extends LayoutInflater { @Override public View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { - View view; + View view = null; + if (name.equals("view")) { + // This is usually done by the superclass but this allows us catching the error and + // reporting something useful. + name = attrs.getAttributeValue(null, "class"); + + if (name == null) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to inflate view tag without " + + "class attribute", null); + // We weren't able to resolve the view so we just pass a mock View to be able to + // continue rendering. + view = new MockView(context, attrs); + ((MockView) view).setText("view"); + } + } + try { - view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttr); + if (view == null) { + view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttr); + } } catch (InflateException e) { // Creation of ContextThemeWrapper code is same as in the super method. // Apply a theme wrapper, if allowed and one is specified. @@ -235,17 +309,29 @@ public final class BridgeInflater extends LayoutInflater { return null; } - private View loadCustomView(String name, AttributeSet attrs) throws Exception { + /** + * Instantiates the given view name and returns the instance. If the view doesn't exist, a + * MockView or null might be returned. + * @param name the custom view name + * @param attrs the {@link AttributeSet} to be passed to the view constructor + * @param silent if true, errors while loading the view won't be reported and, if the view + * doesn't exist, null will be returned. + */ + private View loadCustomView(String name, AttributeSet attrs, boolean silent) throws Exception { if (mLayoutlibCallback != null) { // first get the classname in case it's not the node name if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); + if (name == null) { + return null; + } } mConstructorArgs[1] = attrs; - Object customView = mLayoutlibCallback.loadView(name, mConstructorSignature, - mConstructorArgs); + Object customView = silent ? + mLayoutlibCallback.loadClass(name, mConstructorSignature, mConstructorArgs) + : mLayoutlibCallback.loadView(name, mConstructorSignature, mConstructorArgs); if (customView instanceof View) { return (View)customView; @@ -255,6 +341,10 @@ public final class BridgeInflater extends LayoutInflater { return null; } + private View loadCustomView(String name, AttributeSet attrs) throws Exception { + return loadCustomView(name, attrs, false); + } + private void setupViewInContext(View view, AttributeSet attrs) { Context context = getContext(); context = getBaseContext(context); @@ -300,6 +390,17 @@ public final class BridgeInflater extends LayoutInflater { getDrawerLayoutMap().put(view, attrVal); } } + else if (view instanceof NumberPicker) { + NumberPicker numberPicker = (NumberPicker) view; + String minValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "minValue"); + if (minValue != null) { + numberPicker.setMinValue(Integer.parseInt(minValue)); + } + String maxValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "maxValue"); + if (maxValue != null) { + numberPicker.setMaxValue(Integer.parseInt(maxValue)); + } + } } } diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index 0c3231bcde60..a0ded8745508 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -20,6 +20,7 @@ import android.graphics.Point; import android.graphics.Rect; import com.android.internal.app.IAssistScreenshotReceiver; import com.android.internal.os.IResultReceiver; +import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; @@ -30,6 +31,7 @@ import android.graphics.Bitmap; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.DisplayMetrics; import android.view.AppTransitionAnimationSpec; @@ -79,13 +81,13 @@ public class IWindowManagerImpl implements IWindowManager { public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4, boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9, boolean arg10, Rect arg11, Configuration arg12, int arg13, boolean arg14, boolean arg15, int arg16, - int arg17) + int arg17, boolean arg18) throws RemoteException { // TODO Auto-generated method stub } @Override - public void addWindowToken(IBinder arg0, int arg1) throws RemoteException { + public void addWindowToken(IBinder arg0, int arg1, int arg2) throws RemoteException { // TODO Auto-generated method stub } @@ -276,13 +278,13 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public void removeAppToken(IBinder arg0) throws RemoteException { + public void removeAppToken(IBinder arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub } @Override - public void removeWindowToken(IBinder arg0) throws RemoteException { + public void removeWindowToken(IBinder arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub } @@ -326,7 +328,7 @@ public class IWindowManagerImpl implements IWindowManager { @Override public void setAppTask(IBinder arg0, int arg1, int arg2, Rect arg3, Configuration arg4, - int arg5, boolean arg6) + int arg5, boolean arg6, boolean arg7) throws RemoteException { // TODO Auto-generated method stub } @@ -412,7 +414,8 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public int[] setNewConfiguration(Configuration arg0) throws RemoteException { + public int[] setNewDisplayOverrideConfiguration(Configuration arg0, int displayId) + throws RemoteException { // TODO Auto-generated method stub return null; } @@ -486,7 +489,7 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1) + public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1, int arg2) throws RemoteException { // TODO Auto-generated method stub return null; @@ -514,11 +517,11 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public void dismissKeyguard() { + public void dismissKeyguard(IKeyguardDismissCallback callback) throws RemoteException { } @Override - public void keyguardGoingAway(int flags) throws RemoteException { + public void setSwitchingUser(boolean switching) throws RemoteException { } @Override @@ -581,6 +584,20 @@ public class IWindowManagerImpl implements IWindowManager { } @Override + public void registerPinnedStackListener(int displayId, IPinnedStackListener listener) throws RemoteException { + } + + @Override + public Rect getPictureInPictureDefaultBounds(int displayId) { + return null; + } + + @Override + public Rect getPictureInPictureMovementBounds(int displayId) { + return null; + } + + @Override public void setResizeDimLayer(boolean visible, int targetStackId, float alpha) throws RemoteException { } @@ -595,7 +612,7 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public void getStableInsets(Rect outInsets) throws RemoteException { + public void getStableInsets(int displayId, Rect outInsets) throws RemoteException { } @Override @@ -603,13 +620,24 @@ public class IWindowManagerImpl implements IWindowManager { throws RemoteException {} @Override - public void createWallpaperInputConsumer(InputChannel inputChannel) throws RemoteException {} + public void createInputConsumer(String name, InputChannel inputChannel) + throws RemoteException {} @Override - public void removeWallpaperInputConsumer() throws RemoteException {} + public boolean destroyInputConsumer(String name) throws RemoteException { + return false; + } @Override public Bitmap screenshotWallpaper() throws RemoteException { return null; } + + @Override + public void enableSurfaceTrace(ParcelFileDescriptor fd) throws RemoteException { + } + + @Override + public void disableSurfaceTrace() throws RemoteException { + } } diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java index ea9a255e8561..fad35d2102d8 100644 --- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java +++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java @@ -16,6 +16,7 @@ package android.view; +import com.android.layoutlib.bridge.impl.GcSnapshot; import com.android.layoutlib.bridge.impl.ResourceHelper; import android.graphics.Canvas; @@ -32,6 +33,8 @@ import android.graphics.RectF; import android.graphics.Region.Op; import android.graphics.Shader.TileMode; +import java.awt.Rectangle; + /** * Paints shadow for rounded rectangles. Inspiration from CardView. Couldn't use that directly, * since it modifies the size of the content, that we can't do. @@ -54,12 +57,18 @@ public class RectShadowPainter { if (saved == -1) { return; } + + float radius = viewOutline.getRadius(); + if (radius <= 0) { + // We can not paint a shadow with radius 0 + return; + } + try { Paint cornerPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); cornerPaint.setStyle(Style.FILL); Paint edgePaint = new Paint(cornerPaint); edgePaint.setAntiAlias(false); - float radius = viewOutline.getRadius(); float outerArcRadius = radius + shadowSize; int[] colors = {START_COLOR, START_COLOR, END_COLOR}; cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors, @@ -121,9 +130,16 @@ public class RectShadowPainter { int saved = canvas.save(); // Usually canvas has been translated to the top left corner of the view when this is // called. So, setting a clip rect at 0,0 will clip the top left part of the shadow. - // Thus, we just expand in each direction by width and height of the canvas. - canvas.clipRect(-canvas.getWidth(), -canvas.getHeight(), canvas.getWidth(), - canvas.getHeight(), Op.REPLACE); + // Thus, we just expand in each direction by width and height of the canvas, while staying + // inside the original drawing region. + GcSnapshot snapshot = Canvas_Delegate.getDelegate(canvas).getSnapshot(); + Rectangle originalClip = snapshot.getOriginalClip(); + if (originalClip != null) { + canvas.clipRect(originalClip.x, originalClip.y, originalClip.x + originalClip.width, + originalClip.y + originalClip.height, Op.REPLACE); + canvas.clipRect(-canvas.getWidth(), -canvas.getHeight(), canvas.getWidth(), + canvas.getHeight(), Op.INTERSECT); + } canvas.translate(0, shadowSize / 2f); return saved; } diff --git a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java index 24f788766cc9..a801cb0606e0 100644 --- a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java +++ b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java @@ -21,6 +21,8 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.graphics.Matrix; +import libcore.util.NativeAllocationRegistry_Delegate; + /** * Delegate implementing the native methods of {@link RenderNode} * <p/> @@ -35,7 +37,7 @@ public class RenderNode_Delegate { // ---- delegate manager ---- private static final DelegateManager<RenderNode_Delegate> sManager = new DelegateManager<RenderNode_Delegate>(RenderNode_Delegate.class); - + private static long sFinalizer = -1; private float mLift; private float mTranslationX; @@ -62,8 +64,13 @@ public class RenderNode_Delegate { } @LayoutlibDelegate - /*package*/ static void nDestroyRenderNode(long renderNode) { - sManager.removeJavaReferenceFor(renderNode); + /*package*/ static long nGetNativeFinalizer() { + synchronized (RenderNode_Delegate.class) { + if (sFinalizer == -1) { + sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor); + } + } + return sFinalizer; } @LayoutlibDelegate diff --git a/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java b/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java new file mode 100644 index 000000000000..06874bd57d2b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textservice; + +import com.android.internal.textservice.ISpellCheckerSessionListener; +import com.android.internal.textservice.ITextServicesManager; +import com.android.internal.textservice.ITextServicesSessionListener; + +import android.content.Context; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener; + +import java.util.Locale; + +/** + * System API to the overall text services, which arbitrates interaction between applications + * and text services. You can retrieve an instance of this interface with + * {@link Context#getSystemService(String) Context.getSystemService()}. + * + * The user can change the current text services in Settings. And also applications can specify + * the target text services. + * + * <h3>Architecture Overview</h3> + * + * <p>There are three primary parties involved in the text services + * framework (TSF) architecture:</p> + * + * <ul> + * <li> The <strong>text services manager</strong> as expressed by this class + * is the central point of the system that manages interaction between all + * other parts. It is expressed as the client-side API here which exists + * in each application context and communicates with a global system service + * that manages the interaction across all processes. + * <li> A <strong>text service</strong> implements a particular + * interaction model allowing the client application to retrieve information of text. + * The system binds to the current text service that is in use, causing it to be created and run. + * <li> Multiple <strong>client applications</strong> arbitrate with the text service + * manager for connections to text services. + * </ul> + * + * <h3>Text services sessions</h3> + * <ul> + * <li>The <strong>spell checker session</strong> is one of the text services. + * {@link android.view.textservice.SpellCheckerSession}</li> + * </ul> + * + */ +public final class TextServicesManager { + private static final String TAG = TextServicesManager.class.getSimpleName(); + private static final boolean DBG = false; + + private static TextServicesManager sInstance; + + private final ITextServicesManager mService; + + private TextServicesManager() { + mService = new FakeTextServicesManager(); + } + + /** + * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist. + * @hide + */ + public static TextServicesManager getInstance() { + synchronized (TextServicesManager.class) { + if (sInstance == null) { + sInstance = new TextServicesManager(); + } + return sInstance; + } + } + + /** + * Returns the language component of a given locale string. + */ + private static String parseLanguageFromLocaleString(String locale) { + final int idx = locale.indexOf('_'); + if (idx < 0) { + return locale; + } else { + return locale.substring(0, idx); + } + } + + /** + * Get a spell checker session for the specified spell checker + * @param locale the locale for the spell checker. If {@code locale} is null and + * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be + * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true, + * the locale specified in Settings will be returned only when it is same as {@code locale}. + * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is + * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be + * selected. + * @param listener a spell checker session lister for getting results from a spell checker. + * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled + * languages in settings will be returned. + * @return the spell checker session of the spell checker + */ + public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale, + SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) { + if (listener == null) { + throw new NullPointerException(); + } + if (!referToSpellCheckerLanguageSettings && locale == null) { + throw new IllegalArgumentException("Locale should not be null if you don't refer" + + " settings."); + } + + if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) { + return null; + } + + final SpellCheckerInfo sci; + try { + sci = mService.getCurrentSpellChecker(null); + } catch (RemoteException e) { + return null; + } + if (sci == null) { + return null; + } + SpellCheckerSubtype subtypeInUse = null; + if (referToSpellCheckerLanguageSettings) { + subtypeInUse = getCurrentSpellCheckerSubtype(true); + if (subtypeInUse == null) { + return null; + } + if (locale != null) { + final String subtypeLocale = subtypeInUse.getLocale(); + final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale); + if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) { + return null; + } + } + } else { + final String localeStr = locale.toString(); + for (int i = 0; i < sci.getSubtypeCount(); ++i) { + final SpellCheckerSubtype subtype = sci.getSubtypeAt(i); + final String tempSubtypeLocale = subtype.getLocale(); + final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale); + if (tempSubtypeLocale.equals(localeStr)) { + subtypeInUse = subtype; + break; + } else if (tempSubtypeLanguage.length() >= 2 && + locale.getLanguage().equals(tempSubtypeLanguage)) { + subtypeInUse = subtype; + } + } + } + if (subtypeInUse == null) { + return null; + } + final SpellCheckerSession session = new SpellCheckerSession( + sci, mService, listener, subtypeInUse); + try { + mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(), + session.getTextServicesSessionListener(), + session.getSpellCheckerSessionListener(), bundle); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return session; + } + + /** + * @hide + */ + public SpellCheckerInfo[] getEnabledSpellCheckers() { + try { + final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers(); + if (DBG) { + Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null")); + } + return retval; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public SpellCheckerInfo getCurrentSpellChecker() { + try { + // Passing null as a locale for ICS + return mService.getCurrentSpellChecker(null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public void setCurrentSpellChecker(SpellCheckerInfo sci) { + try { + if (sci == null) { + throw new NullPointerException("SpellCheckerInfo is null."); + } + mService.setCurrentSpellChecker(null, sci.getId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public SpellCheckerSubtype getCurrentSpellCheckerSubtype( + boolean allowImplicitlySelectedSubtype) { + try { + // Passing null as a locale until we support multiple enabled spell checker subtypes. + return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public void setSpellCheckerSubtype(SpellCheckerSubtype subtype) { + try { + final int hashCode; + if (subtype == null) { + hashCode = 0; + } else { + hashCode = subtype.hashCode(); + } + mService.setCurrentSpellCheckerSubtype(null, hashCode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public void setSpellCheckerEnabled(boolean enabled) { + try { + mService.setSpellCheckerEnabled(enabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public boolean isSpellCheckerEnabled() { + try { + return mService.isSpellCheckerEnabled(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static class FakeTextServicesManager implements ITextServicesManager { + + @Override + public void finishSpellCheckerService(ISpellCheckerSessionListener arg0) + throws RemoteException { + // TODO Auto-generated method stub + + } + + @Override + public SpellCheckerInfo getCurrentSpellChecker(String arg0) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + @Override + public SpellCheckerSubtype getCurrentSpellCheckerSubtype(String arg0, boolean arg1) + throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + @Override + public SpellCheckerInfo[] getEnabledSpellCheckers() throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + @Override + public void getSpellCheckerService(String arg0, String arg1, + ITextServicesSessionListener arg2, ISpellCheckerSessionListener arg3, Bundle arg4) + throws RemoteException { + // TODO Auto-generated method stub + + } + + @Override + public boolean isSpellCheckerEnabled() throws RemoteException { + // TODO Auto-generated method stub + return false; + } + + @Override + public void setCurrentSpellChecker(String arg0, String arg1) throws RemoteException { + // TODO Auto-generated method stub + + } + + @Override + public void setCurrentSpellCheckerSubtype(String arg0, int arg1) throws RemoteException { + // TODO Auto-generated method stub + + } + + @Override + public void setSpellCheckerEnabled(boolean arg0) throws RemoteException { + // TODO Auto-generated method stub + + } + + @Override + public IBinder asBinder() { + // TODO Auto-generated method stub + return null; + } + + } +}
\ No newline at end of file diff --git a/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java deleted file mode 100644 index 3017292d8337..000000000000 --- a/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.textservice; - -import com.android.tools.layoutlib.annotations.LayoutlibDelegate; - -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.view.textservice.SpellCheckerInfo; -import android.view.textservice.SpellCheckerSubtype; - - -/** - * Delegate used to provide new implementation of a select few methods of - * {@link ITextServicesManager$Stub} - * - * Through the layoutlib_create tool, the original methods of Stub have been replaced - * by calls to methods of the same name in this delegate class. - * - */ -public class ITextServicesManager_Stub_Delegate { - - @LayoutlibDelegate - public static ITextServicesManager asInterface(IBinder obj) { - // ignore the obj and return a fake interface implementation - return new FakeTextServicesManager(); - } - - private static class FakeTextServicesManager implements ITextServicesManager { - - @Override - public void finishSpellCheckerService(ISpellCheckerSessionListener arg0) - throws RemoteException { - // TODO Auto-generated method stub - - } - - @Override - public SpellCheckerInfo getCurrentSpellChecker(String arg0) throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - @Override - public SpellCheckerSubtype getCurrentSpellCheckerSubtype(String arg0, boolean arg1) - throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - @Override - public SpellCheckerInfo[] getEnabledSpellCheckers() throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - @Override - public void getSpellCheckerService(String arg0, String arg1, - ITextServicesSessionListener arg2, ISpellCheckerSessionListener arg3, Bundle arg4) - throws RemoteException { - // TODO Auto-generated method stub - - } - - @Override - public boolean isSpellCheckerEnabled() throws RemoteException { - // TODO Auto-generated method stub - return false; - } - - @Override - public void setCurrentSpellChecker(String arg0, String arg1) throws RemoteException { - // TODO Auto-generated method stub - - } - - @Override - public void setCurrentSpellCheckerSubtype(String arg0, int arg1) throws RemoteException { - // TODO Auto-generated method stub - - } - - @Override - public void setSpellCheckerEnabled(boolean arg0) throws RemoteException { - // TODO Auto-generated method stub - - } - - @Override - public IBinder asBinder() { - // TODO Auto-generated method stub - return null; - } - - } - } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java index e4cbb2f4c02d..3471165e196b 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java @@ -145,4 +145,10 @@ public final class BridgeContentProvider implements IContentProvider { public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException { return null; } + + @Override + public boolean refresh(String callingPkg, Uri url, Bundle args, + ICancellationSignal cancellationSignal) throws RemoteException { + return false; + } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index 616cb5761402..1b3b563fb51e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -76,6 +76,7 @@ import android.os.Parcel; import android.os.PowerManager; import android.os.RemoteException; import android.os.ResultReceiver; +import android.os.ShellCallback; import android.os.UserHandle; import android.util.AttributeSet; import android.util.DisplayMetrics; @@ -110,6 +111,7 @@ import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_AP */ @SuppressWarnings("deprecation") // For use of Pair. public final class BridgeContext extends Context { + private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat"; /** The map adds cookies to each view so that IDE can link xml tags to views. */ private final HashMap<View, Object> mViewKeyMap = new HashMap<>(); @@ -153,6 +155,7 @@ public final class BridgeContext extends Context { private ClassLoader mClassLoader; private IBinder mBinder; private PackageManager mPackageManager; + private Boolean mIsThemeAppCompat; /** * Some applications that target both pre API 17 and post API 17, set the newer attrs to @@ -371,7 +374,9 @@ public final class BridgeContext extends Context { return true; } - return false; + // If the value is not a valid reference, fallback to pass the value as a string. + outValue.string = value.getValue(); + return true; } @@ -479,6 +484,36 @@ public final class BridgeContext extends Context { return Pair.of(null, Boolean.FALSE); } + /** + * Returns whether the current selected theme is based on AppCompat + */ + public boolean isAppCompatTheme() { + // If a cached value exists, return it. + if (mIsThemeAppCompat != null) { + return mIsThemeAppCompat; + } + // Ideally, we should check if the corresponding activity extends + // android.support.v7.app.ActionBarActivity, and not care about the theme name at all. + StyleResourceValue defaultTheme = mRenderResources.getDefaultTheme(); + // We can't simply check for parent using resources.themeIsParentOf() since the + // inheritance structure isn't really what one would expect. The first common parent + // between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21). + boolean isThemeAppCompat = false; + for (int i = 0; i < 50; i++) { + if (defaultTheme == null) { + break; + } + // for loop ensures that we don't run into cyclic theme inheritance. + if (defaultTheme.getName().startsWith(PREFIX_THEME_APPCOMPAT)) { + isThemeAppCompat = true; + break; + } + defaultTheme = mRenderResources.getParent(defaultTheme); + } + mIsThemeAppCompat = isThemeAppCompat; + return isThemeAppCompat; + } + @SuppressWarnings("deprecation") private ILayoutPullParser getParser(ResourceReference resource) { ILayoutPullParser parser; @@ -831,45 +866,55 @@ public final class BridgeContext extends Context { } } - // if there's no direct value for this attribute in the XML, we look for default - // values in the widget defStyle, and then in the theme. - if (value == null) { - ResourceValue resValue = null; - + // Calculate the default value from the Theme in two cases: + // - If defaultPropMap is not null, get the default value to add it to the list + // of default values of properties. + // - If value is null, it means that the attribute is not directly set as an + // attribute in the XML so try to get the default value. + ResourceValue defaultValue = null; + if (defaultPropMap != null || value == null) { // look for the value in the custom style first (and its parent if needed) if (customStyleValues != null) { - resValue = mRenderResources.findItemInStyle(customStyleValues, - attrName, frameworkAttr); + defaultValue = mRenderResources.findItemInStyle(customStyleValues, attrName, + frameworkAttr); } // then look for the value in the default Style (and its parent if needed) - if (resValue == null && defStyleValues != null) { - resValue = mRenderResources.findItemInStyle(defStyleValues, - attrName, frameworkAttr); + if (defaultValue == null && defStyleValues != null) { + defaultValue = mRenderResources.findItemInStyle(defStyleValues, attrName, + frameworkAttr); } // if the item is not present in the defStyle, we look in the main theme (and // its parent themes) - if (resValue == null) { - resValue = mRenderResources.findItemInTheme(attrName, frameworkAttr); + if (defaultValue == null) { + defaultValue = mRenderResources.findItemInTheme(attrName, frameworkAttr); } // if we found a value, we make sure this doesn't reference another value. // So we resolve it. - if (resValue != null) { - String preResolve = resValue.getValue(); - resValue = mRenderResources.resolveResValue(resValue); + if (defaultValue != null) { + String preResolve = defaultValue.getValue(); + defaultValue = mRenderResources.resolveResValue(defaultValue); if (defaultPropMap != null) { defaultPropMap.put( frameworkAttr ? SdkConstants.PREFIX_ANDROID + attrName : - attrName, - new Property(preResolve, resValue.getValue())); + attrName, new Property(preResolve, defaultValue.getValue())); } + } + } + // Done calculating the defaultValue + // if there's no direct value for this attribute in the XML, we look for default + // values in the widget defStyle, and then in the theme. + if (value == null) { + // if we found a value, we make sure this doesn't reference another value. + // So we resolve it. + if (defaultValue != null) { // If the value is a reference to another theme attribute that doesn't // exist, we should log a warning and omit it. - String val = resValue.getValue(); + String val = defaultValue.getValue(); if (val != null && val.startsWith(SdkConstants.PREFIX_THEME_REF)) { if (!attrName.equals(RTL_ATTRS.get(val)) || getApplicationInfo().targetSdkVersion < @@ -880,11 +925,11 @@ public final class BridgeContext extends Context { String.format("Failed to find '%s' in current theme.", val), val); } - resValue = null; + defaultValue = null; } } - ta.bridgeSetValue(index, attrName, frameworkAttr, resValue); + ta.bridgeSetValue(index, attrName, frameworkAttr, defaultValue); } else { // there is a value in the XML, but we need to resolve it in case it's // referencing another resource or a theme value. @@ -1138,7 +1183,7 @@ public final class BridgeContext extends Context { @Override public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, - String[] args, ResultReceiver resultReceiver) { + String[] args, ShellCallback shellCallback, ResultReceiver resultReceiver) { } }; } @@ -1615,6 +1660,12 @@ public final class BridgeContext extends Context { // pass } + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, Bundle options) { + // pass + } + public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, int appOp) { // pass diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java index b3ed9e1a0164..f47b1050ca73 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java @@ -272,6 +272,11 @@ public class BridgePackageManager extends PackageManager { } @Override + public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) { + return null; + } + + @Override public List<EphemeralApplicationInfo> getEphemeralApplications() { return null; } @@ -490,12 +495,6 @@ public class BridgePackageManager extends PackageManager { } @Override - public Drawable getManagedUserBadgedDrawable(Drawable drawable, Rect badgeLocation, - int badgeDensity) { - return null; - } - - @Override public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) { return null; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java index d432120ccb6f..ab278195f328 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java @@ -21,6 +21,7 @@ import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.RenderParamsFlags; +import com.android.layoutlib.bridge.util.ReflectionUtils; import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException; import android.annotation.NonNull; @@ -116,7 +117,7 @@ public class RecyclerViewUtil { private static void setProperty(@NonNull Object object, @NonNull String propertyClassName, @NonNull Object propertyValue, @NonNull String propertySetter) throws ReflectionException { - Class<?> propertyClass = getClassInstance(propertyValue, propertyClassName); + Class<?> propertyClass = ReflectionUtils.getClassInstance(propertyValue, propertyClassName); setProperty(object, propertyClass, propertyValue, propertySetter); } @@ -126,22 +127,4 @@ public class RecyclerViewUtil { invoke(getMethod(object.getClass(), propertySetter, propertyClass), object, propertyValue); } - /** - * Looks through the class hierarchy of {@code object} at runtime and returns the class matching - * the name {@code className}. - * <p/> - * This is used when we cannot use Class.forName() since the class we want was loaded from a - * different ClassLoader. - */ - @NonNull - private static Class<?> getClassInstance(@NonNull Object object, @NonNull String className) { - Class<?> superClass = object.getClass(); - while (superClass != null) { - if (className.equals(superClass.getName())) { - return superClass; - } - superClass = superClass.getSuperclass(); - } - throw new RuntimeException("invalid object/classname combination."); - } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java new file mode 100644 index 000000000000..6ad9efc81afa --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.android.support; + +import com.android.ide.common.rendering.api.LayoutlibCallback; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.StyleResourceValue; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; +import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.view.ContextThemeWrapper; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.LinearLayout.LayoutParams; +import android.widget.ScrollView; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; + +import static com.android.layoutlib.bridge.util.ReflectionUtils.getAccessibleMethod; +import static com.android.layoutlib.bridge.util.ReflectionUtils.getClassInstance; +import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod; +import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke; + +/** + * Class with utility methods to instantiate Preferences provided by the support library. + * This class uses reflection to access the support preference objects so it heavily depends on + * the API being stable. + */ +public class SupportPreferencesUtil { + private static final String PREFERENCE_PKG = "android.support.v7.preference"; + private static final String PREFERENCE_MANAGER = PREFERENCE_PKG + ".PreferenceManager"; + private static final String PREFERENCE_GROUP = PREFERENCE_PKG + ".PreferenceGroup"; + private static final String PREFERENCE_GROUP_ADAPTER = + PREFERENCE_PKG + ".PreferenceGroupAdapter"; + private static final String PREFERENCE_INFLATER = PREFERENCE_PKG + ".PreferenceInflater"; + + private SupportPreferencesUtil() { + } + + @NonNull + private static Object instantiateClass(@NonNull LayoutlibCallback callback, + @NonNull String className, @Nullable Class[] constructorSignature, + @Nullable Object[] constructorArgs) throws ReflectionException { + try { + Object instance = callback.loadClass(className, constructorSignature, constructorArgs); + if (instance == null) { + throw new ClassNotFoundException(className + " class not found"); + } + return instance; + } catch (ClassNotFoundException e) { + throw new ReflectionException(e); + } + } + + @NonNull + private static Object createPreferenceGroupAdapter(@NonNull LayoutlibCallback callback, + @NonNull Object preferenceScreen) throws ReflectionException { + Class<?> preferenceGroupClass = getClassInstance(preferenceScreen, PREFERENCE_GROUP); + + return instantiateClass(callback, PREFERENCE_GROUP_ADAPTER, + new Class[]{preferenceGroupClass}, new Object[]{preferenceScreen}); + } + + @NonNull + private static Object createInflatedPreference(@NonNull LayoutlibCallback callback, + @NonNull Context context, @NonNull XmlPullParser parser, @NonNull Object preferenceScreen, + @NonNull Object preferenceManager) throws ReflectionException { + Class<?> preferenceGroupClass = getClassInstance(preferenceScreen, PREFERENCE_GROUP); + Object preferenceInflater = instantiateClass(callback, PREFERENCE_INFLATER, + new Class[]{Context.class, preferenceManager.getClass()}, + new Object[]{context, preferenceManager}); + Object inflatedPreference = + invoke(getAccessibleMethod(preferenceInflater.getClass(), "inflate", + XmlPullParser.class, preferenceGroupClass), preferenceInflater, parser, + null); + + if (inflatedPreference == null) { + throw new ReflectionException("inflate method returned null"); + } + + return inflatedPreference; + } + + /** + * Returns a themed wrapper context of {@link BridgeContext} with the theme specified in + * ?attr/preferenceTheme applied to it. + */ + @Nullable + private static Context getThemedContext(@NonNull BridgeContext bridgeContext) { + RenderResources resources = bridgeContext.getRenderResources(); + ResourceValue preferenceTheme = resources.findItemInTheme("preferenceTheme", false); + + if (preferenceTheme != null) { + // resolve it, if needed. + preferenceTheme = resources.resolveResValue(preferenceTheme); + } + if (preferenceTheme instanceof StyleResourceValue) { + int styleId = bridgeContext.getDynamicIdByStyle(((StyleResourceValue) preferenceTheme)); + if (styleId != 0) { + return new ContextThemeWrapper(bridgeContext, styleId); + } + } + + return null; + } + + /** + * Returns a {@link LinearLayout} containing all the UI widgets representing the preferences + * passed in the group adapter. + */ + @Nullable + private static LinearLayout setUpPreferencesListView(@NonNull BridgeContext bridgeContext, + @NonNull Context themedContext, @NonNull ArrayList<Object> viewCookie, + @NonNull Object preferenceGroupAdapter) throws ReflectionException { + // Setup the LinearLayout that will contain the preferences + LinearLayout listView = new LinearLayout(themedContext); + listView.setOrientation(LinearLayout.VERTICAL); + listView.setLayoutParams( + new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + + if (!viewCookie.isEmpty()) { + bridgeContext.addViewKey(listView, viewCookie.get(0)); + } + + // Get all the preferences and add them to the LinearLayout + Integer preferencesCount = + (Integer) invoke(getMethod(preferenceGroupAdapter.getClass(), "getItemCount"), + preferenceGroupAdapter); + if (preferencesCount == null) { + return listView; + } + + Method getItemId = getMethod(preferenceGroupAdapter.getClass(), "getItemId", int.class); + Method getItemViewType = + getMethod(preferenceGroupAdapter.getClass(), "getItemViewType", int.class); + Method onCreateViewHolder = + getMethod(preferenceGroupAdapter.getClass(), "onCreateViewHolder", ViewGroup.class, + int.class); + for (int i = 0; i < preferencesCount; i++) { + Long id = (Long) invoke(getItemId, preferenceGroupAdapter, i); + if (id == null) { + continue; + } + + // Get the type of the preference layout and bind it to a newly created view holder + Integer type = (Integer) invoke(getItemViewType, preferenceGroupAdapter, i); + Object viewHolder = + invoke(onCreateViewHolder, preferenceGroupAdapter, listView, type); + if (viewHolder == null) { + continue; + } + invoke(getMethod(preferenceGroupAdapter.getClass(), "onBindViewHolder", + viewHolder.getClass(), int.class), preferenceGroupAdapter, viewHolder, i); + + try { + // Get the view from the view holder and add it to our layout + View itemView = + (View) viewHolder.getClass().getField("itemView").get(viewHolder); + + int arrayPosition = id.intValue() - 1; // IDs are 1 based + if (arrayPosition >= 0 && arrayPosition < viewCookie.size()) { + bridgeContext.addViewKey(itemView, viewCookie.get(arrayPosition)); + } + listView.addView(itemView); + } catch (IllegalAccessException | NoSuchFieldException ignored) { + } + } + + return listView; + } + + /** + * Inflates a preferences layout using the support library. If the support library is not + * available, this method will return null without advancing the parsers. + */ + @Nullable + public static View inflatePreference(@NonNull BridgeContext bridgeContext, + @NonNull XmlPullParser parser, @Nullable ViewGroup root) { + try { + LayoutlibCallback callback = bridgeContext.getLayoutlibCallback(); + + Context context = getThemedContext(bridgeContext); + if (context == null) { + // Probably we couldn't find the "preferenceTheme" in the theme + return null; + } + + // Create PreferenceManager + Object preferenceManager = + instantiateClass(callback, PREFERENCE_MANAGER, new Class[]{Context.class}, + new Object[]{context}); + + // From this moment on, we can assume that we found the support library and that + // nothing should fail + + // Create PreferenceScreen + Object preferenceScreen = + invoke(getMethod(preferenceManager.getClass(), "createPreferenceScreen", + Context.class), preferenceManager, context); + if (preferenceScreen == null) { + return null; + } + + // Setup a parser that stores the list of cookies in the same order as the preferences + // are inflated. That way we can later reconstruct the list using the preference id + // since they are sequential and start in 1. + ArrayList<Object> viewCookie = new ArrayList<>(); + if (parser instanceof BridgeXmlBlockParser) { + // Setup a parser that stores the XmlTag + parser = new BridgeXmlBlockParser(parser, null, false) { + @Override + public Object getViewCookie() { + return ((BridgeXmlBlockParser) getParser()).getViewCookie(); + } + + @Override + public int next() throws XmlPullParserException, IOException { + int ev = super.next(); + if (ev == XmlPullParser.START_TAG) { + viewCookie.add(this.getViewCookie()); + } + + return ev; + } + }; + } + + // Create the PreferenceInflater + Object inflatedPreference = + createInflatedPreference(callback, context, parser, preferenceScreen, + preferenceManager); + + // Setup the RecyclerView (set adapter and layout manager) + Object preferenceGroupAdapter = + createPreferenceGroupAdapter(callback, inflatedPreference); + + // Instead of just setting the group adapter as adapter for a RecyclerView, we manually + // get all the items and add them to a LinearLayout. This allows us to set the view + // cookies so the preferences are correctly linked to their XML. + LinearLayout listView = setUpPreferencesListView(bridgeContext, context, viewCookie, + preferenceGroupAdapter); + + ScrollView scrollView = new ScrollView(context); + scrollView.setLayoutParams( + new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + scrollView.addView(listView); + + if (root != null) { + root.addView(scrollView); + } + + return scrollView; + } catch (ReflectionException e) { + return null; + } + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java index c59b1a66bb02..11da44583451 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java @@ -24,8 +24,8 @@ import android.util.SparseArray; import java.io.PrintStream; import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.atomic.AtomicLong; /** @@ -33,7 +33,7 @@ import java.util.concurrent.atomic.AtomicLong; * * This is used in conjunction with layoublib_create: certain Android java classes are mere * wrappers around a heavily native based implementation, and we need a way to run these classes - * in our Eclipse rendering framework without bringing all the native code from the Android + * in our Android Studio rendering framework without bringing all the native code from the Android * platform. * * Thus we instruct layoutlib_create to modify the bytecode of these classes to replace their @@ -61,7 +61,7 @@ import java.util.concurrent.atomic.AtomicLong; * following mechanism: * * - {@link #addNewDelegate(Object)} and {@link #removeJavaReferenceFor(long)} adds and removes - * the delegate to/from a list. This list hold the reference and prevents the GC from reclaiming + * the delegate to/from a set. This set holds the reference and prevents the GC from reclaiming * the delegate. * * - {@link #addNewDelegate(Object)} also adds the delegate to a {@link SparseArray} that holds a @@ -76,12 +76,12 @@ public final class DelegateManager<T> { @SuppressWarnings("FieldCanBeLocal") private final Class<T> mClass; private static final SparseWeakArray<Object> sDelegates = new SparseWeakArray<>(); - /** list used to store delegates when their main object holds a reference to them. + /** Set used to store delegates when their main object holds a reference to them. * This is to ensure that the WeakReference in the SparseWeakArray doesn't get GC'ed * @see #addNewDelegate(Object) * @see #removeJavaReferenceFor(long) */ - private static final List<Object> sJavaReferences = new ArrayList<>(); + private static final Set<Object> sJavaReferences = new HashSet<>(); private static final AtomicLong sDelegateCounter = new AtomicLong(1); public DelegateManager(Class<T> theClass) { @@ -129,7 +129,7 @@ public final class DelegateManager<T> { long native_object = sDelegateCounter.getAndIncrement(); synchronized (DelegateManager.class) { sDelegates.put(native_object, newDelegate); - assert !sJavaReferences.contains(newDelegate); + // Only for development: assert !sJavaReferences.contains(newDelegate); sJavaReferences.add(newDelegate); } @@ -165,5 +165,6 @@ public final class DelegateManager<T> { int idx = sDelegates.indexOfValue(reference); out.printf("[%d] %s\n", sDelegates.keyAt(idx), reference.getClass().getSimpleName()); } + out.printf("\nTotal number of objects: %d\n", sJavaReferences.size()); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java index a39eb4d45ca5..7526e090be50 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java @@ -24,12 +24,13 @@ import android.graphics.Canvas; import android.graphics.ColorFilter_Delegate; import android.graphics.Paint; import android.graphics.Paint_Delegate; +import android.graphics.PorterDuff; +import android.graphics.PorterDuff.Mode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.graphics.Region_Delegate; import android.graphics.Shader_Delegate; -import android.graphics.Xfermode_Delegate; import java.awt.AlphaComposite; import java.awt.Color; @@ -40,6 +41,7 @@ import java.awt.RenderingHints; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Area; +import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.util.ArrayList; @@ -620,7 +622,8 @@ public class GcSnapshot { int y = 0; int width; int height; - Rectangle clipBounds = originalGraphics.getClipBounds(); + Rectangle clipBounds = originalGraphics.getClip() != null ? originalGraphics + .getClipBounds() : null; if (clipBounds != null) { if (clipBounds.width == 0 || clipBounds.height == 0) { // Clip is 0 so no need to paint anything. @@ -825,28 +828,9 @@ public class GcSnapshot { g.setComposite(AlphaComposite.getInstance(forceMode, (float) alpha / 255.f)); return; } - Xfermode_Delegate xfermodeDelegate = paint.getXfermode(); - if (xfermodeDelegate != null) { - if (xfermodeDelegate.isSupported()) { - Composite composite = xfermodeDelegate.getComposite(alpha); - assert composite != null; - if (composite != null) { - g.setComposite(composite); - return; - } - } else { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE, - xfermodeDelegate.getSupportMessage(), - null /*throwable*/, null /*data*/); - } - } - // if there was no custom xfermode, but we have alpha (due to a shader and a non - // opaque alpha channel in the paint color), then we create an AlphaComposite anyway - // that will handle the alpha. - if (alpha != 0xFF) { - g.setComposite(AlphaComposite.getInstance( - AlphaComposite.SRC_OVER, (float) alpha / 255.f)); - } + Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode()); + Composite composite = PorterDuffUtility.getComposite(mode, alpha); + g.setComposite(composite); } private void mapRect(AffineTransform matrix, RectF dst, RectF src) { @@ -869,4 +853,33 @@ public class GcSnapshot { dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7])); } + /** + * Returns the clip of the oldest snapshot of the stack, appropriately translated to be + * expressed in the coordinate system of the latest snapshot. + */ + public Rectangle getOriginalClip() { + GcSnapshot originalSnapshot = this; + while (originalSnapshot.mPrevious != null) { + originalSnapshot = originalSnapshot.mPrevious; + } + if (originalSnapshot.mLayers.isEmpty()) { + return null; + } + Graphics2D graphics2D = originalSnapshot.mLayers.get(0).getGraphics(); + Rectangle bounds = graphics2D.getClipBounds(); + if (bounds == null) { + return null; + } + try { + AffineTransform originalTransform = + ((Graphics2D) graphics2D.create()).getTransform().createInverse(); + AffineTransform latestTransform = getTransform().createInverse(); + bounds.x += latestTransform.getTranslateX() - originalTransform.getTranslateX(); + bounds.y += latestTransform.getTranslateY() - originalTransform.getTranslateY(); + } catch (NoninvertibleTransformException e) { + Bridge.getLog().warning(null, "Non invertible transformation", null); + } + return bounds; + } + } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java index 1afd90d39f31..97698df0f22a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java @@ -20,7 +20,6 @@ import com.android.ide.common.rendering.api.HardwareConfig; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.SessionParams; -import com.android.ide.common.rendering.api.StyleResourceValue; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.RenderParamsFlags; @@ -94,7 +93,6 @@ class Layout extends RelativeLayout { private static final String ATTR_WINDOW_TITLE_SIZE = "windowTitleSize"; private static final String ATTR_WINDOW_TRANSLUCENT_STATUS = StatusBar.ATTR_TRANSLUCENT; private static final String ATTR_WINDOW_TRANSLUCENT_NAV = NavigationBar.ATTR_TRANSLUCENT; - private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat"; // Default sizes private static final int DEFAULT_STATUS_BAR_HEIGHT = 25; @@ -236,7 +234,7 @@ class Layout extends RelativeLayout { boolean isMenu = "menu".equals(params.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG)); BridgeActionBar actionBar; - if (mBuilder.isThemeAppCompat() && !isMenu) { + if (context.isAppCompatTheme() && !isMenu) { actionBar = new AppCompatActionBar(context, params); } else { actionBar = new FrameworkActionBar(context, params); @@ -324,8 +322,6 @@ class Layout extends RelativeLayout { private boolean mTranslucentStatus; private boolean mTranslucentNav; - private Boolean mIsThemeAppCompat; - public Builder(@NonNull SessionParams params, @NonNull BridgeContext context) { mParams = params; mContext = context; @@ -364,8 +360,10 @@ class Layout extends RelativeLayout { return; } // Check if an actionbar is needed - boolean windowActionBar = getBooleanThemeValue(mResources, ATTR_WINDOW_ACTION_BAR, - !isThemeAppCompat(), true); + boolean isMenu = "menu".equals(mParams.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG)); + boolean windowActionBar = isMenu || + getBooleanThemeValue(mResources, ATTR_WINDOW_ACTION_BAR, + !mContext.isAppCompatTheme(), true); if (windowActionBar) { mActionBarSize = getDimension(ATTR_ACTION_BAR_SIZE, true, DEFAULT_TITLE_BAR_HEIGHT); } else { @@ -420,33 +418,6 @@ class Layout extends RelativeLayout { return mParams.getHardwareConfig().hasSoftwareButtons(); } - private boolean isThemeAppCompat() { - // If a cached value exists, return it. - if (mIsThemeAppCompat != null) { - return mIsThemeAppCompat; - } - // Ideally, we should check if the corresponding activity extends - // android.support.v7.app.ActionBarActivity, and not care about the theme name at all. - StyleResourceValue defaultTheme = mResources.getDefaultTheme(); - // We can't simply check for parent using resources.themeIsParentOf() since the - // inheritance structure isn't really what one would expect. The first common parent - // between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21). - boolean isThemeAppCompat = false; - for (int i = 0; i < 50; i++) { - if (defaultTheme == null) { - break; - } - // for loop ensures that we don't run into cyclic theme inheritance. - if (defaultTheme.getName().startsWith(PREFIX_THEME_APPCOMPAT)) { - isThemeAppCompat = true; - break; - } - defaultTheme = mResources.getParent(defaultTheme); - } - mIsThemeAppCompat = isThemeAppCompat; - return isThemeAppCompat; - } - /** * Return true if the status bar or nav bar are present, they are not translucent (i.e * content doesn't overlap with them). diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java index e273b2cd75cc..1ae9cb646cf3 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java @@ -39,8 +39,6 @@ public class ParserFactory { public final static boolean LOG_PARSER = false; - private final static String ENCODING = "UTF-8"; //$NON-NLS-1$ - // Used to get a new XmlPullParser from the client. @Nullable private static com.android.ide.common.rendering.api.ParserFactory sParserFactory; @@ -74,7 +72,7 @@ public class ParserFactory { stream = readAndClose(stream, name, size); - parser.setInput(stream, ENCODING); + parser.setInput(stream, null); if (isLayout) { try { return new LayoutParserWrapper(parser).peekTillLayoutStart(); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java index 80d7c68bcf06..70e2eb17794a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java @@ -24,14 +24,12 @@ import android.graphics.BlendComposite.BlendingMode; import android.graphics.PorterDuff; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffColorFilter_Delegate; -import android.graphics.PorterDuffXfermode_Delegate; import java.awt.AlphaComposite; import java.awt.Composite; /** - * Provides various utility methods for {@link PorterDuffColorFilter_Delegate} and {@link - * PorterDuffXfermode_Delegate}. + * Provides various utility methods for {@link PorterDuffColorFilter_Delegate}. */ public final class PorterDuffUtility { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index a8077ccae01a..91a783ae7efc 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -20,6 +20,7 @@ import com.android.ide.common.rendering.api.AdapterBinding; import com.android.ide.common.rendering.api.HardwareConfig; import com.android.ide.common.rendering.api.IAnimationListener; import com.android.ide.common.rendering.api.ILayoutPullParser; +import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.RenderSession; @@ -44,6 +45,7 @@ import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import com.android.layoutlib.bridge.android.RenderParamsFlags; import com.android.layoutlib.bridge.android.graphics.NopCanvas; import com.android.layoutlib.bridge.android.support.DesignLibUtil; +import com.android.layoutlib.bridge.android.support.SupportPreferencesUtil; import com.android.layoutlib.bridge.impl.binding.FakeAdapter; import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter; import com.android.resources.ResourceType; @@ -303,6 +305,20 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { SessionParams params = getParams(); BridgeContext context = getContext(); + if (Bridge.isLocaleRtl(params.getLocale())) { + if (!params.isRtlSupported()) { + Bridge.getLog().warning(LayoutLog.TAG_RTL_NOT_ENABLED, + "You are using a right-to-left " + + "(RTL) locale but RTL is not enabled", null); + } else if (params.getSimulatedPlatformVersion() < 17) { + // This will render ok because we are using the latest layoutlib but at least + // warn the user that this might fail in a real device. + Bridge.getLog().warning(LayoutLog.TAG_RTL_NOT_SUPPORTED, "You are using a " + + "right-to-left " + + "(RTL) locale but RTL is not supported for API level < 17", null); + } + } + // Sets the project callback (custom view loader) to the fragment delegate so that // it can instantiate the custom Fragment. Fragment_Delegate.setLayoutlibCallback(params.getLayoutlibCallback()); @@ -311,8 +327,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { boolean isPreference = "PreferenceScreen".equals(rootTag); View view; if (isPreference) { - view = Preference_Delegate.inflatePreference(getContext(), mBlockParser, + // First try to use the support library inflater. If something fails, fallback + // to the system preference inflater. + view = SupportPreferencesUtil.inflatePreference(getContext(), mBlockParser, mContentRoot); + if (view == null) { + view = Preference_Delegate.inflatePreference(getContext(), mBlockParser, + mContentRoot); + } } else { view = mInflater.inflate(mBlockParser, mContentRoot); } @@ -336,7 +358,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { mMeasuredScreenWidth, MeasureSpec.EXACTLY, mMeasuredScreenHeight, MeasureSpec.EXACTLY); mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight); - mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(), + mSystemViewInfoList = + visitAllChildren(mViewRoot, 0, 0, params.getExtendedViewInfoMode(), false); return SUCCESS.createResult(); @@ -499,7 +522,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { mMeasuredScreenHeight); } - mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(), + mSystemViewInfoList = + visitAllChildren(mViewRoot, 0, 0, params.getExtendedViewInfoMode(), false); // success! @@ -1220,20 +1244,22 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * bounds of all the views. * * @param view the root View - * @param offset an offset for the view bounds. + * @param hOffset horizontal offset for the view bounds. + * @param vOffset vertical offset for the view bounds. * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the * content frame. * * @return {@code ViewInfo} containing the bounds of the view and it children otherwise. */ - private ViewInfo visit(View view, int offset, boolean setExtendedInfo, + private ViewInfo visit(View view, int hOffset, int vOffset, boolean setExtendedInfo, boolean isContentFrame) { - ViewInfo result = createViewInfo(view, offset, setExtendedInfo, isContentFrame); + ViewInfo result = createViewInfo(view, hOffset, vOffset, setExtendedInfo, isContentFrame); if (view instanceof ViewGroup) { ViewGroup group = ((ViewGroup) view); - result.setChildren(visitAllChildren(group, isContentFrame ? 0 : offset, + result.setChildren(visitAllChildren(group, isContentFrame ? 0 : hOffset, + isContentFrame ? 0 : vOffset, setExtendedInfo, isContentFrame)); } return result; @@ -1245,20 +1271,22 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * the children of the {@code mContentRoot}. * * @param viewGroup the root View - * @param offset an offset from the top for the content view frame. + * @param hOffset horizontal offset from the top for the content view frame. + * @param vOffset vertical offset from the top for the content view frame. * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the * content frame. {@code false} if the {@code ViewInfo} to be created is * part of the system decor. */ - private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset, + private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int hOffset, int vOffset, boolean setExtendedInfo, boolean isContentFrame) { if (viewGroup == null) { return null; } if (!isContentFrame) { - offset += viewGroup.getTop(); + vOffset += viewGroup.getTop(); + hOffset += viewGroup.getLeft(); } int childCount = viewGroup.getChildCount(); @@ -1266,7 +1294,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { List<ViewInfo> childrenWithoutOffset = new ArrayList<ViewInfo>(childCount); List<ViewInfo> childrenWithOffset = new ArrayList<ViewInfo>(childCount); for (int i = 0; i < childCount; i++) { - ViewInfo[] childViewInfo = visitContentRoot(viewGroup.getChildAt(i), offset, + ViewInfo[] childViewInfo = + visitContentRoot(viewGroup.getChildAt(i), hOffset, vOffset, setExtendedInfo); childrenWithoutOffset.add(childViewInfo[0]); childrenWithOffset.add(childViewInfo[1]); @@ -1276,7 +1305,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } else { List<ViewInfo> children = new ArrayList<ViewInfo>(childCount); for (int i = 0; i < childCount; i++) { - children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo, + children.add(visit(viewGroup.getChildAt(i), hOffset, vOffset, setExtendedInfo, isContentFrame)); } return children; @@ -1295,16 +1324,18 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * index 1 is with the offset. */ @NonNull - private ViewInfo[] visitContentRoot(View view, int offset, boolean setExtendedInfo) { + private ViewInfo[] visitContentRoot(View view, int hOffset, int vOffset, + boolean setExtendedInfo) { ViewInfo[] result = new ViewInfo[2]; if (view == null) { return result; } - result[0] = createViewInfo(view, 0, setExtendedInfo, true); - result[1] = createViewInfo(view, offset, setExtendedInfo, true); + result[0] = createViewInfo(view, 0, 0, setExtendedInfo, true); + result[1] = createViewInfo(view, hOffset, vOffset, setExtendedInfo, true); if (view instanceof ViewGroup) { - List<ViewInfo> children = visitAllChildren((ViewGroup) view, 0, setExtendedInfo, true); + List<ViewInfo> children = + visitAllChildren((ViewGroup) view, 0, 0, setExtendedInfo, true); result[0].setChildren(children); result[1].setChildren(children); } @@ -1315,9 +1346,12 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * Creates a {@link ViewInfo} for the view. The {@code ViewInfo} corresponding to the children * of the {@code view} are not created. Consequently, the children of {@code ViewInfo} is not * set. - * @param offset an offset for the view bounds. Used only if view is part of the content frame. + * @param hOffset horizontal offset for the view bounds. Used only if view is part of the + * content frame. + * @param vOffset vertial an offset for the view bounds. Used only if view is part of the + * content frame. */ - private ViewInfo createViewInfo(View view, int offset, boolean setExtendedInfo, + private ViewInfo createViewInfo(View view, int hOffset, int vOffset, boolean setExtendedInfo, boolean isContentFrame) { if (view == null) { return null; @@ -1333,9 +1367,9 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // The view is part of the layout added by the user. Hence, // the ViewCookie may be obtained only through the Context. result = new ViewInfo(view.getClass().getName(), - getContext().getViewKey(view), - -scrollX + view.getLeft(), -scrollY + view.getTop() + offset, - -scrollX + view.getRight(), -scrollY + view.getBottom() + offset, + getContext().getViewKey(view), -scrollX + view.getLeft() + hOffset, + -scrollY + view.getTop() + vOffset, -scrollX + view.getRight() + hOffset, + -scrollY + view.getBottom() + vOffset, view, view.getLayoutParams()); } else { // We are part of the system decor. diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java index a21de56066cb..c197e40eb4cf 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java @@ -77,6 +77,7 @@ public final class ResourceHelper { */ public static int getColor(String value) { if (value != null) { + value = value.trim(); if (!value.startsWith("#")) { if (value.startsWith(SdkConstants.PREFIX_THEME_REF)) { throw new NumberFormatException(String.format( diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java index 7ce27b6a55fa..040191e859ba 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java @@ -37,6 +37,15 @@ public class ReflectionUtils { } } + @NonNull + public static Method getAccessibleMethod(@NonNull Class<?> clazz, @NonNull String name, + @Nullable Class<?>... params) throws ReflectionException { + Method method = getMethod(clazz, name, params); + method.setAccessible(true); + + return method; + } + @Nullable public static Object invoke(@NonNull Method method, @Nullable Object object, @Nullable Object... args) throws ReflectionException { @@ -74,6 +83,25 @@ public class ReflectionUtils { } /** + * Looks through the class hierarchy of {@code object} at runtime and returns the class matching + * the name {@code className}. + * <p> + * This is used when we cannot use Class.forName() since the class we want was loaded from a + * different ClassLoader. + */ + @NonNull + public static Class<?> getClassInstance(@NonNull Object object, @NonNull String className) { + Class<?> superClass = object.getClass(); + while (superClass != null) { + if (className.equals(superClass.getName())) { + return superClass; + } + superClass = superClass.getSuperclass(); + } + throw new RuntimeException("invalid object/classname combination."); + } + + /** * Wraps all reflection related exceptions. Created since ReflectiveOperationException was * introduced in 1.7 and we are still on 1.6 */ diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk index 465647606e52..9ee416a3b4ca 100644 --- a/tools/layoutlib/bridge/tests/Android.mk +++ b/tools/layoutlib/bridge/tests/Android.mk @@ -17,7 +17,9 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # Only compile source java files in this lib. -LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) \ + $(call all-java-files-under, res/testApp/MyApplication/src/main/myapplication.widgets) LOCAL_JAVA_RESOURCE_DIRS := res LOCAL_MODULE := layoutlib-tests diff --git a/tools/layoutlib/bridge/tests/res/empty.xml b/tools/layoutlib/bridge/tests/res/empty.xml new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/empty.xml diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle index 4561e1b80125..478166022d73 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle @@ -19,12 +19,12 @@ allprojects { apply plugin: 'com.android.application' android { - compileSdkVersion 22 - buildToolsVersion '21.1.2' + compileSdkVersion 25 + buildToolsVersion '25.0.0' defaultConfig { applicationId 'com.android.layoutlib.test.myapplication' minSdkVersion 21 - targetSdkVersion 22 + targetSdkVersion 25 versionCode 1 versionName '1.0' } @@ -34,6 +34,9 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + lintOptions { + abortOnError false + } compileOptions { sourceCompatibility JavaVersion.VERSION_1_6 targetCompatibility JavaVersion.VERSION_1_6 diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class Binary files differindex e0373cba84a7..f73528a556e2 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/CustomCalendar.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/CustomCalendar.class Binary files differdeleted file mode 100644 index c3630552c7fe..000000000000 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/CustomCalendar.class +++ /dev/null diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/CustomDate.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/CustomDate.class Binary files differdeleted file mode 100644 index edda3de57058..000000000000 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/CustomDate.class +++ /dev/null diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class Binary files differindex ec42017bad67..5bb04fc88caa 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class Binary files differnew file mode 100644 index 000000000000..ff699d1412dc --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class Binary files differindex 0e208f2dc7de..a3931b839c80 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class Binary files differindex 2b77af367a38..e2936771bfa4 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class Binary files differindex fd01b445df0d..d6268bf9601e 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class Binary files differindex 91cf5b6d824b..08b98fbb6f04 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class Binary files differindex 6c351da69d69..f9be1ca4583c 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class Binary files differindex aecbff6add3b..6874b49eb009 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class Binary files differindex fc3f23600d86..a4205a8c4e94 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class Binary files differindex 83ad35bd0ab1..4fb3b61bf9d4 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class Binary files differindex 6d7c71995eaf..dba67fd7efcc 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png Binary files differindex 9bf302ad6906..f274dbfbbdd6 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png Binary files differindex 0e788e0c8264..ef95f83f8b24 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png Binary files differindex bad296bf4a66..6eeb82c93735 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png Binary files differindex 9f266278c352..26aed6a7ed7c 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png Binary files differindex 89009be843e7..aaf1514ddc24 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png Binary files differnew file mode 100644 index 000000000000..4e448c8f2efc --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png Binary files differnew file mode 100644 index 000000000000..290018b6497d --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png Binary files differindex 55d6a20949a2..466eca8d1721 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png Binary files differnew file mode 100644 index 000000000000..940fe5bc44ba --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ArraysCheckWidget.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ArraysCheckWidget.java index 41d75de7ada7..e7f22bfba00a 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ArraysCheckWidget.java +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ArraysCheckWidget.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.layoutlib.test.myapplication; import android.content.Context; diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/CustomCalendar.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomCalendar.java index 80bbaf139b4c..3b819e58e45e 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/CustomCalendar.java +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomCalendar.java @@ -1,4 +1,20 @@ -package com.android.layoutlib.test.myapplication; +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.test.myapplication.widgets; import android.content.Context; import android.util.AttributeSet; @@ -26,6 +42,6 @@ public class CustomCalendar extends CalendarView { } private void init() { - setDate(871703200000L, false, true); + setDate(871732800000L, false, true); } } diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/CustomDate.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomDate.java index cb750f49322b..f3877f1b3d90 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/CustomDate.java +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomDate.java @@ -1,4 +1,20 @@ -package com.android.layoutlib.test.myapplication; +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.test.myapplication.widgets; import android.content.Context; import android.util.AttributeSet; diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/package-info.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/package-info.java new file mode 100644 index 000000000000..58ad46deec2a --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/package-info.java @@ -0,0 +1,6 @@ +/** + * This package contains custom widgets used to set a specific time for the DayTimePicker during + * testing. + * The classes here are both used from the Android project and from the Bridge test project. + */ +package com.android.layoutlib.test.myapplication.widgets;
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/android.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/android.xml new file mode 100644 index 000000000000..42e3beb50b81 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/android.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2016 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:viewportWidth="1102" + android:viewportHeight="642" + android:width="1102px" + android:height="642px"> + + <group + android:translateX="-126.347" + android:translateY="6.7928655e-4"> + + + <path + android:fillColor="#94c147" + android:pathData=" + m 552.777,147.57 + c -14.147,0 -25.622,11.652 -25.622,26.02 + v 101.68 + c 0,14.372 11.475,26.019 25.622,26.019 14.147,0 25.61,-11.646 25.61,-26.019 + V 173.59 + c 0.001,-14.368 -11.462,-26.02 -25.61,-26.02 + z + + m -309.011,0 + c -14.155,0 -25.618,11.652 -25.618,26.02 + v 101.68 + c 0,14.372 11.462,26.019 25.618,26.019 14.153,0 25.623,-11.646 25.623,-26.019 + V 173.59 + c -0.008,-14.368 -11.475,-26.02 -25.623,-26.02 + z" /> + + + <path + android:fillColor="#94c147" + android:pathData="m 284.799,148.364 v 185.768 c 0,11.035 8.948,19.98 19.983,19.98 h 22.815 v 56.567 c 0,14.37 11.47,26.016 25.623,26.016 14.148,0 25.623,-11.646 25.623,-26.016 v -56.567 h 39.878 v 56.567 c 0,14.37 11.463,26.016 25.61,26.016 14.147,0 25.622,-11.646 25.622,-26.016 v -56.567 h 22.828 c 11.022,0 19.971,-8.953 19.971,-19.98 V 148.364 H 284.799 l 0,0 z" /> + + <group + android:name="head" + android:pivotX="400" + android:pivotY="131.105"> + + <path + android:fillColor="#94c147" + android:pathData="m 452.302,52.105 21.057,-30.572 c 1.245,-1.819 0.939,-4.199 -0.695,-5.329 -1.637,-1.123 -3.968,-0.568 -5.225,1.251 l -21.875,31.75 c -14.404,-5.682 -30.418,-8.844 -47.293,-8.844 -16.87,0 -32.893,3.162 -47.297,8.844 l -21.875,-31.75 c -1.257,-1.819 -3.589,-2.375 -5.225,-1.251 -1.636,1.124 -1.946,3.509 -0.696,5.329 l 21.057,30.572 c -33.464,15.57 -56.951,45.166 -59.941,79.706 H 512.25 C 509.259,97.271 485.772,67.676 452.302,52.105 z M 350.187,100.28 c -6.965,0 -12.617,-5.646 -12.617,-12.616 0,-6.958 5.647,-12.61 12.617,-12.61 6.97,0 12.603,5.652 12.603,12.61 0,6.965 -5.64,12.616 -12.603,12.616 z m 97.744,0 c -6.97,0 -12.609,-5.646 -12.609,-12.616 0,-6.958 5.64,-12.61 12.609,-12.61 6.971,0 12.61,5.652 12.61,12.61 0,6.965 -5.64,12.616 -12.61,12.616 z" /> + </group> + + </group> + +</vector>
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/headset.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/headset.xml new file mode 100644 index 000000000000..897c4113ead6 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/headset.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2016 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="150dp" + android:height="150dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="m12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h4v1h-7v2h6c1.66,0 3,-1.34 3,-3V10c0,-4.97 -4.03,-9 -9,-9z"/> +</vector>
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml index 32e6e732bb5a..0998b25a221e 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml @@ -4,7 +4,8 @@ android:height="76dp" android:width="76dp" android:viewportHeight="48" - android:viewportWidth="48"> + android:viewportWidth="48" + android:alpha="0.6"> <group android:name="root" @@ -79,7 +80,7 @@ android:fillType="nonZero" android:strokeWidth="1" android:strokeColor="#AABBCC" - android:fillColor="#AAEFCC" + android:fillColor="#40AAEFCC" android:pathData="M0,-40 l0, 10 l10, 0 l0, -10 l-10,0 m5,0 l0, 10 l10, 0 l0, -10 l-10,0" /> </group> diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/array_check.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/array_check.xml index 50646ab4c388..e9aa9e1378fb 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/array_check.xml +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/array_check.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<com.android.layoutlib.test.myapplication.ArraysCheckWidget xmlns:android="http://schemas.android.com/apk/res/android" +<com.android.layoutlib.test.myapplication.ArraysCheckWidget + xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml index c1f663e9fa8a..ff14ce0a6298 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml @@ -15,10 +15,10 @@ android:checked="true" android:layout_gravity="center" /> - <com.android.layoutlib.test.myapplication.CustomDate + <com.android.layoutlib.test.myapplication.widgets.CustomDate android:layout_width="100dp" android:layout_height="wrap_content"/> - <com.android.layoutlib.test.myapplication.CustomCalendar + <com.android.layoutlib.test.myapplication.widgets.CustomCalendar android:layout_width="200dp" android:layout_gravity="center_horizontal" android:layout_height="200dp"/> diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/simple_activity.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/simple_activity.xml new file mode 100644 index 000000000000..14b93f364a35 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/simple_activity.xml @@ -0,0 +1,32 @@ +<!-- + ~ Copyright (C) 2016 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. + --> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + android:paddingBottom="@dimen/activity_vertical_margin"> + + <TextView + android:text="@string/hello_world" + android:layout_width="wrap_content" + android:layout_height="200dp" + android:background="#FF0000" + android:id="@+id/text1"/> + +</RelativeLayout>
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/vector_drawable_android.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/vector_drawable_android.xml new file mode 100644 index 000000000000..3b01ea093122 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/vector_drawable_android.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2016 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. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:padding="16dp" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:src="@drawable/android"/> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:src="@drawable/headset"/> + +</LinearLayout> + diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml index 88c9cbcb250a..c8a5fec71f09 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml @@ -3,7 +3,7 @@ <!-- Base application theme. --> <style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar"> <item name="myattr">@integer/ten</item> - <!-- Customize your theme here. --> + <item name="android:animateFirstView">false</item> </style> </resources> diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java index 8f9fa8a2bcf6..d3f0f893f2ec 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java @@ -64,7 +64,7 @@ public class ImageUtils { double scale = THUMBNAIL_SIZE / (double)maxDimension; BufferedImage thumbnail = scale(image, scale, scale); - InputStream is = ImageUtils.class.getResourceAsStream(relativePath); + InputStream is = ImageUtils.class.getClassLoader().getResourceAsStream(relativePath); if (is == null) { String message = "Unable to load golden thumbnail: " + relativePath + "\n"; message = saveImageAndAppendMessage(thumbnail, message, relativePath); @@ -179,7 +179,7 @@ public class ImageUtils { g.drawString("Actual", 2 * imageWidth + 10, 20); } - File output = new File(getTempDir(), "delta-" + imageName); + File output = new File(getFailureDir(), "delta-" + imageName); if (output.exists()) { boolean deleted = output.delete(); assertTrue(deleted); @@ -302,15 +302,16 @@ public class ImageUtils { } /** - * Temp directory where to write the thumbnails and deltas. + * Directory where to write the thumbnails and deltas. */ @NonNull - private static File getTempDir() { - if (System.getProperty("os.name").equals("Mac OS X")) { - return new File("/tmp"); //$NON-NLS-1$ - } + private static File getFailureDir() { + String workingDirString = System.getProperty("user.dir"); + File failureDir = new File(workingDirString, "out/failures"); - return new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$ + //noinspection ResultOfMethodCallIgnored + failureDir.mkdirs(); + return failureDir; //$NON-NLS-1$ } /** @@ -319,7 +320,7 @@ public class ImageUtils { @NonNull private static String saveImageAndAppendMessage(@NonNull BufferedImage image, @NonNull String initialMessage, @NonNull String relativePath) throws IOException { - File output = new File(getTempDir(), getName(relativePath)); + File output = new File(getFailureDir(), getName(relativePath)); if (output.exists()) { boolean deleted = output.delete(); assertTrue(deleted); diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java index 8f570aee96b7..24cbbca02b77 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java @@ -39,6 +39,7 @@ import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser; import com.android.resources.Density; import com.android.resources.Navigation; import com.android.resources.ResourceType; +import com.android.tools.layoutlib.java.System_Delegate; import com.android.utils.ILogger; import org.junit.AfterClass; @@ -51,6 +52,7 @@ import org.junit.runner.Description; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.pm.PackageInstaller.Session; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; @@ -58,11 +60,28 @@ import android.util.DisplayMetrics; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.lang.ref.WeakReference; +import java.net.MalformedURLException; import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.CopyOption; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.function.BiPredicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import com.google.android.collect.Lists; @@ -102,9 +121,11 @@ public class Main { private static final String PLATFORM_DIR; private static final String TEST_RES_DIR; /** Location of the app to test inside {@link #TEST_RES_DIR}*/ - private static final String APP_TEST_DIR = "/testApp/MyApplication"; + private static final String APP_TEST_DIR = "testApp/MyApplication"; /** Location of the app's res dir inside {@link #TEST_RES_DIR}*/ private static final String APP_TEST_RES = APP_TEST_DIR + "/src/main/res"; + private static final String APP_CLASSES_LOCATION = + APP_TEST_DIR + "/build/intermediates/classes/debug/"; private static LayoutLog sLayoutLibLog; private static FrameworkResources sFrameworkRepo; @@ -115,8 +136,12 @@ public class Main { /** List of log messages generated by a render call. It can be used to find specific errors */ private static ArrayList<String> sRenderMessages = Lists.newArrayList(); + // Default class loader with access to the app classes + private ClassLoader mDefaultClassLoader = + new ModuleClassLoader(APP_CLASSES_LOCATION, getClass().getClassLoader()); + @Rule - public static TestWatcher sRenderMessageWatcher = new TestWatcher() { + public TestWatcher sRenderMessageWatcher = new TestWatcher() { @Override protected void succeeded(Description description) { // We only check error messages if the rest of the test case was successful. @@ -192,6 +217,7 @@ public class Main { } File[] hosts = host.listFiles(path -> path.isDirectory() && (path.getName().startsWith("linux-") || path.getName().startsWith("darwin-"))); + assert hosts != null; for (File hostOut : hosts) { String platformDir = getPlatformDirFromHostOut(hostOut); if (platformDir != null) { @@ -213,6 +239,7 @@ public class Main { // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7) return path.isDirectory() && path.getName().startsWith("sdk"); }); + assert sdkDirs != null; for (File dir : sdkDirs) { String platformDir = getPlatformDirFromHostOutSdkSdk(dir); if (platformDir != null) { @@ -225,6 +252,7 @@ public class Main { private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) { File[] possibleSdks = sdkDir.listFiles( path -> path.isDirectory() && path.getName().contains("android-sdk")); + assert possibleSdks != null; for (File possibleSdk : possibleSdks) { File platformsDir = new File(possibleSdk, "platforms"); File[] platforms = platformsDir.listFiles( @@ -284,7 +312,8 @@ public class Main { sFrameworkRepo.loadPublicResources(getLogger()); sProjectResources = - new ResourceRepository(new FolderWrapper(TEST_RES_DIR + APP_TEST_RES), false) { + new ResourceRepository(new FolderWrapper(TEST_RES_DIR + "/" + APP_TEST_RES), + false) { @NonNull @Override protected ResourceItem createResourceItem(@NonNull String name) { @@ -312,6 +341,21 @@ public class Main { renderAndVerify("activity.xml", "activity.png"); } + private static void gc() { + // See RuntimeUtil#gc in jlibs (http://jlibs.in/) + Object obj = new Object(); + WeakReference ref = new WeakReference<>(obj); + //noinspection UnusedAssignment + obj = null; + while(ref.get() != null) { + System.gc(); + System.runFinalization(); + } + + System.gc(); + System.runFinalization(); + } + /** Test allwidgets.xml */ @Test public void testAllWidgets() throws ClassNotFoundException { @@ -334,14 +378,34 @@ public class Main { sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported.")); } - private static void gc() { - // See RuntimeUtil#gc in jlibs (http://jlibs.in/) - Object obj = new Object(); - WeakReference ref = new WeakReference<Object>(obj); - obj = null; - while(ref.get() != null) { - System.gc(); - } + @Test + public void testActivityActionBar() throws ClassNotFoundException { + LayoutPullParser parser = createLayoutPullParser("simple_activity.xml"); + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); + layoutLibCallback.initResources(); + + SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, + layoutLibCallback, "Theme.Material.Light.NoActionBar", false, + RenderingMode.V_SCROLL, 22); + + renderAndVerify(params, "simple_activity_noactionbar.png"); + + parser = createLayoutPullParser("simple_activity.xml"); + params = getSessionParams(parser, ConfigGenerator.NEXUS_5, + layoutLibCallback, "Theme.Material.Light", false, + RenderingMode.V_SCROLL, 22); + + renderAndVerify(params, "simple_activity.png"); + + // This also tests that a theme with "NoActionBar" DOES HAVE an action bar when we are + // displaying menus. + parser = createLayoutPullParser("simple_activity.xml"); + params = getSessionParams(parser, ConfigGenerator.NEXUS_5, + layoutLibCallback, "Theme.Material.Light.NoActionBar", false, + RenderingMode.V_SCROLL, 22); + params.setFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG, "menu"); + renderAndVerify(params, "simple_activity.png"); } @AfterClass @@ -364,7 +428,8 @@ public class Main { // Create the layout pull parser. LayoutPullParser parser = createLayoutPullParser("expand_vert_layout.xml"); // Create LayoutLibCallback. - LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); layoutLibCallback.initResources(); ConfigGenerator customConfigGenerator = new ConfigGenerator() @@ -398,7 +463,8 @@ public class Main { // Create the layout pull parser. LayoutPullParser parser = createLayoutPullParser("indeterminate_progressbar.xml"); // Create LayoutLibCallback. - LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); layoutLibCallback.initResources(); SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, @@ -423,7 +489,8 @@ public class Main { // Create the layout pull parser. LayoutPullParser parser = createLayoutPullParser("vector_drawable.xml"); // Create LayoutLibCallback. - LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); layoutLibCallback.initResources(); SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, @@ -433,13 +500,33 @@ public class Main { renderAndVerify(params, "vector_drawable.png", TimeUnit.SECONDS.toNanos(2)); } + /** + * Regression test for http://b.android.com/91383 and http://b.android.com/203797 + */ + @Test + public void testVectorDrawable91383() throws ClassNotFoundException { + // Create the layout pull parser. + LayoutPullParser parser = createLayoutPullParser("vector_drawable_android.xml"); + // Create LayoutLibCallback. + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); + layoutLibCallback.initResources(); + + SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, + layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, + RenderingMode.V_SCROLL, 22); + + renderAndVerify(params, "vector_drawable_91383.png", TimeUnit.SECONDS.toNanos(2)); + } + /** Test activity.xml */ @Test public void testScrolling() throws ClassNotFoundException { // Create the layout pull parser. LayoutPullParser parser = createLayoutPullParser("scrolled.xml"); // Create LayoutLibCallback. - LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); layoutLibCallback.initResources(); SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, @@ -450,6 +537,7 @@ public class Main { RenderResult result = renderAndVerify(params, "scrolled.png"); assertNotNull(result); + assertNotNull(result.getResult()); assertTrue(result.getResult().isSuccess()); ViewInfo rootLayout = result.getRootViews().get(0); @@ -469,7 +557,15 @@ public class Main { @Test public void testGetResourceNameVariants() throws Exception { // Setup - SessionParams params = createSessionParams("", ConfigGenerator.NEXUS_4); + // Create the layout pull parser for our resources (empty.xml can not be part of the test + // app as it won't compile). + LayoutPullParser parser = new LayoutPullParser("/empty.xml"); + // Create LayoutLibCallback. + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); + layoutLibCallback.initResources(); + SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4, + layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22); AssetManager assetManager = AssetManager.getSystem(); DisplayMetrics metrics = new DisplayMetrics(); Configuration configuration = RenderAction.getConfiguration(params); @@ -511,6 +607,8 @@ public class Main { throws ClassNotFoundException { // TODO: Set up action bar handler properly to test menu rendering. // Create session params. + System_Delegate.setBootTimeNanos(TimeUnit.MILLISECONDS.toNanos(871732800000L)); + System_Delegate.setNanosTime(TimeUnit.MILLISECONDS.toNanos(871732800000L)); RenderSession session = sBridge.createSession(params); if (frameTimeNanos != -1) { @@ -578,7 +676,8 @@ public class Main { // Create the layout pull parser. LayoutPullParser parser = createLayoutPullParser(layoutFileName); // Create LayoutLibCallback. - LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); layoutLibCallback.initResources(); // TODO: Set up action bar handler properly to test menu rendering. // Create session params. diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ModuleClassLoader.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ModuleClassLoader.java new file mode 100644 index 000000000000..3fac7782b3ae --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ModuleClassLoader.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.intensive; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import libcore.io.Streams; + +/** + * Module class loader that loads classes from the test project. + */ +public class ModuleClassLoader extends ClassLoader { + private final Map<String, Class<?>> mClasses = new HashMap<>(); + private String myModuleRoot; + + /** + * @param moduleRoot The path to the module root + * @param parent The parent class loader + */ + public ModuleClassLoader(String moduleRoot, ClassLoader parent) { + super(parent); + myModuleRoot = moduleRoot + (moduleRoot.endsWith("/") ? "" : "/"); + } + + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + try { + return super.findClass(name); + } catch (ClassNotFoundException ignored) { + } + + Class<?> clazz = mClasses.get(name); + if (clazz == null) { + String path = name.replace('.', '/').concat(".class"); + try { + byte[] b = Streams.readFully(getResourceAsStream(myModuleRoot + path)); + clazz = defineClass(name, b, 0, b.length); + mClasses.put(name, clazz); + } catch (IOException ignore) { + throw new ClassNotFoundException(name + " not found"); + } + } + + return clazz; + } +} diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java index 96ae523006b3..bae2dc0c25f5 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java @@ -42,6 +42,9 @@ import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; import java.util.Map; import com.google.android.collect.Maps; @@ -59,10 +62,11 @@ public class LayoutLibTestCallback extends LayoutlibCallback { private final Map<ResourceType, Map<String, Integer>> mResources = Maps.newHashMap(); private final ILogger mLog; private final ActionBarCallback mActionBarCallback = new ActionBarCallback(); - private final ClassLoader mModuleClassLoader = new ModuleClassLoader(PROJECT_CLASSES_LOCATION); + private final ClassLoader mModuleClassLoader; - public LayoutLibTestCallback(ILogger logger) { + public LayoutLibTestCallback(ILogger logger, ClassLoader classLoader) { mLog = logger; + mModuleClassLoader = classLoader; } public void initResources() throws ClassNotFoundException { diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java index 111049474461..bc8083f9c40f 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java @@ -42,9 +42,11 @@ public class LayoutPullParser extends KXmlParser implements ILayoutPullParser{ * @param layoutPath Must start with '/' and be relative to test resources. */ public LayoutPullParser(String layoutPath) { - assert layoutPath.startsWith("/"); + if (layoutPath.startsWith("/")) { + layoutPath = layoutPath.substring(1); + } try { - init(getClass().getResourceAsStream(layoutPath)); + init(getClass().getClassLoader().getResourceAsStream(layoutPath)); } catch (XmlPullParserException e) { throw new IOError(e); } diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ModuleClassLoader.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ModuleClassLoader.java deleted file mode 100644 index 110f4c8b9795..000000000000 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ModuleClassLoader.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.layoutlib.bridge.intensive.setup; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; - -import com.google.android.collect.Maps; - -/** - * The ClassLoader to load the project's classes. - */ -public class ModuleClassLoader extends ClassLoader { - - private final Map<String, Class<?>> mClasses = Maps.newHashMap(); - private final String mClassLocation; - - public ModuleClassLoader(String classLocation) { - mClassLocation = classLocation; - } - - @Override - protected Class<?> findClass(String name) throws ClassNotFoundException { - Class<?> aClass = mClasses.get(name); - if (aClass != null) { - return aClass; - } - String pathName = mClassLocation.concat(name.replace('.', '/')).concat(".class"); - InputStream classInputStream = getClass().getResourceAsStream(pathName); - if (classInputStream == null) { - throw new ClassNotFoundException("Unable to find class " + name + " at " + pathName); - } - byte[] data; - try { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - int nRead; - data = new byte[16384]; // 16k - while ((nRead = classInputStream.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); - } - buffer.flush(); - data = buffer.toByteArray(); - } catch (IOException e) { - // Wrap the exception with ClassNotFoundException so that caller can deal with it. - throw new ClassNotFoundException("Unable to load class " + name, e); - } - aClass = defineClass(name, data, 0, data.length); - mClasses.put(name, aClass); - return aClass; - } -} diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java index 3b376123daa4..a2f8372d6eb8 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.ListIterator; @@ -48,8 +49,6 @@ public class AsmGenerator { private final String mOsDestJar; /** List of classes to inject in the final JAR from _this_ archive. */ private final Class<?>[] mInjectClasses; - /** The set of methods to stub out. */ - private final Set<String> mStubMethods; /** All classes to output as-is, except if they have native methods. */ private Map<String, ClassReader> mKeep; /** All dependencies that must be completely stubbed. */ @@ -107,7 +106,6 @@ public class AsmGenerator { } } mInjectClasses = injectedClasses.toArray(new Class<?>[0]); - mStubMethods = new HashSet<>(Arrays.asList(createInfo.getOverriddenMethods())); // Create the map/set of methods to change to delegates mDelegateMethods = new HashMap<>(); @@ -384,7 +382,7 @@ public class AsmGenerator { if (mInjectedMethodsMap.keySet().contains(binaryNewName)) { cv = new InjectMethodsAdapter(cv, mInjectedMethodsMap.get(binaryNewName)); } - cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className), + cv = new TransformClassAdapter(mLog, Collections.emptySet(), mDeleteReturns.get(className), newName, cv, stubNativesOnly); Set<String> delegateMethods = mDelegateMethods.get(className); diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 061bed7b7740..a23bad6ed480 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -64,18 +64,6 @@ public final class CreateInfo implements ICreateInfo { } /** - * Returns The list of methods to stub out. Each entry must be in the form - * "package.package.OuterClass$InnerClass#MethodName". - * The list can be empty but must not be null. - * <p/> - * This usage is deprecated. Please use method 'delegates' instead. - */ - @Override - public String[] getOverriddenMethods() { - return OVERRIDDEN_METHODS; - } - - /** * Returns the list of classes to rename, must be an even list: the binary FQCN * of class to replace followed by the new FQCN. * The list can be empty but must not be null. @@ -186,11 +174,13 @@ public final class CreateInfo implements ICreateInfo { "android.content.res.Resources$Theme#resolveAttributes", "android.content.res.AssetManager#newTheme", "android.content.res.AssetManager#deleteTheme", + "android.content.res.AssetManager#getAssignedPackageIdentifiers", "android.content.res.TypedArray#getValueAt", "android.content.res.TypedArray#obtain", "android.graphics.BitmapFactory#finishDecode", "android.graphics.BitmapFactory#setDensityFromOptions", "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#useLastSeenTarget", + "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#onDraw", "android.graphics.drawable.GradientDrawable#buildRing", "android.graphics.FontFamily#addFont", "android.graphics.Typeface#getSystemFontConfigLocation", @@ -217,7 +207,7 @@ public final class CreateInfo implements ICreateInfo { "android.view.MenuInflater#registerMenu", "android.view.RenderNode#getMatrix", "android.view.RenderNode#nCreate", - "android.view.RenderNode#nDestroyRenderNode", + "android.view.RenderNode#nGetNativeFinalizer", "android.view.RenderNode#nSetElevation", "android.view.RenderNode#nGetElevation", "android.view.RenderNode#nSetTranslationX", @@ -245,7 +235,6 @@ public final class CreateInfo implements ICreateInfo { "android.view.ViewGroup#drawChild", "com.android.internal.view.menu.MenuBuilder#createNewMenuItem", "com.android.internal.util.XmlUtils#convertValueToInt", - "com.android.internal.textservice.ITextServicesManager$Stub#asInterface", "dalvik.system.VMRuntime#newUnpaddedArray", "libcore.io.MemoryMappedFile#mmapRO", "libcore.io.MemoryMappedFile#close", @@ -258,6 +247,7 @@ public final class CreateInfo implements ICreateInfo { */ public final static String[] DELEGATE_CLASS_NATIVES = new String[] { "android.animation.PropertyValuesHolder", + "android.graphics.BaseCanvas", "android.graphics.Bitmap", "android.graphics.BitmapFactory", "android.graphics.BitmapShader", @@ -286,7 +276,6 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.PathEffect", "android.graphics.PathMeasure", "android.graphics.PorterDuffColorFilter", - "android.graphics.PorterDuffXfermode", "android.graphics.RadialGradient", "android.graphics.Rasterizer", "android.graphics.Region", @@ -294,7 +283,6 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.SumPathEffect", "android.graphics.SweepGradient", "android.graphics.Typeface", - "android.graphics.Xfermode", "android.graphics.drawable.AnimatedVectorDrawable", "android.graphics.drawable.VectorDrawable", "android.os.SystemClock", @@ -309,20 +297,13 @@ public final class CreateInfo implements ICreateInfo { }; /** - * The list of methods to stub out. Each entry must be in the form - * "package.package.OuterClass$InnerClass#MethodName". - * This usage is deprecated. Please use method 'delegates' instead. - */ - private final static String[] OVERRIDDEN_METHODS = new String[] { - }; - - /** * The list of classes to rename, must be an even list: the binary FQCN * of class to replace followed by the new FQCN. */ private final static String[] RENAMED_CLASSES = new String[] { "android.os.ServiceManager", "android.os._Original_ServiceManager", + "android.view.textservice.TextServicesManager", "android.view.textservice._Original_TextServicesManager", "android.util.LruCache", "android.util._Original_LruCache", "android.view.SurfaceView", "android.view._Original_SurfaceView", "android.view.accessibility.AccessibilityManager", "android.view.accessibility._Original_AccessibilityManager", diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java index 6c62423a2a89..535a9a8c0b77 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java @@ -45,13 +45,6 @@ public interface ICreateInfo { String[] getDelegateClassNatives(); /** - * Returns The list of methods to stub out. Each entry must be in the form - * "package.package.OuterClass$InnerClass#MethodName". - * The list can be empty but must not be null. - */ - String[] getOverriddenMethods(); - - /** * Returns the list of classes to rename, must be an even list: the binary FQCN * of class to replace followed by the new FQCN. * The list can be empty but must not be null. diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java index 9bb91e599d77..4b6cf4354d91 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java @@ -124,6 +124,7 @@ public class Main { "android.annotation.NonNull", // annotations "android.annotation.Nullable", // annotations "com.android.internal.transition.EpicenterTranslateClipReveal", + "com.android.internal.graphics.drawable.AnimationScaleListDrawable", }, excludeClasses, new String[] { diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java index c4dd7eeafbba..0560d8aca1bd 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java @@ -107,12 +107,6 @@ public class AsmGeneratorTest { } @Override - public String[] getOverriddenMethods() { - // methods to force override - return EMPTY_STRING_ARRAY; - } - - @Override public String[] getRenamedClasses() { // classes to rename (so that we can replace them) return new String[] { @@ -187,12 +181,6 @@ public class AsmGeneratorTest { } @Override - public String[] getOverriddenMethods() { - // methods to force override - return EMPTY_STRING_ARRAY; - } - - @Override public String[] getRenamedClasses() { // classes to rename (so that we can replace them) return EMPTY_STRING_ARRAY; @@ -274,12 +262,6 @@ public class AsmGeneratorTest { } @Override - public String[] getOverriddenMethods() { - // methods to force override - return EMPTY_STRING_ARRAY; - } - - @Override public String[] getRenamedClasses() { // classes to rename (so that we can replace them) return EMPTY_STRING_ARRAY; @@ -360,12 +342,6 @@ public class AsmGeneratorTest { } @Override - public String[] getOverriddenMethods() { - // methods to force override - return EMPTY_STRING_ARRAY; - } - - @Override public String[] getRenamedClasses() { // classes to rename (so that we can replace them) return EMPTY_STRING_ARRAY; diff --git a/tools/streaming_proto/Android.mk b/tools/streaming_proto/Android.mk new file mode 100644 index 000000000000..5a54fd10415d --- /dev/null +++ b/tools/streaming_proto/Android.mk @@ -0,0 +1,41 @@ +# +# Copyright (C) 2015 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. +# +LOCAL_PATH:= $(call my-dir) + +# ========================================================== +# Build the host executable: protoc-gen-javastream +# ========================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := protoc-gen-javastream +LOCAL_SRC_FILES := \ + Errors.cpp \ + string_utils.cpp \ + main.cpp +LOCAL_SHARED_LIBRARIES := \ + libprotoc +include $(BUILD_HOST_EXECUTABLE) + +# ========================================================== +# Build the java test +# ========================================================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := \ + $(call all-java-files-under, test) \ + $(call all-proto-files-under, test) +LOCAL_MODULE := StreamingProtoTest +LOCAL_PROTOC_OPTIMIZE_TYPE := stream +include $(BUILD_JAVA_LIBRARY) + diff --git a/tools/streaming_proto/Errors.cpp b/tools/streaming_proto/Errors.cpp new file mode 100644 index 000000000000..91c6b9245de0 --- /dev/null +++ b/tools/streaming_proto/Errors.cpp @@ -0,0 +1,87 @@ +#include "Errors.h" + +#include <stdlib.h> + +namespace android { +namespace javastream_proto { + +Errors ERRORS; + +const string UNKNOWN_FILE; +const int UNKNOWN_LINE = 0; + +Error::Error() +{ +} + +Error::Error(const Error& that) + :filename(that.filename), + lineno(that.lineno), + message(that.message) +{ +} + +Error::Error(const string& f, int l, const char* m) + :filename(f), + lineno(l), + message(m) +{ +} + +Errors::Errors() + :m_errors() +{ +} + +Errors::~Errors() +{ +} + +void +Errors::Add(const string& filename, int lineno, const char* format, ...) +{ + va_list args; + va_start(args, format); + AddImpl(filename, lineno, format, args); + va_end(args); +} + +void +Errors::AddImpl(const string& filename, int lineno, const char* format, va_list args) +{ + va_list args2; + va_copy(args2, args); + int message_size = vsnprintf((char*)NULL, 0, format, args2); + va_end(args2); + + char* buffer = new char[message_size+1]; + vsnprintf(buffer, message_size, format, args); + Error error(filename, lineno, buffer); + delete[] buffer; + + m_errors.push_back(error); +} + +void +Errors::Print() const +{ + for (vector<Error>::const_iterator it = m_errors.begin(); it != m_errors.end(); it++) { + if (it->filename == UNKNOWN_FILE) { + fprintf(stderr, "%s", it->message.c_str()); + } else if (it->lineno == UNKNOWN_LINE) { + fprintf(stderr, "%s:%s", it->filename.c_str(), it->message.c_str()); + } else { + fprintf(stderr, "%s:%d:%s", it->filename.c_str(), it->lineno, it->message.c_str()); + } + } +} + +bool +Errors::HasErrors() const +{ + return m_errors.size() > 0; +} + +} // namespace javastream_proto +} // namespace android + diff --git a/tools/streaming_proto/Errors.h b/tools/streaming_proto/Errors.h new file mode 100644 index 000000000000..109195a20b06 --- /dev/null +++ b/tools/streaming_proto/Errors.h @@ -0,0 +1,48 @@ +#include <stdio.h> + +#include <string> +#include <vector> + +namespace android { +namespace javastream_proto { + +using namespace std; + +struct Error +{ + Error(); + explicit Error(const Error& that); + Error(const string& filename, int lineno, const char* message); + + string filename; + int lineno; + string message; +}; + +class Errors +{ +public: + Errors(); + ~Errors(); + + // Add an error + void Add(const string& filename, int lineno, const char* format, ...); + + // Print the errors to stderr if there are any. + void Print() const; + + bool HasErrors() const; + +private: + // The errors that have been added + vector<Error> m_errors; + void AddImpl(const string& filename, int lineno, const char* format, va_list ap); +}; + +extern Errors ERRORS; +extern const string UNKNOWN_FILE; +extern const int UNKNOWN_LINE; + + +} // namespace javastream_proto +} // namespace android diff --git a/tools/streaming_proto/main.cpp b/tools/streaming_proto/main.cpp new file mode 100644 index 000000000000..5435728a3d4b --- /dev/null +++ b/tools/streaming_proto/main.cpp @@ -0,0 +1,408 @@ +#include "Errors.h" + +#include "string_utils.h" + +#include "google/protobuf/compiler/plugin.pb.h" +#include "google/protobuf/io/zero_copy_stream_impl.h" +#include "google/protobuf/text_format.h" + +#include <stdio.h> +#include <iomanip> +#include <iostream> +#include <sstream> +#include <map> + +using namespace android::javastream_proto; +using namespace google::protobuf; +using namespace google::protobuf::compiler; +using namespace google::protobuf::io; +using namespace std; + +const int FIELD_TYPE_SHIFT = 32; +const uint64_t FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT; +const uint64_t FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT; + +const int FIELD_COUNT_SHIFT = 40; +const uint64_t FIELD_COUNT_SINGLE = 1L << FIELD_COUNT_SHIFT; +const uint64_t FIELD_COUNT_REPEATED = 2L << FIELD_COUNT_SHIFT; +const uint64_t FIELD_COUNT_PACKED = 5L << FIELD_COUNT_SHIFT; + + +/** + * See if this is the file for this request, and not one of the imported ones. + */ +static bool +should_generate_for_file(const CodeGeneratorRequest& request, const string& file) +{ + const int N = request.file_to_generate_size(); + for (int i=0; i<N; i++) { + if (request.file_to_generate(i) == file) { + return true; + } + } + return false; +} + +/** + * If the descriptor gives us a class name, use that. Otherwise make one up from + * the filename of the .proto file. + */ +static string +make_outer_class_name(const FileDescriptorProto& file_descriptor) +{ + string name = file_descriptor.options().java_outer_classname(); + if (name.size() == 0) { + name = to_camel_case(file_base_name(file_descriptor.name())); + if (name.size() == 0) { + ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, + "Unable to make an outer class name for file: %s", + file_descriptor.name().c_str()); + name = "Unknown"; + } + } + return name; +} + +/** + * Figure out the package name that we are generating. + */ +static string +make_java_package(const FileDescriptorProto& file_descriptor) { + if (file_descriptor.options().has_java_package()) { + return file_descriptor.options().java_package(); + } else { + return file_descriptor.package(); + } +} + +/** + * Figure out the name of the file we are generating. + */ +static string +make_file_name(const FileDescriptorProto& file_descriptor) +{ + string const package = make_java_package(file_descriptor); + string result; + if (package.size() > 0) { + result = replace_string(package, '.', '/'); + result += '/'; + } + + result += make_outer_class_name(file_descriptor); + result += ".java"; + + return result; +} + +static string +indent_more(const string& indent) +{ + return indent + " "; +} + +/** + * Write the constants for an enum. + */ +static void +write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent) +{ + const int N = enu.value_size(); + text << indent << "// enum " << enu.name() << endl; + for (int i=0; i<N; i++) { + const EnumValueDescriptorProto& value = enu.value(i); + text << indent << "public static final int " + << make_constant_name(value.name()) + << " = " << value.number() << ";" << endl; + } + text << endl; +} + +/** + * Get the string name for a field. + */ +static string +get_proto_type(const FieldDescriptorProto& field) +{ + switch (field.type()) { + case FieldDescriptorProto::TYPE_DOUBLE: + return "double"; + case FieldDescriptorProto::TYPE_FLOAT: + return "float"; + case FieldDescriptorProto::TYPE_INT64: + return "int64"; + case FieldDescriptorProto::TYPE_UINT64: + return "uint64"; + case FieldDescriptorProto::TYPE_INT32: + return "int32"; + case FieldDescriptorProto::TYPE_FIXED64: + return "fixed64"; + case FieldDescriptorProto::TYPE_FIXED32: + return "fixed32"; + case FieldDescriptorProto::TYPE_BOOL: + return "bool"; + case FieldDescriptorProto::TYPE_STRING: + return "string"; + case FieldDescriptorProto::TYPE_GROUP: + return "group<unsupported!>"; + case FieldDescriptorProto::TYPE_MESSAGE: + return field.type_name(); + case FieldDescriptorProto::TYPE_BYTES: + return "bytes"; + case FieldDescriptorProto::TYPE_UINT32: + return "uint32"; + case FieldDescriptorProto::TYPE_ENUM: + return field.type_name(); + case FieldDescriptorProto::TYPE_SFIXED32: + return "sfixed32"; + case FieldDescriptorProto::TYPE_SFIXED64: + return "sfixed64"; + case FieldDescriptorProto::TYPE_SINT32: + return "sint32"; + case FieldDescriptorProto::TYPE_SINT64: + return "sint64"; + default: + // won't happen + return "void"; + } +} + +static uint64_t +get_field_id(const FieldDescriptorProto& field) +{ + // Number + uint64_t result = (uint32_t)field.number(); + + // Type + switch (field.type()) { + case FieldDescriptorProto::TYPE_DOUBLE: + result |= FIELD_TYPE_DOUBLE; + break; + case FieldDescriptorProto::TYPE_FLOAT: + result |= FIELD_TYPE_FLOAT; + break; + case FieldDescriptorProto::TYPE_INT64: + result |= FIELD_TYPE_INT64; + break; + case FieldDescriptorProto::TYPE_UINT64: + result |= FIELD_TYPE_UINT64; + break; + case FieldDescriptorProto::TYPE_INT32: + result |= FIELD_TYPE_INT32; + break; + case FieldDescriptorProto::TYPE_FIXED64: + result |= FIELD_TYPE_FIXED64; + break; + case FieldDescriptorProto::TYPE_FIXED32: + result |= FIELD_TYPE_FIXED32; + break; + case FieldDescriptorProto::TYPE_BOOL: + result |= FIELD_TYPE_BOOL; + break; + case FieldDescriptorProto::TYPE_STRING: + result |= FIELD_TYPE_STRING; + break; + case FieldDescriptorProto::TYPE_MESSAGE: + result |= FIELD_TYPE_OBJECT; + break; + case FieldDescriptorProto::TYPE_BYTES: + result |= FIELD_TYPE_BYTES; + break; + case FieldDescriptorProto::TYPE_UINT32: + result |= FIELD_TYPE_UINT32; + break; + case FieldDescriptorProto::TYPE_ENUM: + result |= FIELD_TYPE_ENUM; + break; + case FieldDescriptorProto::TYPE_SFIXED32: + result |= FIELD_TYPE_SFIXED32; + break; + case FieldDescriptorProto::TYPE_SFIXED64: + result |= FIELD_TYPE_SFIXED64; + break; + case FieldDescriptorProto::TYPE_SINT32: + result |= FIELD_TYPE_SINT32; + break; + case FieldDescriptorProto::TYPE_SINT64: + result |= FIELD_TYPE_SINT64; + break; + default: + ; + } + + // Count + if (field.options().packed()) { + result |= FIELD_COUNT_PACKED; + } else if (field.label() == FieldDescriptorProto::LABEL_REPEATED) { + result |= FIELD_COUNT_REPEATED; + } else { + result |= FIELD_COUNT_SINGLE; + } + + return result; +} + +/** + * Write a field. + */ +static void +write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent) +{ + string optional_comment = field.label() == FieldDescriptorProto::LABEL_OPTIONAL + ? "optional " : ""; + string repeated_comment = field.label() == FieldDescriptorProto::LABEL_REPEATED + ? "repeated " : ""; + string proto_type = get_proto_type(field); + string packed_comment = field.options().packed() + ? " [packed=true]" : ""; + text << indent << "// " << optional_comment << repeated_comment << proto_type << ' ' + << field.name() << " = " << field.number() << packed_comment << ';' << endl; + + text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x"; + + ios::fmtflags fmt(text.flags()); + text << setfill('0') << setw(16) << hex << get_field_id(field); + text.flags(fmt); + + text << "L;" << endl; + + text << endl; +} + +/** + * Write a Message constants class. + */ +static void +write_message(stringstream& text, const DescriptorProto& message, const string& indent) +{ + int N; + const string indented = indent_more(indent); + + text << indent << "// message " << message.name() << endl; + text << indent << "public final class " << message.name() << " {" << endl; + text << endl; + + // Enums + N = message.enum_type_size(); + for (int i=0; i<N; i++) { + write_enum(text, message.enum_type(i), indented); + } + + // Nested classes + N = message.nested_type_size(); + for (int i=0; i<N; i++) { + write_message(text, message.nested_type(i), indented); + } + + // Fields + N = message.field_size(); + for (int i=0; i<N; i++) { + write_field(text, message.field(i), indented); + } + + text << indent << "}" << endl; + text << endl; +} + +/** + * Write the contents of a file. + */ +static void +write_file(stringstream& text, const FileDescriptorProto& file_descriptor) +{ + string const package_name = make_java_package(file_descriptor); + string const outer_class_name = make_outer_class_name(file_descriptor); + + text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl; + text << "// source: " << file_descriptor.name() << endl << endl; + + if (package_name.size() > 0) { + if (package_name.size() > 0) { + text << "package " << package_name << ";" << endl; + text << endl; + } + } + + // This bit of policy is android api rules specific: Raw proto classes + // must never be in the API, but they should all be available for testing. + text << "/** @hide */" << endl; + text << "@android.annotation.TestApi" << endl; + + text << "public final class " << outer_class_name << " {" << endl; + text << endl; + + int N; + const string indented = indent_more(""); + + N = file_descriptor.enum_type_size(); + for (int i=0; i<N; i++) { + write_enum(text, file_descriptor.enum_type(i), indented); + } + + N = file_descriptor.message_type_size(); + for (int i=0; i<N; i++) { + write_message(text, file_descriptor.message_type(i), indented); + } + + text << "}" << endl; +} + +/** + * Main. + */ +int +main(int argc, char const*const* argv) +{ + (void)argc; + (void)argv; + + GOOGLE_PROTOBUF_VERIFY_VERSION; + + CodeGeneratorRequest request; + CodeGeneratorResponse response; + + // Read the request + request.ParseFromIstream(&cin); + + // Build the files we need. + const int N = request.proto_file_size(); + for (int i=0; i<N; i++) { + const FileDescriptorProto& file_descriptor = request.proto_file(i); + if (should_generate_for_file(request, file_descriptor.name())) { + // Generate the text + stringstream text; + write_file(text, file_descriptor); + + // Put the text in the response + CodeGeneratorResponse::File* file_response = response.add_file(); + file_response->set_name(make_file_name(file_descriptor)); + file_response->set_content(text.str()); + + cerr << "writing file: " << file_response->name() << endl; + } + } + + // If we had errors, don't write the response. Print the errors and exit. + if (ERRORS.HasErrors()) { + ERRORS.Print(); + return 1; + } + + // If we didn't have errors, write the response and exit happily. + response.SerializeToOstream(&cout); + return 0; +} diff --git a/tools/streaming_proto/string_utils.cpp b/tools/streaming_proto/string_utils.cpp new file mode 100644 index 000000000000..cc738c4c108e --- /dev/null +++ b/tools/streaming_proto/string_utils.cpp @@ -0,0 +1,95 @@ + +#include "string_utils.h" +#include <iostream> + +namespace android { +namespace javastream_proto { + +using namespace std; + +string +to_camel_case(const string& str) +{ + string result; + const int N = str.size(); + result.reserve(N); + bool capitalize_next = true; + for (int i=0; i<N; i++) { + char c = str[i]; + if (c == '_') { + capitalize_next = true; + } else { + if (capitalize_next && c >= 'a' && c <= 'z') { + c = 'A' + c - 'a'; + capitalize_next = false; + } else if (c >= 'A' && c <= 'Z') { + capitalize_next = false; + } else if (c >= '0' && c <= '9') { + capitalize_next = true; + } else { + // All other characters (e.g. non-latin) count as capital. + capitalize_next = false; + } + result += c; + } + } + return result; +} + +string +make_constant_name(const string& str) +{ + string result; + const int N = str.size(); + bool underscore_next = false; + for (int i=0; i<N; i++) { + char c = str[i]; + if (c >= 'A' && c <= 'Z') { + if (underscore_next) { + result += '_'; + underscore_next = false; + } + } else if (c >= 'a' && c <= 'z') { + c = 'A' + c - 'a'; + underscore_next = true; + } else if (c == '_') { + underscore_next = false; + } + result += c; + } + return result; +} + +string +file_base_name(const string& str) +{ + size_t start = str.rfind('/'); + if (start == string::npos) { + start = 0; + } else { + start++; + } + size_t end = str.find('.', start); + if (end == string::npos) { + end = str.size(); + } + return str.substr(start, end-start); +} + +string +replace_string(const string& str, const char replace, const char with) +{ + string result(str); + const int N = result.size(); + for (int i=0; i<N; i++) { + if (result[i] == replace) { + result[i] = with; + } + } + return result; +} + +} // namespace javastream_proto +} // namespace android + + diff --git a/tools/streaming_proto/string_utils.h b/tools/streaming_proto/string_utils.h new file mode 100644 index 000000000000..ffe83ca99704 --- /dev/null +++ b/tools/streaming_proto/string_utils.h @@ -0,0 +1,32 @@ +#include <string> + +namespace android { +namespace javastream_proto { + +using namespace std; + +/** + * Capitalizes the string, removes underscores and makes the next letter + * capitalized, and makes the letter following numbers capitalized. + */ +string to_camel_case(const string& str); + +/** + * Capitalize and insert underscores for CamelCase. + */ +string make_constant_name(const string& str); + +/** + * Returns the part of a file name that isn't a path and isn't a type suffix. + */ +string file_base_name(const string& str); + +/** + * Replace all occurances of 'replace' with 'with'. + */ +string replace_string(const string& str, const char replace, const char with); + + +} // namespace javastream_proto +} // namespace android + diff --git a/tools/streaming_proto/test/imported.proto b/tools/streaming_proto/test/imported.proto new file mode 100644 index 000000000000..05c8f0c54fac --- /dev/null +++ b/tools/streaming_proto/test/imported.proto @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2016 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. + */ + +syntax = "proto2"; + +package com.android.streaming_proto_test; + +/** + * Message that is used from the other file. + */ +message ImportedMessage { + optional int32 data = 1; +}; diff --git a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java b/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java new file mode 100644 index 000000000000..1246f539b44b --- /dev/null +++ b/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java @@ -0,0 +1,7 @@ +package com.android.streaming_proto_test; + +public class Main { + public void main(String[] argv) { + System.out.println("hello world"); + } +} diff --git a/tools/streaming_proto/test/test.proto b/tools/streaming_proto/test/test.proto new file mode 100644 index 000000000000..de80ed6612fc --- /dev/null +++ b/tools/streaming_proto/test/test.proto @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2016 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. + */ + +syntax = "proto2"; + +import "frameworks/base/tools/streaming_proto/test/imported.proto"; + +package com.android.streaming_proto_test; + +/** + * Enum that outside the scope of any classes. + */ +enum Outside { + OUTSIDE_0 = 0; + OUTSIDE_1 = 1; +}; + +message Sibling { + optional int32 int32_field = 1; +} + +/** + * Message with all of the field types. + */ +message All { + /** + * Enum that is inside the scope of a class. + */ + enum Inside { + option allow_alias = true; + INSIDE_0 = 0; + INSIDE_1 = 1; + INSIDE_1A = 1; + }; + + /** + * Message that is recursive. + */ + message Nested { + optional int32 data = 10001; + optional Nested nested = 10002; + }; + + optional double double_field = 10; + repeated double double_field_repeated = 11; + repeated double double_field_packed = 12 [packed=true]; + + optional float float_field = 20; + repeated float float_field_repeated = 21; + repeated float float_field_packed = 22 [packed=true]; + + optional int32 int32_field = 30; + repeated int32 int32_field_repeated = 31; + repeated int32 int32_field_packed = 32 [packed=true]; + + optional int64 int64_field = 40; + repeated int64 int64_field_repeated = 41; + repeated int64 int64_field_packed = 42 [packed=true]; + + optional uint32 uint32_field = 50; + repeated uint32 uint32_field_repeated = 51; + repeated uint32 uint32_field_packed = 52 [packed=true]; + + optional uint64 uint64_field = 60; + repeated uint64 uint64_field_repeated = 61; + repeated uint64 uint64_field_packed = 62 [packed=true]; + + optional sint32 sint32_field = 70; + repeated sint32 sint32_field_repeated = 71; + repeated sint32 sint32_field_packed = 72 [packed=true]; + + optional sint64 sint64_field = 80; + repeated sint64 sint64_field_repeated = 81; + repeated sint64 sint64_field_packed = 82 [packed=true]; + + optional fixed32 fixed32_field = 90; + repeated fixed32 fixed32_field_repeated = 91; + repeated fixed32 fixed32_field_packed = 92 [packed=true]; + + optional fixed64 fixed64_field = 100; + repeated fixed64 fixed64_field_repeated = 101; + repeated fixed64 fixed64_field_packed = 102 [packed=true]; + + optional sfixed32 sfixed32_field = 110; + repeated sfixed32 sfixed32_field_repeated = 111; + repeated sfixed32 sfixed32_field_packed = 112 [packed=true]; + + optional sfixed64 sfixed64_field = 120; + repeated sfixed64 sfixed64_field_repeated = 121; + repeated sfixed64 sfixed64_field_packed = 122 [packed=true]; + + optional bool bool_field = 130; + repeated bool bool_field_repeated = 131; + repeated bool bool_field_packed = 132 [packed=true]; + + optional string string_field = 140; + repeated string string_field_repeated = 141; + + optional bytes bytes_field = 150; + repeated bytes bytes_field_repeated = 151; + + optional Outside outside_field = 160; + repeated Outside outside_field_repeated = 161; + repeated Outside outside_field_packed = 162 [packed=true]; + + optional Nested nested_field = 170; + repeated Nested nested_field_repeated = 171; + + optional ImportedMessage imported_field = 180; + repeated ImportedMessage imported_field_repeated = 181; +}; |