diff options
Diffstat (limited to 'tools')
327 files changed, 36972 insertions, 23904 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..71c5ef2fcda0 --- /dev/null +++ b/tools/aapt2/.clang-format @@ -0,0 +1,3 @@ +BasedOnStyle: Google +ColumnLimit: 100 + 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..a2b216d01b11 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 = "4"; -} // 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..4185937e6e38 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,317 @@ 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()); - } - } + manifest_action["meta-data"] = meta_data_action; - // Super implementation to iterate over the children. - xml::Visitor::visit(el); - } + // Application actions. + xml::XmlNodeAction& application_action = manifest_action["application"]; + application_action.Action(OptionalNameIsJavaClassName); -private: - StringPiece16 mPackage; -}; + // Uses library actions. + application_action["uses-library"]; -static bool renameManifestPackage(const StringPiece16& packageOverride, xml::Element* manifestEl) { - xml::Attribute* attr = manifestEl->findAttribute({}, u"package"); + // Meta-data. + application_action["meta-data"] = meta_data_action; - // We've already verified that the manifest element is present, with a package name specified. - assert(attr); + // Activity actions. + application_action["activity"].Action(RequiredNameIsJavaClassName); + application_action["activity"]["intent-filter"] = intent_filter_action; + application_action["activity"]["meta-data"] = meta_data_action; - std::u16string originalPackage = std::move(attr->value); - attr->value = packageOverride.toString(); + // Activity alias actions. + application_action["activity-alias"]["intent-filter"] = intent_filter_action; + application_action["activity-alias"]["meta-data"] = meta_data_action; - FullyQualifiedClassNameVisitor visitor(originalPackage); - manifestEl->accept(&visitor); - return true; + // Service actions. + application_action["service"].Action(RequiredNameIsJavaClassName); + application_action["service"]["intent-filter"] = intent_filter_action; + application_action["service"]["meta-data"] = meta_data_action; + + // 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..fc6970c8c5bd 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -15,242 +15,341 @@ */ #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, AllowMetaData) { + auto doc = Verify(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <meta-data /> + <application> + <meta-data /> + <activity android:name=".Hi"><meta-data /></activity> + <activity-alias android:name=".Ho"><meta-data /></activity-alias> + <receiver android:name=".OffToWork"><meta-data /></receiver> + <provider android:name=".We"><meta-data /></provider> + <service android:name=".Go"><meta-data /></service> + </application> + </manifest>)EOF"); + ASSERT_NE(nullptr, doc); } 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); + 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); +} - 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); +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..800103307e2b --- /dev/null +++ b/tools/aapt2/readme.md @@ -0,0 +1,44 @@ +# Android Asset Packaging Tool 2.0 (AAPT2) release notes + +## Version 2.4 +### `aapt2 link ...` +- Supports `<meta-data>` tags in `<manifest>`. + +## 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/.idea/libraries/junit.xml b/tools/layoutlib/.idea/libraries/junit.xml index c889f5ff6c97..ba46ebff7d16 100644 --- a/tools/layoutlib/.idea/libraries/junit.xml +++ b/tools/layoutlib/.idea/libraries/junit.xml @@ -1,7 +1,7 @@ <component name="libraryTable"> <library name="junit"> <CLASSES> - <root url="jar://$PROJECT_DIR$/../../../../out/host/common/obj/JAVA_LIBRARIES/junit_intermediates/javalib.jar!/" /> + <root url="jar://$PROJECT_DIR$/../../../../out/host/common/obj/JAVA_LIBRARIES/junit-host_intermediates/javalib.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> 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..35cf9038f9ae 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java @@ -80,7 +80,7 @@ public final class BridgeTypedArray extends TypedArray { public BridgeTypedArray(Resources resources, BridgeContext context, int len, boolean platformFile) { - super(resources, null, null, 0); + super(resources); mBridgeResources = resources; mContext = context; mPlatformFile = platformFile; @@ -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..4b9815d93ac7 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,9 +31,9 @@ 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; import java.lang.Override; @@ -76,16 +77,15 @@ public class IWindowManagerImpl implements IWindowManager { // ---- unused implementation of IWindowManager ---- @Override - 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) - throws RemoteException { + public void addAppToken(int addPos, IApplicationToken token, int taskId, + int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int configChanges, + boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable, + int targetSdkVersion, int rotationAnimationHint) 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 +276,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 } @@ -325,9 +325,7 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public void setAppTask(IBinder arg0, int arg1, int arg2, Rect arg3, Configuration arg4, - int arg5, boolean arg6) - throws RemoteException { + public void addAppToTask(IBinder arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub } @@ -412,7 +410,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 +485,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 +513,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 +580,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 +608,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 +616,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/SurfaceView.java b/tools/layoutlib/bridge/src/android/view/SurfaceView.java index 1e7dfbe0aed9..ebb2af45320e 100644 --- a/tools/layoutlib/bridge/src/android/view/SurfaceView.java +++ b/tools/layoutlib/bridge/src/android/view/SurfaceView.java @@ -21,6 +21,7 @@ import com.android.layoutlib.bridge.MockView; import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; +import android.graphics.Region; import android.util.AttributeSet; /** @@ -49,6 +50,19 @@ public class SurfaceView extends MockView { super(context, attrs, defStyleAttr, defStyleRes); } + public boolean gatherTransparentRegion(Region region) { + return false; + } + + public void setZOrderMediaOverlay(boolean isMediaOverlay) { + } + + public void setZOrderOnTop(boolean onTop) { + } + + public void setSecure(boolean isSecure) { + } + public SurfaceHolder getHolder() { return mSurfaceHolder; } 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/internal/view/animation/NativeInterpolatorFactoryHelper_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/view/animation/NativeInterpolatorFactoryHelper_Delegate.java index ffce1a0496d9..da1ab27b6ff2 100644 --- a/tools/layoutlib/bridge/src/com/android/internal/view/animation/NativeInterpolatorFactoryHelper_Delegate.java +++ b/tools/layoutlib/bridge/src/com/android/internal/view/animation/NativeInterpolatorFactoryHelper_Delegate.java @@ -19,6 +19,7 @@ package com.android.internal.view.animation; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import android.graphics.Path; import android.util.MathUtils; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; @@ -31,6 +32,7 @@ import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.OvershootInterpolator; +import android.view.animation.PathInterpolator; /** * Delegate used to provide new implementation of a select few methods of {@link @@ -93,6 +95,16 @@ public class NativeInterpolatorFactoryHelper_Delegate { return sManager.addNewDelegate(new OvershootInterpolator(tension)); } + @LayoutlibDelegate + /*package*/ static long createPathInterpolator(float[] x, float[] y) { + Path path = new Path(); + path.moveTo(x[0], y[0]); + for (int i = 1; i < x.length; i++) { + path.lineTo(x[i], y[i]); + } + return sManager.addNewDelegate(new PathInterpolator(path)); + } + private static class LutInterpolator extends BaseInterpolator { private final float[] mValues; private final int mSize; 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..c827f178279e 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 @@ -97,8 +97,8 @@ public final class BridgeContentProvider implements IContentProvider { } @Override - public Cursor query(String callingPackage, Uri arg0, String[] arg1, String arg2, String[] arg3, - String arg4, ICancellationSignal arg5) throws RemoteException { + public Cursor query(String callingPackage, Uri arg0, String[] arg1, + Bundle arg3, ICancellationSignal arg4) throws RemoteException { // TODO Auto-generated method stub return null; } @@ -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..726ff223bd8f 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; @@ -168,13 +166,13 @@ class Layout extends RelativeLayout { FrameLayout contentRoot = new FrameLayout(getContext()); LayoutParams params = createLayoutParams(MATCH_PARENT, MATCH_PARENT); int rule = mBuilder.isNavBarVertical() ? START_OF : ABOVE; - if (mBuilder.hasNavBar() && mBuilder.solidBars()) { + if (mBuilder.hasSolidNavBar()) { params.addRule(rule, getId(ID_NAV_BAR)); } int below = -1; if (mBuilder.mActionBarSize <= 0 && mBuilder.mTitleBarSize > 0) { below = getId(ID_TITLE_BAR); - } else if (mBuilder.hasStatusBar() && mBuilder.solidBars()) { + } else if (mBuilder.hasSolidStatusBar()) { below = getId(ID_STATUS_BAR); } if (below != -1) { @@ -236,17 +234,17 @@ 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); } LayoutParams layoutParams = createLayoutParams(MATCH_PARENT, MATCH_PARENT); int rule = mBuilder.isNavBarVertical() ? START_OF : ABOVE; - if (mBuilder.hasNavBar() && mBuilder.solidBars()) { + if (mBuilder.hasSolidNavBar()) { layoutParams.addRule(rule, getId(ID_NAV_BAR)); } - if (mBuilder.hasStatusBar() && mBuilder.solidBars()) { + if (mBuilder.hasSolidStatusBar()) { layoutParams.addRule(BELOW, getId(ID_STATUS_BAR)); } actionBar.getRootView().setLayoutParams(layoutParams); @@ -259,10 +257,10 @@ class Layout extends RelativeLayout { int simulatedPlatformVersion) { TitleBar titleBar = new TitleBar(context, title, simulatedPlatformVersion); LayoutParams params = createLayoutParams(MATCH_PARENT, mBuilder.mTitleBarSize); - if (mBuilder.hasStatusBar() && mBuilder.solidBars()) { + if (mBuilder.hasSolidStatusBar()) { params.addRule(BELOW, getId(ID_STATUS_BAR)); } - if (mBuilder.isNavBarVertical() && mBuilder.solidBars()) { + if (mBuilder.isNavBarVertical() && mBuilder.hasSolidNavBar()) { params.addRule(START_OF, getId(ID_NAV_BAR)); } titleBar.setLayoutParams(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,39 +418,18 @@ 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 nav bar is present and not translucent + */ + private boolean hasSolidNavBar() { + return hasNavBar() && !mTranslucentNav; } /** - * Return true if the status bar or nav bar are present, they are not translucent (i.e - * content doesn't overlap with them). + * Return true if the status bar is present and not translucent */ - private boolean solidBars() { - return !(hasNavBar() && mTranslucentNav) && !(hasStatusBar() && mTranslucentStatus); + private boolean hasSolidStatusBar() { + return hasStatusBar() && !mTranslucentStatus; } private boolean hasNavBar() { 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/four_corners.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners.png Binary files differnew file mode 100644 index 000000000000..868cd51f8bb5 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent.png Binary files differnew file mode 100644 index 000000000000..601f711f32d3 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent_land.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent_land.png Binary files differnew file mode 100644 index 000000000000..0b8f1a96791c --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent_land.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/four_corners.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/four_corners.xml new file mode 100644 index 000000000000..c42284a262ae --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/four_corners.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_centerHorizontal="true" + android:textSize="40sp" + android:text="Bottom Text" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_centerHorizontal="true" + android:textSize="40sp" + android:text="Top text" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:textSize="40sp" + android:text="Start text" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:textSize="40sp" + android:text="End text" /> +</RelativeLayout>
\ No newline at end of file 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..c813a1292318 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,8 @@ 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.resources.ScreenOrientation; +import com.android.tools.layoutlib.java.System_Delegate; import com.android.utils.ILogger; import org.junit.AfterClass; @@ -51,6 +53,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 +61,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 +122,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 +137,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 +218,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 +240,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 +253,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 +313,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 +342,46 @@ public class Main { renderAndVerify("activity.xml", "activity.png"); } + @Test + public void testTranslucentBars() throws ClassNotFoundException { + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); + layoutLibCallback.initResources(); + + LayoutPullParser parser = createLayoutPullParser("four_corners.xml"); + SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, + layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false, + RenderingMode.NORMAL, 22); + renderAndVerify(params, "four_corners_translucent.png"); + + parser = createLayoutPullParser("four_corners.xml"); + params = getSessionParams(parser, ConfigGenerator.NEXUS_5_LAND, + layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false, + RenderingMode.NORMAL, 22); + renderAndVerify(params, "four_corners_translucent_land.png"); + + parser = createLayoutPullParser("four_corners.xml"); + params = getSessionParams(parser, ConfigGenerator.NEXUS_5, + layoutLibCallback, "Theme.Material.Light.NoActionBar", false, + RenderingMode.NORMAL, 22); + renderAndVerify(params, "four_corners.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 +404,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 +454,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 +489,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 +515,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 +526,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 +563,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 +583,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 +633,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 +702,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..5b4ba04b2b58 --- /dev/null +++ b/tools/streaming_proto/main.cpp @@ -0,0 +1,474 @@ +#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, const string& class_name) +{ + string const package = make_java_package(file_descriptor); + string result; + if (package.size() > 0) { + result = replace_string(package, '.', '/'); + result += '/'; + } + + result += class_name; + 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. + * + * If there are enums and generate_outer is false, invalid java code will be generated. + */ +static void +write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor, + const string& filename, bool generate_outer, + const vector<EnumDescriptorProto>& enums, const vector<DescriptorProto>& messages) +{ + stringstream text; + + 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 + text << "/** @hide */" << endl; +// text << "@android.annotation.TestApi" << endl; + + if (generate_outer) { + text << "public final class " << outer_class_name << " {" << endl; + text << endl; + } + + size_t N; + const string indented = generate_outer ? indent_more("") : string(); + + N = enums.size(); + for (size_t i=0; i<N; i++) { + write_enum(text, enums[i], indented); + } + + N = messages.size(); + for (size_t i=0; i<N; i++) { + write_message(text, messages[i], indented); + } + + if (generate_outer) { + text << "}" << endl; + } + + CodeGeneratorResponse::File* file_response = response->add_file(); + file_response->set_name(filename); + file_response->set_content(text.str()); +} + +/** + * Write one file per class. Put all of the enums into the "outer" class. + */ +static void +write_multiple_files(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor) +{ + // If there is anything to put in the outer class file, create one + if (file_descriptor.enum_type_size() > 0) { + vector<EnumDescriptorProto> enums; + int N = file_descriptor.enum_type_size(); + for (int i=0; i<N; i++) { + enums.push_back(file_descriptor.enum_type(i)); + } + + vector<DescriptorProto> messages; + + write_file(response, file_descriptor, + make_file_name(file_descriptor, make_outer_class_name(file_descriptor)), + true, enums, messages); + } + + // For each of the message types, make a file + int N = file_descriptor.message_type_size(); + for (int i=0; i<N; i++) { + vector<EnumDescriptorProto> enums; + + vector<DescriptorProto> messages; + messages.push_back(file_descriptor.message_type(i)); + + write_file(response, file_descriptor, + make_file_name(file_descriptor, file_descriptor.message_type(i).name()), + false, enums, messages); + } +} + +static void +write_single_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor) +{ + int N; + + vector<EnumDescriptorProto> enums; + N = file_descriptor.enum_type_size(); + for (int i=0; i<N; i++) { + enums.push_back(file_descriptor.enum_type(i)); + } + + vector<DescriptorProto> messages; + N = file_descriptor.message_type_size(); + for (int i=0; i<N; i++) { + messages.push_back(file_descriptor.message_type(i)); + } + + write_file(response, file_descriptor, + make_file_name(file_descriptor, make_outer_class_name(file_descriptor)), + true, enums, messages); +} + +/** + * 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())) { + if (file_descriptor.options().java_multiple_files()) { + write_multiple_files(&response, file_descriptor); + } else { + write_single_file(&response, file_descriptor); + } + } + } + + // 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; +}; |