diff options
Diffstat (limited to 'tools')
411 files changed, 32372 insertions, 23419 deletions
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index d346731e63e2..c1cfd0b5f1e7 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -235,7 +235,7 @@ bool AaptLocaleValue::initFromFilterString(const String8& str) { setRegion(part2.string()); } else if (part2.length() == 4 && isAlpha(part2)) { setScript(part2.string()); - } else if (part2.length() >= 5 && part2.length() <= 8) { + } else if (part2.length() >= 4 && part2.length() <= 8) { setVariant(part2.string()); } else { valid = false; @@ -250,7 +250,7 @@ bool AaptLocaleValue::initFromFilterString(const String8& str) { if (((part3.length() == 2 && isAlpha(part3)) || (part3.length() == 3 && isNumber(part3))) && script[0]) { setRegion(part3.string()); - } else if (part3.length() >= 5 && part3.length() <= 8) { + } else if (part3.length() >= 4 && part3.length() <= 8) { setVariant(part3.string()); } else { valid = false; @@ -261,7 +261,7 @@ bool AaptLocaleValue::initFromFilterString(const String8& str) { } const String8& part4 = parts[3]; - if (part4.length() >= 5 && part4.length() <= 8) { + if (part4.length() >= 4 && part4.length() <= 8) { setVariant(part4.string()); } else { valid = false; @@ -280,7 +280,7 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta String8 part = parts[currentIndex]; if (part[0] == 'b' && part[1] == '+') { - // This is a "modified" BCP-47 language tag. Same semantics as BCP-47 tags, + // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags, // except that the separator is "+" and not "-". Vector<String8> subtags = AaptUtil::splitAndLowerCase(part, '+'); subtags.removeItemsAt(0); @@ -296,8 +296,11 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta setRegion(subtags[1]); break; case 4: - setScript(subtags[1]); - break; + if (isAlpha(subtags[1])) { + setScript(subtags[1]); + break; + } + // This is not alphabetical, so we fall through to variant case 5: case 6: case 7: @@ -305,7 +308,7 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta setVariant(subtags[1]); break; default: - fprintf(stderr, "ERROR: Invalid BCP-47 tag in directory name %s\n", + fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name %s\n", part.string()); return -1; } @@ -322,13 +325,13 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta setRegion(subtags[1]); hasRegion = true; } else { - fprintf(stderr, "ERROR: Invalid BCP-47 tag in directory name %s\n", part.string()); + fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name %s\n", part.string()); 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) { + if (subtags[2].size() >= 4) { setVariant(subtags[2]); } else { setRegion(subtags[2]); @@ -339,7 +342,7 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta setRegion(subtags[2]); setVariant(subtags[3]); } else { - fprintf(stderr, "ERROR: Invalid BCP-47 tag in directory name: %s\n", part.string()); + fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name: %s\n", part.string()); return -1; } @@ -370,7 +373,7 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta void AaptLocaleValue::initFromResTable(const ResTable_config& config) { config.unpackLanguage(language); config.unpackRegion(region); - if (config.localeScript[0]) { + if (config.localeScript[0] && !config.localeScriptWasComputed) { memcpy(script, config.localeScript, sizeof(config.localeScript)); } diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk index b991d55dc343..2a490d1097ef 100644 --- a/tools/aapt/Android.mk +++ b/tools/aapt/Android.mk @@ -54,36 +54,26 @@ aaptTests := \ tests/ResourceFilter_test.cpp \ tests/ResourceTable_test.cpp -aaptCIncludes := \ - system/core/base/include \ - external/libpng \ - external/zlib - -aaptHostLdLibs := aaptHostStaticLibs := \ libandroidfw \ libpng \ - liblog \ libutils \ + liblog \ libcutils \ libexpat \ libziparchive-host \ libbase -aaptCFlags := -DAAPT_VERSION=\"$(BUILD_NUMBER)\" +aaptCFlags := -DAAPT_VERSION=\"$(BUILD_NUMBER_FROM_FILE)\" aaptCFlags += -Wall -Werror -ifeq ($(HOST_OS),linux) - aaptHostLdLibs += -lrt -ldl -lpthread -endif +aaptHostLdLibs_linux := -lrt -ldl -lpthread # Statically link libz for MinGW (Win SDK under Linux), # and dynamically link for all others. -ifneq ($(strip $(USE_MINGW)),) - aaptHostStaticLibs += libz -else - aaptHostLdLibs += -lz -endif +aaptHostStaticLibs_windows := libz +aaptHostLdLibs_linux += -lz +aaptHostLdLibs_darwin := -lz # ========================================================== @@ -92,13 +82,13 @@ endif include $(CLEAR_VARS) LOCAL_MODULE := libaapt -LOCAL_CFLAGS += -Wno-format-y2k -DSTATIC_ANDROIDFW_FOR_TOOLS $(aaptCFlags) -LOCAL_CPPFLAGS += $(aaptCppFlags) -ifeq (darwin,$(HOST_OS)) -LOCAL_CFLAGS += -D_DARWIN_UNLIMITED_STREAMS -endif -LOCAL_C_INCLUDES += $(aaptCIncludes) +LOCAL_MODULE_HOST_OS := darwin linux windows +LOCAL_CFLAGS := -Wno-format-y2k -DSTATIC_ANDROIDFW_FOR_TOOLS $(aaptCFlags) +LOCAL_CPPFLAGS := $(aaptCppFlags) +LOCAL_CFLAGS_darwin := -D_DARWIN_UNLIMITED_STREAMS LOCAL_SRC_FILES := $(aaptSources) +LOCAL_STATIC_LIBRARIES := $(aaptHostStaticLibs) +LOCAL_STATIC_LIBRARIES_windows := $(aaptHostStaticLibs_windows) include $(BUILD_HOST_STATIC_LIBRARY) @@ -108,11 +98,14 @@ include $(BUILD_HOST_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := aapt -LOCAL_CFLAGS += $(aaptCFlags) -LOCAL_CPPFLAGS += $(aaptCppFlags) -LOCAL_LDLIBS += $(aaptHostLdLibs) +LOCAL_MODULE_HOST_OS := darwin linux windows +LOCAL_CFLAGS := $(aaptCFlags) +LOCAL_CPPFLAGS := $(aaptCppFlags) +LOCAL_LDLIBS_darwin := $(aaptHostLdLibs_darwin) +LOCAL_LDLIBS_linux := $(aaptHostLdLibs_linux) LOCAL_SRC_FILES := $(aaptMain) -LOCAL_STATIC_LIBRARIES += libaapt $(aaptHostStaticLibs) +LOCAL_STATIC_LIBRARIES := libaapt $(aaptHostStaticLibs) +LOCAL_STATIC_LIBRARIES_windows := $(aaptHostStaticLibs_windows) include $(BUILD_HOST_EXECUTABLE) @@ -121,15 +114,16 @@ include $(BUILD_HOST_EXECUTABLE) # Build the host tests: libaapt_tests # ========================================================== include $(CLEAR_VARS) -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk LOCAL_MODULE := libaapt_tests -LOCAL_CFLAGS += $(aaptCFlags) -LOCAL_CPPFLAGS += $(aaptCppFlags) -LOCAL_LDLIBS += $(aaptHostLdLibs) -LOCAL_SRC_FILES += $(aaptTests) -LOCAL_C_INCLUDES += $(LOCAL_PATH) -LOCAL_STATIC_LIBRARIES += libaapt $(aaptHostStaticLibs) +LOCAL_CFLAGS := $(aaptCFlags) +LOCAL_CPPFLAGS := $(aaptCppFlags) +LOCAL_LDLIBS_darwin := $(aaptHostLdLibs_darwin) +LOCAL_LDLIBS_linux := $(aaptHostLdLibs_linux) +LOCAL_SRC_FILES := $(aaptTests) +LOCAL_C_INCLUDES := $(LOCAL_PATH) +LOCAL_STATIC_LIBRARIES := libaapt $(aaptHostStaticLibs) +LOCAL_STATIC_LIBRARIES_windows := $(aaptHostStaticLibs_windows) include $(BUILD_HOST_NATIVE_TEST) diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index cbe7c5dacc1e..653c1b4d6f97 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -55,7 +55,7 @@ public: mCompressionMethod(0), mJunkPath(false), mOutputAPKFile(NULL), mManifestPackageNameOverride(NULL), mInstrumentationPackageNameOverride(NULL), mAutoAddOverlay(false), mGenDependencies(false), mNoVersionVectors(false), - mCrunchedOutputDir(NULL), mProguardFile(NULL), + mCrunchedOutputDir(NULL), mProguardFile(NULL), mMainDexProguardFile(NULL), mAndroidManifestFile(NULL), mPublicOutputFile(NULL), mRClassDir(NULL), mResourceIntermediatesDir(NULL), mManifestMinSdkVersion(NULL), mMinSdkVersion(NULL), mTargetSdkVersion(NULL), mMaxSdkVersion(NULL), @@ -66,6 +66,7 @@ public: mErrorOnMissingConfigEntry(false), mOutputTextSymbols(NULL), mSingleCrunchInputFile(NULL), mSingleCrunchOutputFile(NULL), mBuildSharedLibrary(false), + mBuildAppAsSharedLibrary(false), mArgc(0), mArgv(NULL) {} ~Bundle(void) {} @@ -126,6 +127,12 @@ public: const android::String8& getPlatformBuildVersionName() { return mPlatformVersionName; } void setPlatformBuildVersionName(const android::String8& name) { mPlatformVersionName = name; } + const android::String8& getPrivateSymbolsPackage() const { return mPrivateSymbolsPackage; } + + void setPrivateSymbolsPackage(const android::String8& package) { + mPrivateSymbolsPackage = package; + } + bool getUTF16StringsOption() { return mWantUTF16 || !isMinSdkAtLeast(SDK_FROYO); } @@ -139,6 +146,8 @@ public: void setCrunchedOutputDir(const char* dir) { mCrunchedOutputDir = dir; } const char* getProguardFile() const { return mProguardFile; } void setProguardFile(const char* file) { mProguardFile = file; } + const char* getMainDexProguardFile() const { return mMainDexProguardFile; } + void setMainDexProguardFile(const char* file) { mMainDexProguardFile = file; } const android::Vector<const char*>& getResourceSourceDirs() const { return mResourceSourceDirs; } void addResourceSourceDir(const char* dir) { mResourceSourceDirs.insertAt(dir,0); } const char* getAndroidManifestFile() const { return mAndroidManifestFile; } @@ -206,6 +215,8 @@ public: void setSingleCrunchOutputFile(const char* val) { mSingleCrunchOutputFile = val; } bool getBuildSharedLibrary() const { return mBuildSharedLibrary; } void setBuildSharedLibrary(bool val) { mBuildSharedLibrary = val; } + bool getBuildAppAsSharedLibrary() const { return mBuildAppAsSharedLibrary; } + void setBuildAppAsSharedLibrary(bool val) { mBuildAppAsSharedLibrary = val; } void setNoVersionVectors(bool val) { mNoVersionVectors = val; } bool getNoVersionVectors() const { return mNoVersionVectors; } @@ -240,7 +251,7 @@ public: * above. SDK levels that have a non-numeric identifier are assumed * to be newer than any SDK level that has a number designated. */ - bool isMinSdkAtLeast(int desired) { + bool isMinSdkAtLeast(int desired) const { /* If the application specifies a minSdkVersion in the manifest * then use that. Otherwise, check what the user specified on * the command line. If neither, it's not available since @@ -290,6 +301,7 @@ private: bool mNoVersionVectors; const char* mCrunchedOutputDir; const char* mProguardFile; + const char* mMainDexProguardFile; const char* mAndroidManifestFile; const char* mPublicOutputFile; const char* mRClassDir; @@ -327,8 +339,10 @@ private: const char* mSingleCrunchInputFile; const char* mSingleCrunchOutputFile; bool mBuildSharedLibrary; + bool mBuildAppAsSharedLibrary; android::String8 mPlatformVersionCode; android::String8 mPlatformVersionName; + android::String8 mPrivateSymbolsPackage; /* file specification */ int mArgc; diff --git a/tools/aapt/CacheUpdater.h b/tools/aapt/CacheUpdater.h index fade53ac2629..10a1bbc2f4aa 100644 --- a/tools/aapt/CacheUpdater.h +++ b/tools/aapt/CacheUpdater.h @@ -12,7 +12,7 @@ #include <sys/stat.h> #include <stdio.h> #include "Images.h" -#ifdef HAVE_MS_C_RUNTIME +#ifdef _WIN32 #include <direct.h> #endif @@ -81,7 +81,7 @@ public: // Advance to the next segment of the path existsPath.appendPath(toCreate.walkPath(&remains)); toCreate = remains; -#ifdef HAVE_MS_C_RUNTIME +#ifdef _WIN32 _mkdir(existsPath.string()); #else mkdir(existsPath.string(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP); diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 8a0a39cfb445..9976d00fa872 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -215,7 +215,7 @@ int doList(Bundle* bundle) goto bail; } -#ifdef HAVE_ANDROID_OS +#ifdef __ANDROID__ static const bool kHaveAndroidOs = true; #else static const bool kHaveAndroidOs = false; @@ -383,6 +383,16 @@ static void printUsesPermission(const String8& name, bool optional=false, int ma } } +static void printUsesPermissionSdk23(const String8& name, int maxSdkVersion=-1) { + printf("uses-permission-sdk-23: "); + + printf("name='%s'", ResTable::normalizeForOutput(name.string()).string()); + if (maxSdkVersion != -1) { + printf(" maxSdkVersion='%d'", maxSdkVersion); + } + printf("\n"); +} + static void printUsesImpliedPermission(const String8& name, const String8& reason) { printf("uses-implied-permission: name='%s' reason='%s'\n", ResTable::normalizeForOutput(name.string()).string(), @@ -463,17 +473,40 @@ static void printComponentPresence(const char* componentName) { * a pre-requisite or some other reason. */ struct ImpliedFeature { + ImpliedFeature() : impliedBySdk23(false) {} + ImpliedFeature(const String8& n, bool sdk23) : name(n), impliedBySdk23(sdk23) {} + /** * Name of the implied feature. */ String8 name; /** + * Was this implied by a permission from SDK 23 (<uses-permission-sdk-23 />)? + */ + bool impliedBySdk23; + + /** * List of human-readable reasons for why this feature was implied. */ SortedVector<String8> reasons; }; +struct Feature { + Feature() : required(false), version(-1) {} + Feature(bool required, int32_t version = -1) : required(required), version(version) {} + + /** + * Whether the feature is required. + */ + bool required; + + /** + * What version of the feature is requested. + */ + int32_t version; +}; + /** * Represents a <feature-group> tag in the AndroidManifest.xml */ @@ -488,7 +521,7 @@ struct FeatureGroup { /** * Explicit features defined in the group */ - KeyedVector<String8, bool> features; + KeyedVector<String8, Feature> features; /** * OpenGL ES version required @@ -497,18 +530,24 @@ struct FeatureGroup { }; static void addImpliedFeature(KeyedVector<String8, ImpliedFeature>* impliedFeatures, - const char* name, const char* reason) { + const char* name, const char* reason, bool sdk23) { String8 name8(name); ssize_t idx = impliedFeatures->indexOfKey(name8); if (idx < 0) { - idx = impliedFeatures->add(name8, ImpliedFeature()); - impliedFeatures->editValueAt(idx).name = name8; + idx = impliedFeatures->add(name8, ImpliedFeature(name8, sdk23)); } - impliedFeatures->editValueAt(idx).reasons.add(String8(reason)); + + ImpliedFeature* feature = &impliedFeatures->editValueAt(idx); + + // A non-sdk 23 implied feature takes precedence. + if (feature->impliedBySdk23 && !sdk23) { + feature->impliedBySdk23 = false; + } + feature->reasons.add(String8(reason)); } -static void printFeatureGroup(const FeatureGroup& grp, - const KeyedVector<String8, ImpliedFeature>* impliedFeatures = NULL) { +static void printFeatureGroupImpl(const FeatureGroup& grp, + const KeyedVector<String8, ImpliedFeature>* impliedFeatures) { printf("feature-group: label='%s'\n", grp.label.string()); if (grp.openGLESVersion > 0) { @@ -517,11 +556,18 @@ static void printFeatureGroup(const FeatureGroup& grp, const size_t numFeatures = grp.features.size(); for (size_t i = 0; i < numFeatures; i++) { - const bool required = grp.features[i]; + const Feature& feature = grp.features[i]; + const bool required = feature.required; + const int32_t version = feature.version; const String8& featureName = grp.features.keyAt(i); - printf(" uses-feature%s: name='%s'\n", (required ? "" : "-not-required"), + printf(" uses-feature%s: name='%s'", (required ? "" : "-not-required"), ResTable::normalizeForOutput(featureName.string()).string()); + + if (version > 0) { + printf(" version='%d'", version); + } + printf("\n"); } const size_t numImpliedFeatures = @@ -536,9 +582,11 @@ static void printFeatureGroup(const FeatureGroup& grp, String8 printableFeatureName(ResTable::normalizeForOutput( impliedFeature.name.string())); - printf(" uses-feature: name='%s'\n", printableFeatureName.string()); - printf(" uses-implied-feature: name='%s' reason='", - printableFeatureName.string()); + const char* sdk23Suffix = impliedFeature.impliedBySdk23 ? "-sdk-23" : ""; + + printf(" uses-feature%s: name='%s'\n", sdk23Suffix, printableFeatureName.string()); + printf(" uses-implied-feature%s: name='%s' reason='", sdk23Suffix, + printableFeatureName.string()); const size_t numReasons = impliedFeature.reasons.size(); for (size_t j = 0; j < numReasons; j++) { printf("%s", impliedFeature.reasons[j].string()); @@ -552,18 +600,27 @@ static void printFeatureGroup(const FeatureGroup& grp, } } +static void printFeatureGroup(const FeatureGroup& grp) { + printFeatureGroupImpl(grp, NULL); +} + +static void printDefaultFeatureGroup(const FeatureGroup& grp, + const KeyedVector<String8, ImpliedFeature>& impliedFeatures) { + printFeatureGroupImpl(grp, &impliedFeatures); +} + static void addParentFeatures(FeatureGroup* grp, const String8& name) { if (name == "android.hardware.camera.autofocus" || name == "android.hardware.camera.flash") { - grp->features.add(String8("android.hardware.camera"), true); + grp->features.add(String8("android.hardware.camera"), Feature(true)); } else if (name == "android.hardware.location.gps" || name == "android.hardware.location.network") { - grp->features.add(String8("android.hardware.location"), true); + grp->features.add(String8("android.hardware.location"), Feature(true)); } else if (name == "android.hardware.touchscreen.multitouch") { - grp->features.add(String8("android.hardware.touchscreen"), true); + grp->features.add(String8("android.hardware.touchscreen"), Feature(true)); } else if (name == "android.hardware.touchscreen.multitouch.distinct") { - grp->features.add(String8("android.hardware.touchscreen.multitouch"), true); - grp->features.add(String8("android.hardware.touchscreen"), true); + grp->features.add(String8("android.hardware.touchscreen.multitouch"), Feature(true)); + grp->features.add(String8("android.hardware.touchscreen"), Feature(true)); } else if (name == "android.hardware.opengles.aep") { const int openGLESVersion31 = 0x00030001; if (openGLESVersion31 > grp->openGLESVersion) { @@ -572,6 +629,72 @@ static void addParentFeatures(FeatureGroup* grp, const String8& name) { } } +static void addImpliedFeaturesForPermission(const int targetSdk, const String8& name, + KeyedVector<String8, ImpliedFeature>* impliedFeatures, + bool impliedBySdk23Permission) { + if (name == "android.permission.CAMERA") { + addImpliedFeature(impliedFeatures, "android.hardware.camera", + String8::format("requested %s permission", name.string()) + .string(), impliedBySdk23Permission); + } else if (name == "android.permission.ACCESS_FINE_LOCATION") { + addImpliedFeature(impliedFeatures, "android.hardware.location.gps", + String8::format("requested %s permission", name.string()) + .string(), impliedBySdk23Permission); + addImpliedFeature(impliedFeatures, "android.hardware.location", + String8::format("requested %s permission", name.string()) + .string(), impliedBySdk23Permission); + } else if (name == "android.permission.ACCESS_MOCK_LOCATION") { + addImpliedFeature(impliedFeatures, "android.hardware.location", + String8::format("requested %s permission", name.string()) + .string(), impliedBySdk23Permission); + } else if (name == "android.permission.ACCESS_COARSE_LOCATION") { + addImpliedFeature(impliedFeatures, "android.hardware.location.network", + String8::format("requested %s permission", name.string()) + .string(), impliedBySdk23Permission); + addImpliedFeature(impliedFeatures, "android.hardware.location", + String8::format("requested %s permission", name.string()) + .string(), impliedBySdk23Permission); + } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" || + name == "android.permission.INSTALL_LOCATION_PROVIDER") { + addImpliedFeature(impliedFeatures, "android.hardware.location", + String8::format("requested %s permission", name.string()) + .string(), impliedBySdk23Permission); + } else if (name == "android.permission.BLUETOOTH" || + name == "android.permission.BLUETOOTH_ADMIN") { + if (targetSdk > 4) { + addImpliedFeature(impliedFeatures, "android.hardware.bluetooth", + String8::format("requested %s permission", name.string()) + .string(), impliedBySdk23Permission); + addImpliedFeature(impliedFeatures, "android.hardware.bluetooth", + "targetSdkVersion > 4", impliedBySdk23Permission); + } + } else if (name == "android.permission.RECORD_AUDIO") { + addImpliedFeature(impliedFeatures, "android.hardware.microphone", + String8::format("requested %s permission", name.string()) + .string(), impliedBySdk23Permission); + } else if (name == "android.permission.ACCESS_WIFI_STATE" || + name == "android.permission.CHANGE_WIFI_STATE" || + name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") { + addImpliedFeature(impliedFeatures, "android.hardware.wifi", + String8::format("requested %s permission", name.string()) + .string(), impliedBySdk23Permission); + } else if (name == "android.permission.CALL_PHONE" || + name == "android.permission.CALL_PRIVILEGED" || + name == "android.permission.MODIFY_PHONE_STATE" || + name == "android.permission.PROCESS_OUTGOING_CALLS" || + name == "android.permission.READ_SMS" || + name == "android.permission.RECEIVE_SMS" || + name == "android.permission.RECEIVE_MMS" || + name == "android.permission.RECEIVE_WAP_PUSH" || + name == "android.permission.SEND_SMS" || + name == "android.permission.WRITE_APN_SETTINGS" || + name == "android.permission.WRITE_SMS") { + addImpliedFeature(impliedFeatures, "android.hardware.telephony", + String8("requested a telephony permission").string(), + impliedBySdk23Permission); + } +} + /* * Handle the "dump" command, to extract select data from an archive. */ @@ -626,6 +749,9 @@ int doDump(Bundle* bundle) return 1; } + // Source for AndroidManifest.xml + const String8 manifestFile = String8::format("%s@AndroidManifest.xml", filename); + // The dynamicRefTable can be null if there are no resources for this asset cookie. // This fine. const DynamicRefTable* dynamicRefTable = res.getDynamicRefTableForCookie(assetsCookie); @@ -633,7 +759,7 @@ int doDump(Bundle* bundle) Asset* asset = NULL; if (strcmp("resources", option) == 0) { -#ifndef HAVE_ANDROID_OS +#ifndef __ANDROID__ res.print(bundle->getValues()); #endif @@ -712,7 +838,8 @@ int doDump(Bundle* bundle) size_t len; ResXMLTree::event_code_t code; int depth = 0; - while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && + code != ResXMLTree::BAD_DOCUMENT) { if (code == ResXMLTree::END_TAG) { depth--; continue; @@ -735,25 +862,53 @@ int doDump(Bundle* bundle) } String8 pkg = AaptXml::getAttribute(tree, NULL, "package", NULL); printf("package: %s\n", ResTable::normalizeForOutput(pkg.string()).string()); - } else if (depth == 2 && tag == "permission") { - String8 error; - String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); - if (error != "") { - fprintf(stderr, "ERROR: %s\n", error.string()); - goto bail; - } - printf("permission: %s\n", - ResTable::normalizeForOutput(name.string()).string()); - } else if (depth == 2 && tag == "uses-permission") { - String8 error; - String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); - if (error != "") { - fprintf(stderr, "ERROR: %s\n", error.string()); - goto bail; + } else if (depth == 2) { + if (tag == "permission") { + String8 error; + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR: %s\n", error.string()); + goto bail; + } + + if (name == "") { + fprintf(stderr, "ERROR: missing 'android:name' for permission\n"); + goto bail; + } + printf("permission: %s\n", + ResTable::normalizeForOutput(name.string()).string()); + } else if (tag == "uses-permission") { + String8 error; + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR: %s\n", error.string()); + goto bail; + } + + if (name == "") { + fprintf(stderr, "ERROR: missing 'android:name' for uses-permission\n"); + goto bail; + } + printUsesPermission(name, + AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0, + AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR)); + } else if (tag == "uses-permission-sdk-23" || tag == "uses-permission-sdk-m") { + String8 error; + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR: %s\n", error.string()); + goto bail; + } + + if (name == "") { + fprintf(stderr, "ERROR: missing 'android:name' for " + "uses-permission-sdk-23\n"); + goto bail; + } + printUsesPermissionSdk23( + name, + AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR)); } - printUsesPermission(name, - AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0, - AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR)); } } } else if (strcmp("badging", option) == 0) { @@ -893,7 +1048,8 @@ int doDump(Bundle* bundle) Vector<FeatureGroup> featureGroups; KeyedVector<String8, ImpliedFeature> impliedFeatures; - while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && + code != ResXMLTree::BAD_DOCUMENT) { if (code == ResXMLTree::END_TAG) { depth--; if (depth < 2) { @@ -924,8 +1080,10 @@ int doDump(Bundle* bundle) ResTable::normalizeForOutput(aName.string()).string()); } printf(" label='%s' icon='%s'\n", - ResTable::normalizeForOutput(activityLabel.string()).string(), - ResTable::normalizeForOutput(activityIcon.string()).string()); + ResTable::normalizeForOutput(activityLabel.string()) + .string(), + ResTable::normalizeForOutput(activityIcon.string()) + .string()); } if (isLeanbackLauncherActivity) { printf("leanback-launchable-activity:"); @@ -934,9 +1092,12 @@ int doDump(Bundle* bundle) ResTable::normalizeForOutput(aName.string()).string()); } printf(" label='%s' icon='%s' banner='%s'\n", - ResTable::normalizeForOutput(activityLabel.string()).string(), - ResTable::normalizeForOutput(activityIcon.string()).string(), - ResTable::normalizeForOutput(activityBanner.string()).string()); + ResTable::normalizeForOutput(activityLabel.string()) + .string(), + ResTable::normalizeForOutput(activityIcon.string()) + .string(), + ResTable::normalizeForOutput(activityBanner.string()) + .string()); } } if (!hasIntentFilter) { @@ -964,18 +1125,21 @@ int doDump(Bundle* bundle) hasLauncher |= catLauncher; hasCameraActivity |= actCamera; hasCameraSecureActivity |= actCameraSecure; - hasOtherActivities |= !actMainActivity && !actCamera && !actCameraSecure; + hasOtherActivities |= + !actMainActivity && !actCamera && !actCameraSecure; } else if (withinReceiver) { hasWidgetReceivers |= actWidgetReceivers; hasDeviceAdminReceiver |= (actDeviceAdminEnabled && hasBindDeviceAdminPermission); - hasOtherReceivers |= (!actWidgetReceivers && !actDeviceAdminEnabled); + hasOtherReceivers |= + (!actWidgetReceivers && !actDeviceAdminEnabled); } else if (withinService) { hasImeService |= actImeService; hasWallpaperService |= actWallpaperService; hasAccessibilityService |= (actAccessibilityService && hasBindAccessibilityServicePermission); - hasPrintService |= (actPrintService && hasBindPrintServicePermission); + hasPrintService |= + (actPrintService && hasBindPrintServicePermission); hasNotificationListenerService |= actNotificationListenerService && hasBindNotificationListenerServicePermission; hasDreamService |= actDreamService && hasBindDreamServicePermission; @@ -984,7 +1148,8 @@ int doDump(Bundle* bundle) !actHostApduService && !actOffHostApduService && !actNotificationListenerService); } else if (withinProvider) { - hasDocumentsProvider |= actDocumentsProvider && hasRequiredSafAttributes; + hasDocumentsProvider |= + actDocumentsProvider && hasRequiredSafAttributes; } } withinIntentFilter = false; @@ -1125,7 +1290,8 @@ int doDump(Bundle* bundle) goto bail; } - String8 banner = AaptXml::getResolvedAttribute(res, tree, BANNER_ATTR, &error); + String8 banner = AaptXml::getResolvedAttribute(res, tree, BANNER_ATTR, + &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n", error.string()); @@ -1135,7 +1301,8 @@ int doDump(Bundle* bundle) ResTable::normalizeForOutput(label.string()).string()); printf("icon='%s'", ResTable::normalizeForOutput(icon.string()).string()); if (banner != "") { - printf(" banner='%s'", ResTable::normalizeForOutput(banner.string()).string()); + printf(" banner='%s'", + ResTable::normalizeForOutput(banner.string()).string()); } printf("\n"); if (testOnly != 0) { @@ -1178,13 +1345,15 @@ int doDump(Bundle* bundle) } } } else if (tag == "uses-sdk") { - int32_t code = AaptXml::getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error); + int32_t code = AaptXml::getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, + &error); if (error != "") { error = ""; String8 name = AaptXml::getResolvedAttribute(res, tree, MIN_SDK_VERSION_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n", + fprintf(stderr, + "ERROR getting 'android:minSdkVersion' attribute: %s\n", error.string()); goto bail; } @@ -1205,7 +1374,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", + fprintf(stderr, + "ERROR getting 'android:targetSdkVersion' attribute: %s\n", error.string()); goto bail; } @@ -1279,10 +1449,28 @@ int doDump(Bundle* bundle) } else if (tag == "uses-feature") { String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { - int req = AaptXml::getIntegerAttribute(tree, - REQUIRED_ATTR, 1); + const char* androidSchema = + "http://schemas.android.com/apk/res/android"; - commonFeatures.features.add(name, req); + int32_t req = AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1, + &error); + if (error != "") { + SourcePos(manifestFile, tree.getLineNumber()).error( + "failed to read attribute 'android:required': %s", + error.string()); + goto bail; + } + + int32_t version = AaptXml::getIntegerAttribute(tree, androidSchema, + "version", 0, &error); + if (error != "") { + SourcePos(manifestFile, tree.getLineNumber()).error( + "failed to read attribute 'android:version': %s", + error.string()); + goto bail; + } + + commonFeatures.features.add(name, Feature(req != 0, version)); if (req) { addParentFeatures(&commonFeatures, name); } @@ -1297,90 +1485,58 @@ int doDump(Bundle* bundle) } } else if (tag == "uses-permission") { String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); - if (name != "" && error == "") { - if (name == "android.permission.CAMERA") { - addImpliedFeature(&impliedFeatures, "android.hardware.camera", - String8::format("requested %s permission", name.string()) - .string()); - } else if (name == "android.permission.ACCESS_FINE_LOCATION") { - addImpliedFeature(&impliedFeatures, "android.hardware.location.gps", - String8::format("requested %s permission", name.string()) - .string()); - addImpliedFeature(&impliedFeatures, "android.hardware.location", - String8::format("requested %s permission", name.string()) - .string()); - } else if (name == "android.permission.ACCESS_MOCK_LOCATION") { - addImpliedFeature(&impliedFeatures, "android.hardware.location", - String8::format("requested %s permission", name.string()) - .string()); - } else if (name == "android.permission.ACCESS_COARSE_LOCATION") { - addImpliedFeature(&impliedFeatures, "android.hardware.location.network", - String8::format("requested %s permission", name.string()) - .string()); - addImpliedFeature(&impliedFeatures, "android.hardware.location", - String8::format("requested %s permission", name.string()) - .string()); - } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" || - name == "android.permission.INSTALL_LOCATION_PROVIDER") { - addImpliedFeature(&impliedFeatures, "android.hardware.location", - String8::format("requested %s permission", name.string()) - .string()); - } else if (name == "android.permission.BLUETOOTH" || - name == "android.permission.BLUETOOTH_ADMIN") { - if (targetSdk > 4) { - addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth", - String8::format("requested %s permission", name.string()) - .string()); - addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth", - "targetSdkVersion > 4"); - } - } else if (name == "android.permission.RECORD_AUDIO") { - addImpliedFeature(&impliedFeatures, "android.hardware.microphone", - String8::format("requested %s permission", name.string()) - .string()); - } else if (name == "android.permission.ACCESS_WIFI_STATE" || - name == "android.permission.CHANGE_WIFI_STATE" || - name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") { - addImpliedFeature(&impliedFeatures, "android.hardware.wifi", - String8::format("requested %s permission", name.string()) - .string()); - } else if (name == "android.permission.CALL_PHONE" || - name == "android.permission.CALL_PRIVILEGED" || - name == "android.permission.MODIFY_PHONE_STATE" || - name == "android.permission.PROCESS_OUTGOING_CALLS" || - name == "android.permission.READ_SMS" || - name == "android.permission.RECEIVE_SMS" || - name == "android.permission.RECEIVE_MMS" || - name == "android.permission.RECEIVE_WAP_PUSH" || - name == "android.permission.SEND_SMS" || - name == "android.permission.WRITE_APN_SETTINGS" || - name == "android.permission.WRITE_SMS") { - addImpliedFeature(&impliedFeatures, "android.hardware.telephony", - String8("requested a telephony permission").string()); - } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") { - hasWriteExternalStoragePermission = true; - } else if (name == "android.permission.READ_EXTERNAL_STORAGE") { - hasReadExternalStoragePermission = true; - } else if (name == "android.permission.READ_PHONE_STATE") { - hasReadPhoneStatePermission = true; - } else if (name == "android.permission.READ_CONTACTS") { - hasReadContactsPermission = true; - } else if (name == "android.permission.WRITE_CONTACTS") { - hasWriteContactsPermission = true; - } else if (name == "android.permission.READ_CALL_LOG") { - hasReadCallLogPermission = true; - } else if (name == "android.permission.WRITE_CALL_LOG") { - hasWriteCallLogPermission = true; - } + if (error != "") { + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", + error.string()); + goto bail; + } + + if (name == "") { + fprintf(stderr, "ERROR: missing 'android:name' for uses-permission\n"); + goto bail; + } - printUsesPermission(name, - AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0, - AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR)); - } else { + addImpliedFeaturesForPermission(targetSdk, name, &impliedFeatures, false); + + if (name == "android.permission.WRITE_EXTERNAL_STORAGE") { + hasWriteExternalStoragePermission = true; + } else if (name == "android.permission.READ_EXTERNAL_STORAGE") { + hasReadExternalStoragePermission = true; + } else if (name == "android.permission.READ_PHONE_STATE") { + hasReadPhoneStatePermission = true; + } else if (name == "android.permission.READ_CONTACTS") { + hasReadContactsPermission = true; + } else if (name == "android.permission.WRITE_CONTACTS") { + hasWriteContactsPermission = true; + } else if (name == "android.permission.READ_CALL_LOG") { + hasReadCallLogPermission = true; + } else if (name == "android.permission.WRITE_CALL_LOG") { + hasWriteCallLogPermission = true; + } + + printUsesPermission(name, + AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0, + AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR)); + + } 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()); goto bail; } + + if (name == "") { + fprintf(stderr, "ERROR: missing 'android:name' for " + "uses-permission-sdk-23\n"); + goto bail; + } + + addImpliedFeaturesForPermission(targetSdk, name, &impliedFeatures, true); + + printUsesPermissionSdk23( + name, AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR)); + } else if (tag == "uses-package") { String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { @@ -1422,7 +1578,8 @@ int doDump(Bundle* bundle) } else if (tag == "package-verifier") { String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { - String8 publicKey = AaptXml::getAttribute(tree, PUBLIC_KEY_ATTR, &error); + String8 publicKey = AaptXml::getAttribute(tree, PUBLIC_KEY_ATTR, + &error); if (publicKey != "" && error == "") { printf("package-verifier: name='%s' publicKey='%s'\n", ResTable::normalizeForOutput(name.string()).string(), @@ -1485,12 +1642,18 @@ int doDump(Bundle* bundle) if (error == "") { if (orien == 0 || orien == 6 || orien == 8) { // Requests landscape, sensorLandscape, or reverseLandscape. - addImpliedFeature(&impliedFeatures, "android.hardware.screen.landscape", - "one or more activities have specified a landscape orientation"); + addImpliedFeature(&impliedFeatures, + "android.hardware.screen.landscape", + "one or more activities have specified a " + "landscape orientation", + false); } else if (orien == 1 || orien == 7 || orien == 9) { // Requests portrait, sensorPortrait, or reversePortrait. - addImpliedFeature(&impliedFeatures, "android.hardware.screen.portrait", - "one or more activities have specified a portrait orientation"); + addImpliedFeature(&impliedFeatures, + "android.hardware.screen.portrait", + "one or more activities have specified a " + "portrait orientation", + false); } } } else if (tag == "uses-library") { @@ -1524,8 +1687,10 @@ int doDump(Bundle* bundle) hasBindDeviceAdminPermission = true; } } else { - fprintf(stderr, "ERROR getting 'android:permission' attribute for" - " receiver '%s': %s\n", receiverName.string(), error.string()); + fprintf(stderr, + "ERROR getting 'android:permission' attribute for" + " receiver '%s': %s\n", + receiverName.string(), error.string()); } } else if (tag == "service") { withinService = true; @@ -1542,20 +1707,24 @@ int doDump(Bundle* bundle) if (error == "") { if (permission == "android.permission.BIND_INPUT_METHOD") { hasBindInputMethodPermission = true; - } else if (permission == "android.permission.BIND_ACCESSIBILITY_SERVICE") { + } else if (permission == + "android.permission.BIND_ACCESSIBILITY_SERVICE") { hasBindAccessibilityServicePermission = true; - } else if (permission == "android.permission.BIND_PRINT_SERVICE") { + } else if (permission == + "android.permission.BIND_PRINT_SERVICE") { hasBindPrintServicePermission = true; - } else if (permission == "android.permission.BIND_NFC_SERVICE") { + } else if (permission == + "android.permission.BIND_NFC_SERVICE") { hasBindNfcServicePermission = true; - } else if (permission == "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE") { + } else if (permission == + "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE") { hasBindNotificationListenerServicePermission = true; } else if (permission == "android.permission.BIND_DREAM_SERVICE") { hasBindDreamServicePermission = true; } } else { - fprintf(stderr, "ERROR getting 'android:permission' attribute for" - " service '%s': %s\n", serviceName.string(), error.string()); + fprintf(stderr, "ERROR getting 'android:permission' attribute for " + "service '%s': %s\n", serviceName.string(), error.string()); } } else if (tag == "provider") { withinProvider = true; @@ -1563,7 +1732,8 @@ int doDump(Bundle* bundle) bool exported = AaptXml::getResolvedIntegerAttribute(res, tree, EXPORTED_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:exported' attribute for provider:" + fprintf(stderr, + "ERROR getting 'android:exported' attribute for provider:" " %s\n", error.string()); goto bail; } @@ -1571,16 +1741,17 @@ int doDump(Bundle* bundle) bool grantUriPermissions = AaptXml::getResolvedIntegerAttribute( res, tree, GRANT_URI_PERMISSIONS_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:grantUriPermissions' attribute for provider:" - " %s\n", error.string()); + fprintf(stderr, + "ERROR getting 'android:grantUriPermissions' attribute for " + "provider: %s\n", 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()); + fprintf(stderr, "ERROR getting 'android:permission' attribute for " + "provider: %s\n", error.string()); goto bail; } @@ -1623,12 +1794,27 @@ int doDump(Bundle* bundle) } } } else if (withinFeatureGroup && tag == "uses-feature") { + const String8 androidSchema("http://schemas.android.com/apk/res/android"); FeatureGroup& top = featureGroups.editTop(); String8 name = AaptXml::getResolvedAttribute(res, tree, NAME_ATTR, &error); if (name != "" && error == "") { - top.features.add(name, true); + Feature feature(true); + + int32_t featureVers = AaptXml::getIntegerAttribute( + tree, androidSchema.string(), "version", 0, &error); + if (error == "") { + feature.version = featureVers; + } else { + SourcePos(manifestFile, tree.getLineNumber()).error( + "failed to read attribute 'android:version': %s", + error.string()); + goto bail; + } + + top.features.add(name, feature); addParentFeatures(&top, name); + } else { int vers = AaptXml::getIntegerAttribute(tree, GL_ES_VERSION_ATTR, &error); @@ -1661,8 +1847,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(), error.string()); + fprintf(stderr, "ERROR getting 'android:name' attribute for " + "meta-data tag in service '%s': %s\n", serviceName.string(), + error.string()); goto bail; } @@ -1676,8 +1863,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", serviceName.string(), error.string()); + fprintf(stderr, "ERROR getting 'android:resource' attribute for " + "meta-data tag in service '%s': %s\n", + serviceName.string(), error.string()); goto bail; } @@ -1731,15 +1919,19 @@ int doDump(Bundle* bundle) actImeService = true; } else if (action == "android.service.wallpaper.WallpaperService") { actWallpaperService = true; - } else if (action == "android.accessibilityservice.AccessibilityService") { + } else if (action == + "android.accessibilityservice.AccessibilityService") { actAccessibilityService = true; - } else if (action == "android.printservice.PrintService") { + } else if (action =="android.printservice.PrintService") { actPrintService = true; - } else if (action == "android.nfc.cardemulation.action.HOST_APDU_SERVICE") { + } else if (action == + "android.nfc.cardemulation.action.HOST_APDU_SERVICE") { actHostApduService = true; - } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") { + } else if (action == + "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") { actOffHostApduService = true; - } else if (action == "android.service.notification.NotificationListenerService") { + } else if (action == + "android.service.notification.NotificationListenerService") { actNotificationListenerService = true; } else if (action == "android.service.dreams.DreamService") { actDreamService = true; @@ -1814,12 +2006,12 @@ int doDump(Bundle* bundle) } addImpliedFeature(&impliedFeatures, "android.hardware.touchscreen", - "default feature for all apps"); + "default feature for all apps", false); const size_t numFeatureGroups = featureGroups.size(); if (numFeatureGroups == 0) { // If no <feature-group> tags were defined, apply auto-implied features. - printFeatureGroup(commonFeatures, &impliedFeatures); + printDefaultFeatureGroup(commonFeatures, impliedFeatures); } else { // <feature-group> tags are defined, so we ignore implied features and @@ -2395,11 +2587,11 @@ int doPackage(Bundle* bundle) // Write the R.java file into the appropriate class directory // e.g. gen/com/foo/app/R.java err = writeResourceSymbols(bundle, assets, assets->getPackage(), true, - bundle->getBuildSharedLibrary()); + bundle->getBuildSharedLibrary() || bundle->getBuildAppAsSharedLibrary()); } else { const String8 customPkg(bundle->getCustomPackage()); err = writeResourceSymbols(bundle, assets, customPkg, true, - bundle->getBuildSharedLibrary()); + bundle->getBuildSharedLibrary() || bundle->getBuildAppAsSharedLibrary()); } if (err < 0) { goto bail; @@ -2414,7 +2606,7 @@ int doPackage(Bundle* bundle) while (packageString != NULL) { // Write the R.java file out with the correct package name err = writeResourceSymbols(bundle, assets, String8(packageString), true, - bundle->getBuildSharedLibrary()); + bundle->getBuildSharedLibrary() || bundle->getBuildAppAsSharedLibrary()); if (err < 0) { goto bail; } @@ -2439,6 +2631,12 @@ int doPackage(Bundle* bundle) goto bail; } + // Write out the Main Dex ProGuard file + err = writeMainDexProguardFile(bundle, assets); + if (err < 0) { + goto bail; + } + // Write the apk if (outputAPKFile) { // Gather all resources and add them to the APK Builder. The builder will then diff --git a/tools/aapt/CrunchCache.cpp b/tools/aapt/CrunchCache.cpp index 6c39d1d7753c..0d574cf127dd 100644 --- a/tools/aapt/CrunchCache.cpp +++ b/tools/aapt/CrunchCache.cpp @@ -5,6 +5,7 @@ // This file defines functions laid out and documented in // CrunchCache.h +#include <utils/Compat.h> #include <utils/Vector.h> #include <utils/String8.h> diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp index e4738f5eda7d..9939c188cdd9 100644 --- a/tools/aapt/Images.cpp +++ b/tools/aapt/Images.cpp @@ -873,14 +873,15 @@ static void dump_image(int w, int h, png_bytepp rows, int color_type) static void analyze_image(const char *imageName, image_info &imageInfo, int grayscaleTolerance, png_colorp rgbPalette, png_bytep alphaPalette, - int *paletteEntries, bool *hasTransparency, int *colorType, - png_bytepp outRows) + int *paletteEntries, int *alphaPaletteEntries, 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 i, j, rr, gg, bb, aa, idx;; + uint32_t opaqueColors[256], alphaColors[256]; + uint32_t col; + int numOpaqueColors = 0, numAlphaColors = 0; int maxGrayDeviation = 0; bool isOpaque = true; @@ -891,6 +892,10 @@ static void analyze_image(const char *imageName, image_info &imageInfo, int gray // 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 + // We will track opaque colors separately from colors with + // alpha. This allows us to reencode the color table more + // efficiently (color tables entries without a corresponding + // alpha value are assumed to be opaque). if (kIsDebug) { printf("Initial image data:\n"); @@ -901,10 +906,34 @@ static void analyze_image(const char *imageName, image_info &imageInfo, int gray png_bytep row = imageInfo.rows[j]; png_bytep out = outRows[j]; for (i = 0; i < w; i++) { - rr = *row++; - gg = *row++; - bb = *row++; - aa = *row++; + + // Make sure any zero alpha pixels are fully zeroed. On average, + // each of our PNG assets seem to have about four distinct pixels + // with zero alpha. + // There are several advantages to setting these to zero: + // (1) Images are more likely able to be encodable with a palette. + // (2) Image palettes will be smaller. + // (3) Premultiplied and unpremultiplied PNG decodes can skip + // writing zeros to memory, often saving significant numbers + // of memory pages. + aa = *(row + 3); + if (aa == 0) { + rr = 0; + gg = 0; + bb = 0; + + // Also set red, green, and blue to zero in "row". If we later + // decide to encode the PNG as RGB or RGBA, we will use the + // values stored there. + *(row) = 0; + *(row + 1) = 0; + *(row + 2) = 0; + } else { + rr = *(row); + gg = *(row + 1); + bb = *(row + 2); + } + row += 4; int odev = maxGrayDeviation; maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation); @@ -943,36 +972,68 @@ static void analyze_image(const char *imageName, image_info &imageInfo, int gray 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; + + if (aa == 0xff) { + for (idx = 0; idx < numOpaqueColors; idx++) { + if (opaqueColors[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 (kIsDebug) { - printf("Found 257th color at %d, %d\n", i, j); + if (!match) { + if (numOpaqueColors < 256) { + opaqueColors[numOpaqueColors] = col; + } + numOpaqueColors++; + } + + // 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. We may also need to overwrite it when we combine + // into a single palette. + *out++ = idx; + } else { + for (idx = 0; idx < numAlphaColors; idx++) { + if (alphaColors[idx] == col) { + match = true; + break; } - isPalette = false; - } else { - colors[num_colors++] = col; } + + if (!match) { + if (numAlphaColors < 256) { + alphaColors[numAlphaColors] = col; + } + numAlphaColors++; + } + + // 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 (numOpaqueColors + numAlphaColors > 256) { + if (kIsDebug) { + printf("Found 257th color at %d, %d\n", i, j); + } + isPalette = false; } } } } + // If we decide to encode the image using a palette, we will reset these counts + // to the appropriate values later. Initializing them here avoids compiler + // complaints about uses of possibly uninitialized variables. *paletteEntries = 0; + *alphaPaletteEntries = 0; + *hasTransparency = !isOpaque; - int bpp = isOpaque ? 3 : 4; - int paletteSize = w * h + bpp * num_colors; + int paletteSize = w * h + 3 * numOpaqueColors + 4 * numAlphaColors; + int bpp = isOpaque ? 3 : 4; if (kIsDebug) { printf("isGrayscale = %s\n", isGrayscale ? "true" : "false"); printf("isOpaque = %s\n", isOpaque ? "true" : "false"); @@ -1017,16 +1078,37 @@ static void analyze_image(const char *imageName, image_info &imageInfo, int gray // color type chosen if (*colorType == PNG_COLOR_TYPE_PALETTE) { + // Combine the alphaColors and the opaqueColors into a single palette. + // The alphaColors must be at the start of the palette. + uint32_t* colors = alphaColors; + memcpy(colors + numAlphaColors, opaqueColors, 4 * numOpaqueColors); + + // Fix the indices of the opaque colors in the image. + for (j = 0; j < h; j++) { + png_bytep row = imageInfo.rows[j]; + png_bytep out = outRows[j]; + for (i = 0; i < w; i++) { + uint32_t pixel = ((uint32_t*) row)[i]; + if (pixel >> 24 == 0xFF) { + out[i] += numAlphaColors; + } + } + } + // Create separate RGB and Alpha palettes and set the number of colors - *paletteEntries = num_colors; + int numColors = numOpaqueColors + numAlphaColors; + *paletteEntries = numColors; + *alphaPaletteEntries = numAlphaColors; // Create the RGB and alpha palettes - for (int idx = 0; idx < num_colors; idx++) { + for (int idx = 0; idx < numColors; 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); + if (idx < numAlphaColors) { + alphaPalette[idx] = (png_byte) (col & 0xff); + } } } 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 @@ -1052,10 +1134,9 @@ static void analyze_image(const char *imageName, image_info &imageInfo, int gray } } - static void write_png(const char* imageName, png_structp write_ptr, png_infop write_info, - image_info& imageInfo, int grayscaleTolerance) + image_info& imageInfo, const Bundle* bundle) { png_uint_32 width, height; int color_type; @@ -1090,16 +1171,26 @@ static void write_png(const char* imageName, png_color rgbPalette[256]; png_byte alphaPalette[256]; bool hasTransparency; - int paletteEntries; + int paletteEntries, alphaPaletteEntries; + int grayscaleTolerance = bundle->getGrayscaleTolerance(); analyze_image(imageName, imageInfo, grayscaleTolerance, rgbPalette, alphaPalette, - &paletteEntries, &hasTransparency, &color_type, outRows); + &paletteEntries, &alphaPaletteEntries, &hasTransparency, &color_type, 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 (imageInfo.is9Patch && (color_type == PNG_COLOR_TYPE_RGB || - color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)) { - color_type = PNG_COLOR_TYPE_RGB_ALPHA; + // Legacy versions of aapt would always encode 9patch PNGs as RGBA. This had the unintended + // benefit of working around a bug decoding paletted images in Android 4.1. + // https://code.google.com/p/android/issues/detail?id=34619 + // + // If SDK_JELLY_BEAN is supported, we need to avoid a paletted encoding in order to not expose + // this bug. + if (!bundle->isMinSdkAtLeast(SDK_JELLY_BEAN_MR1)) { + if (imageInfo.is9Patch && PNG_COLOR_TYPE_PALETTE == color_type) { + if (hasTransparency) { + color_type = PNG_COLOR_TYPE_RGB_ALPHA; + } else { + color_type = PNG_COLOR_TYPE_RGB; + } + } } if (kIsDebug) { @@ -1131,7 +1222,8 @@ static void write_png(const char* imageName, if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_PLTE(write_ptr, write_info, rgbPalette, paletteEntries); if (hasTransparency) { - png_set_tRNS(write_ptr, write_info, alphaPalette, paletteEntries, (png_color_16p) 0); + png_set_tRNS(write_ptr, write_info, alphaPalette, alphaPaletteEntries, + (png_color_16p) 0); } png_set_filter(write_ptr, 0, PNG_NO_FILTERS); } else { @@ -1180,18 +1272,11 @@ static void write_png(const char* imageName, } for (int i = 0; i < chunk_count; i++) { - unknowns[i].location = PNG_HAVE_PLTE; + unknowns[i].location = PNG_HAVE_IHDR; } png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, chunk_names, chunk_count); png_set_unknown_chunks(write_ptr, write_info, unknowns, chunk_count); -#if PNG_LIBPNG_VER < 10600 - /* Deal with unknown chunk location bug in 1.5.x and earlier */ - png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE); - if (imageInfo.haveLayoutBounds) { - png_set_unknown_chunk_location(write_ptr, write_info, 1, PNG_HAVE_PLTE); - } -#endif } @@ -1263,8 +1348,7 @@ static bool write_png_protected(png_structp write_ptr, String8& printableName, p return false; } - write_png(printableName.string(), write_ptr, write_info, *imageInfo, - bundle->getGrayscaleTolerance()); + write_png(printableName.string(), write_ptr, write_info, *imageInfo, bundle); return true; } @@ -1474,8 +1558,7 @@ status_t preProcessImageToCache(const Bundle* bundle, const String8& source, con } // Actually write out to the new png - write_png(dest.string(), write_ptr, write_info, imageInfo, - bundle->getGrayscaleTolerance()); + write_png(dest.string(), write_ptr, write_info, imageInfo, bundle); if (bundle->getVerbose()) { // Find the size of our new file diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index f832c605376c..984d98e30f29 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -6,6 +6,7 @@ #include "Main.h" #include "Bundle.h" +#include <utils/Compat.h> #include <utils/Log.h> #include <utils/threads.h> #include <utils/List.h> @@ -66,6 +67,7 @@ void usage(void) " [--max-res-version VAL] \\\n" " [-I base-package [-I base-package ...]] \\\n" " [-A asset-source-dir] [-G class-list-file] [-P public-definitions-file] \\\n" + " [-D main-dex-class-list-file] \\\n" " [-S resource-sources [-S resource-sources ...]] \\\n" " [-F apk-file] [-J R-file-dir] \\\n" " [--product product1,product2,...] \\\n" @@ -119,6 +121,7 @@ void usage(void) " localization=\"suggested\"\n" " -A additional directory in which to find raw asset files\n" " -G A file to output proguard options into.\n" + " -D A file to output proguard options for the main dex into.\n" " -F specify the apk file to output\n" " -I add an existing package to base include set\n" " -J specify where to output R.java resource constant definitions\n" @@ -199,6 +202,9 @@ void usage(void) " --shared-lib\n" " Make a shared library resource package that can be loaded by an application\n" " at runtime to access the libraries resources. Implies --non-constant-id.\n" + " --app-as-shared-lib\n" + " Make an app resource package that also can be loaded as shared library at runtime.\n" + " Implies --non-constant-id.\n" " --error-on-failed-insert\n" " Forces aapt to return an error if it fails to insert values into the manifest\n" " with --debug-mode, --min-sdk-version, --target-sdk-version --version-code\n" @@ -216,7 +222,9 @@ void usage(void) " Prevents symbols from being generated for strings that do not have a default\n" " localization\n" " --no-version-vectors\n" - " Do not automatically generate versioned copies of vector XML resources.\n", + " Do not automatically generate versioned copies of vector XML resources.\n" + " --private-symbols\n" + " Java package name to use when generating R.java for private resources.\n", gDefaultIgnoreAssets); } @@ -384,6 +392,17 @@ int main(int argc, char* const argv[]) convertPath(argv[0]); bundle.setProguardFile(argv[0]); break; + case 'D': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-D' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setMainDexProguardFile(argv[0]); + break; case 'I': argc--; argv++; @@ -667,6 +686,9 @@ int main(int argc, char* const argv[]) } else if (strcmp(cp, "-shared-lib") == 0) { bundle.setNonConstantId(true); bundle.setBuildSharedLibrary(true); + } else if (strcmp(cp, "-app-as-shared-lib") == 0) { + bundle.setNonConstantId(true); + bundle.setBuildAppAsSharedLibrary(true); } else if (strcmp(cp, "-no-crunch") == 0) { bundle.setUseCrunchCache(true); } else if (strcmp(cp, "-ignore-assets") == 0) { @@ -682,6 +704,16 @@ int main(int argc, char* const argv[]) bundle.setPseudolocalize(PSEUDO_ACCENTED | PSEUDO_BIDI); } else if (strcmp(cp, "-no-version-vectors") == 0) { bundle.setNoVersionVectors(true); + } else if (strcmp(cp, "-private-symbols") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for " + "'--private-symbols' option\n"); + wantUsage = true; + goto bail; + } + bundle.setPrivateSymbolsPackage(String8(argv[0])); } else { fprintf(stderr, "ERROR: Unknown option '-%s'\n", cp); wantUsage = true; diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h index e84c4c503cd2..a493842b8d10 100644 --- a/tools/aapt/Main.h +++ b/tools/aapt/Main.h @@ -54,6 +54,7 @@ extern android::status_t writeResourceSymbols(Bundle* bundle, bool includePrivate, bool emitCallback); extern android::status_t writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets); +extern android::status_t writeMainDexProguardFile(Bundle* bundle, const sp<AaptAssets>& assets); extern bool isValidResourceType(const String8& type); diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp index cb244eccfe21..d631f3531127 100644 --- a/tools/aapt/Package.cpp +++ b/tools/aapt/Package.cpp @@ -33,7 +33,7 @@ static const char* kNoCompressExt[] = { ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl", ".imy", ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", - ".amr", ".awb", ".wma", ".wmv" + ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv" }; /* fwd decls, so I can write this downward */ diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 5d208152e084..e6407332bb90 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -1161,6 +1161,12 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil printf("Creating resources for package %s\n", assets->getPackage().string()); } + // Set the private symbols package if it was declared. + // This can also be declared in XML as <private-symbols name="package" /> + if (bundle->getPrivateSymbolsPackage().size() != 0) { + assets->setSymbolsPrivatePackage(bundle->getPrivateSymbolsPackage()); + } + ResourceTable::PackageType packageType = ResourceTable::App; if (bundle->getBuildSharedLibrary()) { packageType = ResourceTable::SharedLibrary; @@ -1537,12 +1543,20 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue(); while (!workQueue.empty()) { CompileResourceWorkItem& workItem = workQueue.front(); - err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.file, &table, xmlFlags); + int xmlCompilationFlags = xmlFlags | XML_COMPILE_PARSE_VALUES + | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS; + if (!workItem.needsCompiling) { + xmlCompilationFlags &= ~XML_COMPILE_ASSIGN_ATTRIBUTE_IDS; + xmlCompilationFlags &= ~XML_COMPILE_PARSE_VALUES; + } + err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.xmlRoot, + workItem.file, &table, xmlCompilationFlags); + if (err == NO_ERROR) { assets->addResource(workItem.resPath.getPathLeaf(), - workItem.resPath, - workItem.file, - workItem.file->getResourceType()); + workItem.resPath, + workItem.file, + workItem.file->getResourceType()); } else { hasErrors = true; } @@ -1737,9 +1751,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil manifestFile->getGroupEntry(), manifestFile->getResourceType()); err = compileXmlFile(bundle, assets, String16(), manifestFile, - outManifestFile, &table, - XML_COMPILE_ASSIGN_ATTRIBUTE_IDS - | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES); + outManifestFile, &table, XML_COMPILE_STANDARD_RESOURCE & ~XML_COMPILE_STRIP_COMMENTS); if (err < NO_ERROR) { return err; } @@ -1871,8 +1883,6 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil //printf("Comment of %s: %s\n", String8(e).string(), // String8(cmt).string()); syms->appendComment(String8(e), String16(cmt), srcPos); - } else { - //printf("No comment for %s\n", String8(e).string()); } syms->makeSymbolPublic(String8(e), srcPos); } else if (strcmp16(block.getElementName(&len), uses_permission16.string()) == 0) { @@ -2107,7 +2117,7 @@ static status_t writeResourceLoadedCallbackForLayoutClasses( indentStr); } - return hasErrors ? UNKNOWN_ERROR : NO_ERROR; + return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR; } static status_t writeResourceLoadedCallback( @@ -2120,7 +2130,7 @@ static status_t writeResourceLoadedCallback( size_t N = symbols->getSymbols().size(); for (i=0; i<N; i++) { const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i); - if (sym.typeCode == AaptSymbolEntry::TYPE_UNKNOWN) { + if (sym.typeCode != AaptSymbolEntry::TYPE_INT32) { continue; } if (!assets->isJavaSymbol(sym, includePrivate)) { @@ -2245,6 +2255,9 @@ static status_t writeLayoutClasses( if (comment.size() <= 0) { comment = getAttributeComment(assets, name8); } + if (comment.contains(u"@removed")) { + continue; + } if (comment.size() > 0) { const char16_t* p = comment.string(); while (*p != 0 && *p != '.') { @@ -2523,10 +2536,6 @@ static status_t writeSymbolClass( fprintf(fp, "%s/** %s\n", getIndentSpace(indent), cmt.string()); - } else if (sym.isPublic && !includePrivate) { - sym.sourcePos.warning("No comment for public symbol %s:%s/%s", - assets->getPackage().string(), className.string(), - String8(sym.name).string()); } String16 typeComment(sym.typeComment); if (typeComment.size() > 0) { @@ -2569,10 +2578,6 @@ static status_t writeSymbolClass( "%s */\n", getIndentSpace(indent), cmt.string(), getIndentSpace(indent)); - } else if (sym.isPublic && !includePrivate) { - sym.sourcePos.warning("No comment for public symbol %s:%s/%s", - assets->getPackage().string(), className.string(), - String8(sym.name).string()); } ann.printAnnotations(fp, getIndentSpace(indent)); fprintf(fp, "%spublic static final String %s=\"%s\";\n", @@ -2682,7 +2687,7 @@ status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets, if (s > last && (*s == '.' || *s == 0)) { String8 part(last, s-last); dest.appendPath(part); -#ifdef HAVE_MS_C_RUNTIME +#ifdef _WIN32 _mkdir(dest.string()); #else mkdir(dest.string(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP); @@ -2830,7 +2835,7 @@ addProguardKeepMethodRule(ProguardKeepSet* keep, const String8& memberName, } status_t -writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& assets) +writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& assets, bool mainDex) { status_t err; ResXMLTree tree; @@ -2842,6 +2847,7 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass sp<AaptGroup> assGroup; sp<AaptFile> assFile; String8 pkg; + String8 defaultProcess; // First, look for a package file to parse. This is required to // be able to generate the resource information. @@ -2898,6 +2904,15 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass addProguardKeepRule(keep, agent, pkg.string(), assFile->getPrintableSource(), tree.getLineNumber()); } + + if (mainDex) { + defaultProcess = AaptXml::getAttribute(tree, + "http://schemas.android.com/apk/res/android", "process", &error); + if (error != "") { + fprintf(stderr, "ERROR: %s\n", error.string()); + return -1; + } + } } else if (tag == "instrumentation") { keepTag = true; } @@ -2914,7 +2929,23 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass fprintf(stderr, "ERROR: %s\n", error.string()); return -1; } - if (name.length() > 0) { + + 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()); } @@ -3097,6 +3128,31 @@ writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets) } status_t +writeProguardSpec(const char* filename, const ProguardKeepSet& keep, status_t err) +{ + FILE* fp = fopen(filename, "w+"); + if (fp == NULL) { + fprintf(stderr, "ERROR: Unable to open class file %s: %s\n", + filename, strerror(errno)); + return UNKNOWN_ERROR; + } + + const KeyedVector<String8, SortedVector<String8> >& rules = keep.rules; + const size_t N = rules.size(); + for (size_t i=0; i<N; i++) { + const SortedVector<String8>& locations = rules.valueAt(i); + const size_t M = locations.size(); + for (size_t j=0; j<M; j++) { + fprintf(fp, "# %s\n", locations.itemAt(j).string()); + } + fprintf(fp, "%s\n\n", rules.keyAt(i).string()); + } + fclose(fp); + + return err; +} + +status_t writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets) { status_t err = -1; @@ -3107,7 +3163,7 @@ writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets) ProguardKeepSet keep; - err = writeProguardForAndroidManifest(&keep, assets); + err = writeProguardForAndroidManifest(&keep, assets, false); if (err < 0) { return err; } @@ -3117,26 +3173,26 @@ writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets) return err; } - FILE* fp = fopen(bundle->getProguardFile(), "w+"); - if (fp == NULL) { - fprintf(stderr, "ERROR: Unable to open class file %s: %s\n", - bundle->getProguardFile(), strerror(errno)); - return UNKNOWN_ERROR; + return writeProguardSpec(bundle->getProguardFile(), keep, err); +} + +status_t +writeMainDexProguardFile(Bundle* bundle, const sp<AaptAssets>& assets) +{ + status_t err = -1; + + if (!bundle->getMainDexProguardFile()) { + return NO_ERROR; } - const KeyedVector<String8, SortedVector<String8> >& rules = keep.rules; - const size_t N = rules.size(); - for (size_t i=0; i<N; i++) { - const SortedVector<String8>& locations = rules.valueAt(i); - const size_t M = locations.size(); - for (size_t j=0; j<M; j++) { - fprintf(fp, "# %s\n", locations.itemAt(j).string()); - } - fprintf(fp, "%s\n\n", rules.keyAt(i).string()); + ProguardKeepSet keep; + + err = writeProguardForAndroidManifest(&keep, assets, true); + if (err < 0) { + return err; } - fclose(fp); - return err; + return writeProguardSpec(bundle->getMainDexProguardFile(), keep, err); } // Loops through the string paths and writes them to the file pointer diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index d5a09d817b1e..6a4b63789815 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -88,8 +88,11 @@ status_t compileXmlFile(const Bundle* bundle, root->setUTF8(true); } - bool hasErrors = false; + if (table->processBundleFormat(bundle, resourceName, target, root) != NO_ERROR) { + return UNKNOWN_ERROR; + } + bool hasErrors = false; if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) { status_t err = root->assignResourceIds(assets, table); if (err != NO_ERROR) { @@ -97,9 +100,11 @@ status_t compileXmlFile(const Bundle* bundle, } } - status_t err = root->parseValues(assets, table); - if (err != NO_ERROR) { - hasErrors = true; + if ((options&XML_COMPILE_PARSE_VALUES) != 0) { + status_t err = root->parseValues(assets, table); + if (err != NO_ERROR) { + hasErrors = true; + } } if (hasErrors) { @@ -114,7 +119,7 @@ status_t compileXmlFile(const Bundle* bundle, printf("Input XML Resource:\n"); root->print(); } - err = root->flatten(target, + status_t err = root->flatten(target, (options&XML_COMPILE_STRIP_COMMENTS) != 0, (options&XML_COMPILE_STRIP_RAW_VALUES) != 0); if (err != NO_ERROR) { @@ -303,29 +308,11 @@ struct PendingAttribute } added = true; - String16 attr16("attr"); - - if (outTable->hasBagOrEntry(myPackage, attr16, ident)) { - sourcePos.error("Attribute \"%s\" has already been defined\n", - String8(ident).string()); + if (!outTable->makeAttribute(myPackage, ident, sourcePos, type, comment, appendComment)) { hasErrors = true; return UNKNOWN_ERROR; } - - char numberStr[16]; - sprintf(numberStr, "%d", type); - status_t err = outTable->addBag(sourcePos, myPackage, - attr16, ident, String16(""), - String16("^type"), - String16(numberStr), NULL, NULL); - if (err != NO_ERROR) { - hasErrors = true; - return err; - } - outTable->appendComment(myPackage, attr16, ident, comment, appendComment); - //printf("Attribute %s comment: %s\n", String8(ident).string(), - // String8(comment).string()); - return err; + return NO_ERROR; } }; @@ -1136,7 +1123,15 @@ status_t compileResourceFile(Bundle* bundle, } pkg = String16(block.getAttributeStringValue(pkgIdx, &len)); if (!localHasErrors) { - assets->setSymbolsPrivatePackage(String8(pkg)); + SourcePos(in->getPrintableSource(), block.getLineNumber()).warning( + "<private-symbols> is deprecated. Use the command line flag " + "--private-symbols instead.\n"); + if (assets->havePrivateSymbols()) { + SourcePos(in->getPrintableSource(), block.getLineNumber()).warning( + "private symbol package already specified. Ignoring...\n"); + } else { + assets->setSymbolsPrivatePackage(String8(pkg)); + } } while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { @@ -2102,6 +2097,61 @@ bool ResourceTable::appendTypeComment(const String16& package, return false; } +bool ResourceTable::makeAttribute(const String16& package, + const String16& name, + const SourcePos& source, + int32_t format, + const String16& comment, + bool shouldAppendComment) { + const String16 attr16("attr"); + + // First look for this in the included resources... + uint32_t rid = mAssets->getIncludedResources() + .identifierForName(name.string(), name.size(), + attr16.string(), attr16.size(), + package.string(), package.size()); + if (rid != 0) { + source.error("Attribute \"%s\" has already been defined", String8(name).string()); + return false; + } + + sp<ResourceTable::Entry> entry = getEntry(package, attr16, name, source, false); + if (entry == NULL) { + source.error("Failed to create entry attr/%s", String8(name).string()); + return false; + } + + if (entry->makeItABag(source) != NO_ERROR) { + return false; + } + + const String16 formatKey16("^type"); + const String16 formatValue16(String8::format("%d", format)); + + ssize_t idx = entry->getBag().indexOfKey(formatKey16); + if (idx >= 0) { + // We have already set a format for this attribute, check if they are different. + // We allow duplicate attribute definitions so long as they are identical. + // This is to ensure inter-operation with libraries that define the same generic attribute. + const Item& formatItem = entry->getBag().valueAt(idx); + if ((format & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) || + formatItem.value != formatValue16) { + source.error("Attribute \"%s\" already defined with incompatible format.\n" + "%s:%d: Original attribute defined here.", + String8(name).string(), formatItem.sourcePos.file.string(), + formatItem.sourcePos.line); + return false; + } + } else { + entry->addToBag(source, formatKey16, formatValue16); + // Increment the number of resources we have. This is used to determine if we should + // even generate a resource table. + mNumLocal++; + } + appendComment(package, attr16, name, comment, shouldAppendComment); + return true; +} + void ResourceTable::canAddEntry(const SourcePos& pos, const String16& package, const String16& type, const String16& name) { @@ -4755,9 +4805,9 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, newConfig.sdkVersion = sdkVersionToGenerate; sp<AaptFile> newFile = new AaptFile(target->getSourceFile(), AaptGroupEntry(newConfig), target->getResourceType()); - String8 resPath = String8::format("res/%s/%s", + String8 resPath = String8::format("res/%s/%s.xml", newFile->getGroupEntry().toDirName(target->getResourceType()).string(), - target->getSourceFile().getPathLeaf().string()); + String8(resourceName).string()); resPath.convertToResPath(); // Add a resource table entry. @@ -4784,9 +4834,11 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, item.resourceName = resourceName; item.resPath = resPath; item.file = newFile; + item.xmlRoot = newRoot; + item.needsCompiling = false; // This step occurs after we parse/assign, so we don't need + // to do it again. mWorkQueue.push(item); } - return NO_ERROR; } @@ -4825,3 +4877,226 @@ void ResourceTable::getDensityVaryingResources( } } } + +static String16 buildNamespace(const String16& package) { + return String16("http://schemas.android.com/apk/res/") + package; +} + +static sp<XMLNode> findOnlyChildElement(const sp<XMLNode>& parent) { + const Vector<sp<XMLNode> >& children = parent->getChildren(); + sp<XMLNode> onlyChild; + for (size_t i = 0; i < children.size(); i++) { + if (children[i]->getType() != XMLNode::TYPE_CDATA) { + if (onlyChild != NULL) { + return NULL; + } + onlyChild = children[i]; + } + } + return onlyChild; +} + +/** + * Detects use of the `bundle' format and extracts nested resources into their own top level + * resources. The bundle format looks like this: + * + * <!-- res/drawable/bundle.xml --> + * <animated-vector xmlns:aapt="http://schemas.android.com/aapt"> + * <aapt:attr name="android:drawable"> + * <vector android:width="60dp" + * android:height="60dp"> + * <path android:name="v" + * android:fillColor="#000000" + * android:pathData="M300,70 l 0,-70 70,..." /> + * </vector> + * </aapt:attr> + * </animated-vector> + * + * When AAPT sees the <aapt:attr> tag, it will extract its single element and its children + * into a new high-level resource, assigning it a name and ID. Then value of the `name` + * attribute must be a resource attribute. That resource attribute is inserted into the parent + * with the reference to the extracted resource as the value. + * + * <!-- res/drawable/bundle.xml --> + * <animated-vector android:drawable="@drawable/bundle_1.xml"> + * </animated-vector> + * + * <!-- res/drawable/bundle_1.xml --> + * <vector android:width="60dp" + * android:height="60dp"> + * <path android:name="v" + * android:fillColor="#000000" + * android:pathData="M300,70 l 0,-70 70,..." /> + * </vector> + */ +status_t ResourceTable::processBundleFormat(const Bundle* bundle, + const String16& resourceName, + const sp<AaptFile>& target, + const sp<XMLNode>& root) { + Vector<sp<XMLNode> > namespaces; + if (root->getType() == XMLNode::TYPE_NAMESPACE) { + namespaces.push(root); + } + return processBundleFormatImpl(bundle, resourceName, target, root, &namespaces); +} + +status_t ResourceTable::processBundleFormatImpl(const Bundle* bundle, + const String16& resourceName, + const sp<AaptFile>& target, + const sp<XMLNode>& parent, + Vector<sp<XMLNode> >* namespaces) { + const String16 kAaptNamespaceUri16("http://schemas.android.com/aapt"); + const String16 kName16("name"); + const String16 kAttr16("attr"); + const String16 kAssetPackage16(mAssets->getPackage()); + + Vector<sp<XMLNode> >& children = parent->getChildren(); + for (size_t i = 0; i < children.size(); i++) { + const sp<XMLNode>& child = children[i]; + + if (child->getType() == XMLNode::TYPE_CDATA) { + continue; + } else if (child->getType() == XMLNode::TYPE_NAMESPACE) { + namespaces->push(child); + } + + if (child->getElementNamespace() != kAaptNamespaceUri16 || + child->getElementName() != kAttr16) { + status_t result = processBundleFormatImpl(bundle, resourceName, target, child, + namespaces); + if (result != NO_ERROR) { + return result; + } + + if (child->getType() == XMLNode::TYPE_NAMESPACE) { + namespaces->pop(); + } + continue; + } + + // This is the <aapt:attr> tag. Look for the 'name' attribute. + SourcePos source(child->getFilename(), child->getStartLineNumber()); + + sp<XMLNode> nestedRoot = findOnlyChildElement(child); + if (nestedRoot == NULL) { + source.error("<%s:%s> must have exactly one child element", + String8(child->getElementNamespace()).string(), + String8(child->getElementName()).string()); + return UNKNOWN_ERROR; + } + + // Find the special attribute 'parent-attr'. This attribute's value contains + // the resource attribute for which this element should be assigned in the parent. + const XMLNode::attribute_entry* attr = child->getAttribute(String16(), kName16); + if (attr == NULL) { + source.error("inline resource definition must specify an attribute via 'name'"); + return UNKNOWN_ERROR; + } + + // Parse the attribute name. + const char* errorMsg = NULL; + String16 attrPackage, attrType, attrName; + bool result = ResTable::expandResourceRef(attr->string.string(), + attr->string.size(), + &attrPackage, &attrType, &attrName, + &kAttr16, &kAssetPackage16, + &errorMsg, NULL); + if (!result) { + source.error("invalid attribute name for 'name': %s", errorMsg); + return UNKNOWN_ERROR; + } + + if (attrType != kAttr16) { + // The value of the 'name' attribute must be an attribute reference. + source.error("value of 'name' must be an attribute reference."); + return UNKNOWN_ERROR; + } + + // Generate a name for this nested resource and try to add it to the table. + // We do this in a loop because the name may be taken, in which case we will + // increment a suffix until we succeed. + String8 nestedResourceName; + String8 nestedResourcePath; + int suffix = 1; + while (true) { + // This child element will be extracted into its own resource file. + // Generate a name and path for it from its parent. + nestedResourceName = String8::format("%s_%d", + String8(resourceName).string(), suffix++); + nestedResourcePath = String8::format("res/%s/%s.xml", + target->getGroupEntry().toDirName(target->getResourceType()) + .string(), + nestedResourceName.string()); + + // Lookup or create the entry for this name. + sp<Entry> entry = getEntry(kAssetPackage16, + String16(target->getResourceType()), + String16(nestedResourceName), + source, + false, + &target->getGroupEntry().toParams(), + true); + if (entry == NULL) { + return UNKNOWN_ERROR; + } + + if (entry->getType() == Entry::TYPE_UNKNOWN) { + // The value for this resource has never been set, + // meaning we're good! + entry->setItem(source, String16(nestedResourcePath)); + break; + } + + // We failed (name already exists), so try with a different name + // (increment the suffix). + } + + if (bundle->getVerbose()) { + source.printf("generating nested resource %s:%s/%s", + mAssets->getPackage().string(), target->getResourceType().string(), + nestedResourceName.string()); + } + + // Build the attribute reference and assign it to the parent. + String16 nestedResourceRef = String16(String8::format("@%s:%s/%s", + mAssets->getPackage().string(), target->getResourceType().string(), + nestedResourceName.string())); + + String16 attrNs = buildNamespace(attrPackage); + if (parent->getAttribute(attrNs, attrName) != NULL) { + SourcePos(parent->getFilename(), parent->getStartLineNumber()) + .error("parent of nested resource already defines attribute '%s:%s'", + String8(attrPackage).string(), String8(attrName).string()); + return UNKNOWN_ERROR; + } + + // Add the reference to the inline resource. + parent->addAttribute(attrNs, attrName, nestedResourceRef); + + // Remove the <aapt:attr> child element from here. + children.removeAt(i); + i--; + + // Append all namespace declarations that we've seen on this branch in the XML tree + // to this resource. + // We do this because the order of namespace declarations and prefix usage is determined + // by the developer and we do not want to override any decisions. Be conservative. + for (size_t nsIndex = namespaces->size(); nsIndex > 0; nsIndex--) { + const sp<XMLNode>& ns = namespaces->itemAt(nsIndex - 1); + sp<XMLNode> newNs = XMLNode::newNamespace(ns->getFilename(), ns->getNamespacePrefix(), + ns->getNamespaceUri()); + newNs->addChild(nestedRoot); + nestedRoot = newNs; + } + + // Schedule compilation of the nested resource. + CompileResourceWorkItem workItem; + workItem.resPath = nestedResourcePath; + workItem.resourceName = String16(nestedResourceName); + workItem.xmlRoot = nestedRoot; + workItem.file = new AaptFile(target->getSourceFile(), target->getGroupEntry(), + target->getResourceType()); + mWorkQueue.push(workItem); + } + return NO_ERROR; +} diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h index c4bdf09d8b19..cf1e992ec330 100644 --- a/tools/aapt/ResourceTable.h +++ b/tools/aapt/ResourceTable.h @@ -23,13 +23,14 @@ class ResourceTable; enum { XML_COMPILE_STRIP_COMMENTS = 1<<0, XML_COMPILE_ASSIGN_ATTRIBUTE_IDS = 1<<1, - XML_COMPILE_COMPACT_WHITESPACE = 1<<2, - XML_COMPILE_STRIP_WHITESPACE = 1<<3, - XML_COMPILE_STRIP_RAW_VALUES = 1<<4, - XML_COMPILE_UTF8 = 1<<5, + XML_COMPILE_PARSE_VALUES = 1 << 2, + XML_COMPILE_COMPACT_WHITESPACE = 1<<3, + XML_COMPILE_STRIP_WHITESPACE = 1<<4, + XML_COMPILE_STRIP_RAW_VALUES = 1<<5, + XML_COMPILE_UTF8 = 1<<6, XML_COMPILE_STANDARD_RESOURCE = - XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS + XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS | XML_COMPILE_PARSE_VALUES | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES }; @@ -83,6 +84,8 @@ struct CompileResourceWorkItem { String16 resourceName; String8 resPath; sp<AaptFile> file; + sp<XMLNode> xmlRoot; + bool needsCompiling = true; }; class ResourceTable : public ResTable::Accessor @@ -206,6 +209,12 @@ public: const sp<AaptFile>& file, const sp<XMLNode>& root); + status_t processBundleFormat(const Bundle* bundle, + const String16& resourceName, + const sp<AaptFile>& file, + const sp<XMLNode>& parent); + + sp<AaptFile> flatten(Bundle* bundle, const sp<const ResourceFilter>& filter, const bool isBase); @@ -562,6 +571,18 @@ public: void getDensityVaryingResources(KeyedVector<Symbol, Vector<SymbolDefinition> >& resources); + /** + * Make an attribute with the specified format. If another attribute with the same name but + * different format exists, this method returns false. If the name is not taken, or if the + * format is identical, this returns true. + */ + bool makeAttribute(const String16& package, + const String16& name, + const SourcePos& source, + int32_t format, + const String16& comment, + bool appendComment); + private: void writePublicDefinitions(const String16& package, FILE* fp, bool pub); sp<Package> getPackage(const String16& package); @@ -586,6 +607,11 @@ private: Res_value* outValue); int getPublicAttributeSdkLevel(uint32_t attrId) const; + status_t processBundleFormatImpl(const Bundle* bundle, + const String16& resourceName, + const sp<AaptFile>& file, + const sp<XMLNode>& parent, + Vector<sp<XMLNode> >* namespaces); String16 mAssetsPackage; PackageType mPackageType; diff --git a/tools/aapt/StringPool.h b/tools/aapt/StringPool.h index dbe8c8542185..4b0d920c3274 100644 --- a/tools/aapt/StringPool.h +++ b/tools/aapt/StringPool.h @@ -20,8 +20,6 @@ #include <ctype.h> #include <errno.h> -#include <libexpat/expat.h> - using namespace android; #define PRINT_STRING_METRICS 0 diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp index ca3f68748ef9..5b215daeb494 100644 --- a/tools/aapt/XMLNode.cpp +++ b/tools/aapt/XMLNode.cpp @@ -12,7 +12,7 @@ #include <errno.h> #include <string.h> -#ifndef HAVE_MS_C_RUNTIME +#ifndef _WIN32 #define O_BINARY 0 #endif @@ -693,6 +693,12 @@ const Vector<sp<XMLNode> >& XMLNode::getChildren() const return mChildren; } + +Vector<sp<XMLNode> >& XMLNode::getChildren() +{ + return mChildren; +} + const String8& XMLNode::getFilename() const { return mFilename; @@ -717,6 +723,18 @@ const XMLNode::attribute_entry* XMLNode::getAttribute(const String16& ns, return NULL; } +bool XMLNode::removeAttribute(const String16& ns, const String16& name) +{ + for (size_t i = 0; i < mAttributes.size(); i++) { + const attribute_entry& ae(mAttributes.itemAt(i)); + if (ae.ns == ns && ae.name == name) { + removeAttribute(i); + return true; + } + } + return false; +} + XMLNode::attribute_entry* XMLNode::editAttribute(const String16& ns, const String16& name) { diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h index 3161f6500291..749bf9f59bf7 100644 --- a/tools/aapt/XMLNode.h +++ b/tools/aapt/XMLNode.h @@ -10,6 +10,8 @@ #include "StringPool.h" #include "ResourceTable.h" +#include <expat.h> + class XMLNode; extern const char* const RESOURCES_ROOT_NAMESPACE; @@ -53,7 +55,7 @@ public: sp<XMLNode> newCData(const String8& filename) { return new XMLNode(filename); } - + enum type { TYPE_NAMESPACE, TYPE_ELEMENT, @@ -68,6 +70,7 @@ public: const String16& getElementNamespace() const; const String16& getElementName() const; const Vector<sp<XMLNode> >& getChildren() const; + Vector<sp<XMLNode> >& getChildren(); const String8& getFilename() const; @@ -95,6 +98,7 @@ public: const Vector<attribute_entry>& getAttributes() const; const attribute_entry* getAttribute(const String16& ns, const String16& name) const; + bool removeAttribute(const String16& ns, const String16& name); attribute_entry* editAttribute(const String16& ns, const String16& name); diff --git a/tools/aapt/ZipFile.cpp b/tools/aapt/ZipFile.cpp index 36f4e73b3ac2..2840826c32a6 100644 --- a/tools/aapt/ZipFile.cpp +++ b/tools/aapt/ZipFile.cpp @@ -364,7 +364,7 @@ status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size, long lfhPosn, startPosn, endPosn, uncompressedLen; FILE* inputFp = NULL; unsigned long crc; - time_t modWhen; + time_t modWhen = 0; if (mReadOnly) return INVALID_OPERATION; @@ -497,7 +497,6 @@ status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size, */ pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc, compressionMethod); - modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp)); pEntry->setModWhen(modWhen); pEntry->setLFHOffset(lfhPosn); mEOCD.mNumEntries++; diff --git a/tools/aapt/pseudolocalize.h b/tools/aapt/pseudolocalize.h index 71b974b0c269..1faecd14172d 100644 --- a/tools/aapt/pseudolocalize.h +++ b/tools/aapt/pseudolocalize.h @@ -1,7 +1,7 @@ #ifndef HOST_PSEUDOLOCALIZE_H #define HOST_PSEUDOLOCALIZE_H -#include <base/macros.h> +#include <android-base/macros.h> #include "StringPool.h" class PseudoMethodImpl { diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 10f81502f268..3a1e2bb3bf08 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -13,78 +13,110 @@ # See the License for the specific language governing permissions and # limitations under the License. # - -# This tool is prebuilt if we're doing an app-only build. -ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),) +LOCAL_PATH:= $(call my-dir) # ========================================================== # Setup some common variables for the different build # targets here. # ========================================================== -LOCAL_PATH:= $(call my-dir) main := Main.cpp sources := \ - BigBuffer.cpp \ - BinaryResourceParser.cpp \ - BindingXmlPullParser.cpp \ + compile/IdAssigner.cpp \ + compile/Png.cpp \ + compile/PseudolocaleGenerator.cpp \ + compile/Pseudolocalizer.cpp \ + compile/XmlIdCollector.cpp \ + filter/ConfigFilter.cpp \ + flatten/Archive.cpp \ + flatten/TableFlattener.cpp \ + flatten/XmlFlattener.cpp \ + io/FileSystem.cpp \ + io/ZipArchive.cpp \ + link/AutoVersioner.cpp \ + link/ManifestFixer.cpp \ + link/ProductFilter.cpp \ + link/PrivateAttributeMover.cpp \ + link/ReferenceLinker.cpp \ + link/TableMerger.cpp \ + link/XmlReferenceLinker.cpp \ + process/SymbolTable.cpp \ + proto/ProtoHelpers.cpp \ + proto/TableProtoDeserializer.cpp \ + proto/TableProtoSerializer.cpp \ + split/TableSplitter.cpp \ + unflatten/BinaryResourceParser.cpp \ + unflatten/ResChunkPullParser.cpp \ + util/BigBuffer.cpp \ + util/Files.cpp \ + util/Util.cpp \ ConfigDescription.cpp \ Debug.cpp \ - Files.cpp \ - Flag.cpp \ - JavaClassGenerator.cpp \ - Linker.cpp \ + Flags.cpp \ + java/AnnotationProcessor.cpp \ + java/ClassDefinition.cpp \ + java/JavaClassGenerator.cpp \ + java/ManifestClassGenerator.cpp \ + java/ProguardRules.cpp \ Locale.cpp \ - Logger.cpp \ - ManifestMerger.cpp \ - ManifestParser.cpp \ - ManifestValidator.cpp \ - Png.cpp \ - ProguardRules.cpp \ - ResChunkPullParser.cpp \ Resource.cpp \ ResourceParser.cpp \ ResourceTable.cpp \ - ResourceTableResolver.cpp \ + ResourceUtils.cpp \ ResourceValues.cpp \ SdkConstants.cpp \ StringPool.cpp \ - TableFlattener.cpp \ - Util.cpp \ - ScopedXmlPullParser.cpp \ - SourceXmlPullParser.cpp \ - XliffXmlPullParser.cpp \ - XmlDom.cpp \ - XmlFlattener.cpp \ - ZipEntry.cpp \ - ZipFile.cpp + xml/XmlActionExecutor.cpp \ + xml/XmlDom.cpp \ + xml/XmlPullParser.cpp \ + xml/XmlUtil.cpp + +sources += Format.proto testSources := \ - BigBuffer_test.cpp \ - BindingXmlPullParser_test.cpp \ - Compat_test.cpp \ + compile/IdAssigner_test.cpp \ + compile/PseudolocaleGenerator_test.cpp \ + compile/Pseudolocalizer_test.cpp \ + compile/XmlIdCollector_test.cpp \ + filter/ConfigFilter_test.cpp \ + flatten/TableFlattener_test.cpp \ + flatten/XmlFlattener_test.cpp \ + link/AutoVersioner_test.cpp \ + link/ManifestFixer_test.cpp \ + link/PrivateAttributeMover_test.cpp \ + link/ProductFilter_test.cpp \ + link/ReferenceLinker_test.cpp \ + link/TableMerger_test.cpp \ + link/XmlReferenceLinker_test.cpp \ + process/SymbolTable_test.cpp \ + proto/TableProtoSerializer_test.cpp \ + split/TableSplitter_test.cpp \ + util/BigBuffer_test.cpp \ + util/Files_test.cpp \ + util/Maybe_test.cpp \ + util/StringPiece_test.cpp \ + util/Util_test.cpp \ ConfigDescription_test.cpp \ - JavaClassGenerator_test.cpp \ - Linker_test.cpp \ + java/AnnotationProcessor_test.cpp \ + java/JavaClassGenerator_test.cpp \ + java/ManifestClassGenerator_test.cpp \ Locale_test.cpp \ - ManifestMerger_test.cpp \ - ManifestParser_test.cpp \ - Maybe_test.cpp \ - NameMangler_test.cpp \ - ResourceParser_test.cpp \ Resource_test.cpp \ + ResourceParser_test.cpp \ ResourceTable_test.cpp \ - ScopedXmlPullParser_test.cpp \ - StringPiece_test.cpp \ + ResourceUtils_test.cpp \ + SdkConstants_test.cpp \ StringPool_test.cpp \ - Util_test.cpp \ - XliffXmlPullParser_test.cpp \ - XmlDom_test.cpp \ - XmlFlattener_test.cpp + ValueVisitor_test.cpp \ + xml/XmlActionExecutor_test.cpp \ + xml/XmlDom_test.cpp \ + xml/XmlPullParser_test.cpp \ + xml/XmlUtil_test.cpp -cIncludes := \ - external/libpng \ - external/libz +toolSources := \ + compile/Compile.cpp \ + dump/Dump.cpp \ + link/Link.cpp hostLdLibs := @@ -96,46 +128,63 @@ hostStaticLibs := \ libexpat \ libziparchive-host \ libpng \ - libbase + libbase \ + libprotobuf-cpp-lite_static -ifneq ($(strip $(USE_MINGW)),) - hostStaticLibs += libz -else - hostLdLibs += -lz -endif + +# Statically link libz for MinGW (Win SDK under Linux), +# and dynamically link for all others. +hostStaticLibs_windows := libz +hostLdLibs_linux := -lz +hostLdLibs_darwin := -lz cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG -cppFlags := -std=c++11 -Wno-missing-field-initializers -Wno-unused-private-field +cFlags_darwin := -D_DARWIN_UNLIMITED_STREAMS +cFlags_windows := -Wno-maybe-uninitialized # Incorrectly marking use of Maybe.value() as error. +cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions -fno-rtti +protoIncludes := $(call generated-sources-dir-for,STATIC_LIBRARIES,libaapt2,HOST) + +# ========================================================== +# NOTE: Do not add any shared libraries. +# AAPT2 is built to run on many environments +# that may not have the required dependencies. +# ========================================================== # ========================================================== # Build the host static library: libaapt2 # ========================================================== include $(CLEAR_VARS) LOCAL_MODULE := libaapt2 - +LOCAL_MODULE_CLASS := STATIC_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 := $(sources) -LOCAL_C_INCLUDES += $(cIncludes) -LOCAL_CFLAGS += $(cFlags) -LOCAL_CPPFLAGS += $(cppFlags) - +LOCAL_STATIC_LIBRARIES := $(hostStaticLibs) +LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows) include $(BUILD_HOST_STATIC_LIBRARY) - # ========================================================== # Build the host tests: libaapt2_tests # ========================================================== include $(CLEAR_VARS) LOCAL_MODULE := libaapt2_tests LOCAL_MODULE_TAGS := tests - +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 := $(testSources) - -LOCAL_C_INCLUDES += $(cIncludes) -LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs) -LOCAL_LDLIBS += $(hostLdLibs) -LOCAL_CFLAGS += $(cFlags) -LOCAL_CPPFLAGS += $(cppFlags) - +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_NATIVE_TEST) # ========================================================== @@ -143,15 +192,20 @@ include $(BUILD_HOST_NATIVE_TEST) # ========================================================== include $(CLEAR_VARS) LOCAL_MODULE := aapt2 - -LOCAL_SRC_FILES := $(main) - -LOCAL_C_INCLUDES += $(cIncludes) -LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs) -LOCAL_LDLIBS += $(hostLdLibs) -LOCAL_CFLAGS += $(cFlags) -LOCAL_CPPFLAGS += $(cppFlags) - +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 := $(main) $(toolSources) +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_EXECUTABLE) -endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK +ifeq ($(ONE_SHOT_MAKEFILE),) +include $(call all-makefiles-under,$(LOCAL_PATH)) +endif diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/BinaryResourceParser.cpp deleted file mode 100644 index 4f1947ab8364..000000000000 --- a/tools/aapt2/BinaryResourceParser.cpp +++ /dev/null @@ -1,897 +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. - */ - -#include "BinaryResourceParser.h" -#include "Logger.h" -#include "ResChunkPullParser.h" -#include "Resolver.h" -#include "ResourceParser.h" -#include "ResourceTable.h" -#include "ResourceTypeExtensions.h" -#include "ResourceValues.h" -#include "Source.h" -#include "Util.h" - -#include <androidfw/ResourceTypes.h> -#include <androidfw/TypeWrappers.h> -#include <map> -#include <string> - -namespace aapt { - -using namespace android; - -/* - * Visitor that converts a reference's resource ID to a resource name, - * given a mapping from resource ID to resource name. - */ -struct ReferenceIdToNameVisitor : ValueVisitor { - ReferenceIdToNameVisitor(const std::shared_ptr<IResolver>& resolver, - std::map<ResourceId, ResourceName>* cache) : - mResolver(resolver), mCache(cache) { - } - - void visit(Reference& reference, ValueVisitorArgs&) override { - idToName(reference); - } - - void visit(Attribute& attr, ValueVisitorArgs&) override { - for (auto& entry : attr.symbols) { - idToName(entry.symbol); - } - } - - void visit(Style& style, ValueVisitorArgs&) override { - if (style.parent.id.isValid()) { - idToName(style.parent); - } - - for (auto& entry : style.entries) { - idToName(entry.key); - entry.value->accept(*this, {}); - } - } - - void visit(Styleable& styleable, ValueVisitorArgs&) override { - for (auto& attr : styleable.entries) { - idToName(attr); - } - } - - void visit(Array& array, ValueVisitorArgs&) override { - for (auto& item : array.items) { - item->accept(*this, {}); - } - } - - void visit(Plural& plural, ValueVisitorArgs&) override { - for (auto& item : plural.values) { - if (item) { - item->accept(*this, {}); - } - } - } - -private: - void idToName(Reference& reference) { - if (!reference.id.isValid()) { - return; - } - - auto cacheIter = mCache->find(reference.id); - if (cacheIter != mCache->end()) { - reference.name = cacheIter->second; - reference.id = 0; - } else { - Maybe<ResourceName> result = mResolver->findName(reference.id); - if (result) { - reference.name = result.value(); - - // Add to cache. - mCache->insert({reference.id, reference.name}); - - reference.id = 0; - } - } - } - - std::shared_ptr<IResolver> mResolver; - std::map<ResourceId, ResourceName>* mCache; -}; - - -BinaryResourceParser::BinaryResourceParser(const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<IResolver>& resolver, - const Source& source, - const std::u16string& defaultPackage, - const void* data, - size_t len) : - mTable(table), mResolver(resolver), mSource(source), mDefaultPackage(defaultPackage), - 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) { - Logger::warn(mSource) - << "unknown chunk of type '" - << parser.getChunk()->type - << "'." - << std::endl; - continue; - } - - error |= !parseTable(parser.getChunk()); - } - - if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { - Logger::error(mSource) - << "bad document: " - << parser.getLastError() - << "." - << std::endl; - return false; - } - return !error; -} - -bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbol) { - if (!mSymbolEntries || mSymbolEntryCount == 0) { - return false; - } - - if (reinterpret_cast<uintptr_t>(data) < reinterpret_cast<uintptr_t>(mData)) { - return false; - } - - // We only support 32 bit offsets right now. - const uintptr_t offset = reinterpret_cast<uintptr_t>(data) - - reinterpret_cast<uintptr_t>(mData); - if (offset > std::numeric_limits<uint32_t>::max()) { - return false; - } - - for (size_t i = 0; i < mSymbolEntryCount; i++) { - if (mSymbolEntries[i].offset == offset) { - // This offset is a symbol! - const StringPiece16 str = util::getString(mSymbolPool, - mSymbolEntries[i].stringIndex); - StringPiece16 typeStr; - ResourceParser::extractResourceName(str, &outSymbol->package, &typeStr, - &outSymbol->entry); - const ResourceType* type = parseResourceType(typeStr); - if (!type) { - return false; - } - if (outSymbol->package.empty()) { - outSymbol->package = mTable->getPackage(); - } - outSymbol->type = *type; - - // Since we scan the symbol table in order, we can start looking for the - // next symbol from this point. - mSymbolEntryCount -= i + 1; - mSymbolEntries += i + 1; - return true; - } - } - return false; -} - -bool BinaryResourceParser::parseSymbolTable(const ResChunk_header* chunk) { - const SymbolTable_header* symbolTableHeader = convertTo<SymbolTable_header>(chunk); - if (!symbolTableHeader) { - Logger::error(mSource) - << "could not parse chunk as SymbolTable_header." - << std::endl; - return false; - } - - const size_t entrySizeBytes = symbolTableHeader->count * sizeof(SymbolTable_entry); - if (entrySizeBytes > getChunkDataLen(symbolTableHeader->header)) { - Logger::error(mSource) - << "entries extend beyond chunk." - << std::endl; - return false; - } - - mSymbolEntries = reinterpret_cast<const SymbolTable_entry*>( - getChunkData(symbolTableHeader->header)); - mSymbolEntryCount = symbolTableHeader->count; - - ResChunkPullParser parser(getChunkData(symbolTableHeader->header) + entrySizeBytes, - getChunkDataLen(symbolTableHeader->header) - entrySizeBytes); - if (!ResChunkPullParser::isGoodEvent(parser.next())) { - Logger::error(mSource) - << "failed to parse chunk: " - << parser.getLastError() - << "." - << std::endl; - return false; - } - - if (parser.getChunk()->type != android::RES_STRING_POOL_TYPE) { - Logger::error(mSource) - << "expected Symbol string pool." - << std::endl; - return false; - } - - if (mSymbolPool.setTo(parser.getChunk(), parser.getChunk()->size) != NO_ERROR) { - Logger::error(mSource) - << "failed to parse symbol string pool with code: " - << mSymbolPool.getError() - << "." - << std::endl; - return false; - } - return true; -} - -bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) { - const ResTable_header* tableHeader = convertTo<ResTable_header>(chunk); - if (!tableHeader) { - Logger::error(mSource) - << "could not parse chunk as ResTable_header." - << std::endl; - return false; - } - - ResChunkPullParser parser(getChunkData(tableHeader->header), - getChunkDataLen(tableHeader->header)); - while (ResChunkPullParser::isGoodEvent(parser.next())) { - switch (parser.getChunk()->type) { - case android::RES_STRING_POOL_TYPE: - if (mValuePool.getError() == NO_INIT) { - if (mValuePool.setTo(parser.getChunk(), parser.getChunk()->size) != - NO_ERROR) { - Logger::error(mSource) - << "failed to parse value string pool with code: " - << mValuePool.getError() - << "." - << std::endl; - return false; - } - - // Reserve some space for the strings we are going to add. - mTable->getValueStringPool().hintWillAdd( - mValuePool.size(), mValuePool.styleCount()); - } else { - Logger::warn(mSource) - << "unexpected string pool." - << std::endl; - } - break; - - case RES_TABLE_SYMBOL_TABLE_TYPE: - if (!parseSymbolTable(parser.getChunk())) { - return false; - } - break; - - case RES_TABLE_SOURCE_POOL_TYPE: { - if (mSourcePool.setTo(getChunkData(*parser.getChunk()), - getChunkDataLen(*parser.getChunk())) != NO_ERROR) { - Logger::error(mSource) - << "failed to parse source pool with code: " - << mSourcePool.getError() - << "." - << std::endl; - return false; - } - break; - } - - case android::RES_TABLE_PACKAGE_TYPE: - if (!parsePackage(parser.getChunk())) { - return false; - } - break; - - default: - Logger::warn(mSource) - << "unexpected chunk of type " - << parser.getChunk()->type - << "." - << std::endl; - break; - } - } - - if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { - Logger::error(mSource) - << "bad resource table: " << parser.getLastError() - << "." - << std::endl; - return false; - } - return true; -} - -bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) { - if (mValuePool.getError() != NO_ERROR) { - Logger::error(mSource) - << "no value string pool for ResTable." - << std::endl; - return false; - } - - const ResTable_package* packageHeader = convertTo<ResTable_package>(chunk); - if (!packageHeader) { - Logger::error(mSource) - << "could not parse chunk as ResTable_header." - << std::endl; - return false; - } - - if (mTable->getPackageId() == ResourceTable::kUnsetPackageId) { - // This is the first time the table has it's package ID set. - mTable->setPackageId(packageHeader->id); - } else if (mTable->getPackageId() != packageHeader->id) { - Logger::error(mSource) - << "ResTable_package has package ID " - << std::hex << packageHeader->id << std::dec - << " but ResourceTable has package ID " - << std::hex << mTable->getPackageId() << std::dec - << std::endl; - return false; - } - - size_t len = strnlen16(reinterpret_cast<const char16_t*>(packageHeader->name), - sizeof(packageHeader->name) / sizeof(packageHeader->name[0])); - if (mTable->getPackage().empty() && len == 0) { - mTable->setPackage(mDefaultPackage); - } else if (len > 0) { - StringPiece16 thisPackage(reinterpret_cast<const char16_t*>(packageHeader->name), len); - if (mTable->getPackage().empty()) { - mTable->setPackage(thisPackage); - } else if (thisPackage != mTable->getPackage()) { - Logger::error(mSource) - << "incompatible packages: " - << mTable->getPackage() - << " vs. " - << thisPackage - << std::endl; - return false; - } - } - - ResChunkPullParser parser(getChunkData(packageHeader->header), - getChunkDataLen(packageHeader->header)); - while (ResChunkPullParser::isGoodEvent(parser.next())) { - switch (parser.getChunk()->type) { - case android::RES_STRING_POOL_TYPE: - if (mTypePool.getError() == NO_INIT) { - if (mTypePool.setTo(parser.getChunk(), parser.getChunk()->size) != - NO_ERROR) { - Logger::error(mSource) - << "failed to parse type string pool with code " - << mTypePool.getError() - << "." - << std::endl; - return false; - } - } else if (mKeyPool.getError() == NO_INIT) { - if (mKeyPool.setTo(parser.getChunk(), parser.getChunk()->size) != - NO_ERROR) { - Logger::error(mSource) - << "failed to parse key string pool with code " - << mKeyPool.getError() - << "." - << std::endl; - return false; - } - } else { - Logger::warn(mSource) - << "unexpected string pool." - << std::endl; - } - break; - - case android::RES_TABLE_TYPE_SPEC_TYPE: - if (!parseTypeSpec(parser.getChunk())) { - return false; - } - break; - - case android::RES_TABLE_TYPE_TYPE: - if (!parseType(parser.getChunk())) { - return false; - } - break; - - case RES_TABLE_PUBLIC_TYPE: - if (!parsePublic(parser.getChunk())) { - return false; - } - break; - - default: - Logger::warn(mSource) - << "unexpected chunk of type " - << parser.getChunk()->type - << "." - << std::endl; - break; - } - } - - if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { - Logger::error(mSource) - << "bad package: " - << parser.getLastError() - << "." - << std::endl; - return false; - } - - // Now go through the table and change resource ID references to - // symbolic references. - - ReferenceIdToNameVisitor visitor(mResolver, &mIdIndex); - for (auto& type : *mTable) { - for (auto& entry : type->entries) { - for (auto& configValue : entry->values) { - configValue.value->accept(visitor, {}); - } - } - } - return true; -} - -bool BinaryResourceParser::parsePublic(const ResChunk_header* chunk) { - const Public_header* header = convertTo<Public_header>(chunk); - - if (header->typeId == 0) { - Logger::error(mSource) - << "invalid type ID " << header->typeId << std::endl; - return false; - } - - const ResourceType* parsedType = parseResourceType(util::getString(mTypePool, - header->typeId - 1)); - if (!parsedType) { - Logger::error(mSource) - << "invalid type " << util::getString(mTypePool, header->typeId - 1) << std::endl; - return false; - } - - const uintptr_t chunkEnd = reinterpret_cast<uintptr_t>(chunk) + chunk->size; - const Public_entry* entry = reinterpret_cast<const Public_entry*>( - getChunkData(header->header)); - for (uint32_t i = 0; i < header->count; i++) { - if (reinterpret_cast<uintptr_t>(entry) + sizeof(*entry) > chunkEnd) { - Logger::error(mSource) - << "Public_entry extends beyond chunk." - << std::endl; - return false; - } - - const ResourceId resId = { mTable->getPackageId(), header->typeId, entry->entryId }; - const ResourceName name = { - mTable->getPackage(), - *parsedType, - util::getString(mKeyPool, entry->key.index).toString() }; - - SourceLine source; - if (mSourcePool.getError() == NO_ERROR) { - source.path = util::utf16ToUtf8(util::getString(mSourcePool, entry->source.index)); - source.line = entry->sourceLine; - } - - if (!mTable->markPublicAllowMangled(name, resId, source)) { - return false; - } - - // 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 }); - } - - entry++; - } - return true; -} - -bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) { - if (mTypePool.getError() != NO_ERROR) { - Logger::error(mSource) - << "no type string pool available for ResTable_typeSpec." - << std::endl; - return false; - } - - const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk); - if (!typeSpec) { - Logger::error(mSource) - << "could not parse chunk as ResTable_typeSpec." - << std::endl; - return false; - } - - if (typeSpec->id == 0) { - Logger::error(mSource) - << "ResTable_typeSpec has invalid id: " - << typeSpec->id - << "." - << std::endl; - return false; - } - return true; -} - -bool BinaryResourceParser::parseType(const ResChunk_header* chunk) { - if (mTypePool.getError() != NO_ERROR) { - Logger::error(mSource) - << "no type string pool available for ResTable_typeSpec." - << std::endl; - return false; - } - - if (mKeyPool.getError() != NO_ERROR) { - Logger::error(mSource) - << "no key string pool available for ResTable_type." - << std::endl; - return false; - } - - const ResTable_type* type = convertTo<ResTable_type>(chunk); - if (!type) { - Logger::error(mSource) - << "could not parse chunk as ResTable_type." - << std::endl; - return false; - } - - if (type->id == 0) { - Logger::error(mSource) - << "ResTable_type has invalid id: " - << type->id - << "." - << std::endl; - return false; - } - - const ConfigDescription config(type->config); - const StringPiece16 typeName = util::getString(mTypePool, type->id - 1); - - const ResourceType* parsedType = parseResourceType(typeName); - if (!parsedType) { - Logger::error(mSource) - << "invalid type name '" - << typeName - << "' for type with ID " - << uint32_t(type->id) - << "." << std::endl; - return false; - } - - android::TypeVariant tv(type); - for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) { - if (!*it) { - continue; - } - - const ResTable_entry* entry = *it; - const ResourceName name = { - mTable->getPackage(), - *parsedType, - util::getString(mKeyPool, entry->key.index).toString() - }; - - const ResourceId resId = { mTable->getPackageId(), type->id, it.index() }; - - std::unique_ptr<Value> resourceValue; - const ResTable_entry_source* sourceBlock = nullptr; - if (entry->flags & ResTable_entry::FLAG_COMPLEX) { - const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry); - if (mapEntry->size - sizeof(*mapEntry) == sizeof(*sourceBlock)) { - const uint8_t* data = reinterpret_cast<const uint8_t*>(mapEntry); - data += mapEntry->size - sizeof(*sourceBlock); - sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data); - } - - // TODO(adamlesinski): Check that the entry count is valid. - resourceValue = parseMapEntry(name, config, mapEntry); - } else { - if (entry->size - sizeof(*entry) == sizeof(*sourceBlock)) { - const uint8_t* data = reinterpret_cast<const uint8_t*>(entry); - data += entry->size - sizeof(*sourceBlock); - sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data); - } - - const Res_value* value = reinterpret_cast<const Res_value*>( - reinterpret_cast<const uint8_t*>(entry) + entry->size); - resourceValue = parseValue(name, config, value, entry->flags); - } - - if (!resourceValue) { - // TODO(adamlesinski): For now this is ok, but it really shouldn't be. - continue; - } - - SourceLine source = mSource.line(0); - if (sourceBlock) { - size_t len; - const char* str = mSourcePool.string8At(sourceBlock->pathIndex, &len); - if (str) { - source.path.assign(str, len); - } - source.line = sourceBlock->line; - } - - if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue))) { - return false; - } - - if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) { - if (!mTable->markPublicAllowMangled(name, resId, mSource.line(0))) { - return false; - } - } - - // 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 }); - } - } - 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>(); - } - - if (value->dataType == Res_value::TYPE_STRING) { - StringPiece16 str = util::getString(mValuePool, value->data); - - const ResStringPool_span* spans = mValuePool.styleAt(value->data); - if (spans != nullptr) { - 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->getValueStringPool().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->getValueStringPool().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->getValueStringPool().makeRef( - str, StringPool::Context{1, 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 (value->data != 0) { - // This is a normal reference. - return util::make_unique<Reference>(value->data, type); - } - - // This reference has an invalid ID. Check if it is an unresolved symbol. - ResourceNameRef symbol; - if (getSymbol(&value->data, &symbol)) { - return util::make_unique<Reference>(symbol, type); - } - - // This is not an unresolved symbol, so it must be the magic @null reference. - Res_value nullType = {}; - nullType.dataType = Res_value::TYPE_REFERENCE; - return util::make_unique<BinaryPrimitive>(nullType); - } - - if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) { - return util::make_unique<RawString>( - mTable->getValueStringPool().makeRef(util::getString(mValuePool, value->data), - StringPool::Context{ 1, config })); - } - - // Treat this as a raw binary primitive. - return util::make_unique<BinaryPrimitive>(*value); -} - -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::kAttr: - return parseAttr(name, config, map); - case ResourceType::kArray: - return parseArray(name, config, map); - case ResourceType::kStyleable: - return parseStyleable(name, config, map); - case ResourceType::kPlurals: - return parsePlural(name, config, map); - default: - break; - } - return {}; -} - -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 (map->parent.ident == 0) { - // The parent is either not set or it is an unresolved symbol. - // Check to see if it is a symbol. - ResourceNameRef symbol; - if (getSymbol(&map->parent.ident, &symbol)) { - style->parent.name = symbol.toResourceName(); - } - } else { - // The parent is a regular reference to a resource. - style->parent.id = map->parent.ident; - } - - for (const ResTable_map& mapEntry : map) { - style->entries.emplace_back(); - Style::Entry& styleEntry = style->entries.back(); - - if (mapEntry.name.ident == 0) { - // The map entry's key (attribute) is not set. This must be - // a symbol reference, so resolve it. - ResourceNameRef symbol; - bool result = getSymbol(&mapEntry.name.ident, &symbol); - assert(result); - styleEntry.key.name = symbol.toResourceName(); - } else { - // The map entry's key (attribute) is a regular reference. - styleEntry.key.id = mapEntry.name.ident; - } - - // Parse the attribute's value. - styleEntry.value = parseValue(name, config, &mapEntry.value, 0); - assert(styleEntry.value); - } - return style; -} - -std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef& name, - const ConfigDescription& config, - const ResTable_map_entry* map) { - const bool isWeak = (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 entry.name.ident == ResTable_map::ATTR_TYPE; - }); - - if (typeMaskIter != end(map)) { - attr->typeMask = typeMaskIter->value.data; - } - - if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) { - for (const ResTable_map& mapEntry : map) { - if (Res_INTERNALID(mapEntry.name.ident)) { - continue; - } - - Attribute::Symbol symbol; - symbol.value = mapEntry.value.data; - if (mapEntry.name.ident == 0) { - // The map entry's key (id) is not set. This must be - // a symbol reference, so resolve it. - ResourceNameRef symbolName; - bool result = getSymbol(&mapEntry.name.ident, &symbolName); - assert(result); - symbol.symbol.name = symbolName.toResourceName(); - } else { - // The map entry's key (id) is a regular reference. - symbol.symbol.id = mapEntry.name.ident; - } - - attr->symbols.push_back(std::move(symbol)); - } - } - - // TODO(adamlesinski): Find min, max, i80n, etc attributes. - return attr; -} - -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<Styleable> BinaryResourceParser::parseStyleable(const ResourceNameRef& name, - const ConfigDescription& config, - const ResTable_map_entry* map) { - std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); - for (const ResTable_map& mapEntry : map) { - if (mapEntry.name.ident == 0) { - // The map entry's key (attribute) is not set. This must be - // a symbol reference, so resolve it. - ResourceNameRef symbol; - bool result = getSymbol(&mapEntry.name.ident, &symbol); - assert(result); - styleable->entries.emplace_back(symbol); - } else { - // The map entry's key (attribute) is a regular reference. - styleable->entries.emplace_back(mapEntry.name.ident); - } - } - return styleable; -} - -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); - - switch (mapEntry.name.ident) { - case android::ResTable_map::ATTR_ZERO: - plural->values[Plural::Zero] = std::move(item); - break; - case android::ResTable_map::ATTR_ONE: - plural->values[Plural::One] = std::move(item); - break; - case android::ResTable_map::ATTR_TWO: - plural->values[Plural::Two] = std::move(item); - break; - case android::ResTable_map::ATTR_FEW: - plural->values[Plural::Few] = std::move(item); - break; - case android::ResTable_map::ATTR_MANY: - plural->values[Plural::Many] = std::move(item); - break; - case android::ResTable_map::ATTR_OTHER: - plural->values[Plural::Other] = std::move(item); - break; - } - } - return plural; -} - -} // namespace aapt diff --git a/tools/aapt2/BindingXmlPullParser.cpp b/tools/aapt2/BindingXmlPullParser.cpp deleted file mode 100644 index 4b7a656deac6..000000000000 --- a/tools/aapt2/BindingXmlPullParser.cpp +++ /dev/null @@ -1,268 +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. - */ - -#include "BindingXmlPullParser.h" -#include "Util.h" - -#include <iostream> -#include <sstream> -#include <string> -#include <vector> - -namespace aapt { - -constexpr const char16_t* kBindingNamespaceUri = u"http://schemas.android.com/apk/binding"; -constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android"; -constexpr const char16_t* kVariableTagName = u"variable"; -constexpr const char* kBindingTagPrefix = "android:binding_"; - -BindingXmlPullParser::BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) : - mParser(parser), mOverride(false), mNextTagId(0) { -} - -bool BindingXmlPullParser::readVariableDeclaration() { - VarDecl var; - - const auto endAttrIter = mParser->endAttributes(); - for (auto attrIter = mParser->beginAttributes(); attrIter != endAttrIter; ++attrIter) { - if (!attrIter->namespaceUri.empty()) { - continue; - } - - if (attrIter->name == u"name") { - var.name = util::utf16ToUtf8(attrIter->value); - } else if (attrIter->name == u"type") { - var.type = util::utf16ToUtf8(attrIter->value); - } - } - - XmlPullParser::skipCurrentElement(mParser.get()); - - if (var.name.empty()) { - mLastError = "variable declaration missing name"; - return false; - } - - if (var.type.empty()) { - mLastError = "variable declaration missing type"; - return false; - } - - mVarDecls.push_back(std::move(var)); - return true; -} - -bool BindingXmlPullParser::readExpressions() { - mOverride = true; - std::vector<XmlPullParser::Attribute> expressions; - std::string idValue; - - const auto endAttrIter = mParser->endAttributes(); - for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) { - if (attr->namespaceUri == kAndroidNamespaceUri && attr->name == u"id") { - idValue = util::utf16ToUtf8(attr->value); - } else { - StringPiece16 value = util::trimWhitespace(attr->value); - if (util::stringStartsWith<char16_t>(value, u"@{") && - util::stringEndsWith<char16_t>(value, u"}")) { - // This is attribute's value is an expression of the form - // @{expression}. We need to capture the expression inside. - expressions.push_back(XmlPullParser::Attribute{ - attr->namespaceUri, - attr->name, - value.substr(2, value.size() - 3).toString() - }); - } else { - // This is a normal attribute, use as is. - mAttributes.emplace_back(*attr); - } - } - } - - // Check if we have any expressions. - if (!expressions.empty()) { - // We have expressions, so let's assign the target a tag number - // and add it to our targets list. - int32_t targetId = mNextTagId++; - mTargets.push_back(Target{ - util::utf16ToUtf8(mParser->getElementName()), - idValue, - targetId, - std::move(expressions) - }); - - std::stringstream numGen; - numGen << kBindingTagPrefix << targetId; - mAttributes.push_back(XmlPullParser::Attribute{ - std::u16string(kAndroidNamespaceUri), - std::u16string(u"tag"), - util::utf8ToUtf16(numGen.str()) - }); - } - return true; -} - -XmlPullParser::Event BindingXmlPullParser::next() { - // Clear old state in preparation for the next event. - mOverride = false; - mAttributes.clear(); - - while (true) { - Event event = mParser->next(); - if (event == Event::kStartElement) { - if (mParser->getElementNamespace().empty() && - mParser->getElementName() == kVariableTagName) { - // This is a variable tag. Record data from it, and - // then discard the entire element. - if (!readVariableDeclaration()) { - // mLastError is set, so getEvent will return kBadDocument. - return getEvent(); - } - continue; - } else { - // Check for expressions of the form @{} in attribute text. - const auto endAttrIter = mParser->endAttributes(); - for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) { - StringPiece16 value = util::trimWhitespace(attr->value); - if (util::stringStartsWith<char16_t>(value, u"@{") && - util::stringEndsWith<char16_t>(value, u"}")) { - if (!readExpressions()) { - return getEvent(); - } - break; - } - } - } - } else if (event == Event::kStartNamespace || event == Event::kEndNamespace) { - if (mParser->getNamespaceUri() == kBindingNamespaceUri) { - // Skip binding namespace tags. - continue; - } - } - return event; - } - return Event::kBadDocument; -} - -bool BindingXmlPullParser::writeToFile(std::ostream& out) const { - out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; - out << "<Layout directory=\"\" layout=\"\" layoutId=\"\">\n"; - - // Write the variables. - out << " <Variables>\n"; - for (const VarDecl& v : mVarDecls) { - out << " <entries name=\"" << v.name << "\" type=\"" << v.type << "\"/>\n"; - } - out << " </Variables>\n"; - - // Write the imports. - - std::stringstream tagGen; - - // Write the targets. - out << " <Targets>\n"; - for (const Target& t : mTargets) { - tagGen.str({}); - tagGen << kBindingTagPrefix << t.tagId; - out << " <Target boundClass=\"" << t.className << "\" id=\"" << t.id - << "\" tag=\"" << tagGen.str() << "\">\n"; - out << " <Expressions>\n"; - for (const XmlPullParser::Attribute& a : t.expressions) { - out << " <Expression attribute=\"" << a.namespaceUri << ":" << a.name - << "\" text=\"" << a.value << "\"/>\n"; - } - out << " </Expressions>\n"; - out << " </Target>\n"; - } - out << " </Targets>\n"; - - out << "</Layout>\n"; - return bool(out); -} - -XmlPullParser::const_iterator BindingXmlPullParser::beginAttributes() const { - if (mOverride) { - return mAttributes.begin(); - } - return mParser->beginAttributes(); -} - -XmlPullParser::const_iterator BindingXmlPullParser::endAttributes() const { - if (mOverride) { - return mAttributes.end(); - } - return mParser->endAttributes(); -} - -size_t BindingXmlPullParser::getAttributeCount() const { - if (mOverride) { - return mAttributes.size(); - } - return mParser->getAttributeCount(); -} - -XmlPullParser::Event BindingXmlPullParser::getEvent() const { - if (!mLastError.empty()) { - return Event::kBadDocument; - } - return mParser->getEvent(); -} - -const std::string& BindingXmlPullParser::getLastError() const { - if (!mLastError.empty()) { - return mLastError; - } - return mParser->getLastError(); -} - -const std::u16string& BindingXmlPullParser::getComment() const { - return mParser->getComment(); -} - -size_t BindingXmlPullParser::getLineNumber() const { - return mParser->getLineNumber(); -} - -size_t BindingXmlPullParser::getDepth() const { - return mParser->getDepth(); -} - -const std::u16string& BindingXmlPullParser::getText() const { - return mParser->getText(); -} - -const std::u16string& BindingXmlPullParser::getNamespacePrefix() const { - return mParser->getNamespacePrefix(); -} - -const std::u16string& BindingXmlPullParser::getNamespaceUri() const { - return mParser->getNamespaceUri(); -} - -bool BindingXmlPullParser::applyPackageAlias(std::u16string* package, - const std::u16string& defaultPackage) const { - return mParser->applyPackageAlias(package, defaultPackage); -} - -const std::u16string& BindingXmlPullParser::getElementNamespace() const { - return mParser->getElementNamespace(); -} - -const std::u16string& BindingXmlPullParser::getElementName() const { - return mParser->getElementName(); -} - -} // namespace aapt diff --git a/tools/aapt2/BindingXmlPullParser.h b/tools/aapt2/BindingXmlPullParser.h deleted file mode 100644 index cfb16ef477c9..000000000000 --- a/tools/aapt2/BindingXmlPullParser.h +++ /dev/null @@ -1,90 +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. - */ - -#ifndef AAPT_BINDING_XML_PULL_PARSER_H -#define AAPT_BINDING_XML_PULL_PARSER_H - -#include "XmlPullParser.h" - -#include <iostream> -#include <memory> -#include <string> - -namespace aapt { - -class BindingXmlPullParser : public XmlPullParser { -public: - BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser); - BindingXmlPullParser(const BindingXmlPullParser& rhs) = delete; - - Event getEvent() const override; - const std::string& getLastError() const override; - Event next() override; - - const std::u16string& getComment() const override; - size_t getLineNumber() const override; - size_t getDepth() const override; - - const std::u16string& getText() const override; - - const std::u16string& getNamespacePrefix() const override; - const std::u16string& getNamespaceUri() const override; - bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage) - const override; - - const std::u16string& getElementNamespace() const override; - const std::u16string& getElementName() const override; - - const_iterator beginAttributes() const override; - const_iterator endAttributes() const override; - size_t getAttributeCount() const override; - - bool writeToFile(std::ostream& out) const; - -private: - struct VarDecl { - std::string name; - std::string type; - }; - - struct Import { - std::string name; - std::string type; - }; - - struct Target { - std::string className; - std::string id; - int32_t tagId; - - std::vector<XmlPullParser::Attribute> expressions; - }; - - bool readVariableDeclaration(); - bool readExpressions(); - - std::shared_ptr<XmlPullParser> mParser; - std::string mLastError; - bool mOverride; - std::vector<XmlPullParser::Attribute> mAttributes; - std::vector<VarDecl> mVarDecls; - std::vector<Target> mTargets; - int32_t mNextTagId; -}; - -} // namespace aapt - -#endif // AAPT_BINDING_XML_PULL_PARSER_H diff --git a/tools/aapt2/BindingXmlPullParser_test.cpp b/tools/aapt2/BindingXmlPullParser_test.cpp deleted file mode 100644 index 28edcb672840..000000000000 --- a/tools/aapt2/BindingXmlPullParser_test.cpp +++ /dev/null @@ -1,110 +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. - */ - -#include "SourceXmlPullParser.h" -#include "BindingXmlPullParser.h" - -#include <gtest/gtest.h> -#include <sstream> -#include <string> - -namespace aapt { - -constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android"; - -TEST(BindingXmlPullParserTest, SubstituteBindingExpressionsWithTag) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" - << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" - << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n" - << " android:id=\"@+id/content\">\n" - << " <variable name=\"user\" type=\"com.android.test.User\"/>\n" - << " <TextView android:text=\"@{user.name}\" android:layout_width=\"wrap_content\"\n" - << " android:layout_height=\"wrap_content\"/>\n" - << "</LinearLayout>\n"; - std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input); - BindingXmlPullParser parser(sourceParser); - - ASSERT_EQ(XmlPullParser::Event::kStartNamespace, parser.next()); - EXPECT_EQ(std::u16string(u"http://schemas.android.com/apk/res/android"), - parser.getNamespaceUri()); - - ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.next()); - EXPECT_EQ(std::u16string(u"LinearLayout"), parser.getElementName()); - - while (parser.next() == XmlPullParser::Event::kText) {} - - ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent()); - EXPECT_EQ(std::u16string(u"TextView"), parser.getElementName()); - - ASSERT_EQ(3u, parser.getAttributeCount()); - const auto endAttr = parser.endAttributes(); - EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_width")); - EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_height")); - EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"tag")); - - while (parser.next() == XmlPullParser::Event::kText) {} - - ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent()); - - while (parser.next() == XmlPullParser::Event::kText) {} - - ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent()); - ASSERT_EQ(XmlPullParser::Event::kEndNamespace, parser.next()); -} - -TEST(BindingXmlPullParserTest, GenerateVariableDeclarations) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" - << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" - << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n" - << " android:id=\"@+id/content\">\n" - << " <variable name=\"user\" type=\"com.android.test.User\"/>\n" - << "</LinearLayout>\n"; - std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input); - BindingXmlPullParser parser(sourceParser); - - while (XmlPullParser::isGoodEvent(parser.next())) { - ASSERT_NE(XmlPullParser::Event::kBadDocument, parser.getEvent()); - } - - std::stringstream output; - ASSERT_TRUE(parser.writeToFile(output)); - - std::string result = output.str(); - EXPECT_NE(std::string::npos, - result.find("<entries name=\"user\" type=\"com.android.test.User\"/>")); -} - -TEST(BindingXmlPullParserTest, FailOnMissingNameOrTypeInVariableDeclaration) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" - << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" - << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n" - << " android:id=\"@+id/content\">\n" - << " <variable name=\"user\"/>\n" - << "</LinearLayout>\n"; - std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input); - BindingXmlPullParser parser(sourceParser); - - while (XmlPullParser::isGoodEvent(parser.next())) {} - - EXPECT_EQ(XmlPullParser::Event::kBadDocument, parser.getEvent()); - EXPECT_FALSE(parser.getLastError().empty()); -} - - -} // namespace aapt diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp index 6ddf94a681b8..13f8b3b54f68 100644 --- a/tools/aapt2/ConfigDescription.cpp +++ b/tools/aapt2/ConfigDescription.cpp @@ -17,8 +17,8 @@ #include "ConfigDescription.h" #include "Locale.h" #include "SdkConstants.h" -#include "StringPiece.h" -#include "Util.h" +#include "util/StringPiece.h" +#include "util/Util.h" #include <androidfw/ResourceTypes.h> #include <string> @@ -30,6 +30,11 @@ using android::ResTable_config; static const char* kWildcardName = "any"; +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; @@ -164,6 +169,26 @@ static bool parseScreenLayoutLong(const char* name, ResTable_config* out) { 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; +} + static bool parseOrientation(const char* name, ResTable_config* out) { if (strcmp(name, kWildcardName) == 0) { if (out) out->orientation = out->ORIENTATION_ANY; @@ -635,6 +660,13 @@ bool ConfigDescription::parse(const StringPiece& str, ConfigDescription* out) { } } + if (parseScreenRound(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; + } + } + if (parseOrientation(partIter->c_str(), &config)) { ++partIter; if (partIter == partsEnd) { @@ -725,7 +757,9 @@ success: void ConfigDescription::applyVersionForCompatibility(ConfigDescription* config) { uint16_t minSdk = 0; - if (config->density == ResTable_config::DENSITY_ANY) { + 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 diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h index 67b4b75cce0b..5749816f5124 100644 --- a/tools/aapt2/ConfigDescription.h +++ b/tools/aapt2/ConfigDescription.h @@ -17,7 +17,7 @@ #ifndef AAPT_CONFIG_DESCRIPTION_H #define AAPT_CONFIG_DESCRIPTION_H -#include "StringPiece.h" +#include "util/StringPiece.h" #include <androidfw/ResourceTypes.h> #include <ostream> @@ -29,6 +29,11 @@ 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. diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp index c57e35191a76..e68d6be536df 100644 --- a/tools/aapt2/ConfigDescription_test.cpp +++ b/tools/aapt2/ConfigDescription_test.cpp @@ -15,7 +15,9 @@ */ #include "ConfigDescription.h" -#include "StringPiece.h" +#include "SdkConstants.h" + +#include "util/StringPiece.h" #include <gtest/gtest.h> #include <string> @@ -79,4 +81,19 @@ TEST(ConfigDescriptionTest, ParseCarAttribute) { 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()); +} + } // namespace aapt diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index cf222c68de55..19bd5210c840 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -17,7 +17,8 @@ #include "Debug.h" #include "ResourceTable.h" #include "ResourceValues.h" -#include "Util.h" +#include "util/Util.h" +#include "ValueVisitor.h" #include <algorithm> #include <iostream> @@ -29,102 +30,147 @@ namespace aapt { -struct PrintVisitor : ConstValueVisitor { - void visit(const Attribute& attr, ValueVisitorArgs&) override { +class PrintVisitor : public ValueVisitor { +public: + using ValueVisitor::visit; + + void visit(Attribute* attr) override { std::cout << "(attr) type="; - attr.printMask(std::cout); + 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.entry << " (" << symbol.symbol.id << ") = " - << symbol.value; + 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; } } } - void visit(const Style& style, ValueVisitorArgs&) override { + void visit(Style* style) override { std::cout << "(style)"; - if (style.parent.name.isValid() || style.parent.id.isValid()) { + if (style->parent) { + const Reference& parentRef = style->parent.value(); std::cout << " parent="; - if (style.parent.name.isValid()) { - std::cout << style.parent.name << " "; + if (parentRef.name) { + if (parentRef.privateReference) { + std::cout << "*"; + } + std::cout << parentRef.name.value() << " "; } - if (style.parent.id.isValid()) { - std::cout << style.parent.id; + if (parentRef.id) { + std::cout << parentRef.id.value(); } } - for (const auto& entry : style.entries) { + for (const auto& entry : style->entries) { std::cout << "\n "; - if (entry.key.name.isValid()) { - std::cout << entry.key.name.package << ":" << entry.key.name.entry; + 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.isValid()) { - std::cout << "(" << entry.key.id << ")"; + if (entry.key.id) { + std::cout << "(" << entry.key.id.value() << ")"; } std::cout << "=" << *entry.value; } } - void visit(const Array& array, ValueVisitorArgs&) override { - array.print(std::cout); + void visit(Array* array) override { + array->print(&std::cout); } - void visit(const Plural& plural, ValueVisitorArgs&) override { - plural.print(std::cout); + void visit(Plural* plural) override { + plural->print(&std::cout); } - void visit(const Styleable& styleable, ValueVisitorArgs&) override { - styleable.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; + } + + if (attr.id) { + std::cout << "(" << attr.id.value() << ")"; + } + } } - void visitItem(const Item& item, ValueVisitorArgs& args) override { - item.print(std::cout); + void visitItem(Item* item) override { + item->print(&std::cout); } }; -void Debug::printTable(const std::shared_ptr<ResourceTable>& table) { - std::cout << "Package name=" << table->getPackage(); - if (table->getPackageId() != ResourceTable::kUnsetPackageId) { - std::cout << " id=" << std::hex << table->getPackageId() << std::dec; - } - std::cout << std::endl; +void Debug::printTable(ResourceTable* table, const DebugPrintTableOptions& options) { + PrintVisitor visitor; - for (const auto& type : *table) { - std::cout << " type " << type->type; - if (type->typeId != ResourceTableType::kUnsetTypeId) { - std::cout << " id=" << std::hex << type->typeId << 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 { - return a->entryId < b->entryId; - }); - sortedEntries.insert(iter, entry.get()); + 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 ResourceEntry* entry : sortedEntries) { - ResourceId id = { table->getPackageId(), type->typeId, entry->entryId }; - ResourceName name = { table->getPackage(), type->type, entry->name }; - std::cout << " spec resource " << id << " " << name; - if (entry->publicStatus.isPublic) { - std::cout << " PUBLIC"; + 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()); } - std::cout << std::endl; - PrintVisitor visitor; - for (const auto& value : entry->values) { - std::cout << " (" << value.config << ") "; - value.value->accept(visitor, {}); + 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; + } } } } @@ -136,8 +182,7 @@ static size_t getNodeIndex(const std::vector<ResourceName>& names, const Resourc return std::distance(names.begin(), iter); } -void Debug::printStyleGraph(const std::shared_ptr<ResourceTable>& table, - const ResourceName& targetStyle) { +void Debug::printStyleGraph(ResourceTable* table, const ResourceName& targetStyle) { std::map<ResourceName, std::set<ResourceName>> graph; std::queue<ResourceName> stylesToVisit; @@ -150,17 +195,16 @@ void Debug::printStyleGraph(const std::shared_ptr<ResourceTable>& table, continue; } - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = table->findResource(styleName); - if (entry) { + Maybe<ResourceTable::SearchResult> result = table->findResource(styleName); + if (result) { + ResourceEntry* entry = result.value().entry; for (const auto& value : entry->values) { - visitFunc<Style>(*value.value, [&](const Style& style) { - if (style.parent.name.isValid()) { - parents.insert(style.parent.name); - stylesToVisit.push(style.parent.name); + 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()); } - }); + } } } } @@ -189,4 +233,19 @@ void Debug::printStyleGraph(const std::shared_ptr<ResourceTable>& table, std::cout << "}" << 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"; + } + } + + if (len - 1 % 8 != 7) { + std::cerr << std::endl; + } +} + + } // namespace aapt diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h index cdb3dcb6a5d8..fbe64773d4ed 100644 --- a/tools/aapt2/Debug.h +++ b/tools/aapt2/Debug.h @@ -20,14 +20,20 @@ #include "Resource.h" #include "ResourceTable.h" -#include <memory> +// Include for printf-like debugging. +#include <iostream> namespace aapt { +struct DebugPrintTableOptions { + bool showSources = false; +}; + struct Debug { - static void printTable(const std::shared_ptr<ResourceTable>& table); - static void printStyleGraph(const std::shared_ptr<ResourceTable>& table, + 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); }; } // namespace aapt diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h new file mode 100644 index 000000000000..e86f2a8830e8 --- /dev/null +++ b/tools/aapt2/Diagnostics.h @@ -0,0 +1,147 @@ +/* + * 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. + */ + +#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> + +namespace aapt { + +struct DiagMessageActual { + Source source; + std::string message; +}; + +struct DiagMessage { +private: + Source mSource; + std::stringstream mMessage; + +public: + DiagMessage() = default; + + DiagMessage(const StringPiece& src) : mSource(src) { + } + + DiagMessage(const Source& src) : mSource(src) { + } + + DiagMessage(size_t line) : mSource(Source().withLine(line)) { + } + + template <typename T> + DiagMessage& operator<<(const T& value) { + mMessage << value; + return *this; + } + + DiagMessageActual build() const { + return DiagMessageActual{ mSource, mMessage.str() }; + } +}; + +struct IDiagnostics { + virtual ~IDiagnostics() = default; + + enum class Level { + Note, + Warn, + Error + }; + + virtual void log(Level level, DiagMessageActual& actualMsg) = 0; + + 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 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; + } + + if (!actualMsg.source.path.empty()) { + std::cerr << actualMsg.source << ": "; + } + std::cerr << tag << ": " << actualMsg.message << "." << std::endl; + } + +private: + size_t mNumErrors = 0; + + DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics); +}; + +class SourcePathDiagnostics : public IDiagnostics { +public: + SourcePathDiagnostics(const Source& src, IDiagnostics* diag) : mSource(src), mDiag(diag) { + } + + void log(Level level, DiagMessageActual& actualMsg) override { + actualMsg.source.path = mSource.path; + mDiag->log(level, actualMsg); + } + +private: + Source mSource; + IDiagnostics* mDiag; + + DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics); +}; + +} // namespace aapt + +#endif /* AAPT_DIAGNOSTICS_H */ diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp deleted file mode 100644 index 76985da99912..000000000000 --- a/tools/aapt2/Flag.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "Flag.h" -#include "StringPiece.h" - -#include <functional> -#include <iomanip> -#include <iostream> -#include <string> -#include <vector> - -namespace aapt { -namespace flag { - -struct Flag { - std::string name; - std::string description; - std::function<bool(const StringPiece&, std::string*)> action; - bool required; - bool* flagResult; - bool flagValueWhenSet; - bool parsed; -}; - -static std::vector<Flag> sFlags; -static std::vector<std::string> sArgs; - -static std::function<bool(const StringPiece&, std::string*)> wrap( - const std::function<void(const StringPiece&)>& action) { - return [action](const StringPiece& arg, std::string*) -> bool { - action(arg); - return true; - }; -} - -void optionalFlag(const StringPiece& name, const StringPiece& description, - std::function<void(const StringPiece&)> action) { - sFlags.push_back(Flag{ - name.toString(), description.toString(), wrap(action), - false, nullptr, false, false }); -} - -void requiredFlag(const StringPiece& name, const StringPiece& description, - std::function<void(const StringPiece&)> action) { - sFlags.push_back(Flag{ name.toString(), description.toString(), wrap(action), - true, nullptr, false, false }); -} - -void requiredFlag(const StringPiece& name, const StringPiece& description, - std::function<bool(const StringPiece&, std::string*)> action) { - sFlags.push_back(Flag{ name.toString(), description.toString(), action, - true, nullptr, false, false }); -} - -void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet, - bool* result) { - sFlags.push_back(Flag{ - name.toString(), description.toString(), {}, - false, result, resultWhenSet, false }); -} - -void usageAndDie(const StringPiece& command) { - std::cerr << command << " [options]"; - for (const Flag& flag : sFlags) { - if (flag.required) { - std::cerr << " " << flag.name << " arg"; - } - } - std::cerr << " files..." << std::endl << std::endl << "Options:" << std::endl; - - for (const Flag& flag : sFlags) { - std::string command = flag.name; - if (!flag.flagResult) { - command += " arg "; - } - std::cerr << " " << std::setw(30) << std::left << command - << flag.description << std::endl; - } - exit(1); -} - -void parse(int argc, char** argv, const StringPiece& command) { - std::string errorStr; - for (int i = 0; i < argc; i++) { - const StringPiece arg(argv[i]); - if (*arg.data() != '-') { - sArgs.push_back(arg.toString()); - continue; - } - - bool match = false; - for (Flag& flag : sFlags) { - if (arg == flag.name) { - match = true; - flag.parsed = true; - if (flag.flagResult) { - *flag.flagResult = flag.flagValueWhenSet; - } else { - i++; - if (i >= argc) { - std::cerr << flag.name << " missing argument." << std::endl - << std::endl; - usageAndDie(command); - } - - if (!flag.action(argv[i], &errorStr)) { - std::cerr << errorStr << "." << std::endl << std::endl; - usageAndDie(command); - } - } - break; - } - } - - if (!match) { - std::cerr << "unknown option '" << arg << "'." << std::endl << std::endl; - usageAndDie(command); - } - } - - for (const Flag& flag : sFlags) { - if (flag.required && !flag.parsed) { - std::cerr << "missing required flag " << flag.name << std::endl << std::endl; - usageAndDie(command); - } - } -} - -const std::vector<std::string>& getArgs() { - return sArgs; -} - -} // namespace flag -} // namespace aapt diff --git a/tools/aapt2/Flag.h b/tools/aapt2/Flag.h deleted file mode 100644 index e86374283986..000000000000 --- a/tools/aapt2/Flag.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef AAPT_FLAG_H -#define AAPT_FLAG_H - -#include "StringPiece.h" - -#include <functional> -#include <string> -#include <vector> - -namespace aapt { -namespace flag { - -void requiredFlag(const StringPiece& name, const StringPiece& description, - std::function<void(const StringPiece&)> action); - -void requiredFlag(const StringPiece& name, const StringPiece& description, - std::function<bool(const StringPiece&, std::string*)> action); - -void optionalFlag(const StringPiece& name, const StringPiece& description, - std::function<void(const StringPiece&)> action); - -void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet, - bool* result); - -void usageAndDie(const StringPiece& command); - -void parse(int argc, char** argv, const StringPiece& command); - -const std::vector<std::string>& getArgs(); - -} // namespace flag -} // namespace aapt - -#endif // AAPT_FLAG_H diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/Flags.cpp new file mode 100644 index 000000000000..666e8a8efff1 --- /dev/null +++ b/tools/aapt2/Flags.cpp @@ -0,0 +1,168 @@ +/* + * 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 "Flags.h" +#include "util/StringPiece.h" +#include "util/Util.h" + +#include <iomanip> +#include <iostream> +#include <string> +#include <vector> + +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; + }; + + mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false}); + return *this; +} + +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; + }; + + mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false }); + return *this; +} + +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; + }; + + mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false }); + return *this; +} + +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; + }; + + mFlags.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; + }; + + mFlags.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 : mFlags) { + if (flag.required) { + *out << " " << flag.name << " arg"; + } + } + + *out << " files...\n\nOptions:\n"; + + for (const Flag& flag : mFlags) { + std::string argLine = flag.name; + if (flag.numArgs > 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 = " "; + } + } + *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 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 (!match) { + *outError << "unknown option '" << arg << "'.\n\n"; + usage(command, outError); + return false; + } + } + + for (const Flag& flag : mFlags) { + if (flag.required && !flag.parsed) { + *outError << "missing required flag " << flag.name << "\n\n"; + usage(command, outError); + return false; + } + } + return true; +} + +const std::vector<std::string>& Flags::getArgs() { + return mArgs; +} + +} // namespace aapt diff --git a/tools/aapt2/Flags.h b/tools/aapt2/Flags.h new file mode 100644 index 000000000000..ce7a4857eb6e --- /dev/null +++ b/tools/aapt2/Flags.h @@ -0,0 +1,67 @@ +/* + * 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. + */ + +#ifndef AAPT_FLAGS_H +#define AAPT_FLAGS_H + +#include "util/Maybe.h" +#include "util/StringPiece.h" + +#include <functional> +#include <ostream> +#include <string> +#include <vector> + +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); + + void usage(const StringPiece& command, std::ostream* out); + + bool parse(const StringPiece& command, const std::vector<StringPiece>& args, + std::ostream* outError); + + 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; + + bool parsed; + }; + + std::vector<Flag> mFlags; + std::vector<std::string> mArgs; +}; + +} // namespace aapt + +#endif // AAPT_FLAGS_H diff --git a/tools/aapt2/Format.proto b/tools/aapt2/Format.proto new file mode 100644 index 000000000000..d05425c5c64d --- /dev/null +++ b/tools/aapt2/Format.proto @@ -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. + */ + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package aapt.pb; + +message ConfigDescription { + optional bytes data = 1; + optional string product = 2; +} + +message StringPool { + optional bytes data = 1; +} + +message CompiledFile { + message Symbol { + optional string resource_name = 1; + optional uint32 line_no = 2; + } + + optional string resource_name = 1; + optional ConfigDescription config = 2; + optional string source_path = 3; + repeated Symbol exported_symbols = 4; +} + +message ResourceTable { + optional StringPool string_pool = 1; + optional StringPool source_pool = 2; + optional StringPool symbol_pool = 3; + repeated Package packages = 4; +} + +message Package { + optional uint32 package_id = 1; + optional string package_name = 2; + repeated Type types = 3; +} + +message Type { + optional uint32 id = 1; + optional string name = 2; + repeated Entry entries = 3; +} + +message SymbolStatus { + enum Visibility { + Unknown = 0; + Private = 1; + Public = 2; + } + optional Visibility visibility = 1; + optional Source source = 2; + optional string comment = 3; +} + +message Entry { + optional uint32 id = 1; + optional string name = 2; + optional SymbolStatus symbol_status = 3; + repeated ConfigValue config_values = 4; +} + +message ConfigValue { + optional ConfigDescription config = 1; + optional Value value = 2; +} + +message Source { + optional uint32 path_idx = 1; + optional uint32 line_no = 2; + optional uint32 col_no = 3; +} + +message Reference { + enum Type { + Ref = 0; + Attr = 1; + } + optional Type type = 1; + optional uint32 id = 2; + optional uint32 symbol_idx = 3; + optional bool private = 4; +} + +message Id { +} + +message String { + optional uint32 idx = 1; +} + +message RawString { + optional uint32 idx = 1; +} + +message FileReference { + optional uint32 path_idx = 1; +} + +message Primitive { + optional uint32 type = 1; + optional uint32 data = 2; +} + +message Attribute { + message Symbol { + optional Source source = 1; + optional string comment = 2; + optional Reference name = 3; + optional uint32 value = 4; + } + optional uint32 format_flags = 1; + optional int32 min_int = 2; + optional int32 max_int = 3; + repeated Symbol symbols = 4; +} + +message Style { + message Entry { + optional Source source = 1; + optional string comment = 2; + optional Reference key = 3; + optional Item item = 4; + } + + optional Reference parent = 1; + optional Source parent_source = 2; + repeated Entry entries = 3; +} + +message Styleable { + message Entry { + optional Source source = 1; + optional string comment = 2; + optional Reference attr = 3; + } + repeated Entry entries = 1; +} + +message Array { + message Entry { + optional Source source = 1; + optional string comment = 2; + optional Item item = 3; + } + repeated Entry entries = 1; +} + +message Plural { + enum Arity { + Zero = 0; + One = 1; + Two = 2; + Few = 3; + Many = 4; + Other = 5; + } + + message Entry { + optional Source source = 1; + optional string comment = 2; + optional Arity arity = 3; + optional Item item = 4; + } + repeated Entry entries = 1; +} + +message Item { + optional Reference ref = 1; + optional String str = 2; + optional RawString raw_str = 3; + optional FileReference file = 4; + optional Id id = 5; + optional Primitive prim = 6; +} + +message CompoundValue { + optional Attribute attr = 1; + optional Style style = 2; + optional Styleable styleable = 3; + optional Array array = 4; + optional Plural plural = 5; +} + +message Value { + optional Source source = 1; + optional string comment = 2; + optional bool weak = 3; + + optional Item item = 4; + optional CompoundValue compound_value = 5; +} diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/JavaClassGenerator.cpp deleted file mode 100644 index e2ffe79c764d..000000000000 --- a/tools/aapt2/JavaClassGenerator.cpp +++ /dev/null @@ -1,208 +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. - */ - -#include "JavaClassGenerator.h" -#include "NameMangler.h" -#include "Resource.h" -#include "ResourceTable.h" -#include "ResourceValues.h" -#include "StringPiece.h" - -#include <algorithm> -#include <ostream> -#include <set> -#include <sstream> -#include <tuple> - -namespace aapt { - -// The number of attributes to emit per line in a Styleable array. -constexpr size_t kAttribsPerLine = 4; - -JavaClassGenerator::JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table, - Options options) : - mTable(table), mOptions(options) { -} - -static void generateHeader(std::ostream& out, const StringPiece16& package) { - out << "/* 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"; - out << "package " << package << ";" - << std::endl - << std::endl; -} - -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(); -} - -/* - * Java symbols can not contain . or -, but those are valid in a resource name. - * Replace those with '_'. - */ -static std::u16string transform(const StringPiece16& symbol) { - std::u16string output = symbol.toString(); - for (char16_t& c : output) { - if (c == u'.' || c == u'-') { - c = u'_'; - } - } - return output; -} - -struct GenArgs : ValueVisitorArgs { - GenArgs(std::ostream* o, const std::u16string* p, std::u16string* e) : - out(o), package(p), entryName(e) { - } - - std::ostream* out; - const std::u16string* package; - std::u16string* entryName; -}; - -void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) { - const StringPiece finalModifier = mOptions.useFinal ? " final" : ""; - std::ostream* out = static_cast<GenArgs&>(a).out; - const std::u16string* package = static_cast<GenArgs&>(a).package; - std::u16string* entryName = static_cast<GenArgs&>(a).entryName; - - // This must be sorted by resource ID. - std::vector<std::pair<ResourceId, ResourceNameRef>> 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.isValid()) && "no ID set for Styleable entry"); - assert(attr.name.isValid() && "no name set for Styleable entry"); - sortedAttributes.emplace_back(attr.id, attr.name); - } - std::sort(sortedAttributes.begin(), sortedAttributes.end()); - - // First we emit the array containing the IDs of each attribute. - *out << " " - << "public static final int[] " << transform(*entryName) << " = {"; - - const size_t attrCount = sortedAttributes.size(); - for (size_t i = 0; i < attrCount; i++) { - if (i % kAttribsPerLine == 0) { - *out << std::endl << " "; - } - - *out << sortedAttributes[i].first; - if (i != attrCount - 1) { - *out << ", "; - } - } - *out << std::endl << " };" << std::endl; - - // Now we emit the indices into the array. - for (size_t i = 0; i < attrCount; i++) { - *out << " " - << "public static" << finalModifier - << " int " << transform(*entryName); - - // We may reference IDs from other packages, so prefix the entry name with - // the package. - const ResourceNameRef& itemName = sortedAttributes[i].second; - if (itemName.package != *package) { - *out << "_" << transform(itemName.package); - } - *out << "_" << transform(itemName.entry) << " = " << i << ";" << std::endl; - } -} - -bool JavaClassGenerator::generateType(const std::u16string& package, size_t packageId, - const ResourceTableType& type, std::ostream& out) { - const StringPiece finalModifier = mOptions.useFinal ? " final" : ""; - - std::u16string unmangledPackage; - std::u16string unmangledName; - for (const auto& entry : type.entries) { - ResourceId id = { packageId, type.typeId, entry->entryId }; - assert(id.isValid()); - - 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 != unmangledPackage) { - // Skip the entry if it doesn't belong to the package we're writing. - continue; - } - } else { - if (package != mTable->getPackage()) { - // We are processing a mangled package name, - // but this is a non-mangled resource. - continue; - } - } - - if (!isValidSymbol(unmangledName)) { - ResourceNameRef resourceName = { package, type.type, unmangledName }; - std::stringstream err; - err << "invalid symbol name '" << resourceName << "'"; - mError = err.str(); - return false; - } - - if (type.type == ResourceType::kStyleable) { - assert(!entry->values.empty()); - entry->values.front().value->accept(*this, GenArgs{ &out, &package, &unmangledName }); - } else { - out << " " << "public static" << finalModifier - << " int " << transform(unmangledName) << " = " << id << ";" << std::endl; - } - } - return true; -} - -bool JavaClassGenerator::generate(const std::u16string& package, std::ostream& out) { - const size_t packageId = mTable->getPackageId(); - - generateHeader(out, package); - - out << "public final class R {" << std::endl; - - for (const auto& type : *mTable) { - out << " public static final class " << type->type << " {" << std::endl; - if (!generateType(package, packageId, *type, out)) { - return false; - } - out << " }" << std::endl; - } - - out << "}" << std::endl; - return true; -} - -} // namespace aapt diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/JavaClassGenerator.h deleted file mode 100644 index f8b9ee3f1fc8..000000000000 --- a/tools/aapt2/JavaClassGenerator.h +++ /dev/null @@ -1,77 +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. - */ - -#ifndef AAPT_JAVA_CLASS_GENERATOR_H -#define AAPT_JAVA_CLASS_GENERATOR_H - -#include "ResourceTable.h" -#include "ResourceValues.h" - -#include <ostream> -#include <string> - -namespace aapt { - -/* - * Generates the R.java file for a resource table. - */ -class JavaClassGenerator : ConstValueVisitor { -public: - /* - * A set of options for this JavaClassGenerator. - */ - struct Options { - /* - * Specifies whether to use the 'final' modifier - * on resource entries. Default is true. - */ - bool useFinal = true; - }; - - JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table, Options 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 std::u16string& package, std::ostream& out); - - /* - * ConstValueVisitor implementation. - */ - void visit(const Styleable& styleable, ValueVisitorArgs& args); - - const std::string& getError() const; - -private: - bool generateType(const std::u16string& package, size_t packageId, - const ResourceTableType& type, std::ostream& out); - - std::shared_ptr<const ResourceTable> mTable; - Options mOptions; - std::string mError; -}; - -inline const std::string& JavaClassGenerator::getError() const { - return mError; -} - -} // namespace aapt - -#endif // AAPT_JAVA_CLASS_GENERATOR_H diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp deleted file mode 100644 index b385ff4828e1..000000000000 --- a/tools/aapt2/JavaClassGenerator_test.cpp +++ /dev/null @@ -1,146 +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. - */ - -#include "JavaClassGenerator.h" -#include "Linker.h" -#include "MockResolver.h" -#include "ResourceTable.h" -#include "ResourceTableResolver.h" -#include "ResourceValues.h" -#include "Util.h" - -#include <gtest/gtest.h> -#include <sstream> -#include <string> - -namespace aapt { - -struct JavaClassGeneratorTest : public ::testing::Test { - virtual void SetUp() override { - mTable = std::make_shared<ResourceTable>(); - mTable->setPackage(u"android"); - mTable->setPackageId(0x01); - } - - bool addResource(const ResourceNameRef& name, ResourceId id) { - return mTable->addResource(name, id, {}, SourceLine{ "test.xml", 21 }, - util::make_unique<Id>()); - } - - std::shared_ptr<ResourceTable> mTable; -}; - -TEST_F(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) { - ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"class" }, - ResourceId{ 0x01, 0x02, 0x0000 })); - - JavaClassGenerator generator(mTable, {}); - - std::stringstream out; - EXPECT_FALSE(generator.generate(mTable->getPackage(), out)); -} - -TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { - ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"hey-man" }, - ResourceId{ 0x01, 0x02, 0x0000 })); - - ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kAttr, u"cool.attr" }, - ResourceId{ 0x01, 0x01, 0x0000 })); - - std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); - Reference ref(ResourceName{ u"android", ResourceType::kAttr, u"cool.attr"}); - ref.id = ResourceId{ 0x01, 0x01, 0x0000 }; - styleable->entries.emplace_back(ref); - - ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"hey.dude" }, - ResourceId{ 0x01, 0x03, 0x0000 }, {}, - SourceLine{ "test.xml", 21 }, std::move(styleable))); - - JavaClassGenerator generator(mTable, {}); - - std::stringstream out; - EXPECT_TRUE(generator.generate(mTable->getPackage(), 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;")); -} - - -TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) { - ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"foo" }, - ResourceId{ 0x01, 0x02, 0x0000 })); - ResourceTable table; - table.setPackage(u"com.lib"); - ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" }, {}, - SourceLine{ "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>()), - {}); - ASSERT_TRUE(linker.linkAndValidate()); - - JavaClassGenerator generator(mTable, {}); - - std::stringstream out; - EXPECT_TRUE(generator.generate(mTable->getPackage(), out)); - std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("int foo =")); - EXPECT_EQ(std::string::npos, output.find("int test =")); - - out.str(""); - EXPECT_TRUE(generator.generate(u"com.lib", out)); - output = out.str(); - EXPECT_NE(std::string::npos, output.find("int test =")); - EXPECT_EQ(std::string::npos, output.find("int foo =")); -} - -TEST_F(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) { - std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); - styleable->entries.emplace_back(ResourceNameRef{ mTable->getPackage(), - ResourceType::kAttr, - u"bar" }); - styleable->entries.emplace_back(ResourceNameRef{ u"com.lib", ResourceType::kAttr, u"bar" }); - ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"Foo" }, {}, {}, - std::move(styleable))); - - std::shared_ptr<IResolver> resolver = std::make_shared<MockResolver>(mTable, - std::map<ResourceName, ResourceId>({ - { ResourceName{ u"android", ResourceType::kAttr, u"bar" }, - ResourceId{ 0x01, 0x01, 0x0000 } }, - { ResourceName{ u"com.lib", ResourceType::kAttr, u"bar" }, - ResourceId{ 0x02, 0x01, 0x0000 } }})); - - Linker linker(mTable, resolver, {}); - ASSERT_TRUE(linker.linkAndValidate()); - - JavaClassGenerator generator(mTable, {}); - - std::stringstream out; - EXPECT_TRUE(generator.generate(mTable->getPackage(), 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 =")); -} - -} // namespace aapt diff --git a/tools/aapt2/Linker.cpp b/tools/aapt2/Linker.cpp deleted file mode 100644 index c37cc932cd3b..000000000000 --- a/tools/aapt2/Linker.cpp +++ /dev/null @@ -1,290 +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. - */ - -#include "Linker.h" -#include "Logger.h" -#include "NameMangler.h" -#include "Resolver.h" -#include "ResourceParser.h" -#include "ResourceTable.h" -#include "ResourceValues.h" -#include "StringPiece.h" -#include "Util.h" - -#include <androidfw/AssetManager.h> -#include <array> -#include <bitset> -#include <iostream> -#include <map> -#include <ostream> -#include <set> -#include <sstream> -#include <tuple> -#include <vector> - -namespace aapt { - -Linker::Args::Args(const ResourceNameRef& r, const SourceLine& s) : referrer(r), source(s) { -} - -Linker::Linker(const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<IResolver>& resolver, const Options& options) : - mResolver(resolver), mTable(table), mOptions(options), mError(false) { -} - -bool Linker::linkAndValidate() { - std::bitset<256> usedTypeIds; - std::array<std::set<uint16_t>, 256> usedIds; - usedTypeIds.set(0); - - // Collect which resource IDs are already taken. - for (auto& type : *mTable) { - if (type->typeId != ResourceTableType::kUnsetTypeId) { - // The ID for this type has already been set. We - // mark this ID as taken so we don't re-assign it - // later. - usedTypeIds.set(type->typeId); - } - - for (auto& entry : type->entries) { - if (type->typeId != ResourceTableType::kUnsetTypeId && - entry->entryId != ResourceEntry::kUnsetEntryId) { - // The ID for this entry has already been set. We - // mark this ID as taken so we don't re-assign it - // later. - usedIds[type->typeId].insert(entry->entryId); - } - } - } - - // Assign resource IDs that are available. - size_t nextTypeIndex = 0; - for (auto& type : *mTable) { - if (type->typeId == ResourceTableType::kUnsetTypeId) { - while (nextTypeIndex < usedTypeIds.size() && usedTypeIds[nextTypeIndex]) { - nextTypeIndex++; - } - type->typeId = nextTypeIndex++; - } - - const auto endEntryIter = std::end(usedIds[type->typeId]); - auto nextEntryIter = std::begin(usedIds[type->typeId]); - size_t nextIndex = 0; - for (auto& entry : type->entries) { - if (entry->entryId == ResourceTableType::kUnsetTypeId) { - while (nextEntryIter != endEntryIter && - nextIndex == *nextEntryIter) { - nextIndex++; - ++nextEntryIter; - } - entry->entryId = nextIndex++; - } - } - } - - // Now do reference linking. - for (auto& type : *mTable) { - for (auto& entry : type->entries) { - if (entry->publicStatus.isPublic && entry->values.empty()) { - // A public resource has no values. It will not be encoded - // properly without a symbol table. This is a unresolved symbol. - addUnresolvedSymbol(ResourceNameRef{ - mTable->getPackage(), type->type, entry->name }, - entry->publicStatus.source); - continue; - } - - for (auto& valueConfig : entry->values) { - // Dispatch to the right method of this linker - // based on the value's type. - valueConfig.value->accept(*this, Args{ - ResourceNameRef{ mTable->getPackage(), type->type, entry->name }, - valueConfig.source - }); - } - } - } - return !mError; -} - -const Linker::ResourceNameToSourceMap& Linker::getUnresolvedReferences() const { - return mUnresolvedSymbols; -} - -void Linker::doResolveReference(Reference& reference, const SourceLine& source) { - Maybe<ResourceId> result = mResolver->findId(reference.name); - if (!result) { - addUnresolvedSymbol(reference.name, source); - return; - } - assert(result.value().isValid()); - - if (mOptions.linkResourceIds) { - reference.id = result.value(); - } else { - reference.id = 0; - } -} - -const Attribute* Linker::doResolveAttribute(Reference& attribute, const SourceLine& source) { - Maybe<IResolver::Entry> result = mResolver->findAttribute(attribute.name); - if (!result || !result.value().attr) { - addUnresolvedSymbol(attribute.name, source); - return nullptr; - } - - const IResolver::Entry& entry = result.value(); - assert(entry.id.isValid()); - - if (mOptions.linkResourceIds) { - attribute.id = entry.id; - } else { - attribute.id = 0; - } - return entry.attr; -} - -void Linker::visit(Reference& reference, ValueVisitorArgs& a) { - Args& args = static_cast<Args&>(a); - - if (reference.name.entry.empty()) { - // We can't have a completely bad reference. - if (!reference.id.isValid()) { - Logger::error() << "srsly? " << args.referrer << std::endl; - assert(reference.id.isValid()); - } - - // This reference has no name but has an ID. - // It is a really bad error to have no name and have the same - // package ID. - assert(reference.id.packageId() != mTable->getPackageId()); - - // The reference goes outside this package, let it stay as a - // resource ID because it will not change. - return; - } - - doResolveReference(reference, args.source); - - // TODO(adamlesinski): Verify the referencedType is another reference - // or a compatible primitive. -} - -void Linker::processAttributeValue(const ResourceNameRef& name, const SourceLine& source, - const Attribute& attr, std::unique_ptr<Item>& value) { - std::unique_ptr<Item> convertedValue; - visitFunc<RawString>(*value, [&](RawString& str) { - // This is a raw string, so check if it can be converted to anything. - // We can NOT swap value with the converted value in here, since - // we called through the original value. - - auto onCreateReference = [&](const ResourceName& name) { - // We should never get here. All references would have been - // parsed in the parser phase. - assert(false); - }; - - convertedValue = ResourceParser::parseItemForAttribute(*str.value, attr, - onCreateReference); - if (!convertedValue && attr.typeMask & android::ResTable_map::TYPE_STRING) { - // Last effort is to parse as a string. - util::StringBuilder builder; - builder.append(*str.value); - if (builder) { - convertedValue = util::make_unique<String>( - mTable->getValueStringPool().makeRef(builder.str())); - } - } - }); - - if (convertedValue) { - value = std::move(convertedValue); - } - - // Process this new or old value (it can be a reference!). - value->accept(*this, Args{ name, source }); - - // Flatten the value to see what resource type it is. - android::Res_value resValue; - value->flatten(resValue); - - // Always allow references. - const uint32_t typeMask = attr.typeMask | android::ResTable_map::TYPE_REFERENCE; - if (!(typeMask & ResourceParser::androidTypeToAttributeTypeMask(resValue.dataType))) { - Logger::error(source) - << *value - << " is not compatible with attribute " - << attr - << "." - << std::endl; - mError = true; - } -} - -void Linker::visit(Style& style, ValueVisitorArgs& a) { - Args& args = static_cast<Args&>(a); - - if (style.parent.name.isValid() || style.parent.id.isValid()) { - visit(style.parent, a); - } - - for (Style::Entry& styleEntry : style.entries) { - const Attribute* attr = doResolveAttribute(styleEntry.key, args.source); - if (attr) { - processAttributeValue(args.referrer, args.source, *attr, styleEntry.value); - } - } -} - -void Linker::visit(Attribute& attr, ValueVisitorArgs& a) { - static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM | - android::ResTable_map::TYPE_FLAGS; - if (attr.typeMask & kMask) { - for (auto& symbol : attr.symbols) { - visit(symbol.symbol, a); - } - } -} - -void Linker::visit(Styleable& styleable, ValueVisitorArgs& a) { - for (auto& attrRef : styleable.entries) { - visit(attrRef, a); - } -} - -void Linker::visit(Array& array, ValueVisitorArgs& a) { - Args& args = static_cast<Args&>(a); - - for (auto& item : array.items) { - item->accept(*this, Args{ args.referrer, args.source }); - } -} - -void Linker::visit(Plural& plural, ValueVisitorArgs& a) { - Args& args = static_cast<Args&>(a); - - for (auto& item : plural.values) { - if (item) { - item->accept(*this, Args{ args.referrer, args.source }); - } - } -} - -void Linker::addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source) { - mUnresolvedSymbols[name.toResourceName()].push_back(source); -} - -} // namespace aapt diff --git a/tools/aapt2/Linker.h b/tools/aapt2/Linker.h deleted file mode 100644 index 6f0351592fcd..000000000000 --- a/tools/aapt2/Linker.h +++ /dev/null @@ -1,124 +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. - */ - -#ifndef AAPT_LINKER_H -#define AAPT_LINKER_H - -#include "Resolver.h" -#include "ResourceTable.h" -#include "ResourceValues.h" -#include "Source.h" -#include "StringPiece.h" - -#include <androidfw/AssetManager.h> -#include <map> -#include <memory> -#include <ostream> -#include <set> -#include <vector> - -namespace aapt { - -/** - * The Linker has two jobs. It follows resource references - * and verifies that their targert exists and that their - * types are compatible. The Linker will also assign resource - * IDs and fill in all the dependent references with the newly - * assigned resource IDs. - * - * To do this, the Linker builds a graph of references. This - * can be useful to do other analysis, like building a - * dependency graph of source files. The hope is to be able to - * add functionality that operates on the graph without - * overcomplicating the Linker. - * - * TODO(adamlesinski): Build the graph first then run the separate - * steps over the graph. - */ -class Linker : ValueVisitor { -public: - struct Options { - /** - * Assign resource Ids to references when linking. - * When building a static library, set this to false. - */ - bool linkResourceIds = true; - }; - - /** - * Create a Linker for the given resource table with the sources available in - * IResolver. IResolver should contain the ResourceTable as a source too. - */ - Linker(const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<IResolver>& resolver, const Options& options); - - Linker(const Linker&) = delete; - - virtual ~Linker() = default; - - /** - * Entry point to the linker. Assigns resource IDs, follows references, - * and validates types. Returns true if all references to defined values - * are type-compatible. Missing resource references are recorded but do - * not cause this method to fail. - */ - bool linkAndValidate(); - - /** - * Returns any references to resources that were not defined in any of the - * sources. - */ - using ResourceNameToSourceMap = std::map<ResourceName, std::vector<SourceLine>>; - const ResourceNameToSourceMap& getUnresolvedReferences() const; - -protected: - virtual void doResolveReference(Reference& reference, const SourceLine& source); - virtual const Attribute* doResolveAttribute(Reference& attribute, const SourceLine& source); - - std::shared_ptr<IResolver> mResolver; - -private: - struct Args : public ValueVisitorArgs { - Args(const ResourceNameRef& r, const SourceLine& s); - - const ResourceNameRef& referrer; - const SourceLine& source; - }; - - // - // Overrides of ValueVisitor - // - void visit(Reference& reference, ValueVisitorArgs& args) override; - void visit(Attribute& attribute, ValueVisitorArgs& args) override; - void visit(Styleable& styleable, ValueVisitorArgs& args) override; - void visit(Style& style, ValueVisitorArgs& args) override; - void visit(Array& array, ValueVisitorArgs& args) override; - void visit(Plural& plural, ValueVisitorArgs& args) override; - - void processAttributeValue(const ResourceNameRef& name, const SourceLine& source, - const Attribute& attr, std::unique_ptr<Item>& value); - - void addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source); - - std::shared_ptr<ResourceTable> mTable; - std::map<ResourceName, std::vector<SourceLine>> mUnresolvedSymbols; - Options mOptions; - bool mError; -}; - -} // namespace aapt - -#endif // AAPT_LINKER_H diff --git a/tools/aapt2/Linker_test.cpp b/tools/aapt2/Linker_test.cpp deleted file mode 100644 index d897f9824a95..000000000000 --- a/tools/aapt2/Linker_test.cpp +++ /dev/null @@ -1,153 +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. - */ - -#include "Linker.h" -#include "ResourceTable.h" -#include "ResourceTableResolver.h" -#include "ResourceValues.h" -#include "Util.h" - -#include <androidfw/AssetManager.h> -#include <gtest/gtest.h> -#include <string> - -namespace aapt { - -struct LinkerTest : public ::testing::Test { - virtual void SetUp() override { - mTable = std::make_shared<ResourceTable>(); - mTable->setPackage(u"android"); - mTable->setPackageId(0x01); - mLinker = std::make_shared<Linker>(mTable, std::make_shared<ResourceTableResolver>( - mTable, std::vector<std::shared_ptr<const android::AssetManager>>()), - Linker::Options{}); - - // Create a few attributes for use in the tests. - - addResource(ResourceName{ {}, ResourceType::kAttr, u"integer" }, - util::make_unique<Attribute>(false, android::ResTable_map::TYPE_INTEGER)); - - addResource(ResourceName{ {}, ResourceType::kAttr, u"string" }, - util::make_unique<Attribute>(false, android::ResTable_map::TYPE_STRING)); - - addResource(ResourceName{ {}, ResourceType::kId, u"apple" }, util::make_unique<Id>()); - - addResource(ResourceName{ {}, ResourceType::kId, u"banana" }, util::make_unique<Id>()); - - std::unique_ptr<Attribute> flagAttr = util::make_unique<Attribute>( - false, android::ResTable_map::TYPE_FLAGS); - flagAttr->symbols.push_back(Attribute::Symbol{ - ResourceNameRef{ u"android", ResourceType::kId, u"apple" }, 1 }); - flagAttr->symbols.push_back(Attribute::Symbol{ - ResourceNameRef{ u"android", ResourceType::kId, u"banana" }, 2 }); - addResource(ResourceName{ {}, ResourceType::kAttr, u"flags" }, std::move(flagAttr)); - } - - /* - * Convenience method for adding resources with the default configuration and some - * bogus source line. - */ - bool addResource(const ResourceNameRef& name, std::unique_ptr<Value> value) { - return mTable->addResource(name, {}, SourceLine{ "test.xml", 21 }, std::move(value)); - } - - std::shared_ptr<ResourceTable> mTable; - std::shared_ptr<Linker> mLinker; -}; - -TEST_F(LinkerTest, DoNotInterpretEscapedStringAsReference) { - ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kString, u"foo" }, - util::make_unique<String>(mTable->getValueStringPool().makeRef(u"?123")))); - - ASSERT_TRUE(mLinker->linkAndValidate()); - EXPECT_TRUE(mLinker->getUnresolvedReferences().empty()); -} - -TEST_F(LinkerTest, EscapeAndConvertRawString) { - std::unique_ptr<Style> style = util::make_unique<Style>(); - style->entries.push_back(Style::Entry{ - ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" }, - util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u" 123")) - }); - const Style* result = style.get(); - ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" }, - std::move(style))); - - ASSERT_TRUE(mLinker->linkAndValidate()); - EXPECT_TRUE(mLinker->getUnresolvedReferences().empty()); - - EXPECT_NE(nullptr, dynamic_cast<BinaryPrimitive*>(result->entries.front().value.get())); -} - -TEST_F(LinkerTest, FailToConvertRawString) { - std::unique_ptr<Style> style = util::make_unique<Style>(); - style->entries.push_back(Style::Entry{ - ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" }, - util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"yo what is up?")) - }); - ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" }, - std::move(style))); - - ASSERT_FALSE(mLinker->linkAndValidate()); -} - -TEST_F(LinkerTest, ConvertRawStringToString) { - std::unique_ptr<Style> style = util::make_unique<Style>(); - style->entries.push_back(Style::Entry{ - ResourceNameRef{ u"android", ResourceType::kAttr, u"string" }, - util::make_unique<RawString>( - mTable->getValueStringPool().makeRef(u" \"this is \\u00fa\".")) - }); - const Style* result = style.get(); - ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" }, - std::move(style))); - - ASSERT_TRUE(mLinker->linkAndValidate()); - EXPECT_TRUE(mLinker->getUnresolvedReferences().empty()); - - const String* str = dynamic_cast<const String*>(result->entries.front().value.get()); - ASSERT_NE(nullptr, str); - EXPECT_EQ(*str->value, u"this is \u00fa."); -} - -TEST_F(LinkerTest, ConvertRawStringToFlags) { - std::unique_ptr<Style> style = util::make_unique<Style>(); - style->entries.push_back(Style::Entry{ - ResourceNameRef{ u"android", ResourceType::kAttr, u"flags" }, - util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"banana | apple")) - }); - const Style* result = style.get(); - ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" }, - std::move(style))); - - ASSERT_TRUE(mLinker->linkAndValidate()); - EXPECT_TRUE(mLinker->getUnresolvedReferences().empty()); - - const BinaryPrimitive* bin = dynamic_cast<const BinaryPrimitive*>( - result->entries.front().value.get()); - ASSERT_NE(nullptr, bin); - EXPECT_EQ(bin->value.data, 1u | 2u); -} - -TEST_F(LinkerTest, AllowReferenceWithOnlyResourceIdPointingToDifferentPackage) { - ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kInteger, u"foo" }, - util::make_unique<Reference>(ResourceId{ 0x02, 0x01, 0x01 }))); - - ASSERT_TRUE(mLinker->linkAndValidate()); - EXPECT_TRUE(mLinker->getUnresolvedReferences().empty()); -} - -} // namespace aapt diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp index eed0ea71f6c0..be576613b9b2 100644 --- a/tools/aapt2/Locale.cpp +++ b/tools/aapt2/Locale.cpp @@ -15,7 +15,7 @@ */ #include "Locale.h" -#include "Util.h" +#include "util/Util.h" #include <algorithm> #include <ctype.h> @@ -70,7 +70,7 @@ static inline bool isNumber(const std::string& str) { return std::all_of(std::begin(str), std::end(str), ::isdigit); } -bool LocaleValue::initFromFilterString(const std::string& str) { +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, '_'); @@ -96,7 +96,7 @@ bool LocaleValue::initFromFilterString(const std::string& str) { setRegion(part2.c_str()); } else if (part2.length() == 4 && isAlpha(part2)) { setScript(part2.c_str()); - } else if (part2.length() >= 5 && part2.length() <= 8) { + } else if (part2.length() >= 4 && part2.length() <= 8) { setVariant(part2.c_str()); } else { valid = false; @@ -111,7 +111,7 @@ bool LocaleValue::initFromFilterString(const std::string& str) { if (((part3.length() == 2 && isAlpha(part3)) || (part3.length() == 3 && isNumber(part3))) && script[0]) { setRegion(part3.c_str()); - } else if (part3.length() >= 5 && part3.length() <= 8) { + } else if (part3.length() >= 4 && part3.length() <= 8) { setVariant(part3.c_str()); } else { valid = false; @@ -122,7 +122,7 @@ bool LocaleValue::initFromFilterString(const std::string& str) { } const std::string& part4 = parts[3]; - if (part4.length() >= 5 && part4.length() <= 8) { + if (part4.length() >= 4 && part4.length() <= 8) { setVariant(part4.c_str()); } else { valid = false; @@ -141,7 +141,7 @@ ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator 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, + // 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()); @@ -157,8 +157,12 @@ ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter, setRegion(subtags[1].c_str()); break; case 4: - setScript(subtags[1].c_str()); - break; + 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: @@ -184,7 +188,7 @@ ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter, // 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) { + if (subtags[2].size() >= 4) { setVariant(subtags[2].c_str()); } else { setRegion(subtags[2].c_str()); @@ -249,7 +253,7 @@ std::string LocaleValue::toDirName() const { void LocaleValue::initFromResTable(const ResTable_config& config) { config.unpackLanguage(language); config.unpackRegion(region); - if (config.localeScript[0]) { + if (config.localeScript[0] && !config.localeScriptWasComputed) { memcpy(script, config.localeScript, sizeof(config.localeScript)); } diff --git a/tools/aapt2/Locale.h b/tools/aapt2/Locale.h index ceec764ba4fd..b1c80ab27641 100644 --- a/tools/aapt2/Locale.h +++ b/tools/aapt2/Locale.h @@ -17,6 +17,8 @@ #ifndef AAPT_LOCALE_VALUE_H #define AAPT_LOCALE_VALUE_H +#include "util/StringPiece.h" + #include <androidfw/ResourceTypes.h> #include <string> #include <vector> @@ -37,7 +39,7 @@ struct LocaleValue { /** * Initialize this LocaleValue from a config string. */ - bool initFromFilterString(const std::string& config); + bool initFromFilterString(const StringPiece& config); /** * Initialize this LocaleValue from parts of a vector. diff --git a/tools/aapt2/Locale_test.cpp b/tools/aapt2/Locale_test.cpp index 4e154d6720d6..758e1e31c0e7 100644 --- a/tools/aapt2/Locale_test.cpp +++ b/tools/aapt2/Locale_test.cpp @@ -15,7 +15,7 @@ */ #include "Locale.h" -#include "Util.h" +#include "util/Util.h" #include <gtest/gtest.h> #include <string> diff --git a/tools/aapt2/Logger.cpp b/tools/aapt2/Logger.cpp deleted file mode 100644 index 384718567984..000000000000 --- a/tools/aapt2/Logger.cpp +++ /dev/null @@ -1,97 +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. - */ -#include "Logger.h" -#include "Source.h" - -#include <memory> -#include <iostream> - -namespace aapt { - -Log::Log(std::ostream& _out, std::ostream& _err) : out(_out), err(_err) { -} - -std::shared_ptr<Log> Logger::sLog(std::make_shared<Log>(std::cerr, std::cerr)); - -void Logger::setLog(const std::shared_ptr<Log>& log) { - sLog = log; -} - -std::ostream& Logger::error() { - return sLog->err << "error: "; -} - -std::ostream& Logger::error(const Source& source) { - return sLog->err << source << ": error: "; -} - -std::ostream& Logger::error(const SourceLine& source) { - return sLog->err << source << ": error: "; -} - -std::ostream& Logger::warn() { - return sLog->err << "warning: "; -} - -std::ostream& Logger::warn(const Source& source) { - return sLog->err << source << ": warning: "; -} - -std::ostream& Logger::warn(const SourceLine& source) { - return sLog->err << source << ": warning: "; -} - -std::ostream& Logger::note() { - return sLog->out << "note: "; -} - -std::ostream& Logger::note(const Source& source) { - return sLog->err << source << ": note: "; -} - -std::ostream& Logger::note(const SourceLine& source) { - return sLog->err << source << ": note: "; -} - -SourceLogger::SourceLogger(const Source& source) -: mSource(source) { -} - -std::ostream& SourceLogger::error() { - return Logger::error(mSource); -} - -std::ostream& SourceLogger::error(size_t line) { - return Logger::error(SourceLine{ mSource.path, line }); -} - -std::ostream& SourceLogger::warn() { - return Logger::warn(mSource); -} - -std::ostream& SourceLogger::warn(size_t line) { - return Logger::warn(SourceLine{ mSource.path, line }); -} - -std::ostream& SourceLogger::note() { - return Logger::note(mSource); -} - -std::ostream& SourceLogger::note(size_t line) { - return Logger::note(SourceLine{ mSource.path, line }); -} - -} // namespace aapt diff --git a/tools/aapt2/Logger.h b/tools/aapt2/Logger.h deleted file mode 100644 index 1d437ebe6492..000000000000 --- a/tools/aapt2/Logger.h +++ /dev/null @@ -1,81 +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. - */ - -#ifndef AAPT_LOGGER_H -#define AAPT_LOGGER_H - -#include "Source.h" - -#include <memory> -#include <ostream> -#include <string> -#include <utils/String8.h> - -namespace aapt { - -struct Log { - Log(std::ostream& out, std::ostream& err); - Log(const Log& rhs) = delete; - - std::ostream& out; - std::ostream& err; -}; - -class Logger { -public: - static void setLog(const std::shared_ptr<Log>& log); - - static std::ostream& error(); - static std::ostream& error(const Source& source); - static std::ostream& error(const SourceLine& sourceLine); - - static std::ostream& warn(); - static std::ostream& warn(const Source& source); - static std::ostream& warn(const SourceLine& sourceLine); - - static std::ostream& note(); - static std::ostream& note(const Source& source); - static std::ostream& note(const SourceLine& sourceLine); - -private: - static std::shared_ptr<Log> sLog; -}; - -class SourceLogger { -public: - SourceLogger(const Source& source); - - std::ostream& error(); - std::ostream& error(size_t line); - - std::ostream& warn(); - std::ostream& warn(size_t line); - - std::ostream& note(); - std::ostream& note(size_t line); - -private: - Source mSource; -}; - -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()); -} - -} // namespace aapt - -#endif // AAPT_LOGGER_H diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index 54a7329359f1..a2fadd95db3f 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -14,1262 +14,42 @@ * limitations under the License. */ -#include "AppInfo.h" -#include "BigBuffer.h" -#include "BinaryResourceParser.h" -#include "BindingXmlPullParser.h" -#include "Debug.h" -#include "Files.h" -#include "Flag.h" -#include "JavaClassGenerator.h" -#include "Linker.h" -#include "ManifestMerger.h" -#include "ManifestParser.h" -#include "ManifestValidator.h" -#include "NameMangler.h" -#include "Png.h" -#include "ProguardRules.h" -#include "ResourceParser.h" -#include "ResourceTable.h" -#include "ResourceTableResolver.h" -#include "ResourceValues.h" -#include "SdkConstants.h" -#include "SourceXmlPullParser.h" -#include "StringPiece.h" -#include "TableFlattener.h" -#include "Util.h" -#include "XmlFlattener.h" -#include "ZipFile.h" +#include "util/StringPiece.h" -#include <algorithm> -#include <androidfw/AssetManager.h> -#include <cstdlib> -#include <dirent.h> -#include <errno.h> -#include <fstream> #include <iostream> -#include <sstream> -#include <sys/stat.h> -#include <unordered_set> -#include <utils/Errors.h> +#include <vector> -constexpr const char* kAaptVersionStr = "2.0-alpha"; +namespace aapt { -using 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); -/** - * Used with smart pointers to free malloc'ed memory. - */ -struct DeleteMalloc { - void operator()(void* ptr) { - free(ptr); - } -}; - -struct StaticLibraryData { - Source source; - std::unique_ptr<ZipFile> apk; -}; - -/** - * Collect files from 'root', filtering out any files that do not - * match the FileFilter 'filter'. - */ -bool walkTree(const Source& root, const FileFilter& filter, - std::vector<Source>* outEntries) { - bool error = false; - - for (const std::string& dirName : listFiles(root.path)) { - std::string dir = root.path; - appendPath(&dir, dirName); - - FileType ft = getFileType(dir); - if (!filter(dirName, ft)) { - continue; - } - - if (ft != FileType::kDirectory) { - continue; - } - - for (const std::string& fileName : listFiles(dir)) { - std::string file(dir); - appendPath(&file, fileName); - - FileType ft = getFileType(file); - if (!filter(fileName, ft)) { - continue; - } - - if (ft != FileType::kRegular) { - Logger::error(Source{ file }) << "not a regular file." << std::endl; - error = true; - continue; - } - outEntries->push_back(Source{ file }); - } - } - return !error; -} - -void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) { - for (auto& type : *table) { - if (type->type != ResourceType::kStyle) { - continue; - } - - for (auto& entry : type->entries) { - // Add the versioned styles we want to create - // here. They are added to the table after - // iterating over the original set of styles. - // - // A stack is used since auto-generated styles - // from later versions should override - // auto-generated styles from earlier versions. - // Iterating over the styles is done in order, - // so we will always visit sdkVersions from smallest - // to largest. - std::stack<ResourceConfigValue> addStack; - - for (ResourceConfigValue& configValue : entry->values) { - visitFunc<Style>(*configValue.value, [&](Style& style) { - // Collect which entries we've stripped and the smallest - // SDK level which was stripped. - size_t minSdkStripped = std::numeric_limits<size_t>::max(); - std::vector<Style::Entry> stripped; - - // Iterate over the style's entries and erase/record the - // attributes whose SDK level exceeds the config's sdkVersion. - auto iter = style.entries.begin(); - while (iter != style.entries.end()) { - if (iter->key.name.package == u"android") { - size_t sdkLevel = findAttributeSdkLevel(iter->key.name); - if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) { - // Record that we are about to strip this. - stripped.emplace_back(std::move(*iter)); - minSdkStripped = std::min(minSdkStripped, sdkLevel); - - // Erase this from this style. - iter = style.entries.erase(iter); - continue; - } - } - ++iter; - } - - if (!stripped.empty()) { - // We have stripped attributes, so let's create a new style to hold them. - ConfigDescription versionConfig(configValue.config); - versionConfig.sdkVersion = minSdkStripped; - - ResourceConfigValue value = { - versionConfig, - configValue.source, - {}, - - // Create a copy of the original style. - std::unique_ptr<Value>(configValue.value->clone( - &table->getValueStringPool())) - }; - - Style& newStyle = static_cast<Style&>(*value.value); - - // Move the recorded stripped attributes into this new style. - std::move(stripped.begin(), stripped.end(), - std::back_inserter(newStyle.entries)); - - // We will add this style to the table later. If we do it now, we will - // mess up iteration. - addStack.push(std::move(value)); - } - }); - } - - auto comparator = - [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool { - return lhs.config < rhs; - }; - - while (!addStack.empty()) { - ResourceConfigValue& value = addStack.top(); - auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), - value.config, comparator); - if (iter == entry->values.end() || iter->config != value.config) { - entry->values.insert(iter, std::move(value)); - } - addStack.pop(); - } - } - } -} - -struct CompileItem { - ResourceName name; - ConfigDescription config; - Source source; - std::string extension; -}; - -struct LinkItem { - ResourceName name; - ConfigDescription config; - Source source; - std::string originalPath; - ZipFile* apk; - std::u16string originalPackage; -}; - -template <typename TChar> -static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) { - auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.')); - if (iter == str.end()) { - return BasicStringPiece<TChar>(); - } - size_t offset = (iter - str.begin()) + 1; - return str.substr(offset, str.size() - offset); -} - -std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config, - const StringPiece& extension) { - std::stringstream path; - path << "res/" << name.type; - if (config != ConfigDescription{}) { - path << "-" << config; - } - path << "/" << util::utf16ToUtf8(name.entry); - if (!extension.empty()) { - path << "." << extension; - } - return path.str(); -} - -std::string buildFileReference(const CompileItem& item) { - return buildFileReference(item.name, item.config, item.extension); -} - -std::string buildFileReference(const LinkItem& item) { - return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath)); -} - -template <typename T> -bool addFileReference(const std::shared_ptr<ResourceTable>& table, const T& item) { - StringPool& pool = table->getValueStringPool(); - StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)), - StringPool::Context{ 0, item.config }); - return table->addResource(item.name, item.config, item.source.line(0), - util::make_unique<FileReference>(ref)); -} - -struct AaptOptions { - enum class Phase { - Link, - Compile, - Dump, - DumpStyleGraph, - }; - - enum class PackageType { - StandardApp, - StaticLibrary, - }; - - // The phase to process. - Phase phase; - - // The type of package to produce. - PackageType packageType = PackageType::StandardApp; - - // Details about the app. - AppInfo appInfo; - - // The location of the manifest file. - Source manifest; - - // The APK files to link. - std::vector<Source> input; - - // The libraries these files may reference. - std::vector<Source> libraries; - - // Output path. This can be a directory or file - // depending on the phase. - Source output; - - // Directory in which to write binding xml files. - Source bindingOutput; - - // Directory to in which to generate R.java. - Maybe<Source> generateJavaClass; - - // File in which to produce proguard rules. - Maybe<Source> generateProguardRules; - - // Whether to output verbose details about - // compilation. - bool verbose = false; - - // Whether or not to auto-version styles or layouts - // referencing attributes defined in a newer SDK - // level than the style or layout is defined for. - bool versionStylesAndLayouts = true; - - // The target style that will have it's style hierarchy dumped - // when the phase is DumpStyleGraph. - ResourceName dumpStyleTarget; -}; - -struct IdCollector : public xml::Visitor { - IdCollector(const Source& source, const std::shared_ptr<ResourceTable>& table) : - mSource(source), mTable(table) { - } - - virtual void visit(xml::Text* node) override {} - - virtual void visit(xml::Namespace* node) override { - for (const auto& child : node->children) { - child->accept(this); - } - } - - virtual void visit(xml::Element* node) override { - for (const xml::Attribute& attr : node->attributes) { - bool create = false; - bool priv = false; - ResourceNameRef nameRef; - if (ResourceParser::tryParseReference(attr.value, &nameRef, &create, &priv)) { - if (create) { - mTable->addResource(nameRef, {}, mSource.line(node->lineNumber), - util::make_unique<Id>()); - } - } - } - - for (const auto& child : node->children) { - child->accept(this); - } - } - -private: - Source mSource; - std::shared_ptr<ResourceTable> mTable; -}; - -bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, - const CompileItem& item, ZipFile* outApk) { - std::ifstream in(item.source.path, std::ifstream::binary); - if (!in) { - Logger::error(item.source) << strerror(errno) << std::endl; - return false; - } - - SourceLogger logger(item.source); - std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger); - if (!root) { - return false; - } - - // Collect any resource ID's declared here. - IdCollector idCollector(item.source, table); - root->accept(&idCollector); - - BigBuffer outBuffer(1024); - if (!xml::flatten(root.get(), options.appInfo.package, &outBuffer)) { - logger.error() << "failed to encode XML." << std::endl; - return false; - } - - // Write the resulting compiled XML file to the output APK. - if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored, - nullptr) != android::NO_ERROR) { - Logger::error(options.output) << "failed to write compiled '" << item.source - << "' to apk." << std::endl; - return false; - } - return true; -} - -/** - * Determines if a layout should be auto generated based on SDK level. We do not - * generate a layout if there is already a layout defined whose SDK version is greater than - * the one we want to generate. - */ -bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& table, - const ResourceName& name, const ConfigDescription& config, - int sdkVersionToGenerate) { - assert(sdkVersionToGenerate > config.sdkVersion); - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = table->findResource(name); - assert(type && entry); - - auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config, - [](const ResourceConfigValue& lhs, const ConfigDescription& config) -> bool { - return lhs.config < config; - }); - - assert(iter != entry->values.end()); - ++iter; - - if (iter == entry->values.end()) { - return true; - } - - ConfigDescription newConfig = config; - newConfig.sdkVersion = sdkVersionToGenerate; - return newConfig < iter->config; -} - -bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<IResolver>& resolver, const LinkItem& item, - const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue, - proguard::KeepSet* keepSet) { - SourceLogger logger(item.source); - std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger); - if (!root) { - return false; - } - - xml::FlattenOptions xmlOptions; - if (options.packageType == AaptOptions::PackageType::StaticLibrary) { - xmlOptions.keepRawValues = true; - } - - if (options.versionStylesAndLayouts) { - // We strip attributes that do not belong in this version of the resource. - // Non-version qualified resources have an implicit version 1 requirement. - xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1; - } - - if (options.generateProguardRules) { - proguard::collectProguardRules(item.name.type, item.source, root.get(), keepSet); - } - - BigBuffer outBuffer(1024); - Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(), - item.originalPackage, resolver, - xmlOptions, &outBuffer); - if (!minStrippedSdk) { - logger.error() << "failed to encode XML." << std::endl; - return false; - } - - if (minStrippedSdk.value() > 0) { - // Something was stripped, so let's generate a new file - // with the version of the smallest SDK version stripped. - // We can only generate a versioned layout if there doesn't exist a layout - // with sdk version greater than the current one but less than the one we - // want to generate. - if (shouldGenerateVersionedResource(table, item.name, item.config, - minStrippedSdk.value())) { - LinkItem newWork = item; - newWork.config.sdkVersion = minStrippedSdk.value(); - outQueue->push(newWork); - - if (!addFileReference(table, newWork)) { - Logger::error(options.output) << "failed to add auto-versioned resource '" - << newWork.name << "'." << std::endl; - return false; - } - } - } - - if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated, - nullptr) != android::NO_ERROR) { - Logger::error(options.output) << "failed to write linked file '" - << buildFileReference(item) << "' to apk." << std::endl; - return false; - } - return true; -} - -bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) { - std::ifstream in(item.source.path, std::ifstream::binary); - if (!in) { - Logger::error(item.source) << strerror(errno) << std::endl; - return false; - } - - BigBuffer outBuffer(4096); - std::string err; - Png png; - if (!png.process(item.source, in, &outBuffer, {}, &err)) { - Logger::error(item.source) << err << std::endl; - return false; - } - - if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored, - nullptr) != android::NO_ERROR) { - Logger::error(options.output) << "failed to write compiled '" << item.source - << "' to apk." << std::endl; - return false; - } - return true; -} - -bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) { - if (outApk->add(item.source.path.data(), buildFileReference(item).data(), - ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) { - Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk." - << std::endl; - return false; - } - return true; -} - -bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver, - const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks, - const android::ResTable& table, ZipFile* outApk, proguard::KeepSet* keepSet) { - if (options.verbose) { - Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl; - } - - std::ifstream in(options.manifest.path, std::ifstream::binary); - if (!in) { - Logger::error(options.manifest) << strerror(errno) << std::endl; - return false; - } - - SourceLogger logger(options.manifest); - std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger); - if (!root) { - return false; - } - - ManifestMerger merger({}); - if (!merger.setAppManifest(options.manifest, options.appInfo.package, std::move(root))) { - return false; - } - - for (const auto& entry : libApks) { - ZipFile* libApk = entry.second.apk.get(); - const std::u16string& libPackage = entry.first->getPackage(); - const Source& libSource = entry.second.source; - - ZipEntry* zipEntry = libApk->getEntryByName("AndroidManifest.xml"); - if (!zipEntry) { - continue; - } - - std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>( - libApk->uncompress(zipEntry)); - assert(uncompressedData); - - SourceLogger logger(libSource); - std::unique_ptr<xml::Node> libRoot = xml::inflate(uncompressedData.get(), - zipEntry->getUncompressedLen(), &logger); - if (!libRoot) { - return false; - } - - if (!merger.mergeLibraryManifest(libSource, libPackage, std::move(libRoot))) { - return false; - } - } - - if (options.generateProguardRules) { - proguard::collectProguardRulesForManifest(options.manifest, merger.getMergedXml(), - keepSet); - } - - BigBuffer outBuffer(1024); - if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package, - resolver, {}, &outBuffer)) { - return false; - } - - std::unique_ptr<uint8_t[]> data = util::copy(outBuffer); - - android::ResXMLTree tree; - if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) { - return false; - } - - ManifestValidator validator(table); - if (!validator.validate(options.manifest, &tree)) { - return false; - } - - if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml", - ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) { - Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk." - << std::endl; - return false; - } - return true; -} - -static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source, - const ConfigDescription& config) { - std::ifstream in(source.path, std::ifstream::binary); - if (!in) { - Logger::error(source) << strerror(errno) << std::endl; - return false; - } - - std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in); - ResourceParser parser(table, source, config, xmlParser); - return parser.parse(); -} - -struct ResourcePathData { - std::u16string resourceDir; - std::u16string name; - std::string extension; - ConfigDescription config; -}; - -/** - * Resource file paths are expected to look like: - * [--/res/]type[-config]/name - */ -static Maybe<ResourcePathData> extractResourcePathData(const Source& source) { - // TODO(adamlesinski): Use Windows path separator on windows. - std::vector<std::string> parts = util::splitAndLowercase(source.path, '/'); - if (parts.size() < 2) { - Logger::error(source) << "bad resource path." << std::endl; - return {}; - } - - std::string& dir = parts[parts.size() - 2]; - StringPiece dirStr = dir; - - ConfigDescription config; - size_t dashPos = dir.find('-'); - if (dashPos != std::string::npos) { - StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1)); - if (!ConfigDescription::parse(configStr, &config)) { - Logger::error(source) - << "invalid configuration '" - << configStr - << "'." - << std::endl; - 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{ - util::utf8ToUtf16(dirStr), - util::utf8ToUtf16(name), - extension.toString(), - config - }; -} - -bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, - const TableFlattener::Options& flattenerOptions, ZipFile* outApk) { - if (table->begin() != table->end()) { - BigBuffer buffer(1024); - TableFlattener flattener(flattenerOptions); - if (!flattener.flatten(&buffer, *table)) { - Logger::error() << "failed to flatten resource table." << std::endl; - return false; - } - - if (options.verbose) { - Logger::note() << "Final resource table size=" << util::formatSize(buffer.size()) - << std::endl; - } - - if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) != - android::NO_ERROR) { - Logger::note(options.output) << "failed to store resource table." << std::endl; - return false; - } - } - return true; -} - -/** - * For each FileReference in the table, adds a LinkItem to the link queue for processing. - */ -static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source, - const std::shared_ptr<ResourceTable>& table, - const std::unique_ptr<ZipFile>& apk, - std::queue<LinkItem>* outLinkQueue) { - bool mangle = package != table->getPackage(); - for (auto& type : *table) { - for (auto& entry : type->entries) { - ResourceName name = { package, type->type, entry->name }; - if (mangle) { - NameMangler::mangle(table->getPackage(), &name.entry); - } - - for (auto& value : entry->values) { - visitFunc<FileReference>(*value.value, [&](FileReference& ref) { - std::string pathUtf8 = util::utf16ToUtf8(*ref.path); - Source newSource = source; - newSource.path += "/"; - newSource.path += pathUtf8; - outLinkQueue->push(LinkItem{ - name, value.config, newSource, pathUtf8, apk.get(), - table->getPackage() }); - // Now rewrite the file path. - if (mangle) { - ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16( - buildFileReference(name, value.config, - getExtension<char>(pathUtf8)))); - } - }); - } - } - } -} - -static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate | - ZipFile::kOpenReadWrite; - -bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable, - const std::shared_ptr<IResolver>& resolver) { - std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles; - std::unordered_set<std::u16string> linkedPackages; - - // Populate the linkedPackages with our own. - linkedPackages.insert(options.appInfo.package); - - // Load all APK files. - for (const Source& source : options.input) { - std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>(); - if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) { - Logger::error(source) << "failed to open: " << strerror(errno) << std::endl; - return false; - } - - std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>(); - - ZipEntry* entry = zipFile->getEntryByName("resources.arsc"); - if (!entry) { - Logger::error(source) << "missing 'resources.arsc'." << std::endl; - return false; - } - - std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>( - zipFile->uncompress(entry)); - assert(uncompressedData); - - BinaryResourceParser parser(table, resolver, source, options.appInfo.package, - uncompressedData.get(), entry->getUncompressedLen()); - if (!parser.parse()) { - return false; - } - - // Keep track of where this table came from. - apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) }; - - // Add the package to the set of linked packages. - linkedPackages.insert(table->getPackage()); - } - - std::queue<LinkItem> linkQueue; - for (auto& p : apkFiles) { - const std::shared_ptr<ResourceTable>& inTable = p.first; - - // Collect all FileReferences and add them to the queue for processing. - addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk, - &linkQueue); - - // Merge the tables. - if (!outTable->merge(std::move(*inTable))) { - return false; - } - } - - // Version all styles referencing attributes outside of their specified SDK version. - if (options.versionStylesAndLayouts) { - versionStylesForCompat(outTable); - } - - { - // Now that everything is merged, let's link it. - Linker::Options linkerOptions; - if (options.packageType == AaptOptions::PackageType::StaticLibrary) { - linkerOptions.linkResourceIds = false; - } - Linker linker(outTable, resolver, linkerOptions); - if (!linker.linkAndValidate()) { - return false; - } - - // Verify that all symbols exist. - const auto& unresolvedRefs = linker.getUnresolvedReferences(); - if (!unresolvedRefs.empty()) { - for (const auto& entry : unresolvedRefs) { - for (const auto& source : entry.second) { - Logger::error(source) << "unresolved symbol '" << entry.first << "'." - << std::endl; - } - } - return false; - } - } - - // Open the output APK file for writing. - ZipFile outApk; - if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) { - Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl; - return false; - } - - proguard::KeepSet keepSet; - - android::ResTable binTable; - if (!compileManifest(options, resolver, apkFiles, binTable, &outApk, &keepSet)) { - return false; - } - - for (; !linkQueue.empty(); linkQueue.pop()) { - const LinkItem& item = linkQueue.front(); - - assert(!item.originalPackage.empty()); - ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data()); - if (!entry) { - Logger::error(item.source) << "failed to find '" << item.originalPath << "'." - << std::endl; - return false; - } - - if (util::stringEndsWith<char>(item.originalPath, ".xml")) { - void* uncompressedData = item.apk->uncompress(entry); - assert(uncompressedData); - - if (!linkXml(options, outTable, resolver, item, uncompressedData, - entry->getUncompressedLen(), &outApk, &linkQueue, &keepSet)) { - Logger::error(options.output) << "failed to link '" << item.originalPath << "'." - << std::endl; - return false; - } - } else { - if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) != - android::NO_ERROR) { - Logger::error(options.output) << "failed to copy '" << item.originalPath << "'." - << std::endl; - return false; - } - } - } - - // Generate the Java class file. - if (options.generateJavaClass) { - JavaClassGenerator::Options javaOptions; - if (options.packageType == AaptOptions::PackageType::StaticLibrary) { - javaOptions.useFinal = false; - } - JavaClassGenerator generator(outTable, javaOptions); - - for (const std::u16string& package : linkedPackages) { - Source outPath = options.generateJavaClass.value(); - - // Build the output directory from the package name. - // Eg. com.android.app -> com/android/app - const std::string packageUtf8 = util::utf16ToUtf8(package); - for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) { - appendPath(&outPath.path, part); - } - - if (!mkdirs(outPath.path)) { - Logger::error(outPath) << strerror(errno) << std::endl; - return false; - } - - appendPath(&outPath.path, "R.java"); - - if (options.verbose) { - Logger::note(outPath) << "writing Java symbols." << std::endl; - } - - std::ofstream fout(outPath.path); - if (!fout) { - Logger::error(outPath) << strerror(errno) << std::endl; - return false; - } - - if (!generator.generate(package, fout)) { - Logger::error(outPath) << generator.getError() << "." << std::endl; - return false; - } - } - } - - // Generate the Proguard rules file. - if (options.generateProguardRules) { - const Source& outPath = options.generateProguardRules.value(); - - if (options.verbose) { - Logger::note(outPath) << "writing proguard rules." << std::endl; - } - - std::ofstream fout(outPath.path); - if (!fout) { - Logger::error(outPath) << strerror(errno) << std::endl; - return false; - } - - if (!proguard::writeKeepSet(&fout, keepSet)) { - Logger::error(outPath) << "failed to write proguard rules." << std::endl; - return false; - } - } - - outTable->getValueStringPool().prune(); - outTable->getValueStringPool().sort( - [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - if (a.context.priority < b.context.priority) { - return true; - } - - if (a.context.priority > b.context.priority) { - return false; - } - return a.value < b.value; - }); - - - // Flatten the resource table. - TableFlattener::Options flattenerOptions; - if (options.packageType != AaptOptions::PackageType::StaticLibrary) { - flattenerOptions.useExtendedChunks = false; - } - - if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) { - return false; - } - - outApk.flush(); - return true; -} - -bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<IResolver>& resolver) { - std::queue<CompileItem> compileQueue; - bool error = false; - - // Compile all the resource files passed in on the command line. - for (const Source& source : options.input) { - // Need to parse the resource type/config/filename. - Maybe<ResourcePathData> maybePathData = extractResourcePathData(source); - if (!maybePathData) { - return false; - } - - const ResourcePathData& pathData = maybePathData.value(); - if (pathData.resourceDir == u"values") { - // The file is in the values directory, which means its contents will - // go into the resource table. - if (options.verbose) { - Logger::note(source) << "compiling values." << std::endl; - } - - error |= !compileValues(table, source, pathData.config); - } else { - // The file is in a directory like 'layout' or 'drawable'. Find out - // the type. - const ResourceType* type = parseResourceType(pathData.resourceDir); - if (!type) { - Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'." - << std::endl; - return false; - } - - compileQueue.push(CompileItem{ - ResourceName{ table->getPackage(), *type, pathData.name }, - pathData.config, - source, - pathData.extension - }); - } - } - - if (error) { - return false; - } - // Open the output APK file for writing. - ZipFile outApk; - if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) { - Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl; - return false; - } - - // Compile each file. - for (; !compileQueue.empty(); compileQueue.pop()) { - const CompileItem& item = compileQueue.front(); - - // Add the file name to the resource table. - error |= !addFileReference(table, item); - - if (item.extension == "xml") { - error |= !compileXml(options, table, item, &outApk); - } else if (item.extension == "png" || item.extension == "9.png") { - error |= !compilePng(options, item, &outApk); - } else { - error |= !copyFile(options, item, &outApk); - } - } - - if (error) { - return false; - } - - // Link and assign resource IDs. - Linker linker(table, resolver, {}); - if (!linker.linkAndValidate()) { - return false; - } - - // Flatten the resource table. - if (!writeResourceTable(options, table, {}, &outApk)) { - return false; - } - - outApk.flush(); - return true; -} - -bool loadAppInfo(const Source& source, AppInfo* outInfo) { - std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary); - if (!ifs) { - Logger::error(source) << strerror(errno) << std::endl; - return false; - } - - ManifestParser parser; - std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs); - return parser.parse(source, pullParser, outInfo); -} - -static void printCommandsAndDie() { - std::cerr << "The following commands are supported:" << std::endl << std::endl; - std::cerr << "compile compiles a subset of resources" << std::endl; - std::cerr << "link links together compiled resources and libraries" << std::endl; - std::cerr << "dump dumps resource contents to to standard out" << std::endl; - std::cerr << std::endl; - std::cerr << "run aapt2 with one of the commands and the -h flag for extra details." - << std::endl; - exit(1); -} - -static AaptOptions prepareArgs(int argc, char** argv) { - if (argc < 2) { - std::cerr << "no command specified." << std::endl << std::endl; - printCommandsAndDie(); - } - - const StringPiece command(argv[1]); - argc -= 2; - argv += 2; - - AaptOptions options; - - if (command == "--version" || command == "version") { - std::cout << kAaptVersionStr << std::endl; - exit(0); - } else if (command == "link") { - options.phase = AaptOptions::Phase::Link; - } else if (command == "compile") { - options.phase = AaptOptions::Phase::Compile; - } else if (command == "dump") { - options.phase = AaptOptions::Phase::Dump; - } else if (command == "dump-style-graph") { - options.phase = AaptOptions::Phase::DumpStyleGraph; - } else { - std::cerr << "invalid command '" << command << "'." << std::endl << std::endl; - printCommandsAndDie(); - } - - bool isStaticLib = false; - if (options.phase == AaptOptions::Phase::Link) { - flag::requiredFlag("--manifest", "AndroidManifest.xml of your app", - [&options](const StringPiece& arg) { - options.manifest = Source{ arg.toString() }; - }); - - flag::optionalFlag("-I", "add an Android APK to link against", - [&options](const StringPiece& arg) { - options.libraries.push_back(Source{ arg.toString() }); - }); - - flag::optionalFlag("--java", "directory in which to generate R.java", - [&options](const StringPiece& arg) { - options.generateJavaClass = Source{ arg.toString() }; - }); - - flag::optionalFlag("--proguard", "file in which to output proguard rules", - [&options](const StringPiece& arg) { - options.generateProguardRules = Source{ arg.toString() }; - }); - - flag::optionalSwitch("--static-lib", "generate a static Android library", true, - &isStaticLib); - - flag::optionalFlag("--binding", "Output directory for binding XML files", - [&options](const StringPiece& arg) { - options.bindingOutput = Source{ arg.toString() }; - }); - flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning", - false, &options.versionStylesAndLayouts); - } - - if (options.phase == AaptOptions::Phase::Compile || - options.phase == AaptOptions::Phase::Link) { - // Common flags for all steps. - flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) { - options.output = Source{ arg.toString() }; - }); - } - - if (options.phase == AaptOptions::Phase::DumpStyleGraph) { - flag::requiredFlag("--style", "Name of the style to dump", - [&options](const StringPiece& arg, std::string* outError) -> bool { - Reference styleReference; - if (!ResourceParser::parseStyleParentReference(util::utf8ToUtf16(arg), - &styleReference, outError)) { - return false; - } - options.dumpStyleTarget = styleReference.name; - return true; - }); - } - - bool help = false; - flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose); - flag::optionalSwitch("-h", "displays this help menu", true, &help); - - // Build the command string for output (eg. "aapt2 compile"). - std::string fullCommand = "aapt2"; - fullCommand += " "; - fullCommand += command.toString(); - - // Actually read the command line flags. - flag::parse(argc, argv, fullCommand); - - if (help) { - flag::usageAndDie(fullCommand); - } - - if (isStaticLib) { - options.packageType = AaptOptions::PackageType::StaticLibrary; - } - - // Copy all the remaining arguments. - for (const std::string& arg : flag::getArgs()) { - options.input.push_back(Source{ arg }); - } - return options; -} - -static bool doDump(const AaptOptions& options) { - for (const Source& source : options.input) { - std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>(); - if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) { - Logger::error(source) << "failed to open: " << strerror(errno) << std::endl; - return false; - } - - std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>(); - std::shared_ptr<ResourceTableResolver> resolver = - std::make_shared<ResourceTableResolver>( - table, std::vector<std::shared_ptr<const android::AssetManager>>()); - - ZipEntry* entry = zipFile->getEntryByName("resources.arsc"); - if (!entry) { - Logger::error(source) << "missing 'resources.arsc'." << std::endl; - return false; - } - - std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>( - zipFile->uncompress(entry)); - assert(uncompressedData); - - BinaryResourceParser parser(table, resolver, source, {}, uncompressedData.get(), - entry->getUncompressedLen()); - if (!parser.parse()) { - return false; - } - - if (options.phase == AaptOptions::Phase::Dump) { - Debug::printTable(table); - } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) { - Debug::printStyleGraph(table, options.dumpStyleTarget); - } - } - return true; -} +} // namespace aapt int main(int argc, char** argv) { - Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr)); - AaptOptions options = prepareArgs(argc, argv); + if (argc >= 2) { + argv += 1; + argc -= 1; - if (options.phase == AaptOptions::Phase::Dump || - options.phase == AaptOptions::Phase::DumpStyleGraph) { - if (!doDump(options)) { - return 1; - } - return 0; - } - - // If we specified a manifest, go ahead and load the package name from the manifest. - if (!options.manifest.path.empty()) { - if (!loadAppInfo(options.manifest, &options.appInfo)) { - return false; + std::vector<aapt::StringPiece> args; + for (int i = 1; i < argc; i++) { + args.push_back(argv[i]); } - if (options.appInfo.package.empty()) { - Logger::error() << "no package name specified." << std::endl; - return false; + 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); } - } - - // Every phase needs a resource table. - std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>(); - - // The package name is empty when in the compile phase. - table->setPackage(options.appInfo.package); - if (options.appInfo.package == u"android") { - table->setPackageId(0x01); + std::cerr << "unknown command '" << command << "'\n"; } else { - table->setPackageId(0x7f); - } - - // Load the included libraries. - std::vector<std::shared_ptr<const android::AssetManager>> sources; - for (const Source& source : options.libraries) { - std::shared_ptr<android::AssetManager> assetManager = - std::make_shared<android::AssetManager>(); - int32_t cookie; - if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) { - Logger::error(source) << "failed to load library." << std::endl; - return false; - } - - if (cookie == 0) { - Logger::error(source) << "failed to load library." << std::endl; - return false; - } - sources.push_back(assetManager); + std::cerr << "no command specified\n"; } - // Make the resolver that will cache IDs for us. - std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>( - table, sources); - - if (options.phase == AaptOptions::Phase::Compile) { - if (!compile(options, table, resolver)) { - Logger::error() << "aapt exiting with failures." << std::endl; - return 1; - } - } else if (options.phase == AaptOptions::Phase::Link) { - if (!link(options, table, resolver)) { - Logger::error() << "aapt exiting with failures." << std::endl; - return 1; - } - } - return 0; + std::cerr << "\nusage: aapt2 [compile|link|dump] ..." << std::endl; + return 1; } diff --git a/tools/aapt2/ManifestMerger.cpp b/tools/aapt2/ManifestMerger.cpp deleted file mode 100644 index 71d3424c6ad8..000000000000 --- a/tools/aapt2/ManifestMerger.cpp +++ /dev/null @@ -1,376 +0,0 @@ -#include "ManifestMerger.h" -#include "Maybe.h" -#include "ResourceParser.h" -#include "Source.h" -#include "Util.h" -#include "XmlPullParser.h" - -#include <iostream> -#include <memory> -#include <set> -#include <string> - -namespace aapt { - -constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; - -static xml::Element* findManifest(xml::Node* root) { - if (!root) { - return nullptr; - } - - while (root->type == xml::NodeType::kNamespace) { - if (root->children.empty()) { - break; - } - root = root->children[0].get(); - } - - if (root && root->type == xml::NodeType::kElement) { - xml::Element* el = static_cast<xml::Element*>(root); - if (el->namespaceUri.empty() && el->name == u"manifest") { - return el; - } - } - return nullptr; -} - -static xml::Element* findChildWithSameName(xml::Element* parent, xml::Element* src) { - xml::Attribute* attrKey = src->findAttribute(kSchemaAndroid, u"name"); - if (!attrKey) { - return nullptr; - } - return parent->findChildWithAttribute(src->namespaceUri, src->name, attrKey); -} - -static bool attrLess(const xml::Attribute& lhs, const xml::Attribute& rhs) { - return std::tie(lhs.namespaceUri, lhs.name, lhs.value) - < std::tie(rhs.namespaceUri, rhs.name, rhs.value); -} - -static int compare(xml::Element* lhs, xml::Element* rhs) { - int diff = lhs->attributes.size() - rhs->attributes.size(); - if (diff != 0) { - return diff; - } - - std::set<xml::Attribute, decltype(&attrLess)> lhsAttrs(&attrLess); - lhsAttrs.insert(lhs->attributes.begin(), lhs->attributes.end()); - for (auto& attr : rhs->attributes) { - if (lhsAttrs.erase(attr) == 0) { - // The rhs attribute is not in the left. - return -1; - } - } - - if (!lhsAttrs.empty()) { - // The lhs has attributes not in the rhs. - return 1; - } - return 0; -} - -ManifestMerger::ManifestMerger(const Options& options) : - mOptions(options), mAppLogger({}), mLogger({}) { -} - -bool ManifestMerger::setAppManifest(const Source& source, const std::u16string& package, - std::unique_ptr<xml::Node> root) { - - mAppLogger = SourceLogger{ source }; - mRoot = std::move(root); - return true; -} - -bool ManifestMerger::checkEqual(xml::Element* elA, xml::Element* elB) { - if (compare(elA, elB) != 0) { - mLogger.error(elB->lineNumber) - << "library tag '" << elB->name << "' conflicts with app tag." - << std::endl; - mAppLogger.note(elA->lineNumber) - << "app tag '" << elA->name << "' defined here." - << std::endl; - return false; - } - - std::vector<xml::Element*> childrenA = elA->getChildElements(); - std::vector<xml::Element*> childrenB = elB->getChildElements(); - - if (childrenA.size() != childrenB.size()) { - mLogger.error(elB->lineNumber) - << "library tag '" << elB->name << "' children conflict with app tag." - << std::endl; - mAppLogger.note(elA->lineNumber) - << "app tag '" << elA->name << "' defined here." - << std::endl; - return false; - } - - auto cmp = [](xml::Element* lhs, xml::Element* rhs) -> bool { - return compare(lhs, rhs) < 0; - }; - - std::sort(childrenA.begin(), childrenA.end(), cmp); - std::sort(childrenB.begin(), childrenB.end(), cmp); - - for (size_t i = 0; i < childrenA.size(); i++) { - if (!checkEqual(childrenA[i], childrenB[i])) { - return false; - } - } - return true; -} - -bool ManifestMerger::mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB) { - if (!elA) { - parentA->addChild(elB->clone()); - return true; - } - return checkEqual(elA, elB); -} - -bool ManifestMerger::mergePreferRequired(xml::Element* parentA, xml::Element* elA, - xml::Element* elB) { - if (!elA) { - parentA->addChild(elB->clone()); - return true; - } - - xml::Attribute* reqA = elA->findAttribute(kSchemaAndroid, u"required"); - xml::Attribute* reqB = elB->findAttribute(kSchemaAndroid, u"required"); - bool requiredA = !reqA || (reqA->value != u"false" && reqA->value != u"FALSE"); - bool requiredB = !reqB || (reqB->value != u"false" && reqB->value != u"FALSE"); - if (!requiredA && requiredB) { - if (reqA) { - *reqA = xml::Attribute{ kSchemaAndroid, u"required", u"true" }; - } else { - elA->attributes.push_back(xml::Attribute{ kSchemaAndroid, u"required", u"true" }); - } - } - return true; -} - -static int findIntegerValue(xml::Attribute* attr, int defaultValue) { - if (attr) { - std::unique_ptr<BinaryPrimitive> integer = ResourceParser::tryParseInt(attr->value); - if (integer) { - return integer->value.data; - } - } - return defaultValue; -} - -bool ManifestMerger::mergeUsesSdk(xml::Element* elA, xml::Element* elB) { - bool error = false; - xml::Attribute* minAttrA = nullptr; - xml::Attribute* minAttrB = nullptr; - if (elA) { - minAttrA = elA->findAttribute(kSchemaAndroid, u"minSdkVersion"); - } - - if (elB) { - minAttrB = elB->findAttribute(kSchemaAndroid, u"minSdkVersion"); - } - - int minSdkA = findIntegerValue(minAttrA, 1); - int minSdkB = findIntegerValue(minAttrB, 1); - - if (minSdkA < minSdkB) { - std::ostream* out; - if (minAttrA) { - out = &(mAppLogger.error(elA->lineNumber) << "app declares "); - } else if (elA) { - out = &(mAppLogger.error(elA->lineNumber) << "app has implied "); - } else { - out = &(mAppLogger.error() << "app has implied "); - } - - *out << "minSdkVersion=" << minSdkA << " but library expects a higher SDK version." - << std::endl; - - // elB is valid because minSdkB wouldn't be greater than minSdkA if it wasn't. - mLogger.note(elB->lineNumber) - << "library declares minSdkVersion=" << minSdkB << "." - << std::endl; - error = true; - } - - xml::Attribute* targetAttrA = nullptr; - xml::Attribute* targetAttrB = nullptr; - - if (elA) { - targetAttrA = elA->findAttribute(kSchemaAndroid, u"targetSdkVersion"); - } - - if (elB) { - targetAttrB = elB->findAttribute(kSchemaAndroid, u"targetSdkVersion"); - } - - int targetSdkA = findIntegerValue(targetAttrA, minSdkA); - int targetSdkB = findIntegerValue(targetAttrB, minSdkB); - - if (targetSdkA < targetSdkB) { - std::ostream* out; - if (targetAttrA) { - out = &(mAppLogger.warn(elA->lineNumber) << "app declares "); - } else if (elA) { - out = &(mAppLogger.warn(elA->lineNumber) << "app has implied "); - } else { - out = &(mAppLogger.warn() << "app has implied "); - } - - *out << "targetSdkVerion=" << targetSdkA << " but library expects target SDK " - << targetSdkB << "." << std::endl; - - mLogger.note(elB->lineNumber) - << "library declares targetSdkVersion=" << targetSdkB << "." - << std::endl; - error = true; - } - return !error; -} - -bool ManifestMerger::mergeApplication(xml::Element* applicationA, xml::Element* applicationB) { - if (!applicationA || !applicationB) { - return true; - } - - bool error = false; - - // First make sure that the names are identical. - xml::Attribute* nameA = applicationA->findAttribute(kSchemaAndroid, u"name"); - xml::Attribute* nameB = applicationB->findAttribute(kSchemaAndroid, u"name"); - if (nameB) { - if (!nameA) { - applicationA->attributes.push_back(*nameB); - } else if (nameA->value != nameB->value) { - mLogger.error(applicationB->lineNumber) - << "conflicting application name '" - << nameB->value - << "'." << std::endl; - mAppLogger.note(applicationA->lineNumber) - << "application defines application name '" - << nameA->value - << "'." << std::endl; - error = true; - } - } - - // Now we descend into the activity/receiver/service/provider tags - for (xml::Element* elB : applicationB->getChildElements()) { - if (!elB->namespaceUri.empty()) { - continue; - } - - if (elB->name == u"activity" || elB->name == u"activity-alias" - || elB->name == u"service" || elB->name == u"receiver" - || elB->name == u"provider" || elB->name == u"meta-data") { - xml::Element* elA = findChildWithSameName(applicationA, elB); - error |= !mergeNewOrEqual(applicationA, elA, elB); - } else if (elB->name == u"uses-library") { - xml::Element* elA = findChildWithSameName(applicationA, elB); - error |= !mergePreferRequired(applicationA, elA, elB); - } - } - return !error; -} - -bool ManifestMerger::mergeLibraryManifest(const Source& source, const std::u16string& package, - std::unique_ptr<xml::Node> libRoot) { - mLogger = SourceLogger{ source }; - xml::Element* manifestA = findManifest(mRoot.get()); - xml::Element* manifestB = findManifest(libRoot.get()); - if (!manifestA) { - mAppLogger.error() << "missing manifest tag." << std::endl; - return false; - } - - if (!manifestB) { - mLogger.error() << "library missing manifest tag." << std::endl; - return false; - } - - bool error = false; - - // Do <application> first. - xml::Element* applicationA = manifestA->findChild({}, u"application"); - xml::Element* applicationB = manifestB->findChild({}, u"application"); - error |= !mergeApplication(applicationA, applicationB); - - // Do <uses-sdk> next. - xml::Element* usesSdkA = manifestA->findChild({}, u"uses-sdk"); - xml::Element* usesSdkB = manifestB->findChild({}, u"uses-sdk"); - error |= !mergeUsesSdk(usesSdkA, usesSdkB); - - for (xml::Element* elB : manifestB->getChildElements()) { - if (!elB->namespaceUri.empty()) { - continue; - } - - if (elB->name == u"uses-permission" || elB->name == u"permission" - || elB->name == u"permission-group" || elB->name == u"permission-tree") { - xml::Element* elA = findChildWithSameName(manifestA, elB); - error |= !mergeNewOrEqual(manifestA, elA, elB); - } else if (elB->name == u"uses-feature") { - xml::Element* elA = findChildWithSameName(manifestA, elB); - error |= !mergePreferRequired(manifestA, elA, elB); - } else if (elB->name == u"uses-configuration" || elB->name == u"supports-screen" - || elB->name == u"compatible-screens" || elB->name == u"supports-gl-texture") { - xml::Element* elA = findChildWithSameName(manifestA, elB); - error |= !checkEqual(elA, elB); - } - } - return !error; -} - -static void printMerged(xml::Node* node, int depth) { - std::string indent; - for (int i = 0; i < depth; i++) { - indent += " "; - } - - switch (node->type) { - case xml::NodeType::kNamespace: - std::cerr << indent << "N: " - << "xmlns:" << static_cast<xml::Namespace*>(node)->namespacePrefix - << "=\"" << static_cast<xml::Namespace*>(node)->namespaceUri - << "\"\n"; - break; - - case xml::NodeType::kElement: - std::cerr << indent << "E: " - << static_cast<xml::Element*>(node)->namespaceUri - << ":" << static_cast<xml::Element*>(node)->name - << "\n"; - for (const auto& attr : static_cast<xml::Element*>(node)->attributes) { - std::cerr << indent << " A: " - << attr.namespaceUri - << ":" << attr.name - << "=\"" << attr.value << "\"\n"; - } - break; - - case xml::NodeType::kText: - std::cerr << indent << "T: \"" << static_cast<xml::Text*>(node)->text << "\"\n"; - break; - } - - for (auto& child : node->children) { - printMerged(child.get(), depth + 1); - } -} - -xml::Node* ManifestMerger::getMergedXml() { - return mRoot.get(); -} - -bool ManifestMerger::printMerged() { - if (!mRoot) { - return false; - } - - ::aapt::printMerged(mRoot.get(), 0); - return true; -} - -} // namespace aapt diff --git a/tools/aapt2/ManifestMerger.h b/tools/aapt2/ManifestMerger.h deleted file mode 100644 index c6219dbba65e..000000000000 --- a/tools/aapt2/ManifestMerger.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef AAPT_MANIFEST_MERGER_H -#define AAPT_MANIFEST_MERGER_H - -#include "Logger.h" -#include "Source.h" -#include "XmlDom.h" - -#include <memory> -#include <string> - -namespace aapt { - -class ManifestMerger { -public: - struct Options { - }; - - ManifestMerger(const Options& options); - - bool setAppManifest(const Source& source, const std::u16string& package, - std::unique_ptr<xml::Node> root); - - bool mergeLibraryManifest(const Source& source, const std::u16string& package, - std::unique_ptr<xml::Node> libRoot); - - xml::Node* getMergedXml(); - - bool printMerged(); - -private: - bool mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB); - bool mergePreferRequired(xml::Element* parentA, xml::Element* elA, xml::Element* elB); - bool checkEqual(xml::Element* elA, xml::Element* elB); - bool mergeApplication(xml::Element* applicationA, xml::Element* applicationB); - bool mergeUsesSdk(xml::Element* elA, xml::Element* elB); - - Options mOptions; - std::unique_ptr<xml::Node> mRoot; - SourceLogger mAppLogger; - SourceLogger mLogger; -}; - -} // namespace aapt - -#endif // AAPT_MANIFEST_MERGER_H diff --git a/tools/aapt2/ManifestMerger_test.cpp b/tools/aapt2/ManifestMerger_test.cpp deleted file mode 100644 index 6838253dad20..000000000000 --- a/tools/aapt2/ManifestMerger_test.cpp +++ /dev/null @@ -1,121 +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. - */ - -#include "ManifestMerger.h" -#include "SourceXmlPullParser.h" - -#include <gtest/gtest.h> -#include <sstream> -#include <string> - -namespace aapt { - -constexpr const char* kAppManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android"> - <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" /> - <uses-permission android:name="android.permission.INTERNET"/> - <uses-feature android:name="android.hardware.GPS" android:required="false" /> - <application android:name="com.android.library.Application"> - <activity android:name="com.android.example.MainActivity"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.DEFAULT" /> - </intent-filter> - </activity> - <service android:name="com.android.library.Service"> - <intent-filter> - <action android:name="com.android.library.intent.action.SYNC" /> - </intent-filter> - </service> - </application> -</manifest> -)EOF"; - -constexpr const char* kLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android"> - <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="21" /> - <uses-permission android:name="android.permission.INTERNET" /> - <uses-feature android:name="android.hardware.GPS" /> - <uses-permission android:name="android.permission.GPS" /> - <application android:name="com.android.library.Application"> - <service android:name="com.android.library.Service"> - <intent-filter> - <action android:name="com.android.library.intent.action.SYNC" /> - </intent-filter> - </service> - <provider android:name="com.android.library.DocumentProvider" - android:authorities="com.android.library.documents" - android:grantUriPermission="true" - android:exported="true" - android:permission="android.permission.MANAGE_DOCUMENTS" - android:enabled="@bool/atLeastKitKat"> - <intent-filter> - <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> - </intent-filter> - </provider> - </application> -</manifest> -)EOF"; - -constexpr const char* kBadLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android"> - <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="22" /> - <uses-permission android:name="android.permission.INTERNET" /> - <uses-feature android:name="android.hardware.GPS" /> - <uses-permission android:name="android.permission.GPS" /> - <application android:name="com.android.library.Application2"> - <service android:name="com.android.library.Service"> - <intent-filter> - <action android:name="com.android.library.intent.action.SYNC_ACTION" /> - </intent-filter> - </service> - </application> -</manifest> -)EOF"; - -TEST(ManifestMergerTest, MergeManifestsSuccess) { - std::stringstream inA(kAppManifest); - std::stringstream inB(kLibManifest); - - const Source sourceA = { "AndroidManifest.xml" }; - const Source sourceB = { "lib.apk/AndroidManifest.xml" }; - SourceLogger loggerA(sourceA); - SourceLogger loggerB(sourceB); - - ManifestMerger merger({}); - EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example", - xml::inflate(&inA, &loggerA))); - EXPECT_TRUE(merger.mergeLibraryManifest(sourceB, u"com.android.library", - xml::inflate(&inB, &loggerB))); -} - -TEST(ManifestMergerTest, MergeManifestFail) { - std::stringstream inA(kAppManifest); - std::stringstream inB(kBadLibManifest); - - const Source sourceA = { "AndroidManifest.xml" }; - const Source sourceB = { "lib.apk/AndroidManifest.xml" }; - SourceLogger loggerA(sourceA); - SourceLogger loggerB(sourceB); - - ManifestMerger merger({}); - EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example", - xml::inflate(&inA, &loggerA))); - EXPECT_FALSE(merger.mergeLibraryManifest(sourceB, u"com.android.library", - xml::inflate(&inB, &loggerB))); -} - -} // namespace aapt diff --git a/tools/aapt2/ManifestParser.cpp b/tools/aapt2/ManifestParser.cpp deleted file mode 100644 index b8f0a430bcee..000000000000 --- a/tools/aapt2/ManifestParser.cpp +++ /dev/null @@ -1,84 +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. - */ - -#include "AppInfo.h" -#include "Logger.h" -#include "ManifestParser.h" -#include "Source.h" -#include "XmlPullParser.h" - -#include <string> - -namespace aapt { - -bool ManifestParser::parse(const Source& source, std::shared_ptr<XmlPullParser> parser, - AppInfo* outInfo) { - SourceLogger logger = { source }; - - int depth = 0; - while (XmlPullParser::isGoodEvent(parser->next())) { - XmlPullParser::Event event = parser->getEvent(); - if (event == XmlPullParser::Event::kEndElement) { - depth--; - continue; - } else if (event != XmlPullParser::Event::kStartElement) { - continue; - } - - depth++; - - const std::u16string& element = parser->getElementName(); - if (depth == 1) { - if (element == u"manifest") { - if (!parseManifest(logger, parser, outInfo)) { - return false; - } - } else { - logger.error() - << "unexpected top-level element '" - << element - << "'." - << std::endl; - return false; - } - } else { - XmlPullParser::skipCurrentElement(parser.get()); - } - } - - if (parser->getEvent() == XmlPullParser::Event::kBadDocument) { - logger.error(parser->getLineNumber()) - << "failed to parse manifest: " - << parser->getLastError() - << "." - << std::endl; - return false; - } - return true; -} - -bool ManifestParser::parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser, - AppInfo* outInfo) { - auto attrIter = parser->findAttribute(u"", u"package"); - if (attrIter == parser->endAttributes() || attrIter->value.empty()) { - logger.error() << "no 'package' attribute found for element <manifest>." << std::endl; - return false; - } - outInfo->package = attrIter->value; - return true; -} - -} // namespace aapt diff --git a/tools/aapt2/ManifestParser_test.cpp b/tools/aapt2/ManifestParser_test.cpp deleted file mode 100644 index be3a6fbe614a..000000000000 --- a/tools/aapt2/ManifestParser_test.cpp +++ /dev/null @@ -1,42 +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. - */ - -#include "AppInfo.h" -#include "ManifestParser.h" -#include "SourceXmlPullParser.h" - -#include <gtest/gtest.h> -#include <sstream> -#include <string> - -namespace aapt { - -TEST(ManifestParserTest, FindPackage) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" - "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" - "package=\"android\">\n" - "</manifest>\n"; - - ManifestParser parser; - AppInfo info; - std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(input); - ASSERT_TRUE(parser.parse(Source{ "AndroidManifest.xml" }, xmlParser, &info)); - - EXPECT_EQ(std::u16string(u"android"), info.package); -} - -} // namespace aapt diff --git a/tools/aapt2/ManifestValidator.cpp b/tools/aapt2/ManifestValidator.cpp deleted file mode 100644 index 123b9fae2fba..000000000000 --- a/tools/aapt2/ManifestValidator.cpp +++ /dev/null @@ -1,217 +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. - */ - -#include "Logger.h" -#include "ManifestValidator.h" -#include "Maybe.h" -#include "Source.h" -#include "Util.h" - -#include <androidfw/ResourceTypes.h> - -namespace aapt { - -ManifestValidator::ManifestValidator(const android::ResTable& table) -: mTable(table) { -} - -bool ManifestValidator::validate(const Source& source, android::ResXMLParser* parser) { - SourceLogger logger(source); - - android::ResXMLParser::event_code_t code; - while ((code = parser->next()) != android::ResXMLParser::END_DOCUMENT && - code != android::ResXMLParser::BAD_DOCUMENT) { - if (code != android::ResXMLParser::START_TAG) { - continue; - } - - size_t len = 0; - const StringPiece16 namespaceUri(parser->getElementNamespace(&len), len); - if (!namespaceUri.empty()) { - continue; - } - - const StringPiece16 name(parser->getElementName(&len), len); - if (name.empty()) { - logger.error(parser->getLineNumber()) - << "failed to get the element name." - << std::endl; - return false; - } - - if (name == u"manifest") { - if (!validateManifest(source, parser)) { - return false; - } - } - } - return true; -} - -Maybe<StringPiece16> ManifestValidator::getAttributeValue(android::ResXMLParser* parser, - size_t idx) { - android::Res_value value; - if (parser->getAttributeValue(idx, &value) < 0) { - return StringPiece16(); - } - - const android::ResStringPool* pool = &parser->getStrings(); - if (value.dataType == android::Res_value::TYPE_REFERENCE) { - ssize_t strIdx = mTable.resolveReference(&value, 0x10000000u); - if (strIdx < 0) { - return {}; - } - pool = mTable.getTableStringBlock(strIdx); - } - - if (value.dataType != android::Res_value::TYPE_STRING || !pool) { - return {}; - } - return util::getString(*pool, value.data); -} - -Maybe<StringPiece16> ManifestValidator::getAttributeInlineValue(android::ResXMLParser* parser, - size_t idx) { - android::Res_value value; - if (parser->getAttributeValue(idx, &value) < 0) { - return StringPiece16(); - } - - if (value.dataType != android::Res_value::TYPE_STRING) { - return {}; - } - return util::getString(parser->getStrings(), value.data); -} - -bool ManifestValidator::validateInlineAttribute(android::ResXMLParser* parser, size_t idx, - SourceLogger& logger, - const StringPiece16& charSet) { - size_t len = 0; - StringPiece16 element(parser->getElementName(&len), len); - StringPiece16 attributeName(parser->getAttributeName(idx, &len), len); - Maybe<StringPiece16> result = getAttributeInlineValue(parser, idx); - if (!result) { - logger.error(parser->getLineNumber()) - << "<" - << element - << "> must have a '" - << attributeName - << "' attribute with a string literal value." - << std::endl; - return false; - } - return validateAttributeImpl(element, attributeName, result.value(), charSet, - parser->getLineNumber(), logger); -} - -bool ManifestValidator::validateAttribute(android::ResXMLParser* parser, size_t idx, - SourceLogger& logger, const StringPiece16& charSet) { - size_t len = 0; - StringPiece16 element(parser->getElementName(&len), len); - StringPiece16 attributeName(parser->getAttributeName(idx, &len), len); - Maybe<StringPiece16> result = getAttributeValue(parser, idx); - if (!result) { - logger.error(parser->getLineNumber()) - << "<" - << element - << "> must have a '" - << attributeName - << "' attribute that points to a string." - << std::endl; - return false; - } - return validateAttributeImpl(element, attributeName, result.value(), charSet, - parser->getLineNumber(), logger); -} - -bool ManifestValidator::validateAttributeImpl(const StringPiece16& element, - const StringPiece16& attributeName, - const StringPiece16& attributeValue, - const StringPiece16& charSet, size_t lineNumber, - SourceLogger& logger) { - StringPiece16::const_iterator badIter = - util::findNonAlphaNumericAndNotInSet(attributeValue, charSet); - if (badIter != attributeValue.end()) { - logger.error(lineNumber) - << "tag <" - << element - << "> attribute '" - << attributeName - << "' has invalid character '" - << StringPiece16(badIter, 1) - << "'." - << std::endl; - return false; - } - - if (!attributeValue.empty()) { - StringPiece16 trimmed = util::trimWhitespace(attributeValue); - if (attributeValue.begin() != trimmed.begin()) { - logger.error(lineNumber) - << "tag <" - << element - << "> attribute '" - << attributeName - << "' can not start with whitespace." - << std::endl; - return false; - } - - if (attributeValue.end() != trimmed.end()) { - logger.error(lineNumber) - << "tag <" - << element - << "> attribute '" - << attributeName - << "' can not end with whitespace." - << std::endl; - return false; - } - } - return true; -} - -constexpr const char16_t* kPackageIdentSet = u"._"; - -bool ManifestValidator::validateManifest(const Source& source, android::ResXMLParser* parser) { - bool error = false; - SourceLogger logger(source); - - const StringPiece16 kAndroid = u"android"; - const StringPiece16 kPackage = u"package"; - const StringPiece16 kSharedUserId = u"sharedUserId"; - - ssize_t idx; - - idx = parser->indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()); - if (idx < 0) { - logger.error(parser->getLineNumber()) - << "missing package attribute." - << std::endl; - error = true; - } else { - error |= !validateInlineAttribute(parser, idx, logger, kPackageIdentSet); - } - - idx = parser->indexOfAttribute(kAndroid.data(), kAndroid.size(), - kSharedUserId.data(), kSharedUserId.size()); - if (idx >= 0) { - error |= !validateInlineAttribute(parser, idx, logger, kPackageIdentSet); - } - return !error; -} - -} // namespace aapt diff --git a/tools/aapt2/ManifestValidator.h b/tools/aapt2/ManifestValidator.h deleted file mode 100644 index 318878499cfa..000000000000 --- a/tools/aapt2/ManifestValidator.h +++ /dev/null @@ -1,55 +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. - */ - -#ifndef AAPT_MANIFEST_VALIDATOR_H -#define AAPT_MANIFEST_VALIDATOR_H - -#include "Logger.h" -#include "Maybe.h" -#include "Source.h" -#include "StringPiece.h" - -#include <androidfw/ResourceTypes.h> - -namespace aapt { - -class ManifestValidator { -public: - ManifestValidator(const android::ResTable& table); - ManifestValidator(const ManifestValidator&) = delete; - - bool validate(const Source& source, android::ResXMLParser* parser); - -private: - bool validateManifest(const Source& source, android::ResXMLParser* parser); - - Maybe<StringPiece16> getAttributeInlineValue(android::ResXMLParser* parser, size_t idx); - Maybe<StringPiece16> getAttributeValue(android::ResXMLParser* parser, size_t idx); - - bool validateInlineAttribute(android::ResXMLParser* parser, size_t idx, - SourceLogger& logger, const StringPiece16& charSet); - bool validateAttribute(android::ResXMLParser* parser, size_t idx, SourceLogger& logger, - const StringPiece16& charSet); - bool validateAttributeImpl(const StringPiece16& element, const StringPiece16& attributeName, - const StringPiece16& attributeValue, const StringPiece16& charSet, - size_t lineNumber, SourceLogger& logger); - - const android::ResTable& mTable; -}; - -} // namespace aapt - -#endif // AAPT_MANIFEST_VALIDATOR_H diff --git a/tools/aapt2/MockResolver.h b/tools/aapt2/MockResolver.h deleted file mode 100644 index 0c9b95464186..000000000000 --- a/tools/aapt2/MockResolver.h +++ /dev/null @@ -1,93 +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. - */ - -#ifndef AAPT_MOCK_RESOLVER_H -#define AAPT_MOCK_RESOLVER_H - -#include "Maybe.h" -#include "Resolver.h" -#include "Resource.h" -#include "ResourceTable.h" -#include "ResourceTableResolver.h" -#include "ResourceValues.h" -#include "StringPiece.h" - -#include <map> -#include <string> - -namespace aapt { - -struct MockResolver : public IResolver { - MockResolver(const std::shared_ptr<ResourceTable>& table, - const std::map<ResourceName, ResourceId>& items) : - mResolver(std::make_shared<ResourceTableResolver>( - table, std::vector<std::shared_ptr<const android::AssetManager>>())), - mAttr(false, android::ResTable_map::TYPE_ANY), mItems(items) { - } - - virtual Maybe<ResourceId> findId(const ResourceName& name) override { - Maybe<ResourceId> result = mResolver->findId(name); - if (result) { - return result; - } - - const auto iter = mItems.find(name); - if (iter != mItems.end()) { - return iter->second; - } - return {}; - } - - virtual Maybe<Entry> findAttribute(const ResourceName& name) override { - Maybe<Entry> tableResult = mResolver->findAttribute(name); - if (tableResult) { - return tableResult; - } - - Maybe<ResourceId> result = findId(name); - if (result) { - if (name.type == ResourceType::kAttr) { - return Entry{ result.value(), &mAttr }; - } else { - return Entry{ result.value() }; - } - } - return {}; - } - - virtual Maybe<ResourceName> findName(ResourceId resId) override { - Maybe<ResourceName> result = mResolver->findName(resId); - if (result) { - return result; - } - - for (auto& p : mItems) { - if (p.second == resId) { - return p.first; - } - } - return {}; - } - -private: - std::shared_ptr<ResourceTableResolver> mResolver; - Attribute mAttr; - std::map<ResourceName, ResourceId> mItems; -}; - -} // namespace aapt - -#endif // AAPT_MOCK_RESOLVER_H diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h index 1e15e2071e65..054b9ee116f4 100644 --- a/tools/aapt2/NameMangler.h +++ b/tools/aapt2/NameMangler.h @@ -17,19 +17,63 @@ #ifndef AAPT_NAME_MANGLER_H #define AAPT_NAME_MANGLER_H +#include "Resource.h" + +#include "util/Maybe.h" + +#include <set> #include <string> namespace aapt { -struct NameMangler { +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; +}; + +class NameMangler { +private: + NameManglerPolicy mPolicy; + +public: + NameMangler(NameManglerPolicy policy) : mPolicy(policy) { + } + + 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) + }; + } + + bool shouldMangle(const std::u16string& package) const { + if (package.empty() || mPolicy.targetPackageName == package) { + return false; + } + return mPolicy.packagesToMangle.count(package) != 0; + } + /** - * Mangles the name in `outName` with the `package` and stores the mangled - * result in `outName`. The mangled name should contain symbols that are - * illegal to define in XML, so that there will never be name mangling - * collisions. + * 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 void mangle(const std::u16string& package, std::u16string* outName) { - *outName = package + u"$" + *outName; + static std::u16string mangleEntry(const std::u16string& package, const std::u16string& name) { + return package + u"$" + name; } /** diff --git a/tools/aapt2/Resolver.h b/tools/aapt2/Resolver.h deleted file mode 100644 index cb9318e24917..000000000000 --- a/tools/aapt2/Resolver.h +++ /dev/null @@ -1,75 +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. - */ - -#ifndef AAPT_RESOLVER_H -#define AAPT_RESOLVER_H - -#include "Maybe.h" -#include "Resource.h" -#include "ResourceValues.h" - -#include <androidfw/ResourceTypes.h> - -namespace aapt { - -/** - * Resolves symbolic references (package:type/entry) into resource IDs/objects. - */ -class IResolver { -public: - virtual ~IResolver() {}; - - /** - * Holds the result of a resource name lookup. - */ - struct Entry { - /** - * The ID of the resource. ResourceId::isValid() may - * return false if the resource has not been assigned - * an ID. - */ - ResourceId id; - - /** - * If the resource is an attribute, this will point - * to a valid Attribute object, or else it will be - * nullptr. - */ - const Attribute* attr; - }; - - /** - * Returns a ResourceID if the name is found. The ResourceID - * may not be valid if the resource was not assigned an ID. - */ - virtual Maybe<ResourceId> findId(const ResourceName& name) = 0; - - /** - * Returns an Entry if the name is found. Entry::attr - * may be nullptr if the resource is not an attribute. - */ - virtual Maybe<Entry> findAttribute(const ResourceName& name) = 0; - - /** - * Find a resource by ID. Resolvers may contain resources without - * resource IDs assigned to them. - */ - virtual Maybe<ResourceName> findName(ResourceId resId) = 0; -}; - -} // namespace aapt - -#endif // AAPT_RESOLVER_H diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp index 287d8de1b767..9328b697719d 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -15,7 +15,7 @@ */ #include "Resource.h" -#include "StringPiece.h" +#include "util/StringPiece.h" #include <map> #include <string> @@ -28,7 +28,7 @@ StringPiece16 toString(ResourceType type) { case ResourceType::kAnimator: return u"animator"; case ResourceType::kArray: return u"array"; case ResourceType::kAttr: return u"attr"; - case ResourceType::kAttrPrivate: 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"; @@ -36,7 +36,6 @@ StringPiece16 toString(ResourceType type) { case ResourceType::kFraction: return u"fraction"; case ResourceType::kId: return u"id"; case ResourceType::kInteger: return u"integer"; - case ResourceType::kIntegerArray: return u"integer-array"; case ResourceType::kInterpolator: return u"interpolator"; case ResourceType::kLayout: return u"layout"; case ResourceType::kMenu: return u"menu"; @@ -65,7 +64,6 @@ static const std::map<StringPiece16, ResourceType> sResourceTypeMap { { u"fraction", ResourceType::kFraction }, { u"id", ResourceType::kId }, { u"integer", ResourceType::kInteger }, - { u"integer-array", ResourceType::kIntegerArray }, { u"interpolator", ResourceType::kInterpolator }, { u"layout", ResourceType::kLayout }, { u"menu", ResourceType::kMenu }, @@ -87,4 +85,12 @@ const ResourceType* parseResourceType(const StringPiece16& str) { return &iter->second; } +bool operator<(const ResourceKey& a, const ResourceKey& b) { + 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); +} + } // namespace aapt diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index fa9ac07b1779..03ca42b286d6 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -17,12 +17,16 @@ #ifndef AAPT_RESOURCE_H #define AAPT_RESOURCE_H -#include "StringPiece.h" +#include "ConfigDescription.h" +#include "Source.h" + +#include "util/StringPiece.h" #include <iomanip> #include <limits> #include <string> #include <tuple> +#include <vector> namespace aapt { @@ -43,7 +47,6 @@ enum class ResourceType { kFraction, kId, kInteger, - kIntegerArray, kInterpolator, kLayout, kMenu, @@ -74,10 +77,11 @@ struct ResourceName { ResourceType type; std::u16string entry; + ResourceName() : type(ResourceType::kRaw) {} + ResourceName(const StringPiece16& p, ResourceType t, const StringPiece16& e); + bool isValid() const; - bool operator<(const ResourceName& rhs) const; - bool operator==(const ResourceName& rhs) const; - bool operator!=(const ResourceName& rhs) const; + std::u16string toString() const; }; /** @@ -102,10 +106,6 @@ struct ResourceNameRef { ResourceName toResourceName() const; bool isValid() const; - - bool operator<(const ResourceNameRef& rhs) const; - bool operator==(const ResourceNameRef& rhs) const; - bool operator!=(const ResourceNameRef& rhs) const; }; /** @@ -125,16 +125,63 @@ struct ResourceId { ResourceId(); ResourceId(const ResourceId& rhs); ResourceId(uint32_t resId); - ResourceId(size_t p, size_t t, size_t e); + 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 operator<(const ResourceId& rhs) const; - bool operator==(const ResourceId& rhs) const; }; +struct SourcedResourceName { + ResourceName name; + size_t line; +}; + +struct ResourceFile { + // Name + ResourceName name; + + // Configuration + ConfigDescription config; + + // Source + Source source; + + // Exported symbols + std::vector<SourcedResourceName> exportedSymbols; +}; + +/** + * Useful struct used as a key to represent a unique resource in associative containers. + */ +struct ResourceKey { + 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. + * Holds a reference to the name, so that name better live longer than this key! + */ +struct ResourceKeyRef { + ResourceNameRef name; + ConfigDescription config; + + 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; +}; + +bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b); + // // ResourceId implementation. // @@ -148,17 +195,7 @@ inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) { inline ResourceId::ResourceId(uint32_t resId) : id(resId) { } -inline ResourceId::ResourceId(size_t p, size_t t, size_t e) : id(0) { - if (p > std::numeric_limits<uint8_t>::max() || - t > std::numeric_limits<uint8_t>::max() || - e > std::numeric_limits<uint16_t>::max()) { - // This will leave the ResourceId in an invalid state. - return; - } - - id = (static_cast<uint8_t>(p) << 24) | - (static_cast<uint8_t>(t) << 16) | - static_cast<uint16_t>(e); +inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e) : id((p << 24) | (t << 16) | e) { } inline bool ResourceId::isValid() const { @@ -177,16 +214,23 @@ inline uint16_t ResourceId::entryId() const { return static_cast<uint16_t>(id); } -inline bool ResourceId::operator<(const ResourceId& rhs) const { - return id < rhs.id; +inline bool operator<(const ResourceId& lhs, const ResourceId& rhs) { + return lhs.id < rhs.id; } -inline bool ResourceId::operator==(const ResourceId& rhs) const { - return id == rhs.id; +inline bool operator>(const ResourceId& lhs, const ResourceId& rhs) { + return lhs.id > rhs.id; } -inline ::std::ostream& operator<<(::std::ostream& out, - const ResourceId& resId) { +inline bool operator==(const ResourceId& lhs, const ResourceId& rhs) { + return lhs.id == rhs.id; +} + +inline bool operator!=(const ResourceId& lhs, const ResourceId& rhs) { + 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) @@ -208,25 +252,37 @@ inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) // ResourceName implementation. // +inline ResourceName::ResourceName(const StringPiece16& p, ResourceType t, const StringPiece16& e) : + package(p.toString()), type(t), entry(e.toString()) { +} + inline bool ResourceName::isValid() const { return !package.empty() && !entry.empty(); } -inline bool ResourceName::operator<(const ResourceName& rhs) const { - return std::tie(package, type, 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); } -inline bool ResourceName::operator==(const ResourceName& rhs) const { - return std::tie(package, type, 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); } -inline bool ResourceName::operator!=(const ResourceName& rhs) const { - return std::tie(package, type, 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); } +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 << ":"; @@ -263,18 +319,18 @@ inline bool ResourceNameRef::isValid() const { return !package.empty() && !entry.empty(); } -inline bool ResourceNameRef::operator<(const ResourceNameRef& rhs) const { - return std::tie(package, type, 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); } -inline bool ResourceNameRef::operator==(const ResourceNameRef& rhs) const { - return std::tie(package, type, 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); } -inline bool ResourceNameRef::operator!=(const ResourceNameRef& rhs) const { - return std::tie(package, type, 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); } @@ -285,6 +341,18 @@ inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNameRef& na return out << name.type << "/" << name.entry; } +inline bool operator<(const ResourceName& lhs, const ResourceNameRef& b) { + return ResourceNameRef(lhs) < b; +} + +inline bool operator!=(const ResourceName& lhs, const ResourceNameRef& rhs) { + return ResourceNameRef(lhs) != rhs; +} + +inline bool operator==(const SourcedResourceName& lhs, const SourcedResourceName& rhs) { + return lhs.name == rhs.name && lhs.line == rhs.line; +} + } // namespace aapt #endif // AAPT_RESOURCE_H diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 13f916bfc8f3..9704d97029b7 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -14,489 +14,139 @@ * limitations under the License. */ -#include "Logger.h" #include "ResourceParser.h" +#include "ResourceTable.h" +#include "ResourceUtils.h" #include "ResourceValues.h" -#include "ScopedXmlPullParser.h" -#include "SourceXmlPullParser.h" -#include "Util.h" -#include "XliffXmlPullParser.h" +#include "ValueVisitor.h" +#include "util/ImmutableMap.h" +#include "util/Util.h" +#include "xml/XmlPullParser.h" +#include <functional> #include <sstream> namespace aapt { -void ResourceParser::extractResourceName(const StringPiece16& str, StringPiece16* outPackage, - StringPiece16* outType, StringPiece16* outEntry) { - 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'/') { - outType->assign(start, current - start); - start = current + 1; - } else if (outPackage->size() == 0 && *current == u':') { - outPackage->assign(start, current - start); - start = current + 1; - } - current++; - } - outEntry->assign(start, end - start); -} - -bool ResourceParser::tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, - bool* outCreate, bool* outPrivate) { - StringPiece16 trimmedStr(util::trimWhitespace(str)); - if (trimmedStr.empty()) { - return false; - } - - if (trimmedStr.data()[0] == u'@') { - size_t offset = 1; - *outCreate = false; - if (trimmedStr.data()[1] == u'+') { - *outCreate = true; - offset += 1; - } else if (trimmedStr.data()[1] == u'*') { - *outPrivate = true; - offset += 1; - } - StringPiece16 package; - StringPiece16 type; - StringPiece16 entry; - extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), - &package, &type, &entry); - - const ResourceType* parsedType = parseResourceType(type); - if (!parsedType) { - return false; - } - - if (*outCreate && *parsedType != ResourceType::kId) { - return false; - } - - outRef->package = package; - outRef->type = *parsedType; - outRef->entry = entry; - return true; - } - return false; -} - -bool ResourceParser::tryParseAttributeReference(const StringPiece16& str, - ResourceNameRef* outRef) { - StringPiece16 trimmedStr(util::trimWhitespace(str)); - if (trimmedStr.empty()) { - return false; - } - - if (*trimmedStr.data() == u'?') { - StringPiece16 package; - StringPiece16 type; - StringPiece16 entry; - extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry); - - if (!type.empty() && type != u"attr") { - return false; - } - - outRef->package = package; - outRef->type = ResourceType::kAttr; - outRef->entry = entry; - return true; - } - return false; -} +constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2"; -/* - * Style parent's are a bit different. We accept the following formats: - * - * @[package:]style/<entry> - * ?[package:]style/<entry> - * <package>:[style/]<entry> - * [package:style/]<entry> +/** + * Returns true if the element is <skip> or <eat-comment> and can be safely ignored. */ -bool ResourceParser::parseStyleParentReference(const StringPiece16& str, Reference* outReference, - std::string* outError) { - if (str.empty()) { - return true; - } - - StringPiece16 name = str; - - bool hasLeadingIdentifiers = false; - bool privateRef = false; - - // 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); - if (name.data()[0] == u'*') { - privateRef = true; - name = name.substr(1, name.size() - 1); - } - } - - 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 false; - } - } else { - // No type was defined, this should not have a leading identifier. - if (hasLeadingIdentifiers) { - std::stringstream err; - err << "invalid parent reference '" << str << "'"; - *outError = err.str(); - return false; - } - } - - if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) { - std::stringstream err; - err << "invalid parent reference '" << str << "'"; - *outError = err.str(); - return false; - } - - outReference->name = ref.toResourceName(); - outReference->privateReference = privateRef; - return true; -} - -std::unique_ptr<Reference> ResourceParser::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; - } - - if (tryParseAttributeReference(str, &ref)) { - *outCreate = false; - return util::make_unique<Reference>(ref, Reference::Type::kAttribute); - } - return {}; -} - -std::unique_ptr<BinaryPrimitive> ResourceParser::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 bool shouldIgnoreElement(const StringPiece16& ns, const StringPiece16& name) { + return ns.empty() && (name == u"skip" || name == u"eat-comment"); } -std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseEnumSymbol(const Attribute& enumAttr, - const StringPiece16& str) { - StringPiece16 trimmedStr(util::trimWhitespace(str)); - for (const auto& entry : 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 = entry.symbol.name; - if (trimmedStr == enumSymbolResourceName.entry) { - android::Res_value value = {}; - value.dataType = android::Res_value::TYPE_INT_DEC; - value.data = entry.value; - return util::make_unique<BinaryPrimitive>(value); - } - } - return {}; +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; } -std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFlagSymbol(const Attribute& flagAttr, - const StringPiece16& str) { - android::Res_value flags = {}; - flags.dataType = android::Res_value::TYPE_INT_DEC; - +static uint32_t parseFormatAttribute(const StringPiece16& str) { + uint32_t mask = 0; for (StringPiece16 part : util::tokenize(str, u'|')) { StringPiece16 trimmedPart = util::trimWhitespace(part); - - bool flagSet = false; - for (const auto& entry : 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 = entry.symbol.name; - if (trimmedPart == flagSymbolResourceName.entry) { - flags.data |= entry.value; - flagSet = true; - break; - } - } - - if (!flagSet) { - return {}; + uint32_t type = parseFormatType(trimmedPart); + if (type == 0) { + return 0; } + mask |= type; } - return util::make_unique<BinaryPrimitive>(flags); -} - -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; - } -} - -std::unique_ptr<BinaryPrimitive> ResourceParser::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 {}; - } - - 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> ResourceParser::tryParseBool(const StringPiece16& str) { - StringPiece16 trimmedStr(util::trimWhitespace(str)); - uint32_t data = 0; - if (trimmedStr == u"true" || trimmedStr == u"TRUE") { - data = 0xffffffffu; - } else if (trimmedStr != u"false" && trimmedStr != u"FALSE") { - return {}; - } - android::Res_value value = {}; - value.dataType = android::Res_value::TYPE_INT_BOOLEAN; - value.data = data; - return util::make_unique<BinaryPrimitive>(value); -} - -std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseInt(const StringPiece16& str) { - android::Res_value value; - if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) { - return {}; - } - return util::make_unique<BinaryPrimitive>(value); -} - -std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFloat(const StringPiece16& str) { - android::Res_value value; - if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) { - return {}; - } - return util::make_unique<BinaryPrimitive>(value); -} - -uint32_t ResourceParser::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; - - case android::Res_value::TYPE_STRING: - return android::ResTable_map::TYPE_STRING; - - case android::Res_value::TYPE_FLOAT: - return android::ResTable_map::TYPE_FLOAT; - - case android::Res_value::TYPE_DIMENSION: - return android::ResTable_map::TYPE_DIMENSION; - - case android::Res_value::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; - - case android::Res_value::TYPE_INT_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; - - default: - return 0; - }; + return mask; } -std::unique_ptr<Item> ResourceParser::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); - } +/** + * 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; +}; - bool create = false; - std::unique_ptr<Reference> reference = tryParseReference(value, &create); - if (reference) { - if (create && onCreateReference) { - onCreateReference(reference->name); - } - return std::move(reference); +// 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 (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 (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 (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 (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 (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); + if (!table->addResource(res->name, res->id, res->config, res->product, + std::move(res->value), diag)) { + return false; } } - 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); - } - } + bool error = false; + for (ParsedResource& child : res->childResources) { + error |= !addResourcesToTable(table, diag, &child); } - return {}; + return !error; } -/** - * We successively try to parse the string as a resource type that the Attribute - * allows. - */ -std::unique_ptr<Item> ResourceParser::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 {}; -} +// Convenient aliases for more readable function calls. +enum { + kAllowRawString = true, + kNoRawString = false +}; -ResourceParser::ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source, +ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, const ConfigDescription& config, - const std::shared_ptr<XmlPullParser>& parser) : - mTable(table), mSource(source), mConfig(config), mLogger(source), - mParser(std::make_shared<XliffXmlPullParser>(parser)) { + const ResourceParserOptions& options) : + mDiag(diag), mTable(table), mSource(source), mConfig(config), mOptions(options) { } /** * Build a string from XML that converts nested elements into Span objects. */ -bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString, +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 (XmlPullParser::isGoodEvent(parser->next())) { - const XmlPullParser::Event event = parser->getEvent(); - if (event == XmlPullParser::Event::kEndElement) { + 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; @@ -506,21 +156,21 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou outStyleString->spans.push_back(spanStack.back()); spanStack.pop_back(); - } else if (event == XmlPullParser::Event::kText) { - // TODO(adamlesinski): Verify format strings. + } else if (event == xml::XmlPullParser::Event::kText) { outRawString->append(parser->getText()); builder.append(parser->getText()); - } else if (event == XmlPullParser::Event::kStartElement) { - if (parser->getElementNamespace().size() > 0) { - mLogger.warn(parser->getLineNumber()) - << "skipping element '" - << parser->getElementName() - << "' with unknown namespace '" - << parser->getElementNamespace() - << "'." - << std::endl; - XmlPullParser::skipCurrentElement(parser); + } 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++; @@ -536,205 +186,288 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou } if (builder.str().size() > std::numeric_limits<uint32_t>::max()) { - mLogger.error(parser->getLineNumber()) - << "style string '" - << builder.str() - << "' is too long." - << std::endl; - return false; + 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()) }); } - spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) }); - } else if (event == XmlPullParser::Event::kComment) { + } else if (event == xml::XmlPullParser::Event::kComment) { // Skip } else { - mLogger.warn(parser->getLineNumber()) - << "unknown event " - << event - << "." - << std::endl; + assert(false); } } assert(spanStack.empty() && "spans haven't been fully processed"); outStyleString->str = builder.str(); - return true; + return !error; } -bool ResourceParser::parse() { - while (XmlPullParser::isGoodEvent(mParser->next())) { - if (mParser->getEvent() != XmlPullParser::Event::kStartElement) { +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; } - ScopedXmlPullParser parser(mParser.get()); - if (!parser.getElementNamespace().empty() || - parser.getElementName() != u"resources") { - mLogger.error(parser.getLineNumber()) - << "root element must be <resources> in the global namespace." - << std::endl; + if (!parser->getElementNamespace().empty() || parser->getElementName() != u"resources") { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "root element must be <resources>"); return false; } - if (!parseResources(&parser)) { - return false; - } - } + error |= !parseResources(parser); + break; + }; - if (mParser->getEvent() == XmlPullParser::Event::kBadDocument) { - mLogger.error(mParser->getLineNumber()) - << mParser->getLastError() - << std::endl; + if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "xml parser error: " << parser->getLastError()); return false; } - return true; + return !error; } -bool ResourceParser::parseResources(XmlPullParser* parser) { - bool success = true; +bool ResourceParser::parseResources(xml::XmlPullParser* parser) { + std::set<ResourceName> strippedResources; + bool error = false; std::u16string comment; - while (XmlPullParser::isGoodEvent(parser->next())) { - const XmlPullParser::Event event = parser->getEvent(); - if (event == XmlPullParser::Event::kComment) { + 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 == XmlPullParser::Event::kText) { + if (event == xml::XmlPullParser::Event::kText) { if (!util::trimWhitespace(parser->getText()).empty()) { - comment = u""; + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "plain text not allowed here"); + error = true; } continue; } - if (event != XmlPullParser::Event::kStartElement) { - continue; - } + assert(event == xml::XmlPullParser::Event::kStartElement); - ScopedXmlPullParser childParser(parser); - - if (!childParser.getElementNamespace().empty()) { + if (!parser->getElementNamespace().empty()) { // Skip unknown namespace. continue; } - StringPiece16 name = childParser.getElementName(); - if (name == u"skip" || name == u"eat-comment") { + std::u16string elementName = parser->getElementName(); + if (elementName == u"skip" || elementName == u"eat-comment") { + comment = u""; continue; } - if (name == u"private-symbols") { - // Handle differently. - mLogger.note(childParser.getLineNumber()) - << "got a <private-symbols> tag." - << std::endl; - continue; + ParsedResource parsedResource; + parsedResource.config = mConfig; + parsedResource.source = mSource.withLine(parser->getLineNumber()); + parsedResource.comment = std::move(comment); + + // Extract the product name if it exists. + if (Maybe<StringPiece16> maybeProduct = xml::findNonEmptyAttribute(parser, u"product")) { + parsedResource.product = util::utf16ToUtf8(maybeProduct.value()); } - const auto endAttrIter = childParser.endAttributes(); - auto attrIter = childParser.findAttribute(u"", u"name"); - if (attrIter == endAttrIter || attrIter->value.empty()) { - mLogger.error(childParser.getLineNumber()) - << "<" << name << "> tag must have a 'name' attribute." - << std::endl; - success = false; + // Parse the resource regardless of product. + if (!parseResource(parser, &parsedResource)) { + error = true; continue; } - // Copy because our iterator will go out of scope when - // we parse more XML. - std::u16string attributeName = attrIter->value; - - if (name == u"item") { - // Items simply have their type encoded in the type attribute. - auto typeIter = childParser.findAttribute(u"", u"type"); - if (typeIter == endAttrIter || typeIter->value.empty()) { - mLogger.error(childParser.getLineNumber()) - << "<item> must have a 'type' attribute." - << std::endl; - success = false; - continue; - } - name = typeIter->value; + if (!addResourcesToTable(mTable, mDiag, &parsedResource)) { + error = true; } + } - if (name == u"id") { - success &= mTable->addResource(ResourceNameRef{ {}, ResourceType::kId, attributeName }, - {}, mSource.line(childParser.getLineNumber()), - util::make_unique<Id>()); - } else if (name == u"string") { - success &= parseString(&childParser, - ResourceNameRef{ {}, ResourceType::kString, attributeName }); - } else if (name == u"color") { - success &= parseColor(&childParser, - ResourceNameRef{ {}, ResourceType::kColor, attributeName }); - } else if (name == u"drawable") { - success &= parseColor(&childParser, - ResourceNameRef{ {}, ResourceType::kDrawable, attributeName }); - } else if (name == u"bool") { - success &= parsePrimitive(&childParser, - ResourceNameRef{ {}, ResourceType::kBool, attributeName }); - } else if (name == u"integer") { - success &= parsePrimitive( - &childParser, - ResourceNameRef{ {}, ResourceType::kInteger, attributeName }); - } else if (name == u"dimen") { - success &= parsePrimitive(&childParser, - ResourceNameRef{ {}, ResourceType::kDimen, attributeName }); - } else if (name == u"fraction") { -// success &= parsePrimitive( -// &childParser, -// ResourceNameRef{ {}, ResourceType::kFraction, attributeName }); - } else if (name == u"style") { - success &= parseStyle(&childParser, - ResourceNameRef{ {}, ResourceType::kStyle, attributeName }); - } else if (name == u"plurals") { - success &= parsePlural(&childParser, - ResourceNameRef{ {}, ResourceType::kPlurals, attributeName }); - } else if (name == u"array") { - success &= parseArray(&childParser, - ResourceNameRef{ {}, ResourceType::kArray, attributeName }, - android::ResTable_map::TYPE_ANY); - } else if (name == u"string-array") { - success &= parseArray(&childParser, - ResourceNameRef{ {}, ResourceType::kArray, attributeName }, - android::ResTable_map::TYPE_STRING); - } else if (name == u"integer-array") { - success &= parseArray(&childParser, - ResourceNameRef{ {}, ResourceType::kArray, attributeName }, - android::ResTable_map::TYPE_INTEGER); - } else if (name == u"public") { - success &= parsePublic(&childParser, attributeName); - } else if (name == u"declare-styleable") { - success &= parseDeclareStyleable( - &childParser, - ResourceNameRef{ {}, ResourceType::kStyleable, attributeName }); - } else if (name == u"attr") { - success &= parseAttr(&childParser, - ResourceNameRef{ {}, ResourceType::kAttr, attributeName }); - } else if (name == u"bag") { - } else if (name == u"public-padding") { - } else if (name == u"java-symbol") { - } else if (name == u"add-resource") { - } - } - - if (parser->getEvent() == XmlPullParser::Event::kBadDocument) { - mLogger.error(parser->getLineNumber()) - << parser->getLastError() - << std::endl; - return false; + // 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; + } } - return success; + + return !error; } +bool ResourceParser::parseResource(xml::XmlPullParser* parser, ParsedResource* outResource) { + struct ItemTypeFormat { + ResourceType type; + uint32_t format; + }; -enum { - kAllowRawString = true, - kNoRawString = false -}; + 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; + } + + 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; + } + } + } + + // 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"); + + 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; + } + + const auto itemIter = elToItemMap.find(resourceType); + if (itemIter != elToItemMap.end()) { + // This is an item, record its type and format and start parsing. + + if (!maybeName) { + mDiag->error(DiagMessage(outResource->source) + << "<" << parser->getElementName() << "> missing 'name' attribute"); + return false; + } + + outResource->name.type = itemIter->second.type; + outResource->name.entry = maybeName.value().toString(); + + // Only use the implicit format for this type if it wasn't overridden. + if (!resourceFormat) { + resourceFormat = itemIter->second.format; + } + + if (!parseItem(parser, outResource, resourceFormat)) { + return false; + } + return true; + } + + // 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; + } + + // 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; + } + + 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; + } + + mDiag->warn(DiagMessage(outResource->source) + << "unknown resource type '" << parser->getElementName() << "'"); + return false; +} + +bool ResourceParser::parseItem(xml::XmlPullParser* parser, ParsedResource* outResource, + 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; +} /** * Reads the entire XML subtree and attempts to parse it as some Item, @@ -743,8 +476,8 @@ enum { * an Item. If allowRawValue is false, nullptr is returned in this * case. */ -std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t typeMask, - bool allowRawValue) { +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; @@ -753,34 +486,27 @@ std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t t return {}; } - StringPool& pool = mTable->getValueStringPool(); - if (!styleString.spans.empty()) { // This can only be a StyledString. return util::make_unique<StyledString>( - pool.makeRef(styleString, StringPool::Context{ 1, mConfig })); + 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. - mTable->addResource(name, {}, mSource.line(beginXmlLine), util::make_unique<Id>()); + 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 = parseItemForAttribute(rawValue, typeMask, - onCreateReference); + std::unique_ptr<Item> processedItem = ResourceUtils::parseItemForAttribute(rawValue, typeMask, + onCreateReference); if (processedItem) { // Fix up the reference. - visitFunc<Reference>(*processedItem, [&](Reference& ref) { - if (!ref.name.package.empty()) { - // The package name was set, so lookup its alias. - parser->applyPackageAlias(&ref.name.package, mTable->getPackage()); - } else { - // The package name was left empty, so it assumes the default package - // without alias lookup. - ref.name.package = mTable->getPackage(); - } - }); + if (Reference* ref = valueCast<Reference>(processedItem.get())) { + transformReferenceFromNamespace(parser, u"", ref); + } return processedItem; } @@ -788,320 +514,428 @@ std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t t if (typeMask & android::ResTable_map::TYPE_STRING) { // Use the trimmed, escaped string. return util::make_unique<String>( - pool.makeRef(styleString.str, StringPool::Context{ 1, mConfig })); + mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig })); } - // We can't parse this so return a RawString if we are allowed. if (allowRawValue) { + // We can't parse this so return a RawString if we are allowed. return util::make_unique<RawString>( - pool.makeRef(rawValue, StringPool::Context{ 1, mConfig })); + mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig })); } return {}; } -bool ResourceParser::parseString(XmlPullParser* parser, const ResourceNameRef& resourceName) { - const SourceLine source = mSource.line(parser->getLineNumber()); - - // Mark the string as untranslateable if needed. - const auto endAttrIter = parser->endAttributes(); - auto attrIter = parser->findAttribute(u"", u"untranslateable"); - // bool untranslateable = attrIter != endAttrIter; - // TODO(adamlesinski): Do something with this (mark the string). - - // Deal with the product. - attrIter = parser->findAttribute(u"", u"product"); - if (attrIter != endAttrIter) { - if (attrIter->value != u"default" && attrIter->value != u"phone") { - // TODO(adamlesinski): Match products. - return true; +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; } } - std::unique_ptr<Item> processedItem = parseXml(parser, android::ResTable_map::TYPE_STRING, - kNoRawString); - if (!processedItem) { - mLogger.error(source.line) - << "not a valid string." - << std::endl; - 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; + } } - return mTable->addResource(resourceName, mConfig, source, std::move(processedItem)); -} - -bool ResourceParser::parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName) { - const SourceLine source = mSource.line(parser->getLineNumber()); - - std::unique_ptr<Item> item = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString); - if (!item) { - mLogger.error(source.line) << "invalid color." << std::endl; + outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString); + if (!outResource->value) { + mDiag->error(DiagMessage(outResource->source) << "not a valid string"); return false; } - return mTable->addResource(resourceName, mConfig, source, std::move(item)); -} - -bool ResourceParser::parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName) { - const SourceLine source = mSource.line(parser->getLineNumber()); - - uint32_t typeMask = 0; - switch (resourceName.type) { - case ResourceType::kInteger: - typeMask |= android::ResTable_map::TYPE_INTEGER; - break; - case ResourceType::kDimen: - typeMask |= android::ResTable_map::TYPE_DIMENSION - | android::ResTable_map::TYPE_FLOAT - | android::ResTable_map::TYPE_FRACTION; - break; + if (String* stringValue = valueCast<String>(outResource->value.get())) { + stringValue->setTranslateable(translateable); - case ResourceType::kBool: - typeMask |= android::ResTable_map::TYPE_BOOLEAN; - break; + 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; + } - default: - assert(false); - break; - } + mDiag->warn(msg); + } + } - std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString); - if (!item) { - mLogger.error(source.line) - << "invalid " - << resourceName.type - << "." - << std::endl; - return false; + } else if (StyledString* stringValue = valueCast<StyledString>(outResource->value.get())) { + stringValue->setTranslateable(translateable); } - - return mTable->addResource(resourceName, mConfig, source, std::move(item)); + return true; } -bool ResourceParser::parsePublic(XmlPullParser* parser, const StringPiece16& name) { - const SourceLine source = mSource.line(parser->getLineNumber()); - - const auto endAttrIter = parser->endAttributes(); - const auto typeAttrIter = parser->findAttribute(u"", u"type"); - if (typeAttrIter == endAttrIter || typeAttrIter->value.empty()) { - mLogger.error(source.line) - << "<public> must have a 'type' attribute." - << std::endl; +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(typeAttrIter->value); + const ResourceType* parsedType = parseResourceType(maybeType.value()); if (!parsedType) { - mLogger.error(source.line) - << "invalid resource type '" - << typeAttrIter->value - << "' in <public>." - << std::endl; + mDiag->error(DiagMessage(outResource->source) + << "invalid resource type '" << maybeType.value() << "' in <public>"); return false; } - ResourceNameRef resourceName { {}, *parsedType, name }; - ResourceId resourceId; + outResource->name.type = *parsedType; - const auto idAttrIter = parser->findAttribute(u"", u"id"); - if (idAttrIter != endAttrIter && !idAttrIter->value.empty()) { + if (Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"id")) { android::Res_value val; - bool result = android::ResTable::stringToInt(idAttrIter->value.data(), - idAttrIter->value.size(), &val); - resourceId.id = val.data; + bool result = android::ResTable::stringToInt(maybeId.value().data(), + maybeId.value().size(), &val); + ResourceId resourceId(val.data); if (!result || !resourceId.isValid()) { - mLogger.error(source.line) - << "invalid resource ID '" - << idAttrIter->value - << "' in <public>." - << std::endl; + 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. - mTable->addResource(resourceName, {}, source, util::make_unique<Id>()); + outResource->value = util::make_unique<Id>(); } - return mTable->markPublic(resourceName, resourceId, source); + outResource->symbolState = SymbolState::kPublic; + return true; } -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; -} +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; + } -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; + 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; + } + + 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 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; } - mask |= type; } - return mask; + return !error; } -bool ResourceParser::parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName) { - const SourceLine source = mSource.line(parser->getLineNumber()); - ResourceName actualName = resourceName.toResourceName(); - std::unique_ptr<Attribute> attr = parseAttrImpl(parser, &actualName, false); - if (!attr) { +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; + } + + const ResourceType* parsedType = parseResourceType(maybeType.value()); + if (!parsedType) { + mDiag->error(DiagMessage(outResource->source) + << "invalid resource type '" << maybeType.value() + << "' in <" << parser->getElementName() << ">"); return false; } - return mTable->addResource(actualName, mConfig, source, std::move(attr)); + + outResource->name.type = *parsedType; + return true; +} + +bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) { + if (parseSymbolImpl(parser, outResource)) { + outResource->symbolState = 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::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) { + return parseAttrImpl(parser, outResource, false); } -std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser, - ResourceName* resourceName, - bool weak) { +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; - const auto endAttrIter = parser->endAttributes(); - const auto formatAttrIter = parser->findAttribute(u"", u"format"); - if (formatAttrIter != endAttrIter) { - typeMask = parseFormatAttribute(formatAttrIter->value); + Maybe<StringPiece16> maybeFormat = xml::findAttribute(parser, u"format"); + if (maybeFormat) { + typeMask = parseFormatAttribute(maybeFormat.value()); if (typeMask == 0) { - mLogger.error(parser->getLineNumber()) - << "invalid attribute format '" - << formatAttrIter->value - << "'." - << std::endl; - return {}; + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "invalid attribute format '" << maybeFormat.value() << "'"); + return false; } } - // If this is a declaration, the package name may be in the name. Separate these out. - // Eg. <attr name="android:text" /> - // No format attribute is allowed. - if (weak && formatAttrIter == endAttrIter) { - StringPiece16 package, type, name; - extractResourceName(resourceName->entry, &package, &type, &name); - if (type.empty() && !package.empty()) { - resourceName->package = package.toString(); - resourceName->entry = name.toString(); + 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; } } - std::vector<Attribute::Symbol> items; + 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); + } + } - bool error = false; - while (XmlPullParser::isGoodEvent(parser->next())) { - if (parser->getEvent() != XmlPullParser::Event::kStartElement) { - continue; + if (!maybeMax) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "invalid 'max' value '" << maxStr << "'"); + return false; } + } + + 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; + } - ScopedXmlPullParser childParser(parser); + struct SymbolComparator { + bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) { + return a.symbol.name.value() < b.symbol.name.value(); + } + }; - const std::u16string& name = childParser.getElementName(); - if (!childParser.getElementNamespace().empty() - || (name != u"flag" && name != u"enum")) { - mLogger.error(childParser.getLineNumber()) - << "unexpected tag <" - << name - << "> in <attr>." - << std::endl; - error = true; + 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 (name == u"enum") { - if (typeMask & android::ResTable_map::TYPE_FLAGS) { - mLogger.error(childParser.getLineNumber()) - << "can not define an <enum>; already defined a <flag>." - << std::endl; - error = true; - 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"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; } - typeMask |= android::ResTable_map::TYPE_ENUM; - } else if (name == u"flag") { - if (typeMask & android::ResTable_map::TYPE_ENUM) { - mLogger.error(childParser.getLineNumber()) - << "can not define a <flag>; already defined an <enum>." - << std::endl; - error = true; - continue; - } - typeMask |= android::ResTable_map::TYPE_FLAGS; - } - Attribute::Symbol item; - if (parseEnumOrFlagItem(&childParser, name, &item)) { - if (!mTable->addResource(item.symbol.name, mConfig, - mSource.line(childParser.getLineNumber()), - util::make_unique<Id>())) { - error = true; + 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 { - items.push_back(std::move(item)); + error = true; } - } else { + } else if (!shouldIgnoreElement(elementNamespace, elementName)) { + mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">"); error = true; } + + comment = {}; } if (error) { - return {}; + return false; } std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak); - attr->symbols.swap(items); + attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end()); attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY); - return attr; + if (maybeMin) { + attr->minInt = maybeMin.value(); + } + + if (maybeMax) { + attr->maxInt = maybeMax.value(); + } + outResource->value = std::move(attr); + return true; } -bool ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag, - Attribute::Symbol* outSymbol) { - const auto attrIterEnd = parser->endAttributes(); - const auto nameAttrIter = parser->findAttribute(u"", u"name"); - if (nameAttrIter == attrIterEnd || nameAttrIter->value.empty()) { - mLogger.error(parser->getLineNumber()) - << "no attribute 'name' found for tag <" << tag << ">." - << std::endl; - return false; +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 {}; } - const auto valueAttrIter = parser->findAttribute(u"", u"value"); - if (valueAttrIter == attrIterEnd || valueAttrIter->value.empty()) { - mLogger.error(parser->getLineNumber()) - << "no attribute 'value' found for tag <" << tag << ">." - << std::endl; - return false; + 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(valueAttrIter->value.data(), - valueAttrIter->value.size(), &val)) { - mLogger.error(parser->getLineNumber()) - << "invalid value '" - << valueAttrIter->value - << "' for <" << tag << ">; must be an integer." - << std::endl; - return false; + 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 {}; } - outSymbol->symbol.name = ResourceName { - mTable->getPackage(), ResourceType::kId, nameAttrIter->value }; - outSymbol->value = val.data; - return true; + return Attribute::Symbol{ + Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())), val.data }; } -static bool parseXmlAttributeName(StringPiece16 str, ResourceName* outName) { +static Maybe<Reference> parseXmlAttributeName(StringPiece16 str) { str = util::trimWhitespace(str); - const char16_t* const start = str.data(); + 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++; + } + StringPiece16 package; StringPiece16 name; while (p != end) { @@ -1113,289 +947,314 @@ static bool parseXmlAttributeName(StringPiece16 str, ResourceName* outName) { p++; } - outName->package = package.toString(); - outName->type = ResourceType::kAttr; - if (name.size() == 0) { - outName->entry = str.toString(); - } else { - outName->entry = name.toString(); - } - return true; + ref.name = ResourceName(package.toString(), ResourceType::kAttr, + name.empty() ? str.toString() : name.toString()); + return Maybe<Reference>(std::move(ref)); } -bool ResourceParser::parseUntypedItem(XmlPullParser* parser, Style& style) { - const auto endAttrIter = parser->endAttributes(); - const auto nameAttrIter = parser->findAttribute(u"", u"name"); - if (nameAttrIter == endAttrIter || nameAttrIter->value.empty()) { - mLogger.error(parser->getLineNumber()) - << "<item> must have a 'name' attribute." - << std::endl; +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"); return false; } - ResourceName key; - if (!parseXmlAttributeName(nameAttrIter->value, &key)) { - mLogger.error(parser->getLineNumber()) - << "invalid attribute name '" - << nameAttrIter->value - << "'." - << std::endl; + Maybe<Reference> maybeKey = parseXmlAttributeName(maybeName.value()); + if (!maybeKey) { + mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'"); return false; } - if (!key.package.empty()) { - // We have a package name set, so lookup its alias. - parser->applyPackageAlias(&key.package, mTable->getPackage()); - } else { - // The package name was omitted, so use the default package name with - // no alias lookup. - key.package = mTable->getPackage(); - } + 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; } - style.entries.push_back(Style::Entry{ Reference(key), std::move(value) }); + style->entries.push_back(Style::Entry{ std::move(maybeKey.value()), std::move(value) }); return true; } -bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName) { - const SourceLine source = mSource.line(parser->getLineNumber()); +bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) { + outResource->name.type = ResourceType::kStyle; + std::unique_ptr<Style> style = util::make_unique<Style>(); - const auto endAttrIter = parser->endAttributes(); - const auto parentAttrIter = parser->findAttribute(u"", u"parent"); - if (parentAttrIter != endAttrIter) { - std::string errStr; - if (!parseStyleParentReference(parentAttrIter->value, &style->parent, &errStr)) { - mLogger.error(source.line) << errStr << "." << std::endl; - return false; - } + 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; + } - if (!style->parent.name.package.empty()) { - // Try to interpret the package name as an alias. These take precedence. - parser->applyPackageAlias(&style->parent.name.package, mTable->getPackage()); - } else { - // If no package is specified, this can not be an alias and is the local package. - style->parent.name.package = mTable->getPackage(); + // 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 = resourceName.entry.toString(); + std::u16string styleName = outResource->name.entry; size_t pos = styleName.find_last_of(u'.'); if (pos != std::string::npos) { style->parentInferred = true; - style->parent.name.package = mTable->getPackage(); - style->parent.name.type = ResourceType::kStyle; - style->parent.name.entry = styleName.substr(0, pos); + style->parent = Reference(ResourceName({}, ResourceType::kStyle, + styleName.substr(0, pos))); } } - bool success = true; - while (XmlPullParser::isGoodEvent(parser->next())) { - if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + 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; } - ScopedXmlPullParser childParser(parser); - const std::u16string& name = childParser.getElementName(); - if (name == u"item") { - success &= parseUntypedItem(&childParser, *style); - } else { - mLogger.error(childParser.getLineNumber()) - << "unexpected tag <" - << name - << "> in <style> resource." - << std::endl; - success = false; + const std::u16string& elementNamespace = parser->getElementNamespace(); + const std::u16string& elementName = parser->getElementName(); + if (elementNamespace == u"" && elementName == u"item") { + error |= !parseStyleItem(parser, style.get()); + + } else if (!shouldIgnoreElement(elementNamespace, elementName)) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << ":" << elementName << ">"); + error = true; } } - if (!success) { + if (error) { return false; } - return mTable->addResource(resourceName, mConfig, source, std::move(style)); + outResource->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(XmlPullParser* parser, const ResourceNameRef& resourceName, - uint32_t typeMask) { - const SourceLine source = mSource.line(parser->getLineNumber()); +bool ResourceParser::parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource) { + return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_INTEGER); +} + +bool ResourceParser::parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource) { + return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_STRING); +} + +bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource, + const uint32_t typeMask) { + outResource->name.type = ResourceType::kArray; + std::unique_ptr<Array> array = util::make_unique<Array>(); bool error = false; - while (XmlPullParser::isGoodEvent(parser->next())) { - if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + const size_t depth = parser->getDepth(); + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { + // Skip text and comments. continue; } - ScopedXmlPullParser childParser(parser); - - if (childParser.getElementName() != u"item") { - mLogger.error(childParser.getLineNumber()) - << "unexpected tag <" - << childParser.getElementName() - << "> in <array> resource." - << std::endl; - error = true; - 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)); - std::unique_ptr<Item> item = parseXml(&childParser, typeMask, kNoRawString); - if (!item) { + } else if (!shouldIgnoreElement(elementNamespace, elementName)) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "unknown tag <" << elementNamespace << ":" << elementName << ">"); error = true; - continue; } - array->items.emplace_back(std::move(item)); } if (error) { return false; } - return mTable->addResource(resourceName, mConfig, source, std::move(array)); + outResource->value = std::move(array); + return true; } -bool ResourceParser::parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName) { - const SourceLine source = mSource.line(parser->getLineNumber()); +bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource) { + outResource->name.type = ResourceType::kPlurals; + std::unique_ptr<Plural> plural = util::make_unique<Plural>(); - bool success = true; - while (XmlPullParser::isGoodEvent(parser->next())) { - if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + 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; } - ScopedXmlPullParser childParser(parser); - - if (!childParser.getElementNamespace().empty() || - childParser.getElementName() != u"item") { - success = false; - 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; + } - const auto endAttrIter = childParser.endAttributes(); - auto attrIter = childParser.findAttribute(u"", u"quantity"); - if (attrIter == endAttrIter || attrIter->value.empty()) { - mLogger.error(childParser.getLineNumber()) - << "<item> in <plurals> requires attribute 'quantity'." - << std::endl; - success = false; - 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; + } - StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->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 { - mLogger.error(childParser.getLineNumber()) - << "<item> in <plural> has invalid value '" - << trimmedQuantity - << "' for attribute 'quantity'." - << std::endl; - success = false; - continue; - } + if (plural->values[index]) { + mDiag->error(DiagMessage(itemSource) + << "duplicate quantity '" << trimmedQuantity << "'"); + error = true; + continue; + } - if (plural->values[index]) { - mLogger.error(childParser.getLineNumber()) - << "duplicate quantity '" - << trimmedQuantity - << "'." - << std::endl; - success = false; - continue; - } + if (!(plural->values[index] = parseXml(parser, android::ResTable_map::TYPE_STRING, + kNoRawString))) { + error = true; + } + plural->values[index]->setSource(itemSource); - if (!(plural->values[index] = parseXml(&childParser, android::ResTable_map::TYPE_STRING, - kNoRawString))) { - success = false; + } else if (!shouldIgnoreElement(elementNamespace, elementName)) { + mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":" + << elementName << ">"); + error = true; } } - if (!success) { + if (error) { return false; } - return mTable->addResource(resourceName, mConfig, source, std::move(plural)); + outResource->value = std::move(plural); + return true; } -bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, - const ResourceNameRef& resourceName) { - const SourceLine source = mSource.line(parser->getLineNumber()); +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>(); - bool success = true; - while (XmlPullParser::isGoodEvent(parser->next())) { - if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + 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; } - ScopedXmlPullParser childParser(parser); - - const std::u16string& elementName = childParser.getElementName(); - if (elementName == u"attr") { - const auto endAttrIter = childParser.endAttributes(); - auto attrIter = childParser.findAttribute(u"", u"name"); - if (attrIter == endAttrIter || attrIter->value.empty()) { - mLogger.error(childParser.getLineNumber()) - << "<attr> tag must have a 'name' attribute." - << std::endl; - success = 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"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; } - // Copy because our iterator will be invalidated. - ResourceName attrResourceName = { - mTable->getPackage(), - ResourceType::kAttr, - attrIter->value - }; - - std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, &attrResourceName, true); - if (!attr) { - success = false; + // 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; } - styleable->entries.emplace_back(attrResourceName); + Reference& childRef = maybeRef.value(); + xml::transformReferenceFromNamespace(parser, u"", &childRef); - // The package may have been corrected to another package. If that is so, - // we don't add the declaration. - if (attrResourceName.package == mTable->getPackage()) { - success &= mTable->addResource(attrResourceName, mConfig, - mSource.line(childParser.getLineNumber()), - std::move(attr)); + // 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; } - } else if (elementName != u"eat-comment" && elementName != u"skip") { - mLogger.error(childParser.getLineNumber()) - << "<" - << elementName - << "> is not allowed inside <declare-styleable>." - << std::endl; - success = false; + // 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 (!success) { + if (error) { return false; } - return mTable->addResource(resourceName, mConfig, source, std::move(styleable)); + outResource->value = std::move(styleable); + return true; } } // namespace aapt diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 7618999f0023..ee5b33788312 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -18,135 +18,43 @@ #define AAPT_RESOURCE_PARSER_H #include "ConfigDescription.h" -#include "Logger.h" +#include "Diagnostics.h" #include "ResourceTable.h" #include "ResourceValues.h" -#include "StringPiece.h" #include "StringPool.h" -#include "XmlPullParser.h" +#include "util/Maybe.h" +#include "util/StringPiece.h" +#include "xml/XmlPullParser.h" -#include <istream> #include <memory> namespace aapt { -/* - * Parses an XML file for resources and adds them to a ResourceTable. - */ -class ResourceParser { -public: - /* - * Extracts the package, type, and name from a string of the format: - * - * [package:]type/name - * - * where the package can be empty. Validation must be performed on each - * individual extracted piece to verify that the pieces are valid. - */ - static void extractResourceName(const StringPiece16& str, StringPiece16* outPackage, - StringPiece16* outType, StringPiece16* outEntry); - - /* - * Returns true if the string was parsed as a reference (@[+][package:]type/name), with - * `outReference` 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. - */ - static bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference, - bool* outCreate, bool* outPrivate); - - /* - * Returns true if the string was parsed as an attribute reference (?[package:]type/name), - * with `outReference` set to the parsed reference. - */ - static bool tryParseAttributeReference(const StringPiece16& str, - ResourceNameRef* outReference); - - /* - * Returns true 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: - * - * @[package:]style/<entry> or - * ?[package:]style/<entry> or - * <package>:[style/]<entry> - */ - static bool parseStyleParentReference(const StringPiece16& str, Reference* outReference, - std::string* outError); - - /* - * 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. - */ - static std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, - bool* outCreate); - - /* - * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed - * as one. - */ - static std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str); - - /* - * Returns a BinaryPrimitve object representing a color if the string was parsed - * as one. - */ - static std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str); - - /* - * Returns a BinaryPrimitve object representing a boolean if the string was parsed - * as one. - */ - static std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str); - - /* - * Returns a BinaryPrimitve object representing an integer if the string was parsed - * as one. - */ - static std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str); +struct ParsedResource; - /* - * Returns a BinaryPrimitve object representing a floating point number - * (float, dimension, etc) if the string was parsed as one. +struct ResourceParserOptions { + /** + * Whether the default setting for this parser is to allow translation. */ - static std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str); + bool translatable = true; - /* - * Returns a BinaryPrimitve object representing an enum symbol if the string was parsed - * as one. + /** + * Whether positional arguments in formatted strings are treated as errors or warnings. */ - static std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute& enumAttr, - const StringPiece16& str); - - /* - * Returns a BinaryPrimitve object representing a flag symbol if the string was parsed - * as one. - */ - static std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute& enumAttr, - const StringPiece16& str); - /* - * 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 - * reference to an ID that must be created (@+id/foo). - */ - static std::unique_ptr<Item> parseItemForAttribute( - const StringPiece16& value, const Attribute& attr, - std::function<void(const ResourceName&)> onCreateReference = {}); - - static std::unique_ptr<Item> parseItemForAttribute( - const StringPiece16& value, uint32_t typeMask, - std::function<void(const ResourceName&)> onCreateReference = {}); - - static uint32_t androidTypeToAttributeTypeMask(uint16_t type); + bool errorOnPositionalArguments = true; +}; - ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source, - const ConfigDescription& config, const std::shared_ptr<XmlPullParser>& parser); +/* + * 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(); + bool parse(xml::XmlPullParser* parser); private: /* @@ -155,39 +63,47 @@ private: * contains the escaped and whitespace trimmed text, while `outRawString` * contains the unescaped text. Returns true on success. */ - bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,\ + bool flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString, StyleString* outStyleString); /* - * Parses the XML subtree and converts it to 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 nullptr. + * 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(XmlPullParser* parser, uint32_t typeMask, bool allowRawValue); - - bool parseResources(XmlPullParser* parser); - bool parseString(XmlPullParser* parser, const ResourceNameRef& resourceName); - bool parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName); - bool parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName); - bool parsePublic(XmlPullParser* parser, const StringPiece16& name); - bool parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName); - std::unique_ptr<Attribute> parseAttrImpl(XmlPullParser* parser, - ResourceName* resourceName, - bool weak); - bool parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag, - Attribute::Symbol* outSymbol); - bool parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName); - bool parseUntypedItem(XmlPullParser* parser, Style& style); - bool parseDeclareStyleable(XmlPullParser* parser, const ResourceNameRef& resourceName); - bool parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName, uint32_t typeMask); - bool parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName); - - std::shared_ptr<ResourceTable> mTable; + 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; - SourceLogger mLogger; - std::shared_ptr<XmlPullParser> mParser; + ResourceParserOptions mOptions; }; } // namespace aapt diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index a93d0ff7a835..3450de9078bb 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -16,8 +16,10 @@ #include "ResourceParser.h" #include "ResourceTable.h" +#include "ResourceUtils.h" #include "ResourceValues.h" -#include "SourceXmlPullParser.h" +#include "test/Context.h" +#include "xml/XmlPullParser.h" #include <gtest/gtest.h> #include <sstream> @@ -27,156 +29,47 @@ namespace aapt { constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; -TEST(ResourceParserReferenceTest, ParseReferenceWithNoPackage) { - ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" }; - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceParser::tryParseReference(u"@color/foo", &actual, &create, &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_FALSE(privateRef); -} - -TEST(ResourceParserReferenceTest, ParseReferenceWithPackage) { - ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" }; - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceParser::tryParseReference(u"@android:color/foo", &actual, &create, - &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_FALSE(privateRef); -} - -TEST(ResourceParserReferenceTest, ParseReferenceWithSurroundingWhitespace) { - ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" }; - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceParser::tryParseReference(u"\t @android:color/foo\n \n\t", &actual, - &create, &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_FALSE(privateRef); -} - -TEST(ResourceParserReferenceTest, ParseAutoCreateIdReference) { - ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" }; - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceParser::tryParseReference(u"@+android:id/foo", &actual, &create, - &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_TRUE(create); - EXPECT_FALSE(privateRef); -} - -TEST(ResourceParserReferenceTest, ParsePrivateReference) { - ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" }; - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceParser::tryParseReference(u"@*android:id/foo", &actual, &create, - &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_TRUE(privateRef); -} - -TEST(ResourceParserReferenceTest, FailToParseAutoCreateNonIdReference) { - bool create = false; - bool privateRef = false; - ResourceNameRef actual; - EXPECT_FALSE(ResourceParser::tryParseReference(u"@+android:color/foo", &actual, &create, - &privateRef)); -} - -TEST(ResourceParserReferenceTest, ParseStyleParentReference) { - Reference ref; - std::string errStr; - EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@android:style/foo", &ref, &errStr)); - EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); - - EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@style/foo", &ref, &errStr)); - EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); - - EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?android:style/foo", &ref, &errStr)); - EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); - - EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?style/foo", &ref, &errStr)); - EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); - - EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:style/foo", &ref, &errStr)); - EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); - - EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:foo", &ref, &errStr)); - EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); - - EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"foo", &ref, &errStr)); - EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); +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)); } struct ResourceParserTest : public ::testing::Test { - virtual void SetUp() override { - mTable = std::make_shared<ResourceTable>(); - mTable->setPackage(u"android"); + ResourceTable mTable; + std::unique_ptr<IAaptContext> mContext; + + void SetUp() override { + mContext = test::ContextBuilder().build(); } ::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; - ResourceParser parser(mTable, Source{ "test" }, {}, - std::make_shared<SourceXmlPullParser>(input)); - if (parser.parse()) { + 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(); } - - template <typename T> - const T* findResource(const ResourceNameRef& name, const ConfigDescription& config) { - using std::begin; - using std::end; - - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = mTable->findResource(name); - if (!type || !entry) { - return nullptr; - } - - for (const auto& configValue : entry->values) { - if (configValue.config == config) { - return dynamic_cast<const T*>(configValue.value.get()); - } - } - return nullptr; - } - - template <typename T> - const T* findResource(const ResourceNameRef& name) { - return findResource<T>(name, {}); - } - - std::shared_ptr<ResourceTable> mTable; }; -TEST_F(ResourceParserTest, FailToParseWithNoRootResourcesElement) { - std::stringstream input(kXmlPreamble); - input << "<attr name=\"foo\"/>" << std::endl; - ResourceParser parser(mTable, {}, {}, std::make_shared<SourceXmlPullParser>(input)); - ASSERT_FALSE(parser.parse()); -} - TEST_F(ResourceParserTest, ParseQuotedString) { std::string input = "<string name=\"foo\"> \" hey there \" </string>"; ASSERT_TRUE(testParse(input)); - const String* str = findResource<String>(ResourceName{ - u"android", ResourceType::kString, u"foo"}); + String* str = test::getValue<String>(&mTable, u"@string/foo"); ASSERT_NE(nullptr, str); EXPECT_EQ(std::u16string(u" hey there "), *str->value); } @@ -185,12 +78,30 @@ TEST_F(ResourceParserTest, ParseEscapedString) { std::string input = "<string name=\"foo\">\\?123</string>"; ASSERT_TRUE(testParse(input)); - const String* str = findResource<String>(ResourceName{ - u"android", ResourceType::kString, u"foo" }); + String* str = test::getValue<String>(&mTable, u"@string/foo"); ASSERT_NE(nullptr, str); EXPECT_EQ(std::u16string(u"?123"), *str->value); } +TEST_F(ResourceParserTest, ParseFormattedString) { + 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)); +} + +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)); + + 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)); +} + TEST_F(ResourceParserTest, ParseNull) { std::string input = "<integer name=\"foo\">@null</integer>"; ASSERT_TRUE(testParse(input)); @@ -199,8 +110,7 @@ TEST_F(ResourceParserTest, ParseNull) { // 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. - const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{ - u"android", ResourceType::kInteger, u"foo" }); + 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); @@ -210,8 +120,7 @@ TEST_F(ResourceParserTest, ParseEmpty) { std::string input = "<integer name=\"foo\">@empty</integer>"; ASSERT_TRUE(testParse(input)); - const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{ - u"android", ResourceType::kInteger, u"foo" }); + 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); @@ -222,17 +131,51 @@ TEST_F(ResourceParserTest, ParseAttr) { "<attr name=\"bar\"/>"; ASSERT_TRUE(testParse(input)); - const Attribute* attr = findResource<Attribute>(ResourceName{ - u"android", ResourceType::kAttr, u"foo"}); - EXPECT_NE(nullptr, attr); + Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); + ASSERT_NE(nullptr, attr); EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); - attr = findResource<Attribute>(ResourceName{ - u"android", ResourceType::kAttr, u"bar"}); - EXPECT_NE(nullptr, attr); + attr = test::getValue<Attribute>(&mTable, u"@attr/bar"); + ASSERT_NE(nullptr, attr); EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask); } +// 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( + <attr name="foo" /> + <declare-styleable name="bar"> + <attr name="baz" /> + </declare-styleable>)EOF"; + ASSERT_TRUE(testParse(input, watchConfig)); + + 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_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")); +} + +TEST_F(ResourceParserTest, ParseAttrWithMinMax) { + 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); +} + +TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) { + 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" @@ -240,8 +183,7 @@ TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { "<attr name=\"foo\" format=\"string\"/>"; ASSERT_TRUE(testParse(input)); - const Attribute* attr = findResource<Attribute>(ResourceName{ - u"android", ResourceType::kAttr, u"foo"}); + Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); ASSERT_NE(nullptr, attr); EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); } @@ -255,8 +197,7 @@ TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { "</declare-styleable>"; ASSERT_TRUE(testParse(input)); - const Attribute* attr = findResource<Attribute>(ResourceName{ - u"android", ResourceType::kAttr, u"foo"}); + Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); ASSERT_NE(nullptr, attr); EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask); } @@ -269,19 +210,21 @@ TEST_F(ResourceParserTest, ParseEnumAttr) { "</attr>"; ASSERT_TRUE(testParse(input)); - const Attribute* enumAttr = findResource<Attribute>(ResourceName{ - u"android", ResourceType::kAttr, u"foo"}); + 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); - EXPECT_EQ(enumAttr->symbols[0].symbol.name.entry, u"bar"); + 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); - EXPECT_EQ(enumAttr->symbols[1].symbol.name.entry, u"bat"); + 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); - EXPECT_EQ(enumAttr->symbols[2].symbol.name.entry, u"baz"); + 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); } @@ -293,24 +236,26 @@ TEST_F(ResourceParserTest, ParseFlagAttr) { "</attr>"; ASSERT_TRUE(testParse(input)); - const Attribute* flagAttr = findResource<Attribute>(ResourceName{ - u"android", ResourceType::kAttr, u"foo"}); - ASSERT_NE(flagAttr, nullptr); + 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); - EXPECT_EQ(flagAttr->symbols[0].symbol.name.entry, u"bar"); + 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); - EXPECT_EQ(flagAttr->symbols[1].symbol.name.entry, u"bat"); + 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); - EXPECT_EQ(flagAttr->symbols[2].symbol.name.entry, u"baz"); + 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); - std::unique_ptr<BinaryPrimitive> flagValue = - ResourceParser::tryParseFlagSymbol(*flagAttr, u"baz|bat"); - ASSERT_NE(flagValue, nullptr); + std::unique_ptr<BinaryPrimitive> flagValue = ResourceUtils::tryParseFlagSymbol(flagAttr, + u"baz|bat"); + ASSERT_NE(nullptr, flagValue); EXPECT_EQ(flagValue->value.data, 1u | 2u); } @@ -331,28 +276,32 @@ TEST_F(ResourceParserTest, ParseStyle) { "</style>"; ASSERT_TRUE(testParse(input)); - const Style* style = findResource<Style>(ResourceName{ - u"android", ResourceType::kStyle, u"foo"}); - ASSERT_NE(style, nullptr); - EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"fu"), style->parent.name); - ASSERT_EQ(style->entries.size(), 3u); + 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()); - EXPECT_EQ(style->entries[0].key.name, - (ResourceName{ u"android", ResourceType::kAttr, u"bar" })); - EXPECT_EQ(style->entries[1].key.name, - (ResourceName{ u"android", ResourceType::kAttr, u"bat" })); - EXPECT_EQ(style->entries[2].key.name, - (ResourceName{ u"android", ResourceType::kAttr, u"baz" })); + AAPT_ASSERT_TRUE(style->entries[2].key.name); + EXPECT_EQ(test::parseNameOrDie(u"@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)); - const Style* style = findResource<Style>( - ResourceName{ u"android", ResourceType::kStyle, u"foo" }); - ASSERT_NE(style, nullptr); - EXPECT_EQ(ResourceNameRef(u"com.app", ResourceType::kStyle, u"Theme"), style->parent.name); + 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()); } TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { @@ -360,10 +309,11 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { " name=\"foo\" parent=\"app:Theme\"/>"; ASSERT_TRUE(testParse(input)); - const Style* style = findResource<Style>(ResourceName{ - u"android", ResourceType::kStyle, u"foo" }); - ASSERT_NE(style, nullptr); - EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"Theme"), style->parent.name); + 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()); } TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { @@ -373,22 +323,21 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { "</style>"; ASSERT_TRUE(testParse(input)); - const Style* style = findResource<Style>(ResourceName{ - u"android", ResourceType::kStyle, u"foo" }); - ASSERT_NE(style, nullptr); + Style* style = test::getValue<Style>(&mTable, u"@style/foo"); + ASSERT_NE(nullptr, style); ASSERT_EQ(1u, style->entries.size()); - EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kAttr, u"bar"), - style->entries[0].key.name); + EXPECT_EQ(test::parseNameOrDie(u"@android:attr/bar"), style->entries[0].key.name.value()); } TEST_F(ResourceParserTest, ParseStyleWithInferredParent) { std::string input = "<style name=\"foo.bar\"/>"; ASSERT_TRUE(testParse(input)); - const Style* style = findResource<Style>(ResourceName{ - u"android", ResourceType::kStyle, u"foo.bar" }); - ASSERT_NE(style, nullptr); - EXPECT_EQ(style->parent.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); + 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); } @@ -396,18 +345,27 @@ TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAtt std::string input = "<style name=\"foo.bar\" parent=\"\"/>"; ASSERT_TRUE(testParse(input)); - const Style* style = findResource<Style>(ResourceName{ - u"android", ResourceType::kStyle, u"foo.bar" }); - ASSERT_NE(style, nullptr); - EXPECT_FALSE(style->parent.name.isValid()); + Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar"); + ASSERT_NE(nullptr, style); + AAPT_EXPECT_FALSE(style->parent); EXPECT_FALSE(style->parentInferred); } +TEST_F(ResourceParserTest, ParseStyleWithPrivateParentReference) { + 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); +} + TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { std::string input = "<string name=\"foo\">@+id/bar</string>"; ASSERT_TRUE(testParse(input)); - const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"bar"}); + Id* id = test::getValue<Id>(&mTable, u"@id/bar"); ASSERT_NE(id, nullptr); } @@ -415,25 +373,57 @@ 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)); - const Attribute* attr = findResource<Attribute>(ResourceName{ - u"android", ResourceType::kAttr, u"bar"}); + 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 = findResource<Attribute>(ResourceName{ u"android", ResourceType::kAttr, u"bat"}); + 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")); - const Styleable* styleable = findResource<Styleable>(ResourceName{ - u"android", ResourceType::kStyleable, u"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()); +} + +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_EQ((ResourceName{u"android", ResourceType::kAttr, u"bar"}), styleable->entries[0].name); - EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bat"}), styleable->entries[1].name); + 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); } TEST_F(ResourceParserTest, ParseArray) { @@ -444,14 +434,21 @@ TEST_F(ResourceParserTest, ParseArray) { "</array>"; ASSERT_TRUE(testParse(input)); - const Array* array = findResource<Array>(ResourceName{ - u"android", ResourceType::kArray, u"foo" }); + Array* array = test::getValue<Array>(&mTable, u"@array/foo"); ASSERT_NE(array, nullptr); ASSERT_EQ(3u, array->items.size()); - EXPECT_NE(nullptr, dynamic_cast<const Reference*>(array->items[0].get())); - EXPECT_NE(nullptr, dynamic_cast<const String*>(array->items[1].get())); - EXPECT_NE(nullptr, dynamic_cast<const 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")); } TEST_F(ResourceParserTest, ParsePlural) { @@ -463,18 +460,67 @@ TEST_F(ResourceParserTest, ParsePlural) { } TEST_F(ResourceParserTest, ParseCommentsWithResource) { - std::string input = "<!-- This is a comment -->\n" + std::string input = "<!--This is a comment-->\n" "<string name=\"foo\">Hi</string>"; ASSERT_TRUE(testParse(input)); - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = mTable->findResource(ResourceName{ - u"android", ResourceType::kString, u"foo"}); - ASSERT_NE(type, nullptr); - ASSERT_NE(entry, nullptr); - ASSERT_FALSE(entry->values.empty()); - EXPECT_EQ(entry->values.front().comment, u"This is a comment"); + String* value = test::getValue<String>(&mTable, u"@string/foo"); + ASSERT_NE(nullptr, value); + EXPECT_EQ(value->getComment(), u"This is a comment"); +} + +TEST_F(ResourceParserTest, DoNotCombineMultipleComments) { + std::string input = "<!--One-->\n" + "<!--Two-->\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"Two"); +} + +TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) { + std::string input = "<!--One-->\n" + "<string name=\"foo\">\n" + " Hi\n" + "<!--Two-->\n" + "</string>"; + + ASSERT_TRUE(testParse(input)); + + String* value = test::getValue<String>(&mTable, u"@string/foo"); + ASSERT_NE(nullptr, value); + EXPECT_EQ(value->getComment(), u"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( + <declare-styleable name="foo"> + <!-- The name of the bar --> + <attr name="barName" format="string|reference" /> + </declare-styleable> + + <attr name="foo"> + <!-- The very first --> + <enum name="one" value="1" /> + </attr>)EOF"; + ASSERT_TRUE(testParse(input)); + + Styleable* styleable = test::getValue<Styleable>(&mTable, u"@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()); + + Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); + ASSERT_NE(nullptr, attr); + ASSERT_EQ(1u, attr->symbols.size()); + + EXPECT_EQ(StringPiece16(u"The very first"), attr->symbols.front().symbol.getComment()); } /* @@ -485,8 +531,101 @@ TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { std::string input = "<public type=\"id\" name=\"foo\"/>"; ASSERT_TRUE(testParse(input)); - const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"foo" }); + Id* id = test::getValue<Id>(&mTable, u"@id/foo"); ASSERT_NE(nullptr, id); } +TEST_F(ResourceParserTest, KeepAllProducts) { + 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> + <string name="baz">woo</string> + <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")); +} + +TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) { + 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)); + + Maybe<ResourceTable::SearchResult> result = mTable.findResource( + test::parseNameOrDie(u"@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); + + result = mTable.findResource(test::parseNameOrDie(u"@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); +} + +TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) { + 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)); +} + +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); +} + +TEST_F(ResourceParserTest, ParseItemElementWithFormat) { + 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); + + EXPECT_EQ(uint32_t(android::Res_value::TYPE_FLOAT), val->value.dataType); +} + } // namespace aapt diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index c93ecc768022..8d734f3fc36d 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -15,11 +15,11 @@ */ #include "ConfigDescription.h" -#include "Logger.h" #include "NameMangler.h" #include "ResourceTable.h" #include "ResourceValues.h" -#include "Util.h" +#include "ValueVisitor.h" +#include "util/Util.h" #include <algorithm> #include <androidfw/ResourceTypes.h> @@ -29,73 +29,180 @@ namespace aapt { -static bool compareConfigs(const ResourceConfigValue& lhs, const ConfigDescription& rhs) { - return lhs.config < rhs; -} - static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) { return lhs->type < rhs; } -static bool lessThanEntry(const std::unique_ptr<ResourceEntry>& lhs, const StringPiece16& 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; } -ResourceTable::ResourceTable() : mPackageId(kUnsetPackageId) { - // Make sure attrs always have type ID 1. - findOrCreateType(ResourceType::kAttr)->typeId = 1; +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; } -std::unique_ptr<ResourceTableType>& ResourceTable::findOrCreateType(ResourceType type) { - auto last = mTypes.end(); - auto iter = std::lower_bound(mTypes.begin(), last, type, lessThanType); - if (iter != last) { - if ((*iter)->type == type) { - return *iter; +ResourceTablePackage* ResourceTable::findPackageById(uint8_t id) { + for (auto& package : packages) { + if (package->id && package->id.value() == id) { + return package.get(); } } - return *mTypes.emplace(iter, new ResourceTableType{ type }); + return nullptr; } -std::unique_ptr<ResourceEntry>& ResourceTable::findOrCreateEntry( - std::unique_ptr<ResourceTableType>& type, const StringPiece16& name) { - auto last = type->entries.end(); - auto iter = std::lower_bound(type->entries.begin(), last, name, lessThanEntry); - if (iter != last) { - if (name == (*iter)->name) { - return *iter; - } +ResourceTablePackage* ResourceTable::createPackage(const StringPiece16& name, Maybe<uint8_t> id) { + ResourceTablePackage* package = findOrCreatePackage(name); + if (id && !package->id) { + package->id = id; + return package; } - return *type->entries.emplace(iter, new ResourceEntry{ name }); + + if (id && package->id && package->id.value() != id.value()) { + return nullptr; + } + return package; } -struct IsAttributeVisitor : ConstValueVisitor { - bool isAttribute = false; +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(); + } + + std::unique_ptr<ResourceTablePackage> newPackage = util::make_unique<ResourceTablePackage>(); + newPackage->name = name.toString(); + return packages.emplace(iter, std::move(newPackage))->get(); +} - void visit(const Attribute&, ValueVisitorArgs&) override { - isAttribute = true; +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::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(); +} - operator bool() { - return isAttribute; +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::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(); +} + +ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config) { + return findValue(config, StringPiece()); +} + +struct ConfigKey { + 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; +} + +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; +} + +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; + } + } + + for (; iter != values.end(); ++iter) { + ResourceConfigValue* value = iter->get(); + if (value->config == config) { + results.push_back(value); + } + } + 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. */ -static int defaultCollisionHandler(const Value& existing, const Value& incoming) { - IsAttributeVisitor existingIsAttr, incomingIsAttr; - existing.accept(existingIsAttr, {}); - incoming.accept(incomingIsAttr, {}); +int ResourceTable::resolveValueCollision(Value* existing, Value* incoming) { + Attribute* existingAttr = valueCast<Attribute>(existing); + Attribute* incomingAttr = valueCast<Attribute>(incoming); - if (!incomingIsAttr) { - if (incoming.isWeak()) { + 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()) { + } else if (existing->isWeak()) { // Override the weak resource with the new strong resource. return 1; } @@ -104,8 +211,8 @@ static int defaultCollisionHandler(const Value& existing, const Value& incoming) return 0; } - if (!existingIsAttr) { - if (existing.isWeak()) { + if (!existingAttr) { + if (existing->isWeak()) { // The existing value is not an attribute and it is weak, // so take the incoming attribute value. return 1; @@ -115,27 +222,27 @@ static int defaultCollisionHandler(const Value& existing, const Value& incoming) 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. // - const Attribute& existingAttr = static_cast<const Attribute&>(existing); - const Attribute& incomingAttr = static_cast<const Attribute&>(incoming); - if (existingAttr.typeMask == incomingAttr.typeMask) { + 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; + return existingAttr->isWeak() ? 1 : -1; } - if (existingAttr.isWeak() && existingAttr.typeMask == android::ResTable_map::TYPE_ANY) { + 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) { + 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; @@ -146,285 +253,281 @@ static int defaultCollisionHandler(const Value& existing, const Value& incoming) static constexpr const char16_t* kValidNameChars = u"._-"; static constexpr const char16_t* kValidNameMangledChars = u"._-$"; -bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config, - const SourceLine& source, std::unique_ptr<Value> value) { - return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars); +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); +} + +bool ResourceTable::addResource(const ResourceNameRef& name, + const ResourceId resId, + 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); } -bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId, - const ConfigDescription& config, const SourceLine& source, - std::unique_ptr<Value> value) { - return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars); +bool ResourceTable::addFileReference(const ResourceNameRef& name, + const ConfigDescription& config, + const Source& source, + const StringPiece16& path, + IDiagnostics* 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::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::addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config, - const SourceLine& source, - std::unique_ptr<Value> value) { - return addResourceImpl(name, ResourceId{}, config, source, std::move(value), - kValidNameMangledChars); + const StringPiece& product, + std::unique_ptr<Value> value, + IDiagnostics* diag) { + return addResourceImpl(name, ResourceId{}, config, product, std::move(value), + kValidNameMangledChars, resolveValueCollision, diag); } -bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId, - const ConfigDescription& config, const SourceLine& source, - std::unique_ptr<Value> value, const char16_t* validChars) { - if (!name.package.empty() && name.package != mPackage) { - Logger::error(source) - << "resource '" - << name - << "' has incompatible package. Must be '" - << mPackage - << "'." - << std::endl; - return false; - } +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); +} + +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()) { - Logger::error(source) - << "resource '" - << name - << "' has invalid entry name '" - << name.entry - << "'. Invalid character '" - << StringPiece16(badCharIter, 1) - << "'." - << std::endl; + diag->error(DiagMessage(value->getSource()) + << "resource '" + << name + << "' has invalid entry name '" + << name.entry + << "'. Invalid character '" + << StringPiece16(badCharIter, 1) + << "'"); return false; } - std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type); - if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId && - type->typeId != resId.typeId()) { - Logger::error(source) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but type '" - << type->type - << "' already has ID " - << std::hex << type->typeId << std::dec - << "." - << std::endl; + 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; } - std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry); - if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId && - entry->entryId != resId.entryId()) { - Logger::error(source) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but resource already has ID " - << ResourceId(mPackageId, type->typeId, entry->entryId) - << "." - << std::endl; + 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; } - const auto endIter = std::end(entry->values); - auto iter = std::lower_bound(std::begin(entry->values), endIter, config, compareConfigs); - if (iter == endIter || iter->config != config) { - // This resource did not exist before, add it. - entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) }); + 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())); + 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 = defaultCollisionHandler(*iter->value, *value); + int collisionResult = conflictResolver(configValue->value.get(), value.get()); if (collisionResult > 0) { // Take the incoming value. - *iter = ResourceConfigValue{ config, source, {}, std::move(value) }; + configValue->value = std::move(value); } else if (collisionResult == 0) { - Logger::error(source) - << "duplicate value for resource '" << name << "' " - << "with config '" << iter->config << "'." - << std::endl; - - Logger::error(iter->source) - << "resource previously defined here." - << std::endl; + 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; } } if (resId.isValid()) { - type->typeId = resId.typeId(); - entry->entryId = resId.entryId(); + package->id = resId.packageId(); + type->id = resId.typeId(); + entry->id = resId.entryId(); } return true; } -bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId, - const SourceLine& source) { - return markPublicImpl(name, resId, source, kValidNameChars); +bool ResourceTable::setSymbolState(const ResourceNameRef& name, const ResourceId resId, + const Symbol& symbol, IDiagnostics* diag) { + return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag); } -bool ResourceTable::markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId, - const SourceLine& source) { - return markPublicImpl(name, resId, source, kValidNameMangledChars); +bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name, + const ResourceId resId, + const Symbol& symbol, IDiagnostics* diag) { + return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag); } -bool ResourceTable::markPublicImpl(const ResourceNameRef& name, const ResourceId resId, - const SourceLine& source, const char16_t* validChars) { - if (!name.package.empty() && name.package != mPackage) { - Logger::error(source) - << "resource '" - << name - << "' has incompatible package. Must be '" - << mPackage - << "'." - << std::endl; - return false; - } +bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId, + const Symbol& symbol, const char16_t* validChars, + IDiagnostics* diag) { + assert(diag && "diagnostics can't be nullptr"); auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars); if (badCharIter != name.entry.end()) { - Logger::error(source) - << "resource '" - << name - << "' has invalid entry name '" - << name.entry - << "'. Invalid character '" - << StringPiece16(badCharIter, 1) - << "'." - << std::endl; + diag->error(DiagMessage(symbol.source) + << "resource '" + << name + << "' has invalid entry name '" + << name.entry + << "'. Invalid character '" + << StringPiece16(badCharIter, 1) + << "'"); return false; } - std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type); - if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId && - type->typeId != resId.typeId()) { - Logger::error(source) - << "trying to make resource '" - << name - << "' public with ID " - << resId - << " but type '" - << type->type - << "' already has ID " - << std::hex << type->typeId << std::dec - << "." - << std::endl; + 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; } - std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry); - if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId && - entry->entryId != resId.entryId()) { - Logger::error(source) - << "trying to make resource '" - << name - << "' public with ID " - << resId - << " but resource already has ID " - << ResourceId(mPackageId, type->typeId, entry->entryId) - << "." - << std::endl; + 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; } - type->publicStatus.isPublic = true; - entry->publicStatus.isPublic = true; - entry->publicStatus.source = source; + 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()) { - type->typeId = resId.typeId(); - entry->entryId = resId.entryId(); + package->id = resId.packageId(); + type->id = resId.typeId(); + entry->id = resId.entryId(); } - return true; -} -bool ResourceTable::merge(ResourceTable&& other) { - const bool mangleNames = mPackage != other.getPackage(); - std::u16string mangledName; - - for (auto& otherType : other) { - std::unique_ptr<ResourceTableType>& type = findOrCreateType(otherType->type); - if (otherType->publicStatus.isPublic) { - if (type->publicStatus.isPublic && type->typeId != otherType->typeId) { - Logger::error() << "can not merge type '" << type->type - << "': conflicting public IDs " - << "(" << type->typeId << " vs " << otherType->typeId << ")." - << std::endl; - return false; - } - type->publicStatus = std::move(otherType->publicStatus); - type->typeId = otherType->typeId; - } + // Only mark the type state as public, it doesn't care about being private. + if (symbol.state == SymbolState::kPublic) { + type->symbolStatus.state = SymbolState::kPublic; + } - for (auto& otherEntry : otherType->entries) { - const std::u16string* nameToAdd = &otherEntry->name; - if (mangleNames) { - mangledName = otherEntry->name; - NameMangler::mangle(other.getPackage(), &mangledName); - nameToAdd = &mangledName; - } - - std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, *nameToAdd); - if (otherEntry->publicStatus.isPublic) { - if (entry->publicStatus.isPublic && entry->entryId != otherEntry->entryId) { - Logger::error() << "can not merge entry '" << type->type << "/" << entry->name - << "': conflicting public IDs " - << "(" << entry->entryId << " vs " << entry->entryId << ")." - << std::endl; - return false; - } - entry->publicStatus = std::move(otherEntry->publicStatus); - entry->entryId = otherEntry->entryId; - } - - for (ResourceConfigValue& otherValue : otherEntry->values) { - auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), - otherValue.config, compareConfigs); - if (iter != entry->values.end() && iter->config == otherValue.config) { - int collisionResult = defaultCollisionHandler(*iter->value, *otherValue.value); - if (collisionResult > 0) { - // Take the incoming value. - iter->source = std::move(otherValue.source); - iter->comment = std::move(otherValue.comment); - iter->value = std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)); - } else if (collisionResult == 0) { - ResourceNameRef resourceName = { mPackage, type->type, entry->name }; - Logger::error(otherValue.source) - << "resource '" << resourceName << "' has a conflicting value for " - << "configuration (" << otherValue.config << ")." - << std::endl; - Logger::note(iter->source) << "originally defined here." << std::endl; - return false; - } - } else { - entry->values.insert(iter, ResourceConfigValue{ - otherValue.config, - std::move(otherValue.source), - std::move(otherValue.comment), - std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)), - }); - } - } - } + 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); return true; } -std::tuple<const ResourceTableType*, const ResourceEntry*> -ResourceTable::findResource(const ResourceNameRef& name) const { - if (name.package != mPackage) { +Maybe<ResourceTable::SearchResult> +ResourceTable::findResource(const ResourceNameRef& name) { + ResourceTablePackage* package = findPackage(name.package); + if (!package) { return {}; } - auto iter = std::lower_bound(mTypes.begin(), mTypes.end(), name.type, lessThanType); - if (iter == mTypes.end() || (*iter)->type != name.type) { + ResourceTableType* type = package->findType(name.type); + if (!type) { return {}; } - const std::unique_ptr<ResourceTableType>& type = *iter; - auto iter2 = std::lower_bound(type->entries.begin(), type->entries.end(), name.entry, - lessThanEntry); - if (iter2 == type->entries.end() || name.entry != (*iter2)->name) { + ResourceEntry* entry = type->findEntry(name.entry); + if (!entry) { return {}; } - return std::make_tuple(iter->get(), iter2->get()); + return SearchResult{ package, type, entry }; } } // namespace aapt diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 706f56a2776f..7f5c2b8c0f37 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -18,46 +18,68 @@ #define AAPT_RESOURCE_TABLE_H #include "ConfigDescription.h" +#include "Diagnostics.h" #include "Resource.h" #include "ResourceValues.h" #include "Source.h" #include "StringPool.h" +#include "io/File.h" +#include <android-base/macros.h> +#include <map> #include <memory> #include <string> #include <tuple> +#include <unordered_map> #include <vector> namespace aapt { +enum class SymbolState { + kUndefined, + kPublic, + kPrivate +}; + /** * The Public status of a resource. */ -struct Public { - bool isPublic = false; - SourceLine source; +struct Symbol { + SymbolState state = SymbolState::kUndefined; + Source source; std::u16string comment; }; -/** - * The resource value for a specific configuration. - */ -struct ResourceConfigValue { - ConfigDescription config; - SourceLine source; - std::u16string 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); }; /** * Represents a resource entry, which may have * varying values for each defined configuration. */ -struct ResourceEntry { - enum { - kUnsetEntryId = 0xffffffffu - }; - +class ResourceEntry { +public: /** * The name of the resource. Immutable, as * this determines the order of this resource @@ -68,32 +90,36 @@ struct ResourceEntry { /** * The entry ID for this resource. */ - size_t entryId; + Maybe<uint16_t> id; /** - * Whether this resource is public (and must maintain the same - * entry ID across builds). + * Whether this resource is public (and must maintain the same entry ID across builds). */ - Public publicStatus; + Symbol symbolStatus; /** * The resource's values for each configuration. */ - std::vector<ResourceConfigValue> values; + std::vector<std::unique_ptr<ResourceConfigValue>> values; + + ResourceEntry(const StringPiece16& name) : name(name.toString()) { } - inline ResourceEntry(const StringPiece16& _name); - inline ResourceEntry(const ResourceEntry* rhs); + 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); + +private: + DISALLOW_COPY_AND_ASSIGN(ResourceEntry); }; /** * Represents a resource type, which holds entries defined * for this type. */ -struct ResourceTableType { - enum { - kUnsetTypeId = 0xffffffffu - }; - +class ResourceTableType { +public: /** * The logical type of resource (string, drawable, layout, etc.). */ @@ -102,21 +128,49 @@ struct ResourceTableType { /** * The type ID for this resource. */ - size_t typeId; + Maybe<uint8_t> id; /** * Whether this type is public (and must maintain the same * type ID across builds). */ - Public publicStatus; + Symbol symbolStatus; /** * List of resources for this type. */ std::vector<std::unique_ptr<ResourceEntry>> entries; - ResourceTableType(const ResourceType _type); - ResourceTableType(const ResourceTableType* rhs); + explicit ResourceTableType(const ResourceType type) : type(type) { } + + ResourceEntry* findEntry(const StringPiece16& name); + ResourceEntry* findOrCreateEntry(const StringPiece16& name); + +private: + DISALLOW_COPY_AND_ASSIGN(ResourceTableType); +}; + +enum class PackageType { + System, + Vendor, + App, + Dynamic +}; + +class ResourceTablePackage { +public: + PackageType type = PackageType::App; + Maybe<uint8_t> id; + std::u16string name; + + std::vector<std::unique_ptr<ResourceTableType>> types; + + ResourceTablePackage() = default; + ResourceTableType* findType(ResourceType type); + ResourceTableType* findOrCreateType(const ResourceType type); + +private: + DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage); }; /** @@ -125,153 +179,134 @@ struct ResourceTableType { */ class ResourceTable { public: - using iterator = std::vector<std::unique_ptr<ResourceTableType>>::iterator; - using const_iterator = std::vector<std::unique_ptr<ResourceTableType>>::const_iterator; - - enum { - kUnsetPackageId = 0xffffffff - }; - - ResourceTable(); - - size_t getPackageId() const; - void setPackageId(size_t packageId); + ResourceTable() = default; - const std::u16string& getPackage() const; - void setPackage(const StringPiece16& package); - - bool addResource(const ResourceNameRef& name, const ConfigDescription& config, - const SourceLine& source, std::unique_ptr<Value> value); + /** + * 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 SourceLine& source, std::unique_ptr<Value> value); + 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, + IDiagnostics* diag); + + struct SearchResult { + ResourceTablePackage* package; + ResourceTableType* type; + ResourceEntry* entry; + }; - bool addResource(const ResourceNameRef& name, const ResourceId resId, - const ConfigDescription& config, const SourceLine& source, - std::unique_ptr<Value> value); + Maybe<SearchResult> findResource(const ResourceNameRef& name); - bool markPublic(const ResourceNameRef& name, const ResourceId resId, const SourceLine& source); - bool markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId, - const SourceLine& source); + /** + * 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; - /* - * Merges the resources from `other` into this table, mangling the names of the resources - * if `other` has a different package name. + /** + * The list of packages in this table, sorted alphabetically by package name. */ - bool merge(ResourceTable&& other); + std::vector<std::unique_ptr<ResourceTablePackage>> packages; /** - * Returns the string pool used by this ResourceTable. - * Values that reference strings should use this pool to create - * their strings. + * 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. */ - StringPool& getValueStringPool(); - const StringPool& getValueStringPool() const; + ResourceTablePackage* findPackage(const StringPiece16& name); - std::tuple<const ResourceTableType*, const ResourceEntry*> - findResource(const ResourceNameRef& name) const; + ResourceTablePackage* findPackageById(uint8_t id); - iterator begin(); - iterator end(); - const_iterator begin() const; - const_iterator end() const; + ResourceTablePackage* createPackage(const StringPiece16& name, Maybe<uint8_t> id = {}); private: - std::unique_ptr<ResourceTableType>& findOrCreateType(ResourceType type); - std::unique_ptr<ResourceEntry>& findOrCreateEntry(std::unique_ptr<ResourceTableType>& type, - const StringPiece16& name); - - bool addResourceImpl(const ResourceNameRef& name, const ResourceId resId, - const ConfigDescription& config, const SourceLine& source, - std::unique_ptr<Value> value, const char16_t* validChars); - bool markPublicImpl(const ResourceNameRef& name, const ResourceId resId, - const SourceLine& source, const char16_t* validChars); - - std::u16string mPackage; - size_t mPackageId; - - // StringPool must come before mTypes so that it is destroyed after. - // When StringPool references are destroyed (as they will be when mTypes - // is destroyed), they decrement a refCount, which would cause invalid - // memory access if the pool was already destroyed. - StringPool mValuePool; - - std::vector<std::unique_ptr<ResourceTableType>> mTypes; + 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, + 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, + ResourceId resId, + const Symbol& symbol, + const char16_t* validChars, + IDiagnostics* diag); + + DISALLOW_COPY_AND_ASSIGN(ResourceTable); }; -// -// ResourceEntry implementation. -// - -inline ResourceEntry::ResourceEntry(const StringPiece16& _name) : - name(_name.toString()), entryId(kUnsetEntryId) { -} - -inline ResourceEntry::ResourceEntry(const ResourceEntry* rhs) : - name(rhs->name), entryId(rhs->entryId), publicStatus(rhs->publicStatus) { -} - -// -// ResourceTableType implementation. -// - -inline ResourceTableType::ResourceTableType(const ResourceType _type) : - type(_type), typeId(kUnsetTypeId) { -} - -inline ResourceTableType::ResourceTableType(const ResourceTableType* rhs) : - type(rhs->type), typeId(rhs->typeId), publicStatus(rhs->publicStatus) { -} - -// -// ResourceTable implementation. -// - -inline StringPool& ResourceTable::getValueStringPool() { - return mValuePool; -} - -inline const StringPool& ResourceTable::getValueStringPool() const { - return mValuePool; -} - -inline ResourceTable::iterator ResourceTable::begin() { - return mTypes.begin(); -} - -inline ResourceTable::iterator ResourceTable::end() { - return mTypes.end(); -} - -inline ResourceTable::const_iterator ResourceTable::begin() const { - return mTypes.begin(); -} - -inline ResourceTable::const_iterator ResourceTable::end() const { - return mTypes.end(); -} - -inline const std::u16string& ResourceTable::getPackage() const { - return mPackage; -} - -inline size_t ResourceTable::getPackageId() const { - return mPackageId; -} - -inline void ResourceTable::setPackage(const StringPiece16& package) { - mPackage = package.toString(); -} - -inline void ResourceTable::setPackageId(size_t packageId) { - mPackageId = packageId; -} - } // namespace aapt #endif // AAPT_RESOURCE_TABLE_H diff --git a/tools/aapt2/ResourceTableResolver.cpp b/tools/aapt2/ResourceTableResolver.cpp deleted file mode 100644 index 910c2c07fb84..000000000000 --- a/tools/aapt2/ResourceTableResolver.cpp +++ /dev/null @@ -1,202 +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. - */ - -#include "Maybe.h" -#include "NameMangler.h" -#include "Resource.h" -#include "ResourceTable.h" -#include "ResourceTableResolver.h" -#include "ResourceValues.h" -#include "Util.h" - -#include <androidfw/AssetManager.h> -#include <androidfw/ResourceTypes.h> -#include <memory> -#include <vector> - -namespace aapt { - -ResourceTableResolver::ResourceTableResolver( - std::shared_ptr<const ResourceTable> table, - const std::vector<std::shared_ptr<const android::AssetManager>>& sources) : - mTable(table), mSources(sources) { - for (const auto& assetManager : mSources) { - const android::ResTable& resTable = assetManager->getResources(false); - const size_t packageCount = resTable.getBasePackageCount(); - for (size_t i = 0; i < packageCount; i++) { - std::u16string packageName = resTable.getBasePackageName(i).string(); - mIncludedPackages.insert(std::move(packageName)); - } - } -} - -Maybe<ResourceId> ResourceTableResolver::findId(const ResourceName& name) { - Maybe<Entry> result = findAttribute(name); - if (result) { - return result.value().id; - } - return {}; -} - -Maybe<IResolver::Entry> ResourceTableResolver::findAttribute(const ResourceName& name) { - auto cacheIter = mCache.find(name); - if (cacheIter != std::end(mCache)) { - return Entry{ cacheIter->second.id, cacheIter->second.attr.get() }; - } - - ResourceName mangledName; - const ResourceName* nameToSearch = &name; - if (name.package != mTable->getPackage()) { - // This may be a reference to an included resource or - // to a mangled resource. - if (mIncludedPackages.find(name.package) == mIncludedPackages.end()) { - // This is not in our included set, so mangle the name and - // check for that. - mangledName.entry = name.entry; - NameMangler::mangle(name.package, &mangledName.entry); - mangledName.package = mTable->getPackage(); - mangledName.type = name.type; - nameToSearch = &mangledName; - } else { - const CacheEntry* cacheEntry = buildCacheEntry(name); - if (cacheEntry) { - return Entry{ cacheEntry->id, cacheEntry->attr.get() }; - } - return {}; - } - } - - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = mTable->findResource(*nameToSearch); - if (type && entry) { - Entry result = {}; - if (mTable->getPackageId() != ResourceTable::kUnsetPackageId && - type->typeId != ResourceTableType::kUnsetTypeId && - entry->entryId != ResourceEntry::kUnsetEntryId) { - result.id = ResourceId(mTable->getPackageId(), type->typeId, entry->entryId); - } - - if (!entry->values.empty()) { - visitFunc<Attribute>(*entry->values.front().value, [&result](Attribute& attr) { - result.attr = &attr; - }); - } - return result; - } - return {}; -} - -Maybe<ResourceName> ResourceTableResolver::findName(ResourceId resId) { - for (const auto& assetManager : mSources) { - const android::ResTable& table = assetManager->getResources(false); - - android::ResTable::resource_name resourceName; - if (!table.getResourceName(resId.id, false, &resourceName)) { - continue; - } - - const ResourceType* type = parseResourceType(StringPiece16(resourceName.type, - resourceName.typeLen)); - assert(type); - return ResourceName{ - { resourceName.package, resourceName.packageLen }, - *type, - { resourceName.name, resourceName.nameLen } }; - } - return {}; -} - -/** - * This is called when we need to lookup a resource name in the AssetManager. - * Since the values in the AssetManager are not parsed like in a ResourceTable, - * we must create Attribute objects here if we find them. - */ -const ResourceTableResolver::CacheEntry* ResourceTableResolver::buildCacheEntry( - const ResourceName& name) { - for (const auto& assetManager : mSources) { - const android::ResTable& table = assetManager->getResources(false); - - const StringPiece16 type16 = toString(name.type); - ResourceId resId { - table.identifierForName( - name.entry.data(), name.entry.size(), - type16.data(), type16.size(), - name.package.data(), name.package.size()) - }; - - if (!resId.isValid()) { - continue; - } - - CacheEntry& entry = mCache[name]; - entry.id = resId; - - // - // Now check to see if this resource is an Attribute. - // - - const android::ResTable::bag_entry* bagBegin; - ssize_t bags = table.lockBag(resId.id, &bagBegin); - if (bags < 1) { - table.unlockBag(bagBegin); - return &entry; - } - - // Look for the ATTR_TYPE key in the bag and check the types it supports. - uint32_t attrTypeMask = 0; - for (ssize_t i = 0; i < bags; i++) { - if (bagBegin[i].map.name.ident == android::ResTable_map::ATTR_TYPE) { - attrTypeMask = bagBegin[i].map.value.data; - } - } - - entry.attr = util::make_unique<Attribute>(false); - - if (attrTypeMask & android::ResTable_map::TYPE_ENUM || - attrTypeMask & android::ResTable_map::TYPE_FLAGS) { - for (ssize_t i = 0; i < bags; i++) { - if (Res_INTERNALID(bagBegin[i].map.name.ident)) { - // Internal IDs are special keys, which are not enum/flag symbols, so skip. - continue; - } - - android::ResTable::resource_name symbolName; - bool result = table.getResourceName(bagBegin[i].map.name.ident, false, - &symbolName); - assert(result); - const ResourceType* type = parseResourceType( - StringPiece16(symbolName.type, symbolName.typeLen)); - assert(type); - - entry.attr->symbols.push_back(Attribute::Symbol{ - Reference(ResourceNameRef( - StringPiece16(symbolName.package, symbolName.packageLen), - *type, - StringPiece16(symbolName.name, symbolName.nameLen))), - bagBegin[i].map.value.data - }); - } - } - - entry.attr->typeMask |= attrTypeMask; - table.unlockBag(bagBegin); - return &entry; - } - return nullptr; -} - -} // namespace aapt diff --git a/tools/aapt2/ResourceTableResolver.h b/tools/aapt2/ResourceTableResolver.h deleted file mode 100644 index 8f6b0b5993e4..000000000000 --- a/tools/aapt2/ResourceTableResolver.h +++ /dev/null @@ -1,70 +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. - */ - -#ifndef AAPT_RESOURCE_TABLE_RESOLVER_H -#define AAPT_RESOURCE_TABLE_RESOLVER_H - -#include "Maybe.h" -#include "Resolver.h" -#include "Resource.h" -#include "ResourceTable.h" -#include "ResourceValues.h" - -#include <androidfw/AssetManager.h> -#include <memory> -#include <vector> -#include <unordered_set> - -namespace aapt { - -/** - * Encapsulates the search of library sources as well as the local ResourceTable. - */ -class ResourceTableResolver : public IResolver { -public: - /** - * Creates a resolver with a local ResourceTable and an AssetManager - * loaded with library packages. - */ - ResourceTableResolver( - std::shared_ptr<const ResourceTable> table, - const std::vector<std::shared_ptr<const android::AssetManager>>& sources); - - ResourceTableResolver(const ResourceTableResolver&) = delete; // Not copyable. - - virtual Maybe<ResourceId> findId(const ResourceName& name) override; - - virtual Maybe<Entry> findAttribute(const ResourceName& name) override; - - virtual Maybe<ResourceName> findName(ResourceId resId) override; - -private: - struct CacheEntry { - ResourceId id; - std::unique_ptr<Attribute> attr; - }; - - const CacheEntry* buildCacheEntry(const ResourceName& name); - - std::shared_ptr<const ResourceTable> mTable; - std::vector<std::shared_ptr<const android::AssetManager>> mSources; - std::map<ResourceName, CacheEntry> mCache; - std::unordered_set<std::u16string> mIncludedPackages; -}; - -} // namespace aapt - -#endif // AAPT_RESOURCE_TABLE_RESOLVER_H diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index 06d8699730de..d6c52ab83d93 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -14,9 +14,12 @@ * limitations under the License. */ +#include "Diagnostics.h" #include "ResourceTable.h" #include "ResourceValues.h" -#include "Util.h" +#include "util/Util.h" + +#include "test/Builders.h" #include <algorithm> #include <gtest/gtest.h> @@ -25,204 +28,126 @@ namespace aapt { -struct TestValue : public Value { - std::u16string value; - - TestValue(StringPiece16 str) : value(str.toString()) { - } - - TestValue* clone(StringPool* /*newPool*/) const override { - return new TestValue(value); - } - - void print(std::ostream& out) const override { - out << "(test) " << value; - } - - virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {} - virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {} -}; - -struct TestWeakValue : public Value { - bool isWeak() const override { - return true; - } - - TestWeakValue* clone(StringPool* /*newPool*/) const override { - return new TestWeakValue(); - } - - void print(std::ostream& out) const override { - out << "(test) [weak]"; - } - - virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {} - virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {} -}; - TEST(ResourceTableTest, FailToAddResourceWithBadName) { ResourceTable table; - table.setPackage(u"android"); EXPECT_FALSE(table.addResource( - ResourceNameRef{ u"android", ResourceType::kId, u"hey,there" }, - {}, SourceLine{ "test.xml", 21 }, - util::make_unique<TestValue>(u"rawValue"))); + 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" }, - {}, SourceLine{ "test.xml", 21 }, - util::make_unique<TestValue>(u"rawValue"))); + ResourceNameRef(u"android", ResourceType::kId, u"hey:there"), + ConfigDescription{}, "", + test::ValueBuilder<Id>().setSource("test.xml", 21u).build(), + test::getDiagnostics())); } TEST(ResourceTableTest, AddOneResource) { - const std::u16string kAndroidPackage = u"android"; - ResourceTable table; - table.setPackage(kAndroidPackage); - - const ResourceName name = { kAndroidPackage, ResourceType::kAttr, u"id" }; - - EXPECT_TRUE(table.addResource(name, {}, SourceLine{ "test/path/file.xml", 23 }, - util::make_unique<TestValue>(u"rawValue"))); - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = table.findResource(name); - ASSERT_NE(nullptr, type); - ASSERT_NE(nullptr, entry); - EXPECT_EQ(name.entry, entry->name); + EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"), + ConfigDescription{}, + "", + test::ValueBuilder<Id>() + .setSource("test/path/file.xml", 23u).build(), + test::getDiagnostics())); - ASSERT_NE(std::end(entry->values), - std::find_if(std::begin(entry->values), std::end(entry->values), - [](const ResourceConfigValue& val) -> bool { - return val.config == ConfigDescription{}; - })); + ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id")); } TEST(ResourceTableTest, AddMultipleResources) { - const std::u16string kAndroidPackage = u"android"; ResourceTable table; - table.setPackage(kAndroidPackage); ConfigDescription config; ConfigDescription languageConfig; memcpy(languageConfig.language, "pl", sizeof(languageConfig.language)); EXPECT_TRUE(table.addResource( - ResourceName{ kAndroidPackage, ResourceType::kAttr, u"layout_width" }, - config, SourceLine{ "test/path/file.xml", 10 }, - util::make_unique<TestValue>(u"rawValue"))); + test::parseNameOrDie(u"@android:attr/layout_width"), + config, + "", + test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(), + test::getDiagnostics())); EXPECT_TRUE(table.addResource( - ResourceName{ kAndroidPackage, ResourceType::kAttr, u"id" }, - config, SourceLine{ "test/path/file.xml", 12 }, - util::make_unique<TestValue>(u"rawValue"))); + test::parseNameOrDie(u"@android:attr/id"), + config, + "", + test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(), + test::getDiagnostics())); EXPECT_TRUE(table.addResource( - ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" }, - config, SourceLine{ "test/path/file.xml", 14 }, - util::make_unique<TestValue>(u"Ok"))); + test::parseNameOrDie(u"@android:string/ok"), + config, + "", + test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(), + test::getDiagnostics())); EXPECT_TRUE(table.addResource( - ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" }, - languageConfig, SourceLine{ "test/path/file.xml", 20 }, - util::make_unique<TestValue>(u"Tak"))); - - const auto endTypeIter = std::end(table); - auto typeIter = std::begin(table); - - ASSERT_NE(endTypeIter, typeIter); - EXPECT_EQ(ResourceType::kAttr, (*typeIter)->type); - - { - const std::unique_ptr<ResourceTableType>& type = *typeIter; - const auto endEntryIter = std::end(type->entries); - auto entryIter = std::begin(type->entries); - ASSERT_NE(endEntryIter, entryIter); - EXPECT_EQ(std::u16string(u"id"), (*entryIter)->name); - - ++entryIter; - ASSERT_NE(endEntryIter, entryIter); - EXPECT_EQ(std::u16string(u"layout_width"), (*entryIter)->name); - - ++entryIter; - ASSERT_EQ(endEntryIter, entryIter); - } - - ++typeIter; - ASSERT_NE(endTypeIter, typeIter); - EXPECT_EQ(ResourceType::kString, (*typeIter)->type); - - { - const std::unique_ptr<ResourceTableType>& type = *typeIter; - const auto endEntryIter = std::end(type->entries); - auto entryIter = std::begin(type->entries); - ASSERT_NE(endEntryIter, entryIter); - EXPECT_EQ(std::u16string(u"ok"), (*entryIter)->name); - - { - const std::unique_ptr<ResourceEntry>& entry = *entryIter; - const auto endConfigIter = std::end(entry->values); - auto configIter = std::begin(entry->values); - - ASSERT_NE(endConfigIter, configIter); - EXPECT_EQ(config, configIter->config); - const TestValue* value = - dynamic_cast<const TestValue*>(configIter->value.get()); - ASSERT_NE(nullptr, value); - EXPECT_EQ(std::u16string(u"Ok"), value->value); - - ++configIter; - ASSERT_NE(endConfigIter, configIter); - EXPECT_EQ(languageConfig, configIter->config); - EXPECT_NE(nullptr, configIter->value); - - value = dynamic_cast<const TestValue*>(configIter->value.get()); - ASSERT_NE(nullptr, value); - EXPECT_EQ(std::u16string(u"Tak"), value->value); - - ++configIter; - EXPECT_EQ(endConfigIter, configIter); - } - - ++entryIter; - ASSERT_EQ(endEntryIter, entryIter); - } - - ++typeIter; - EXPECT_EQ(endTypeIter, typeIter); + 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)); } TEST(ResourceTableTest, OverrideWeakResourceValue) { - const std::u16string kAndroid = u"android"; + ResourceTable table; + + ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@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()); + + ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@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()); +} + +TEST(ResourceTableTest, ProductVaryingValues) { ResourceTable table; - table.setPackage(kAndroid); - table.setPackageId(0x01); - - ASSERT_TRUE(table.addResource( - ResourceName{ kAndroid, ResourceType::kAttr, u"foo" }, - {}, {}, util::make_unique<TestWeakValue>())); - - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = table.findResource( - ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" }); - ASSERT_NE(nullptr, type); - ASSERT_NE(nullptr, entry); - ASSERT_EQ(entry->values.size(), 1u); - EXPECT_TRUE(entry->values.front().value->isWeak()); - - ASSERT_TRUE(table.addResource(ResourceName{ kAndroid, ResourceType::kAttr, u"foo" }, {}, {}, - util::make_unique<TestValue>(u"bar"))); - - std::tie(type, entry) = table.findResource( - ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" }); - ASSERT_NE(nullptr, type); - ASSERT_NE(nullptr, entry); - ASSERT_EQ(entry->values.size(), 1u); - EXPECT_FALSE(entry->values.front().value->isWeak()); + + 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); } } // namespace aapt diff --git a/tools/aapt2/ResourceTypeExtensions.h b/tools/aapt2/ResourceTypeExtensions.h deleted file mode 100644 index dcbe9233f6b0..000000000000 --- a/tools/aapt2/ResourceTypeExtensions.h +++ /dev/null @@ -1,147 +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. - */ - -#ifndef AAPT_RESOURCE_TYPE_EXTENSIONS_H -#define AAPT_RESOURCE_TYPE_EXTENSIONS_H - -#include <androidfw/ResourceTypes.h> - -namespace aapt { - -/** - * New android::ResChunk_header types defined - * for AAPT to use. - * - * TODO(adamlesinski): Consider reserving these - * enums in androidfw/ResourceTypes.h to avoid - * future collisions. - */ -enum { - RES_TABLE_PUBLIC_TYPE = 0x000d, - - /** - * A chunk that holds the string pool - * for source entries (path/to/source:line). - */ - RES_TABLE_SOURCE_POOL_TYPE = 0x000e, - - /** - * A chunk holding names of externally - * defined symbols and offsets to where - * they are referenced in the table. - */ - RES_TABLE_SYMBOL_TABLE_TYPE = 0x000f, -}; - -/** - * New resource types that are meant to only be used - * by AAPT and will not end up on the device. - */ -struct ExtendedTypes { - enum { - /** - * A raw string value that hasn't had its escape sequences - * processed nor whitespace removed. - */ - TYPE_RAW_STRING = 0xfe - }; -}; - -struct Public_header { - android::ResChunk_header header; - - /** - * The ID of the type this structure refers to. - */ - uint8_t typeId; - - /** - * Reserved. Must be 0. - */ - uint8_t res0; - - /** - * Reserved. Must be 0. - */ - uint16_t res1; - - /** - * Number of public entries. - */ - uint32_t count; -}; - -struct Public_entry { - uint16_t entryId; - uint16_t res0; - android::ResStringPool_ref key; - android::ResStringPool_ref source; - uint32_t sourceLine; -}; - -/** - * A chunk with type RES_TABLE_SYMBOL_TABLE_TYPE. - * Following the header are count number of SymbolTable_entry - * structures, followed by an android::ResStringPool_header. - */ -struct SymbolTable_header { - android::ResChunk_header header; - - /** - * Number of SymbolTable_entry structures following - * this header. - */ - uint32_t count; -}; - -struct SymbolTable_entry { - /** - * Offset from the beginning of the resource table - * where the symbol entry is referenced. - */ - uint32_t offset; - - /** - * The index into the string pool where the name of this - * symbol exists. - */ - uint32_t stringIndex; -}; - -/** - * A structure representing the source of a resourc entry. - * Appears after an android::ResTable_entry or android::ResTable_map_entry. - * - * TODO(adamlesinski): This causes some issues when runtime code checks - * the size of an android::ResTable_entry. It assumes it is an - * android::ResTable_map_entry if the size is bigger than an android::ResTable_entry - * which may not be true if this structure is present. - */ -struct ResTable_entry_source { - /** - * Index into the source string pool. - */ - uint32_t pathIndex; - - /** - * Line number this resource was defined on. - */ - uint32_t line; -}; - -} // namespace aapt - -#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp new file mode 100644 index 000000000000..74c48b0f8426 --- /dev/null +++ b/tools/aapt2/ResourceUtils.cpp @@ -0,0 +1,580 @@ +/* + * 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 "NameMangler.h" +#include "ResourceUtils.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); + + return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty()); +} + +bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) { + if (str.empty()) { + return false; + } + + size_t offset = 0; + bool priv = false; + if (str.data()[0] == u'*') { + priv = true; + offset = 1; + } + + StringPiece16 package; + StringPiece16 type; + StringPiece16 entry; + if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) { + return false; + } + + const ResourceType* parsedType = parseResourceType(type); + if (!parsedType) { + return false; + } + + if (entry.empty()) { + return false; + } + + if (outRef) { + outRef->package = package; + outRef->type = *parsedType; + outRef->entry = entry; + } + + if (outPrivate) { + *outPrivate = priv; + } + return true; +} + +bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate, + bool* outPrivate) { + StringPiece16 trimmedStr(util::trimWhitespace(str)); + if (trimmedStr.empty()) { + return false; + } + + 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; + } + return false; +} + +bool isReference(const StringPiece16& str) { + return tryParseReference(str, nullptr, nullptr, nullptr); +} + +bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) { + StringPiece16 trimmedStr(util::trimWhitespace(str)); + if (trimmedStr.empty()) { + 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; + } + return false; +} + +bool isAttributeReference(const StringPiece16& str) { + return tryParseAttributeReference(str, nullptr); +} + +/* + * Style parent's are a bit different. We accept the following formats: + * + * @[[*]package:][style/]<entry> + * ?[[*]package:]style/<entry> + * <[*]package>:[style/]<entry> + * [[*]package:style/]<entry> + */ +Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) { + if (str.empty()) { + return {}; + } + + StringPiece16 name = str; + + bool hasLeadingIdentifiers = false; + bool privateRef = false; + + // 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); + } + + if (name.data()[0] == u'*') { + privateRef = true; + name = name.substr(1, name.size() - 1); + } + + 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 {}; + } + } + + if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) { + std::stringstream err; + err << "invalid parent reference '" << str << "'"; + *outError = err.str(); + return {}; + } + + Reference result(ref); + result.privateReference = privateRef; + return result; +} + +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; + } + + if (tryParseAttributeReference(str, &ref)) { + if (outCreate) { + *outCreate = false; + } + return util::make_unique<Reference>(ref, Reference::Type::kAttribute); + } + return {}; +} + +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); +} + +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); + } + } + return {}; +} + +std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr, + const StringPiece16& str) { + android::Res_value flags = { }; + flags.dataType = android::Res_value::TYPE_INT_DEC; + 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); +} + +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; + } +} + +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 {}; + } + + 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); +} + +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; +} + +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> tryParseInt(const StringPiece16& str) { + android::Res_value value; + if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) { + return {}; + } + return util::make_unique<BinaryPrimitive>(value); +} + +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); +} + +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; + + case android::Res_value::TYPE_STRING: + return android::ResTable_map::TYPE_STRING; + + case android::Res_value::TYPE_FLOAT: + return android::ResTable_map::TYPE_FLOAT; + + case android::Res_value::TYPE_DIMENSION: + return android::ResTable_map::TYPE_DIMENSION; + + case android::Res_value::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; + + case android::Res_value::TYPE_INT_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; + + default: + 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 {}; +} + +/** + * 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::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(); +} + +} // namespace ResourceUtils +} // namespace aapt diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h new file mode 100644 index 000000000000..a0fbcc6e700b --- /dev/null +++ b/tools/aapt2/ResourceUtils.h @@ -0,0 +1,172 @@ +/* + * 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. + */ + +#ifndef AAPT_RESOURCEUTILS_H +#define AAPT_RESOURCEUTILS_H + +#include "NameMangler.h" +#include "Resource.h" +#include "ResourceValues.h" +#include "util/StringPiece.h" + +#include <functional> +#include <memory> + +namespace aapt { +namespace ResourceUtils { + +/* + * Extracts the package, type, and name from a string of the format: + * + * [package:]type/name + * + * where the package can be empty. Validation must be performed on each + * 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); + +/** + * 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. + */ +bool parseResourceName(const StringPiece16& str, ResourceNameRef* outResource, + bool* outPrivate = nullptr); + +/* + * Returns true if the string was parsed as a reference (@[+][package:]type/name), with + * `outReference` 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. + */ +bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference, + bool* outCreate = nullptr, bool* outPrivate = nullptr); + +/* + * Returns true if the string is in the form of a resource reference (@[+][package:]type/name). + */ +bool isReference(const StringPiece16& str); + +/* + * Returns true if the string was parsed as an attribute reference (?[package:][type/]name), + * with `outReference` set to the parsed reference. + */ +bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outReference); + +/** + * Returns true if the string is in the form of an attribute reference(?[package:][type/]name). + */ +bool isAttributeReference(const StringPiece16& str); + +/** + * Returns true if the value is a boolean, putting the result in `outValue`. + */ +bool tryParseBool(const StringPiece16& str, bool* outValue); + +/* + * 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: + * + * @[package:]style/<entry> or + * ?[package:]style/<entry> or + * <package>:[style/]<entry> + */ +Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError); + +/* + * 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); + +/* + * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed + * as one. + */ +std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str); + +/* + * Returns a BinaryPrimitve object representing a color if the string was parsed + * as one. + */ +std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str); + +/* + * Returns a BinaryPrimitve object representing a boolean if the string was parsed + * as one. + */ +std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str); + +/* + * Returns a BinaryPrimitve object representing an integer if the string was parsed + * as one. + */ +std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& 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); + +/* + * 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); + +/* + * 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); +/* + * 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 + * 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> parseItemForAttribute( + const StringPiece16& value, uint32_t typeMask, + std::function<void(const ResourceName&)> onCreateReference = {}); + +uint32_t androidTypeToAttributeTypeMask(uint16_t type); + +/** + * 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 + * requires mangling. + */ +std::string buildResourceFileName(const ResourceFile& resFile, const NameMangler* mangler); + +} // namespace ResourceUtils +} // namespace aapt + +#endif /* AAPT_RESOURCEUTILS_H */ diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp new file mode 100644 index 000000000000..7425f97ef8de --- /dev/null +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -0,0 +1,207 @@ +/* + * 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 "Resource.h" +#include "ResourceUtils.h" +#include "test/Builders.h" +#include "test/Common.h" + +#include <gtest/gtest.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); +} + +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)); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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); +} + +TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) { + bool create = false; + bool privateRef = false; + ResourceNameRef actual; + EXPECT_FALSE(ResourceUtils::tryParseReference(u"@+android:color/foo", &actual, &create, + &privateRef)); +} + +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")); +} + +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")); +} + +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); +} + +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); +} + +} // namespace aapt diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index aabb375e6c5e..dd7ff013e524 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -15,42 +15,45 @@ */ #include "Resource.h" -#include "ResourceTypeExtensions.h" +#include "ResourceUtils.h" #include "ResourceValues.h" -#include "Util.h" +#include "ValueVisitor.h" +#include "io/File.h" +#include "util/Util.h" #include <androidfw/ResourceTypes.h> #include <limits> namespace aapt { -bool Value::isItem() const { - return false; +template <typename Derived> +void BaseValue<Derived>::accept(RawValueVisitor* visitor) { + visitor->visit(static_cast<Derived*>(this)); } -bool Value::isWeak() const { - return false; -} - -bool Item::isItem() const { - return true; +template <typename Derived> +void BaseItem<Derived>::accept(RawValueVisitor* visitor) { + visitor->visit(static_cast<Derived*>(this)); } RawString::RawString(const StringPool::Ref& ref) : value(ref) { } RawString* RawString::clone(StringPool* newPool) const { - return new RawString(newPool->makeRef(*value)); + RawString* rs = new RawString(newPool->makeRef(*value)); + rs->mComment = mComment; + rs->mSource = mSource; + return rs; } -bool RawString::flatten(android::Res_value& outValue) const { - outValue.dataType = ExtendedTypes::TYPE_RAW_STRING; - outValue.data = static_cast<uint32_t>(value.getIndex()); +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; } -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) { @@ -63,177 +66,200 @@ Reference::Reference(const ResourceNameRef& n, Type t) : Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) { } -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 = id.id; +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; } Reference* Reference::clone(StringPool* /*newPool*/) const { - Reference* ref = new Reference(); - ref->referenceType = referenceType; - ref->name = name; - ref->id = id; - return ref; + return new Reference(*this); } -void Reference::print(std::ostream& out) const { - out << "(reference) "; +void Reference::print(std::ostream* out) const { + *out << "(reference) "; if (referenceType == Reference::Type::kResource) { - out << "@"; + *out << "@"; + if (privateReference) { + *out << "*"; + } } else { - out << "?"; + *out << "?"; } - if (name.isValid()) { - out << name; + if (name) { + *out << name.value(); } - if (id.isValid() || Res_INTERNALID(id.id)) { - out << " " << id; + if (id && !Res_INTERNALID(id.value().id)) { + *out << " " << id.value(); } } -bool Id::isWeak() const { +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 = 0; - return true; +Id* Id::clone(StringPool* /*newPool*/) const { + return new Id(*this); } -Id* Id::clone(StringPool* /*newPool*/) const { - return new Id(); +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), mTranslateable(true) { } -String::String(const StringPool::Ref& ref) : value(ref) { +void String::setTranslateable(bool val) { + mTranslateable = val; } -bool String::flatten(android::Res_value& outValue) const { - // Verify that our StringPool index is within encodeable limits. +bool String::isTranslateable() const { + return mTranslateable; +} + +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 = static_cast<uint32_t>(value.getIndex()); + outValue->dataType = android::Res_value::TYPE_STRING; + outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex())); return true; } String* String::clone(StringPool* newPool) const { - return new String(newPool->makeRef(*value)); + String* str = new String(newPool->makeRef(*value)); + str->mComment = mComment; + str->mSource = mSource; + return str; +} + +void String::print(std::ostream* out) const { + *out << "(string) \"" << *value << "\""; } -void String::print(std::ostream& out) const { - out << "(string) \"" << *value << "\""; +StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref), mTranslateable(true) { } -StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) { +void StyledString::setTranslateable(bool val) { + mTranslateable = val; } -bool StyledString::flatten(android::Res_value& outValue) const { +bool StyledString::isTranslateable() const { + return mTranslateable; +} + +bool StyledString::flatten(android::Res_value* outValue) const { if (value.getIndex() > std::numeric_limits<uint32_t>::max()) { return false; } - outValue.dataType = android::Res_value::TYPE_STRING; - outValue.data = static_cast<uint32_t>(value.getIndex()); + outValue->dataType = android::Res_value::TYPE_STRING; + outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex())); return true; } StyledString* StyledString::clone(StringPool* newPool) const { - return new StyledString(newPool->makeRef(value)); + StyledString* str = new StyledString(newPool->makeRef(value)); + str->mComment = mComment; + str->mSource = mSource; + return str; } -void StyledString::print(std::ostream& out) const { - out << "(styled string) \"" << *value->str << "\""; +void StyledString::print(std::ostream* out) const { + *out << "(styled string) \"" << *value->str << "\""; } FileReference::FileReference(const StringPool::Ref& _path) : path(_path) { } -bool FileReference::flatten(android::Res_value& outValue) const { +bool FileReference::flatten(android::Res_value* outValue) const { if (path.getIndex() > std::numeric_limits<uint32_t>::max()) { return false; } - outValue.dataType = android::Res_value::TYPE_STRING; - outValue.data = static_cast<uint32_t>(path.getIndex()); + outValue->dataType = android::Res_value::TYPE_STRING; + outValue->data = util::hostToDevice32(static_cast<uint32_t>(path.getIndex())); return true; } FileReference* FileReference::clone(StringPool* newPool) const { - return new FileReference(newPool->makeRef(*path)); + FileReference* fr = new FileReference(newPool->makeRef(*path)); + fr->file = file; + fr->mComment = mComment; + fr->mSource = mSource; + 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) { } -bool BinaryPrimitive::flatten(android::Res_value& outValue) const { - outValue = value; +BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) { + value.dataType = dataType; + value.data = 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(value); + return new BinaryPrimitive(*this); } -void BinaryPrimitive::print(std::ostream& out) const { +void BinaryPrimitive::print(std::ostream* out) const { switch (value.dataType) { case android::Res_value::TYPE_NULL: - out << "(null)"; + *out << "(null)"; break; case android::Res_value::TYPE_INT_DEC: - out << "(integer) " << value.data; + *out << "(integer) " << static_cast<int32_t>(value.data); break; case android::Res_value::TYPE_INT_HEX: - out << "(integer) " << std::hex << value.data << std::dec; + *out << "(integer) " << std::hex << value.data << std::dec; break; case android::Res_value::TYPE_INT_BOOLEAN: - out << "(boolean) " << (value.data != 0 ? "true" : "false"); + *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; + *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; + *out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x" + << std::hex << value.data << std::dec; break; } } -Attribute::Attribute(bool w, uint32_t t) : weak(w), typeMask(t) { -} - -bool Attribute::isWeak() const { - return weak; +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; } Attribute* Attribute::clone(StringPool* /*newPool*/) const { - Attribute* attr = new Attribute(weak); - attr->typeMask = typeMask; - std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols)); - return attr; + return new Attribute(*this); } -void Attribute::printMask(std::ostream& out) const { +void Attribute::printMask(std::ostream* out) const { if (typeMask == android::ResTable_map::TYPE_ANY) { - out << "any"; + *out << "any"; return; } @@ -242,110 +268,189 @@ void Attribute::printMask(std::ostream& out) const { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "reference"; + *out << "reference"; } if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "string"; + *out << "string"; } if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "integer"; + *out << "integer"; } if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "boolean"; + *out << "boolean"; } if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "color"; + *out << "color"; } if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "float"; + *out << "float"; } if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "dimension"; + *out << "dimension"; } if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "fraction"; + *out << "fraction"; } if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "enum"; + *out << "enum"; } if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "flags"; + *out << "flags"; } } -void Attribute::print(std::ostream& out) const { - out << "(attr) "; +void Attribute::print(std::ostream* out) const { + *out << "(attr) "; printMask(out); - out << " [" - << util::joiner(symbols.begin(), symbols.end(), ", ") - << "]"; + if (!symbols.empty()) { + *out << " [" + << util::joiner(symbols.begin(), symbols.end(), ", ") + << "]"; + } - if (weak) { - out << " [weak]"; + if (isWeak()) { + *out << " [weak]"; } } +static void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr, + const Item* value) { + *msg << "expected"; + if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) { + *msg << " boolean"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_COLOR) { + *msg << " color"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) { + *msg << " dimension"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_ENUM) { + *msg << " enum"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) { + *msg << " flags"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) { + *msg << " float"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) { + *msg << " fraction"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) { + *msg << " integer"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) { + *msg << " reference"; + } + + if (attr->typeMask & 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; +} + 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, @@ -355,38 +460,50 @@ Style* Style::clone(StringPool* newPool) const { return style; } -void Style::print(std::ostream& out) const { - out << "(style) "; - if (!parent.name.entry.empty()) { - out << parent.name; +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 << " [" + *out << " [" << util::joiner(entries.begin(), entries.end(), ", ") << "]"; } static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value) { - out << value.key.name << " = "; - value.value->print(out); + if (value.key.name) { + out << value.key.name.value(); + } else { + out << "???"; + } + out << " = "; + value.value->print(&out); return out; } 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))); } return array; } -void Array::print(std::ostream& out) const { - out << "(array) [" +void Array::print(std::ostream* out) const { + *out << "(array) [" << util::joiner(items.begin(), items.end(), ", ") << "]"; } 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]) { @@ -396,8 +513,8 @@ Plural* Plural::clone(StringPool* newPool) const { return p; } -void Plural::print(std::ostream& out) const { - out << "(plural)"; +void Plural::print(std::ostream* out) const { + *out << "(plural)"; } static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) { @@ -405,13 +522,11 @@ static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Ite } Styleable* Styleable::clone(StringPool* /*newPool*/) const { - Styleable* styleable = new Styleable(); - std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries)); - return styleable; + return new Styleable(*this); } -void Styleable::print(std::ostream& out) const { - out << "(styleable) " << " [" +void Styleable::print(std::ostream* out) const { + *out << "(styleable) " << " [" << util::joiner(entries.begin(), entries.end(), ", ") << "]"; } diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index ef6594e6f231..43354acf1d0b 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -17,8 +17,11 @@ #ifndef AAPT_RESOURCE_VALUES_H #define AAPT_RESOURCE_VALUES_H +#include "Diagnostics.h" #include "Resource.h" #include "StringPool.h" +#include "io/File.h" +#include "util/Maybe.h" #include <array> #include <androidfw/ResourceTypes.h> @@ -27,9 +30,7 @@ namespace aapt { -struct ValueVisitor; -struct ConstValueVisitor; -struct ValueVisitorArgs; +struct RawValueVisitor; /** * A resource value. This is an all-encompassing representation @@ -39,26 +40,54 @@ struct ValueVisitorArgs; * but it is the simplest strategy. */ struct Value { + virtual ~Value() = default; + /** - * Whether or not this is an Item. + * Whether this value is weak and can be overridden without + * warning or error. Default is false. */ - virtual bool isItem() const; + bool isWeak() const { + return mWeak; + } + + void setWeak(bool val) { + mWeak = val; + } /** - * Whether this value is weak and can be overriden without - * warning or error. Default for base class is false. + * Returns the source where this value was defined. */ - virtual bool isWeak() const; + const Source& getSource() const { + return mSource; + } + + void setSource(const Source& source) { + mSource = source; + } + + void setSource(Source&& source) { + mSource = std::move(source); + } /** - * Calls the appropriate overload of ValueVisitor. + * Returns the comment that was associated with this resource. */ - virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) = 0; + StringPiece16 getComment() const { + return mComment; + } + + void setComment(const StringPiece16& str) { + mComment = str.toString(); + } + + void setComment(std::u16string&& str) { + mComment = std::move(str); + } /** - * Const version of accept(). + * Calls the appropriate overload of ValueVisitor. */ - virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const = 0; + virtual void accept(RawValueVisitor* visitor) = 0; /** * Clone the value. @@ -68,7 +97,12 @@ struct Value { /** * Human readable printout of this value. */ - virtual void print(std::ostream& out) const = 0; + virtual void print(std::ostream* out) const = 0; + +protected: + Source mSource; + std::u16string mComment; + bool mWeak = false; }; /** @@ -76,8 +110,7 @@ struct Value { */ template <typename Derived> struct BaseValue : public Value { - virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override; - virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override; + void accept(RawValueVisitor* visitor) override; }; /** @@ -85,20 +118,15 @@ struct BaseValue : public Value { */ struct Item : public Value { /** - * An Item is, of course, an Item. - */ - virtual bool isItem() const override; - - /** * 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 ocurred. + * Returns false if an error occurred. */ - virtual bool flatten(android::Res_value& outValue) const = 0; + virtual bool flatten(android::Res_value* outValue) const = 0; }; /** @@ -106,8 +134,7 @@ struct Item : public Value { */ template <typename Derived> struct BaseItem : public Item { - virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override; - virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override; + void accept(RawValueVisitor* visitor) override; }; /** @@ -122,28 +149,28 @@ struct Reference : public BaseItem<Reference> { kAttribute, }; - ResourceName name; - ResourceId id; + Maybe<ResourceName> name; + Maybe<ResourceId> id; Reference::Type referenceType; bool privateReference = false; Reference(); - Reference(const ResourceNameRef& n, Type type = Type::kResource); - Reference(const ResourceId& i, Type type = Type::kResource); + explicit Reference(const ResourceNameRef& n, Type type = Type::kResource); + explicit Reference(const ResourceId& i, Type type = Type::kResource); - bool flatten(android::Res_value& outValue) const override; + bool flatten(android::Res_value* outValue) const override; Reference* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; /** * An ID resource. Has no real value, just a place holder. */ struct Id : public BaseItem<Id> { - bool isWeak() const override; - bool flatten(android::Res_value& out) const override; + Id() { mWeak = true; } + bool flatten(android::Res_value* out) const override; Id* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; /** @@ -156,9 +183,9 @@ struct RawString : public BaseItem<RawString> { RawString(const StringPool::Ref& ref); - bool flatten(android::Res_value& outValue) const override; + bool flatten(android::Res_value* outValue) const override; RawString* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; struct String : public BaseItem<String> { @@ -166,9 +193,17 @@ struct String : public BaseItem<String> { String(const StringPool::Ref& ref); - bool flatten(android::Res_value& outValue) const override; + // Whether the string is marked as translateable. This does not persist when flattened. + // It is only used during compilation phase. + void setTranslateable(bool val); + bool isTranslateable() const; + + bool flatten(android::Res_value* outValue) const override; String* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; + +private: + bool mTranslateable; }; struct StyledString : public BaseItem<StyledString> { @@ -176,20 +211,33 @@ struct StyledString : public BaseItem<StyledString> { StyledString(const StringPool::StyleRef& ref); - bool flatten(android::Res_value& outValue) const override; + // Whether the string is marked as translateable. This does not persist when flattened. + // It is only used during compilation phase. + void setTranslateable(bool val); + bool isTranslateable() const; + + bool flatten(android::Res_value* outValue) const override; StyledString* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; + +private: + bool mTranslateable; }; struct FileReference : public BaseItem<FileReference> { StringPool::Ref path; + /** + * A handle to the file object from which this file can be read. + */ + io::IFile* file = nullptr; + FileReference() = default; FileReference(const StringPool::Ref& path); - bool flatten(android::Res_value& outValue) const override; + bool flatten(android::Res_value* outValue) const override; FileReference* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; /** @@ -200,10 +248,11 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> { BinaryPrimitive() = default; BinaryPrimitive(const android::Res_value& val); + BinaryPrimitive(uint8_t dataType, uint32_t data); - bool flatten(android::Res_value& outValue) const override; + bool flatten(android::Res_value* outValue) const override; BinaryPrimitive* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; struct Attribute : public BaseValue<Attribute> { @@ -212,18 +261,17 @@ struct Attribute : public BaseValue<Attribute> { uint32_t value; }; - bool weak; uint32_t typeMask; - uint32_t minInt; - uint32_t maxInt; + int32_t minInt; + int32_t maxInt; std::vector<Symbol> symbols; Attribute(bool w, uint32_t t = 0u); - bool isWeak() const override; - virtual Attribute* clone(StringPool* newPool) const override; - void printMask(std::ostream& out) const; - virtual void print(std::ostream& out) 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 Style : public BaseValue<Style> { @@ -232,7 +280,7 @@ struct Style : public BaseValue<Style> { std::unique_ptr<Item> value; }; - Reference parent; + Maybe<Reference> parent; /** * If set to true, the parent was auto inferred from the @@ -243,14 +291,14 @@ struct Style : public BaseValue<Style> { std::vector<Entry> entries; Style* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; struct Array : public BaseValue<Array> { std::vector<std::unique_ptr<Item>> items; Array* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; struct Plural : public BaseValue<Plural> { @@ -267,180 +315,31 @@ struct Plural : public BaseValue<Plural> { std::array<std::unique_ptr<Item>, Count> values; Plural* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; struct Styleable : public BaseValue<Styleable> { std::vector<Reference> entries; Styleable* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; /** * Stream operator for printing Value objects. */ inline ::std::ostream& operator<<(::std::ostream& out, const Value& value) { - value.print(out); + value.print(&out); return out; } inline ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) { - return out << s.symbol.name.entry << "=" << s.value; -} - -/** - * The argument object that gets passed through the value - * back to the ValueVisitor. Subclasses of ValueVisitor should - * subclass ValueVisitorArgs to contain the data they need - * to operate. - */ -struct ValueVisitorArgs {}; - -/** - * Visits a value and runs the appropriate method based on its type. - */ -struct ValueVisitor { - virtual void visit(Reference& reference, ValueVisitorArgs& args) { - visitItem(reference, args); - } - - virtual void visit(RawString& string, ValueVisitorArgs& args) { - visitItem(string, args); - } - - virtual void visit(String& string, ValueVisitorArgs& args) { - visitItem(string, args); + if (s.symbol.name) { + out << s.symbol.name.value().entry; + } else { + out << "???"; } - - virtual void visit(StyledString& string, ValueVisitorArgs& args) { - visitItem(string, args); - } - - virtual void visit(FileReference& file, ValueVisitorArgs& args) { - visitItem(file, args); - } - - virtual void visit(Id& id, ValueVisitorArgs& args) { - visitItem(id, args); - } - - virtual void visit(BinaryPrimitive& primitive, ValueVisitorArgs& args) { - visitItem(primitive, args); - } - - virtual void visit(Attribute& attr, ValueVisitorArgs& args) {} - virtual void visit(Style& style, ValueVisitorArgs& args) {} - virtual void visit(Array& array, ValueVisitorArgs& args) {} - virtual void visit(Plural& array, ValueVisitorArgs& args) {} - virtual void visit(Styleable& styleable, ValueVisitorArgs& args) {} - - virtual void visitItem(Item& item, ValueVisitorArgs& args) {} -}; - -/** - * Const version of ValueVisitor. - */ -struct ConstValueVisitor { - virtual void visit(const Reference& reference, ValueVisitorArgs& args) { - visitItem(reference, args); - } - - virtual void visit(const RawString& string, ValueVisitorArgs& args) { - visitItem(string, args); - } - - virtual void visit(const String& string, ValueVisitorArgs& args) { - visitItem(string, args); - } - - virtual void visit(const StyledString& string, ValueVisitorArgs& args) { - visitItem(string, args); - } - - virtual void visit(const FileReference& file, ValueVisitorArgs& args) { - visitItem(file, args); - } - - virtual void visit(const Id& id, ValueVisitorArgs& args) { - visitItem(id, args); - } - - virtual void visit(const BinaryPrimitive& primitive, ValueVisitorArgs& args) { - visitItem(primitive, args); - } - - virtual void visit(const Attribute& attr, ValueVisitorArgs& args) {} - virtual void visit(const Style& style, ValueVisitorArgs& args) {} - virtual void visit(const Array& array, ValueVisitorArgs& args) {} - virtual void visit(const Plural& array, ValueVisitorArgs& args) {} - virtual void visit(const Styleable& styleable, ValueVisitorArgs& args) {} - - virtual void visitItem(const Item& item, ValueVisitorArgs& args) {} -}; - -/** - * Convenience Visitor that forwards a specific type to a function. - * Args are not used as the function can bind variables. Do not use - * directly, use the wrapper visitFunc() method. - */ -template <typename T, typename TFunc> -struct ValueVisitorFunc : ValueVisitor { - TFunc func; - - ValueVisitorFunc(TFunc f) : func(f) { - } - - void visit(T& value, ValueVisitorArgs&) override { - func(value); - } -}; - -/** - * Const version of ValueVisitorFunc. - */ -template <typename T, typename TFunc> -struct ConstValueVisitorFunc : ConstValueVisitor { - TFunc func; - - ConstValueVisitorFunc(TFunc f) : func(f) { - } - - void visit(const T& value, ValueVisitorArgs&) override { - func(value); - } -}; - -template <typename T, typename TFunc> -void visitFunc(Value& value, TFunc f) { - ValueVisitorFunc<T, TFunc> visitor(f); - value.accept(visitor, ValueVisitorArgs{}); -} - -template <typename T, typename TFunc> -void visitFunc(const Value& value, TFunc f) { - ConstValueVisitorFunc<T, TFunc> visitor(f); - value.accept(visitor, ValueVisitorArgs{}); -} - -template <typename Derived> -void BaseValue<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) { - visitor.visit(static_cast<Derived&>(*this), args); -} - -template <typename Derived> -void BaseValue<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const { - visitor.visit(static_cast<const Derived&>(*this), args); -} - -template <typename Derived> -void BaseItem<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) { - visitor.visit(static_cast<Derived&>(*this), args); -} - -template <typename Derived> -void BaseItem<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const { - visitor.visit(static_cast<const Derived&>(*this), args); + return out << "=" << s.value; } } // namespace aapt diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp index d957999f492b..48dc521d843c 100644 --- a/tools/aapt2/Resource_test.cpp +++ b/tools/aapt2/Resource_test.cpp @@ -69,10 +69,6 @@ TEST(ResourceTypeTest, ParseResourceTypes) { ASSERT_NE(type, nullptr); EXPECT_EQ(*type, ResourceType::kInteger); - type = parseResourceType(u"integer-array"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kIntegerArray); - type = parseResourceType(u"interpolator"); ASSERT_NE(type, nullptr); EXPECT_EQ(*type, ResourceType::kInterpolator); diff --git a/tools/aapt2/ScopedXmlPullParser.cpp b/tools/aapt2/ScopedXmlPullParser.cpp deleted file mode 100644 index 48da93edaa02..000000000000 --- a/tools/aapt2/ScopedXmlPullParser.cpp +++ /dev/null @@ -1,104 +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. - */ - -#include "ScopedXmlPullParser.h" - -#include <string> - -namespace aapt { - -ScopedXmlPullParser::ScopedXmlPullParser(XmlPullParser* parser) : - mParser(parser), mDepth(parser->getDepth()), mDone(false) { -} - -ScopedXmlPullParser::~ScopedXmlPullParser() { - while (isGoodEvent(next())); -} - -XmlPullParser::Event ScopedXmlPullParser::next() { - if (mDone) { - return Event::kEndDocument; - } - - const Event event = mParser->next(); - if (mParser->getDepth() <= mDepth) { - mDone = true; - } - return event; -} - -XmlPullParser::Event ScopedXmlPullParser::getEvent() const { - return mParser->getEvent(); -} - -const std::string& ScopedXmlPullParser::getLastError() const { - return mParser->getLastError(); -} - -const std::u16string& ScopedXmlPullParser::getComment() const { - return mParser->getComment(); -} - -size_t ScopedXmlPullParser::getLineNumber() const { - return mParser->getLineNumber(); -} - -size_t ScopedXmlPullParser::getDepth() const { - const size_t depth = mParser->getDepth(); - if (depth < mDepth) { - return 0; - } - return depth - mDepth; -} - -const std::u16string& ScopedXmlPullParser::getText() const { - return mParser->getText(); -} - -const std::u16string& ScopedXmlPullParser::getNamespacePrefix() const { - return mParser->getNamespacePrefix(); -} - -const std::u16string& ScopedXmlPullParser::getNamespaceUri() const { - return mParser->getNamespaceUri(); -} - -bool ScopedXmlPullParser::applyPackageAlias(std::u16string* package, - const std::u16string& defaultPackage) const { - return mParser->applyPackageAlias(package, defaultPackage); -} - -const std::u16string& ScopedXmlPullParser::getElementNamespace() const { - return mParser->getElementNamespace(); -} - -const std::u16string& ScopedXmlPullParser::getElementName() const { - return mParser->getElementName(); -} - -size_t ScopedXmlPullParser::getAttributeCount() const { - return mParser->getAttributeCount(); -} - -XmlPullParser::const_iterator ScopedXmlPullParser::beginAttributes() const { - return mParser->beginAttributes(); -} - -XmlPullParser::const_iterator ScopedXmlPullParser::endAttributes() const { - return mParser->endAttributes(); -} - -} // namespace aapt diff --git a/tools/aapt2/ScopedXmlPullParser.h b/tools/aapt2/ScopedXmlPullParser.h deleted file mode 100644 index a040f6097fc3..000000000000 --- a/tools/aapt2/ScopedXmlPullParser.h +++ /dev/null @@ -1,85 +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. - */ - -#ifndef AAPT_SCOPED_XML_PULL_PARSER_H -#define AAPT_SCOPED_XML_PULL_PARSER_H - -#include "XmlPullParser.h" - -#include <string> - -namespace aapt { - -/** - * An XmlPullParser that will not read past the depth - * of the underlying parser. When this parser is destroyed, - * it moves the underlying parser to the same depth it - * started with. - * - * You can write code like this: - * - * while (XmlPullParser::isGoodEvent(parser.next())) { - * if (parser.getEvent() != XmlPullParser::Event::StartElement) { - * continue; - * } - * - * ScopedXmlPullParser scoped(parser); - * if (parser.getElementName() == u"id") { - * // do work. - * } else { - * // do nothing, as all the sub elements will be skipped - * // when scoped goes out of scope. - * } - * } - */ -class ScopedXmlPullParser : public XmlPullParser { -public: - ScopedXmlPullParser(XmlPullParser* parser); - ScopedXmlPullParser(const ScopedXmlPullParser&) = delete; - ScopedXmlPullParser& operator=(const ScopedXmlPullParser&) = delete; - ~ScopedXmlPullParser(); - - Event getEvent() const override; - const std::string& getLastError() const override; - Event next() override; - - const std::u16string& getComment() const override; - size_t getLineNumber() const override; - size_t getDepth() const override; - - const std::u16string& getText() const override; - - const std::u16string& getNamespacePrefix() const override; - const std::u16string& getNamespaceUri() const override; - bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage) - const override; - - const std::u16string& getElementNamespace() const override; - const std::u16string& getElementName() const override; - - const_iterator beginAttributes() const override; - const_iterator endAttributes() const override; - size_t getAttributeCount() const override; - -private: - XmlPullParser* mParser; - size_t mDepth; - bool mDone; -}; - -} // namespace aapt - -#endif // AAPT_SCOPED_XML_PULL_PARSER_H diff --git a/tools/aapt2/ScopedXmlPullParser_test.cpp b/tools/aapt2/ScopedXmlPullParser_test.cpp deleted file mode 100644 index 342f305bb11d..000000000000 --- a/tools/aapt2/ScopedXmlPullParser_test.cpp +++ /dev/null @@ -1,106 +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. - */ - -#include "ScopedXmlPullParser.h" -#include "SourceXmlPullParser.h" - -#include <gtest/gtest.h> -#include <sstream> -#include <string> - -namespace aapt { - -TEST(ScopedXmlPullParserTest, StopIteratingAtNoNZeroDepth) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl - << "<resources><string></string></resources>" << std::endl; - - SourceXmlPullParser sourceParser(input); - EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName()); - - EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName()); - - { - ScopedXmlPullParser scopedParser(&sourceParser); - EXPECT_EQ(XmlPullParser::Event::kEndElement, scopedParser.next()); - EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName()); - - EXPECT_EQ(XmlPullParser::Event::kEndDocument, scopedParser.next()); - } - - EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName()); - - EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next()); -} - -TEST(ScopedXmlPullParserTest, FinishCurrentElementOnDestruction) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl - << "<resources><string></string></resources>" << std::endl; - - SourceXmlPullParser sourceParser(input); - EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName()); - - EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName()); - - { - ScopedXmlPullParser scopedParser(&sourceParser); - EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName()); - } - - EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName()); - - EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next()); -} - -TEST(ScopedXmlPullParserTest, NestedParsersOperateCorrectly) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl - << "<resources><string><foo></foo></string></resources>" << std::endl; - - SourceXmlPullParser sourceParser(input); - EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName()); - - EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName()); - - { - ScopedXmlPullParser scopedParser(&sourceParser); - EXPECT_EQ(std::u16string(u"string"), scopedParser.getElementName()); - while (XmlPullParser::isGoodEvent(scopedParser.next())) { - if (scopedParser.getEvent() != XmlPullParser::Event::kStartElement) { - continue; - } - - ScopedXmlPullParser subScopedParser(&scopedParser); - EXPECT_EQ(std::u16string(u"foo"), subScopedParser.getElementName()); - } - } - - EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName()); - - EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next()); -} - -} // namespace aapt diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp index 9bdae490412f..c2a22bf2a373 100644 --- a/tools/aapt2/SdkConstants.cpp +++ b/tools/aapt2/SdkConstants.cpp @@ -34,8 +34,9 @@ static const std::vector<std::pair<uint16_t, size_t>> sAttrIdMap = { { 0x02bd, SDK_FROYO }, { 0x02cb, SDK_GINGERBREAD }, { 0x0361, SDK_HONEYCOMB }, - { 0x0366, SDK_HONEYCOMB_MR1 }, - { 0x03a6, SDK_HONEYCOMB_MR2 }, + { 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 }, diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h index 803da03743c5..282ed9a56f5c 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -42,6 +42,7 @@ enum { SDK_KITKAT_WATCH = 20, SDK_LOLLIPOP = 21, SDK_LOLLIPOP_MR1 = 22, + SDK_MARSHMALLOW = 23, }; size_t findAttributeSdkLevel(ResourceId id); diff --git a/tools/aapt2/SdkConstants_test.cpp b/tools/aapt2/SdkConstants_test.cpp new file mode 100644 index 000000000000..e81f412dda15 --- /dev/null +++ b/tools/aapt2/SdkConstants_test.cpp @@ -0,0 +1,38 @@ +/* + * 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 "SdkConstants.h" + +#include <gtest/gtest.h> + +namespace aapt { + +TEST(SdkConstantsTest, FirstAttributeIsSdk1) { + 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_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))); +} + +} // namespace aapt diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h index 3606488591ba..319528e0ea1b 100644 --- a/tools/aapt2/Source.h +++ b/tools/aapt2/Source.h @@ -17,72 +17,62 @@ #ifndef AAPT_SOURCE_H #define AAPT_SOURCE_H +#include "util/Maybe.h" +#include "util/StringPiece.h" + #include <ostream> #include <string> -#include <tuple> namespace aapt { -struct SourceLineColumn; -struct SourceLine; - /** * Represents a file on disk. Used for logging and * showing errors. */ struct Source { std::string path; + Maybe<size_t> line; - inline SourceLine line(size_t line) const; -}; + Source() = default; -/** - * Represents a file on disk and a line number in that file. - * Used for logging and showing errors. - */ -struct SourceLine { - std::string path; - size_t line; + inline Source(const StringPiece& path) : path(path.toString()) { + } - inline SourceLineColumn column(size_t column) const; -}; + inline Source(const StringPiece& path, size_t line) : path(path.toString()), line(line) { + } -/** - * Represents a file on disk and a line:column number in that file. - * Used for logging and showing errors. - */ -struct SourceLineColumn { - std::string path; - size_t line; - size_t column; + inline Source withLine(size_t line) const { + return Source(path, line); + } }; // // Implementations // -SourceLine Source::line(size_t line) const { - return SourceLine{ path, line }; -} - -SourceLineColumn SourceLine::column(size_t column) const { - return SourceLineColumn{ path, line, column }; -} - inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) { - return out << source.path; -} - -inline ::std::ostream& operator<<(::std::ostream& out, const SourceLine& source) { - return out << source.path << ":" << source.line; + out << source.path; + if (source.line) { + out << ":" << source.line.value(); + } + return out; } -inline ::std::ostream& operator<<(::std::ostream& out, const SourceLineColumn& source) { - return out << source.path << ":" << source.line << ":" << source.column; +inline bool operator==(const Source& lhs, const Source& rhs) { + return lhs.path == rhs.path && lhs.line == rhs.line; } -inline bool operator<(const SourceLine& lhs, const SourceLine& rhs) { - return std::tie(lhs.path, lhs.line) < std::tie(rhs.path, 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; + } + return bool(rhs.line); } } // namespace aapt diff --git a/tools/aapt2/SourceXmlPullParser.h b/tools/aapt2/SourceXmlPullParser.h deleted file mode 100644 index 15936d655745..000000000000 --- a/tools/aapt2/SourceXmlPullParser.h +++ /dev/null @@ -1,91 +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. - */ - -#ifndef AAPT_SOURCE_XML_PULL_PARSER_H -#define AAPT_SOURCE_XML_PULL_PARSER_H - -#include "XmlPullParser.h" - -#include <istream> -#include <libexpat/expat.h> -#include <queue> -#include <stack> -#include <string> -#include <vector> - -namespace aapt { - -class SourceXmlPullParser : public XmlPullParser { -public: - SourceXmlPullParser(std::istream& in); - SourceXmlPullParser(const SourceXmlPullParser& rhs) = delete; - ~SourceXmlPullParser(); - - Event getEvent() const override; - const std::string& getLastError() const override ; - Event next() override ; - - const std::u16string& getComment() const override; - size_t getLineNumber() const override; - size_t getDepth() const override; - - const std::u16string& getText() const override; - - const std::u16string& getNamespacePrefix() const override; - const std::u16string& getNamespaceUri() const override; - bool applyPackageAlias(std::u16string* package, - const std::u16string& defaultPackage) const override; - - - const std::u16string& getElementNamespace() const override; - const std::u16string& getElementName() const override; - - const_iterator beginAttributes() const override; - const_iterator endAttributes() const override; - size_t getAttributeCount() const override; - -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::u16string comment; - 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; - std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases; -}; - -} // namespace aapt - -#endif // AAPT_SOURCE_XML_PULL_PARSER_H diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp index c19aa98a70ac..aadb00b6be2a 100644 --- a/tools/aapt2/StringPool.cpp +++ b/tools/aapt2/StringPool.cpp @@ -14,10 +14,10 @@ * limitations under the License. */ -#include "BigBuffer.h" -#include "StringPiece.h" #include "StringPool.h" -#include "Util.h" +#include "util/BigBuffer.h" +#include "util/StringPiece.h" +#include "util/Util.h" #include <algorithm> #include <androidfw/ResourceTypes.h> @@ -219,7 +219,7 @@ void StringPool::prune() { auto indexIter = std::begin(mIndexedStrings); while (indexIter != iterEnd) { if (indexIter->second->ref <= 0) { - mIndexedStrings.erase(indexIter++); + indexIter = mIndexedStrings.erase(indexIter); } else { ++indexIter; } @@ -241,6 +241,12 @@ void StringPool::prune() { // 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) { @@ -336,7 +342,14 @@ bool StringPool::flatten(BigBuffer* out, const StringPool& pool, bool utf8) { // Encode the actual UTF16 string length. data = encodeLength(data, entry->value.size()); - strncpy16(data, entry->value.data(), entry->value.size()); + const size_t byteLength = entry->value.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); + + // The null-terminating character is already here due to the block of data being set + // to 0s on allocation. } } diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h index 14304a6e6b1a..509e3041e081 100644 --- a/tools/aapt2/StringPool.h +++ b/tools/aapt2/StringPool.h @@ -17,9 +17,9 @@ #ifndef AAPT_STRING_POOL_H #define AAPT_STRING_POOL_H -#include "BigBuffer.h" +#include "util/BigBuffer.h" #include "ConfigDescription.h" -#include "StringPiece.h" +#include "util/StringPiece.h" #include <functional> #include <map> diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp index 9552937d4ad4..2b2d348fd17c 100644 --- a/tools/aapt2/StringPool_test.cpp +++ b/tools/aapt2/StringPool_test.cpp @@ -15,13 +15,11 @@ */ #include "StringPool.h" -#include "Util.h" +#include "util/Util.h" #include <gtest/gtest.h> #include <string> -using namespace android; - namespace aapt { TEST(StringPoolTest, InsertOneString) { @@ -67,15 +65,23 @@ TEST(StringPoolTest, MaintainInsertionOrderIndex) { 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(1u, pool.size()); + EXPECT_EQ(2u, pool.size()); } + StringPool::Ref refB = pool.makeRef(u"bar"); - EXPECT_EQ(1u, pool.size()); + EXPECT_EQ(3u, pool.size()); pool.prune(); - EXPECT_EQ(0u, pool.size()); + 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); } TEST(StringPoolTest, SortAndMaintainIndexesInReferences) { @@ -163,18 +169,40 @@ TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) { } TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { + using namespace android; // For NO_ERROR on Windows. + StringPool pool; BigBuffer buffer(1024); StringPool::flattenUtf8(&buffer, pool); std::unique_ptr<uint8_t[]> data = util::copy(buffer); - android::ResStringPool test; - ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR); + ResStringPool test; + ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); +} + +TEST(StringPoolTest, FlattenOddCharactersUtf16) { + using namespace android; // For NO_ERROR on Windows. + + StringPool pool; + pool.makeRef(u"\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 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"); @@ -195,8 +223,8 @@ TEST(StringPoolTest, FlattenUtf8) { std::unique_ptr<uint8_t[]> data = util::copy(buffer); { - android::ResStringPool test; - ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR); + 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"); diff --git a/tools/aapt2/TableFlattener.cpp b/tools/aapt2/TableFlattener.cpp deleted file mode 100644 index b7c04f06cff5..000000000000 --- a/tools/aapt2/TableFlattener.cpp +++ /dev/null @@ -1,570 +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. - */ - -#include "BigBuffer.h" -#include "ConfigDescription.h" -#include "Logger.h" -#include "ResourceTable.h" -#include "ResourceTypeExtensions.h" -#include "ResourceValues.h" -#include "StringPool.h" -#include "TableFlattener.h" -#include "Util.h" - -#include <algorithm> -#include <androidfw/ResourceTypes.h> -#include <sstream> - -namespace aapt { - -struct FlatEntry { - const ResourceEntry* entry; - const Value* value; - uint32_t entryKey; - uint32_t sourcePathKey; - uint32_t sourceLine; -}; - -/** - * Visitor that knows how to encode Map values. - */ -class MapFlattener : public ConstValueVisitor { -public: - MapFlattener(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols) : - mOut(out), mSymbols(symbols) { - mMap = mOut->nextBlock<android::ResTable_map_entry>(); - mMap->key.index = flatEntry.entryKey; - mMap->flags = android::ResTable_entry::FLAG_COMPLEX; - if (flatEntry.entry->publicStatus.isPublic) { - mMap->flags |= android::ResTable_entry::FLAG_PUBLIC; - } - if (flatEntry.value->isWeak()) { - mMap->flags |= android::ResTable_entry::FLAG_WEAK; - } - - ResTable_entry_source* sourceBlock = mOut->nextBlock<ResTable_entry_source>(); - sourceBlock->pathIndex = flatEntry.sourcePathKey; - sourceBlock->line = flatEntry.sourceLine; - - mMap->size = sizeof(*mMap) + sizeof(*sourceBlock); - } - - void flattenParent(const Reference& ref) { - if (!ref.id.isValid()) { - mSymbols->push_back({ - ResourceNameRef(ref.name), - (mOut->size() - mMap->size) + sizeof(*mMap) - sizeof(android::ResTable_entry) - }); - } - mMap->parent.ident = ref.id.id; - } - - void flattenEntry(const Reference& key, const Item& value) { - mMap->count++; - - android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>(); - - // Write the key. - if (!Res_INTERNALID(key.id.id) && !key.id.isValid()) { - assert(!key.name.entry.empty()); - mSymbols->push_back(std::make_pair(ResourceNameRef(key.name), - mOut->size() - sizeof(*outMapEntry))); - } - outMapEntry->name.ident = key.id.id; - - // Write the value. - value.flatten(outMapEntry->value); - - if (outMapEntry->value.data == 0x0) { - visitFunc<Reference>(value, [&](const Reference& reference) { - mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name), - mOut->size() - sizeof(outMapEntry->value.data))); - }); - } - outMapEntry->value.size = sizeof(outMapEntry->value); - } - - void flattenValueOnly(const Item& value) { - mMap->count++; - - android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>(); - - // Write the value. - value.flatten(outMapEntry->value); - - if (outMapEntry->value.data == 0x0) { - visitFunc<Reference>(value, [&](const Reference& reference) { - mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name), - mOut->size() - sizeof(outMapEntry->value.data))); - }); - } - outMapEntry->value.size = sizeof(outMapEntry->value); - } - - static bool compareStyleEntries(const Style::Entry* lhs, const Style::Entry* rhs) { - return lhs->key.id < rhs->key.id; - } - - void visit(const Style& style, ValueVisitorArgs&) override { - if (style.parent.name.isValid()) { - flattenParent(style.parent); - } - - // First sort the entries by ID. - std::vector<const Style::Entry*> sortedEntries; - for (const auto& styleEntry : style.entries) { - auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), - &styleEntry, compareStyleEntries); - sortedEntries.insert(iter, &styleEntry); - } - - for (const Style::Entry* styleEntry : sortedEntries) { - flattenEntry(styleEntry->key, *styleEntry->value); - } - } - - void visit(const Attribute& attr, ValueVisitorArgs&) override { - android::Res_value tempVal; - tempVal.dataType = android::Res_value::TYPE_INT_DEC; - tempVal.data = attr.typeMask; - flattenEntry(Reference(ResourceId{android::ResTable_map::ATTR_TYPE}), - BinaryPrimitive(tempVal)); - - for (const auto& symbol : attr.symbols) { - tempVal.data = symbol.value; - flattenEntry(symbol.symbol, BinaryPrimitive(tempVal)); - } - } - - void visit(const Styleable& styleable, ValueVisitorArgs&) override { - for (const auto& attr : styleable.entries) { - flattenEntry(attr, BinaryPrimitive(android::Res_value{})); - } - } - - void visit(const Array& array, ValueVisitorArgs&) override { - for (const auto& item : array.items) { - flattenValueOnly(*item); - } - } - - void visit(const Plural& plural, ValueVisitorArgs&) 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; - } - - flattenEntry(Reference(q), *plural.values[i]); - } - } - -private: - BigBuffer* mOut; - SymbolEntryVector* mSymbols; - android::ResTable_map_entry* mMap; -}; - -/** - * Flattens a value, with special handling for References. - */ -struct ValueFlattener : ConstValueVisitor { - ValueFlattener(BigBuffer* out, SymbolEntryVector* symbols) : - result(false), mOut(out), mOutValue(nullptr), mSymbols(symbols) { - mOutValue = mOut->nextBlock<android::Res_value>(); - } - - virtual void visit(const Reference& ref, ValueVisitorArgs& a) override { - visitItem(ref, a); - if (mOutValue->data == 0x0) { - mSymbols->push_back({ - ResourceNameRef(ref.name), - mOut->size() - sizeof(mOutValue->data)}); - } - } - - virtual void visitItem(const Item& item, ValueVisitorArgs&) override { - result = item.flatten(*mOutValue); - mOutValue->res0 = 0; - mOutValue->size = sizeof(*mOutValue); - } - - bool result; - -private: - BigBuffer* mOut; - android::Res_value* mOutValue; - SymbolEntryVector* mSymbols; -}; - -TableFlattener::TableFlattener(Options options) -: mOptions(options) { -} - -bool TableFlattener::flattenValue(BigBuffer* out, const FlatEntry& flatEntry, - SymbolEntryVector* symbols) { - if (flatEntry.value->isItem()) { - android::ResTable_entry* entry = out->nextBlock<android::ResTable_entry>(); - - if (flatEntry.entry->publicStatus.isPublic) { - entry->flags |= android::ResTable_entry::FLAG_PUBLIC; - } - - if (flatEntry.value->isWeak()) { - entry->flags |= android::ResTable_entry::FLAG_WEAK; - } - - entry->key.index = flatEntry.entryKey; - entry->size = sizeof(*entry); - - if (mOptions.useExtendedChunks) { - // Write the extra source block. This will be ignored by - // the Android runtime. - ResTable_entry_source* sourceBlock = out->nextBlock<ResTable_entry_source>(); - sourceBlock->pathIndex = flatEntry.sourcePathKey; - sourceBlock->line = flatEntry.sourceLine; - entry->size += sizeof(*sourceBlock); - } - - const Item* item = static_cast<const Item*>(flatEntry.value); - ValueFlattener flattener(out, symbols); - item->accept(flattener, {}); - return flattener.result; - } - - MapFlattener flattener(out, flatEntry, symbols); - flatEntry.value->accept(flattener, {}); - return true; -} - -bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) { - const size_t beginning = out->size(); - - if (table.getPackageId() == ResourceTable::kUnsetPackageId) { - Logger::error() - << "ResourceTable has no package ID set." - << std::endl; - return false; - } - - SymbolEntryVector symbolEntries; - - StringPool typePool; - StringPool keyPool; - StringPool sourcePool; - - // Sort the types by their IDs. They will be inserted into the StringPool - // in this order. - std::vector<ResourceTableType*> sortedTypes; - for (const auto& type : table) { - if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) { - continue; - } - - auto iter = std::lower_bound(std::begin(sortedTypes), std::end(sortedTypes), type.get(), - [](const ResourceTableType* lhs, const ResourceTableType* rhs) -> bool { - return lhs->typeId < rhs->typeId; - }); - sortedTypes.insert(iter, type.get()); - } - - BigBuffer typeBlock(1024); - size_t expectedTypeId = 1; - for (const ResourceTableType* type : sortedTypes) { - if (type->typeId == ResourceTableType::kUnsetTypeId - || type->typeId == 0) { - Logger::error() - << "resource type '" - << type->type - << "' from package '" - << table.getPackage() - << "' has no ID." - << std::endl; - return false; - } - - // 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->typeId > expectedTypeId) { - std::u16string typeName(u"?"); - typeName += expectedTypeId; - typePool.makeRef(typeName); - expectedTypeId++; - } - expectedTypeId++; - typePool.makeRef(toString(type->type)); - - android::ResTable_typeSpec* spec = typeBlock.nextBlock<android::ResTable_typeSpec>(); - spec->header.type = android::RES_TABLE_TYPE_SPEC_TYPE; - spec->header.headerSize = sizeof(*spec); - spec->header.size = spec->header.headerSize + (type->entries.size() * sizeof(uint32_t)); - spec->id = type->typeId; - spec->entryCount = type->entries.size(); - - if (type->entries.empty()) { - continue; - } - - // Reserve space for the masks of each resource in this type. These - // show for which configuration axis the resource changes. - uint32_t* configMasks = typeBlock.nextBlock<uint32_t>(type->entries.size()); - - // Sort the entries by entry ID and write their configuration masks. - std::vector<ResourceEntry*> entries; - const size_t entryCount = type->entries.size(); - for (size_t entryIndex = 0; entryIndex < entryCount; entryIndex++) { - const auto& entry = type->entries[entryIndex]; - - if (entry->entryId == ResourceEntry::kUnsetEntryId) { - Logger::error() - << "resource '" - << ResourceName{ table.getPackage(), type->type, entry->name } - << "' has no ID." - << std::endl; - return false; - } - - auto iter = std::lower_bound(std::begin(entries), std::end(entries), entry.get(), - [](const ResourceEntry* lhs, const ResourceEntry* rhs) -> bool { - return lhs->entryId < rhs->entryId; - }); - entries.insert(iter, entry.get()); - - // Populate the config masks for this entry. - if (entry->publicStatus.isPublic) { - configMasks[entry->entryId] |= android::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->entryId] |= config.diff(entry->values[j].config); - } - } - } - - const size_t beforePublicHeader = typeBlock.size(); - Public_header* publicHeader = nullptr; - if (mOptions.useExtendedChunks) { - publicHeader = typeBlock.nextBlock<Public_header>(); - publicHeader->header.type = RES_TABLE_PUBLIC_TYPE; - publicHeader->header.headerSize = sizeof(*publicHeader); - publicHeader->typeId = type->typeId; - } - - // 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>> data; - for (const ResourceEntry* entry : entries) { - size_t keyIndex = keyPool.makeRef(entry->name).getIndex(); - - if (keyIndex > std::numeric_limits<uint32_t>::max()) { - Logger::error() - << "resource key string pool exceeded max size." - << std::endl; - return false; - } - - if (publicHeader && entry->publicStatus.isPublic) { - // Write the public status of this entry. - Public_entry* publicEntry = typeBlock.nextBlock<Public_entry>(); - publicEntry->entryId = static_cast<uint32_t>(entry->entryId); - publicEntry->key.index = static_cast<uint32_t>(keyIndex); - publicEntry->source.index = static_cast<uint32_t>(sourcePool.makeRef( - util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex()); - publicEntry->sourceLine = static_cast<uint32_t>(entry->publicStatus.source.line); - publicHeader->count += 1; - } - - for (const auto& configValue : entry->values) { - data[configValue.config].push_back(FlatEntry{ - entry, - configValue.value.get(), - static_cast<uint32_t>(keyIndex), - static_cast<uint32_t>(sourcePool.makeRef(util::utf8ToUtf16( - configValue.source.path)).getIndex()), - static_cast<uint32_t>(configValue.source.line) - }); - } - } - - if (publicHeader) { - typeBlock.align4(); - publicHeader->header.size = - static_cast<uint32_t>(typeBlock.size() - beforePublicHeader); - } - - // Begin flattening a configuration for the current type. - for (const auto& entry : data) { - const size_t typeHeaderStart = typeBlock.size(); - android::ResTable_type* typeHeader = typeBlock.nextBlock<android::ResTable_type>(); - typeHeader->header.type = android::RES_TABLE_TYPE_TYPE; - typeHeader->header.headerSize = sizeof(*typeHeader); - typeHeader->id = type->typeId; - typeHeader->entryCount = type->entries.size(); - typeHeader->entriesStart = typeHeader->header.headerSize - + (sizeof(uint32_t) * type->entries.size()); - typeHeader->config = entry.first; - - uint32_t* indices = typeBlock.nextBlock<uint32_t>(type->entries.size()); - memset(indices, 0xff, type->entries.size() * sizeof(uint32_t)); - - const size_t entryStart = typeBlock.size(); - for (const FlatEntry& flatEntry : entry.second) { - assert(flatEntry.entry->entryId < type->entries.size()); - indices[flatEntry.entry->entryId] = typeBlock.size() - entryStart; - if (!flattenValue(&typeBlock, flatEntry, &symbolEntries)) { - Logger::error() - << "failed to flatten resource '" - << ResourceNameRef { - table.getPackage(), type->type, flatEntry.entry->name } - << "' for configuration '" - << entry.first - << "'." - << std::endl; - return false; - } - } - - typeBlock.align4(); - typeHeader->header.size = typeBlock.size() - typeHeaderStart; - } - } - - const size_t beforeTable = out->size(); - android::ResTable_header* header = out->nextBlock<android::ResTable_header>(); - header->header.type = android::RES_TABLE_TYPE; - header->header.headerSize = sizeof(*header); - header->packageCount = 1; - - SymbolTable_entry* symbolEntryData = nullptr; - if (!symbolEntries.empty() && mOptions.useExtendedChunks) { - const size_t beforeSymbolTable = out->size(); - StringPool symbolPool; - SymbolTable_header* symbolHeader = out->nextBlock<SymbolTable_header>(); - symbolHeader->header.type = RES_TABLE_SYMBOL_TABLE_TYPE; - symbolHeader->header.headerSize = sizeof(*symbolHeader); - symbolHeader->count = symbolEntries.size(); - - symbolEntryData = out->nextBlock<SymbolTable_entry>(symbolHeader->count); - - size_t i = 0; - for (const auto& entry : symbolEntries) { - symbolEntryData[i].offset = entry.second; - StringPool::Ref ref = symbolPool.makeRef( - entry.first.package.toString() + u":" + - toString(entry.first.type).toString() + u"/" + - entry.first.entry.toString()); - symbolEntryData[i].stringIndex = ref.getIndex(); - i++; - } - - StringPool::flattenUtf8(out, symbolPool); - out->align4(); - symbolHeader->header.size = out->size() - beforeSymbolTable; - } - - if (sourcePool.size() > 0 && mOptions.useExtendedChunks) { - const size_t beforeSourcePool = out->size(); - android::ResChunk_header* sourceHeader = out->nextBlock<android::ResChunk_header>(); - sourceHeader->type = RES_TABLE_SOURCE_POOL_TYPE; - sourceHeader->headerSize = sizeof(*sourceHeader); - StringPool::flattenUtf8(out, sourcePool); - out->align4(); - sourceHeader->size = out->size() - beforeSourcePool; - } - - StringPool::flattenUtf8(out, table.getValueStringPool()); - - const size_t beforePackageIndex = out->size(); - android::ResTable_package* package = out->nextBlock<android::ResTable_package>(); - package->header.type = android::RES_TABLE_PACKAGE_TYPE; - package->header.headerSize = sizeof(*package); - - if (table.getPackageId() > std::numeric_limits<uint8_t>::max()) { - Logger::error() - << "package ID 0x'" - << std::hex << table.getPackageId() << std::dec - << "' is invalid." - << std::endl; - return false; - } - package->id = table.getPackageId(); - - if (table.getPackage().size() >= sizeof(package->name) / sizeof(package->name[0])) { - Logger::error() - << "package name '" - << table.getPackage() - << "' is too long." - << std::endl; - return false; - } - memcpy(package->name, reinterpret_cast<const uint16_t*>(table.getPackage().data()), - table.getPackage().length() * sizeof(char16_t)); - package->name[table.getPackage().length()] = 0; - - package->typeStrings = package->header.headerSize; - StringPool::flattenUtf16(out, typePool); - package->keyStrings = out->size() - beforePackageIndex; - StringPool::flattenUtf16(out, keyPool); - - if (symbolEntryData != nullptr) { - for (size_t i = 0; i < symbolEntries.size(); i++) { - symbolEntryData[i].offset += out->size() - beginning; - } - } - - out->appendBuffer(std::move(typeBlock)); - - package->header.size = out->size() - beforePackageIndex; - header->header.size = out->size() - beforeTable; - return true; -} - -} // namespace aapt diff --git a/tools/aapt2/TableFlattener.h b/tools/aapt2/TableFlattener.h deleted file mode 100644 index ccbb737059f9..000000000000 --- a/tools/aapt2/TableFlattener.h +++ /dev/null @@ -1,61 +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. - */ - -#ifndef AAPT_TABLE_FLATTENER_H -#define AAPT_TABLE_FLATTENER_H - -#include "BigBuffer.h" -#include "ResourceTable.h" - -namespace aapt { - -using SymbolEntryVector = std::vector<std::pair<ResourceNameRef, uint32_t>>; - -struct FlatEntry; - -/** - * Flattens a ResourceTable into a binary format suitable - * for loading into a ResTable on the host or device. - */ -struct TableFlattener { - /** - * A set of options for this TableFlattener. - */ - struct Options { - /** - * Specifies whether to output extended chunks, like - * source information and mising symbol entries. Default - * is true. - * - * Set this to false when emitting the final table to be used - * on device. - */ - bool useExtendedChunks = true; - }; - - TableFlattener(Options options); - - bool flatten(BigBuffer* out, const ResourceTable& table); - -private: - bool flattenValue(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols); - - Options mOptions; -}; - -} // namespace aapt - -#endif // AAPT_TABLE_FLATTENER_H diff --git a/tools/aapt2/Util_test.cpp b/tools/aapt2/Util_test.cpp deleted file mode 100644 index 0b08d240cad3..000000000000 --- a/tools/aapt2/Util_test.cpp +++ /dev/null @@ -1,136 +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. - */ - -#include <gtest/gtest.h> -#include <string> - -#include "StringPiece.h" -#include "Util.h" - -namespace aapt { - -TEST(UtilTest, TrimOnlyWhitespace) { - const std::u16string full = u"\n "; - - StringPiece16 trimmed = util::trimWhitespace(full); - EXPECT_TRUE(trimmed.empty()); - EXPECT_EQ(0u, trimmed.size()); -} - -TEST(UtilTest, StringEndsWith) { - EXPECT_TRUE(util::stringEndsWith<char>("hello.xml", ".xml")); -} - -TEST(UtilTest, StringStartsWith) { - EXPECT_TRUE(util::stringStartsWith<char>("hello.xml", "he")); -} - -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(StringPiece16(u" wow, so many \t spaces. what?"), - util::StringBuilder().append(u" \" wow, so many \t ") - .append(u"spaces. \"what? ") - .str()); - - EXPECT_EQ(StringPiece16(u"where is the pie?"), - util::StringBuilder().append(u" where \t ") - .append(u" \nis the "" pie?") - .str()); -} - -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(StringPiece16(u"@?#\\\'"), - util::StringBuilder().append(u"\\@\\?\\#\\\\\\'") - .str()); -} - -TEST(UtilTest, StringBuilderMisplacedQuote) { - util::StringBuilder builder{}; - EXPECT_FALSE(builder.append(u"they're coming!")); -} - -TEST(UtilTest, StringBuilderUnicodeCodes) { - EXPECT_EQ(StringPiece16(u"\u00AF\u0AF0 woah"), - util::StringBuilder().append(u"\\u00AF\\u0AF0 woah") - .str()); - - EXPECT_FALSE(util::StringBuilder().append(u"\\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); -} - -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")); -} - -TEST(UtilTest, FullyQualifiedClassName) { - Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf"); - ASSERT_TRUE(res); - EXPECT_EQ(res.value(), u"android.asdf"); - - res = util::getFullyQualifiedClassName(u"android", u".asdf"); - ASSERT_TRUE(res); - EXPECT_EQ(res.value(), u"android.asdf"); - - res = util::getFullyQualifiedClassName(u"android", u".a.b"); - ASSERT_TRUE(res); - EXPECT_EQ(res.value(), u"android.a.b"); - - res = util::getFullyQualifiedClassName(u"android", u"a.b"); - ASSERT_TRUE(res); - EXPECT_EQ(res.value(), u"a.b"); - - res = util::getFullyQualifiedClassName(u"", u"a.b"); - ASSERT_TRUE(res); - EXPECT_EQ(res.value(), u"a.b"); - - res = util::getFullyQualifiedClassName(u"", u""); - ASSERT_FALSE(res); - - res = util::getFullyQualifiedClassName(u"android", u"./Apple"); - ASSERT_FALSE(res); -} - - -} // namespace aapt diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h new file mode 100644 index 000000000000..ea2aa55764c1 --- /dev/null +++ b/tools/aapt2/ValueVisitor.h @@ -0,0 +1,163 @@ +/* + * 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. + */ + +#ifndef AAPT_VALUE_VISITOR_H +#define AAPT_VALUE_VISITOR_H + +#include "ResourceValues.h" +#include "ResourceTable.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. + */ +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) {} +}; + +#define DECL_VISIT_COMPOUND_VALUE(T) \ + virtual void visit(T* value) { \ + visitSubValues(value); \ + } + +/** + * 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); + } + } + + void visitSubValues(Style* style) { + if (style->parent) { + visit(&style->parent.value()); + } + + 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(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); + } + } + + 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); +}; + +/** + * Do not use directly. Helper struct for dyn_cast. + */ +template <typename T> +struct DynCastVisitor : public RawValueVisitor { + T* value = nullptr; + + void visit(T* v) override { + value = v; + } +}; + +/** + * Specialization that checks if the value is an Item. + */ +template <> +struct DynCastVisitor<Item> : public RawValueVisitor { + Item* value = nullptr; + + void visitItem(Item* item) override { + value = item; + } +}; + +/** + * Returns a valid pointer to T if the Value is of subtype T. + * Otherwise, returns nullptr. + */ +template <typename T> +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 visitAllValuesInTable(ResourceTable* table, RawValueVisitor* visitor) { + for (auto& pkg : table->packages) { + visitAllValuesInPackage(pkg.get(), visitor); + } +} + +} // namespace aapt + +#endif // AAPT_VALUE_VISITOR_H diff --git a/tools/aapt2/ValueVisitor_test.cpp b/tools/aapt2/ValueVisitor_test.cpp new file mode 100644 index 000000000000..1624079727bb --- /dev/null +++ b/tools/aapt2/ValueVisitor_test.cpp @@ -0,0 +1,87 @@ +/* + * 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 <gtest/gtest.h> +#include <string> + +#include "ResourceValues.h" +#include "util/Util.h" +#include "ValueVisitor.h" +#include "test/Builders.h" + +namespace aapt { + +struct SingleReferenceVisitor : public ValueVisitor { + using ValueVisitor::visit; + + Reference* visited = nullptr; + + void visit(Reference* ref) override { + visited = ref; + } +}; + +struct StyleVisitor : public ValueVisitor { + using ValueVisitor::visit; + + std::list<Reference*> visitedRefs; + Style* visitedStyle = nullptr; + + void visit(Reference* ref) override { + visitedRefs.push_back(ref); + } + + void visit(Style* style) override { + visitedStyle = style; + ValueVisitor::visit(style); + } +}; + +TEST(ValueVisitorTest, VisitsReference) { + Reference ref(ResourceName{u"android", ResourceType::kAttr, u"foo"}); + SingleReferenceVisitor visitor; + ref.accept(&visitor); + + 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(); + + StyleVisitor visitor; + style->accept(&visitor); + + ASSERT_EQ(style.get(), visitor.visitedStyle); + + // Entry attribute references, plus the parent reference, plus one value reference. + ASSERT_EQ(style->entries.size() + 2, visitor.visitedRefs.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); +} + +} // namespace aapt diff --git a/tools/aapt2/XliffXmlPullParser.cpp b/tools/aapt2/XliffXmlPullParser.cpp deleted file mode 100644 index 31115f28f58f..000000000000 --- a/tools/aapt2/XliffXmlPullParser.cpp +++ /dev/null @@ -1,113 +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. - */ - -#include "XliffXmlPullParser.h" - -#include <string> - -namespace aapt { - -XliffXmlPullParser::XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) : - mParser(parser) { -} - -XmlPullParser::Event XliffXmlPullParser::next() { - while (XmlPullParser::isGoodEvent(mParser->next())) { - Event event = mParser->getEvent(); - if (event != Event::kStartElement && event != Event::kEndElement) { - break; - } - - if (mParser->getElementNamespace() != - u"urn:oasis:names:tc:xliff:document:1.2") { - break; - } - - const std::u16string& name = mParser->getElementName(); - if (name != u"bpt" - && name != u"ept" - && name != u"it" - && name != u"ph" - && name != u"g" - && name != u"bx" - && name != u"ex" - && name != u"x") { - break; - } - - // We hit a tag that was ignored, so get the next event. - } - return mParser->getEvent(); -} - -XmlPullParser::Event XliffXmlPullParser::getEvent() const { - return mParser->getEvent(); -} - -const std::string& XliffXmlPullParser::getLastError() const { - return mParser->getLastError(); -} - -const std::u16string& XliffXmlPullParser::getComment() const { - return mParser->getComment(); -} - -size_t XliffXmlPullParser::getLineNumber() const { - return mParser->getLineNumber(); -} - -size_t XliffXmlPullParser::getDepth() const { - return mParser->getDepth(); -} - -const std::u16string& XliffXmlPullParser::getText() const { - return mParser->getText(); -} - -const std::u16string& XliffXmlPullParser::getNamespacePrefix() const { - return mParser->getNamespacePrefix(); -} - -const std::u16string& XliffXmlPullParser::getNamespaceUri() const { - return mParser->getNamespaceUri(); -} - -bool XliffXmlPullParser::applyPackageAlias(std::u16string* package, - const std::u16string& defaultPackage) const { - return mParser->applyPackageAlias(package, defaultPackage); -} - -const std::u16string& XliffXmlPullParser::getElementNamespace() const { - return mParser->getElementNamespace(); -} - -const std::u16string& XliffXmlPullParser::getElementName() const { - return mParser->getElementName(); -} - -size_t XliffXmlPullParser::getAttributeCount() const { - return mParser->getAttributeCount(); -} - -XmlPullParser::const_iterator XliffXmlPullParser::beginAttributes() const { - return mParser->beginAttributes(); -} - -XmlPullParser::const_iterator XliffXmlPullParser::endAttributes() const { - return mParser->endAttributes(); -} - -} // namespace aapt diff --git a/tools/aapt2/XliffXmlPullParser.h b/tools/aapt2/XliffXmlPullParser.h deleted file mode 100644 index 77912277b31e..000000000000 --- a/tools/aapt2/XliffXmlPullParser.h +++ /dev/null @@ -1,64 +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. - */ - -#ifndef AAPT_XLIFF_XML_PULL_PARSER_H -#define AAPT_XLIFF_XML_PULL_PARSER_H - -#include "XmlPullParser.h" - -#include <memory> -#include <string> - -namespace aapt { - -/** - * Strips xliff elements and provides the caller with a view of the - * underlying XML without xliff. - */ -class XliffXmlPullParser : public XmlPullParser { -public: - XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser); - XliffXmlPullParser(const XliffXmlPullParser& rhs) = delete; - - Event getEvent() const override; - const std::string& getLastError() const override; - Event next() override; - - const std::u16string& getComment() const override; - size_t getLineNumber() const override; - size_t getDepth() const override; - - const std::u16string& getText() const override; - - const std::u16string& getNamespacePrefix() const override; - const std::u16string& getNamespaceUri() const override; - bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage) - const override; - - const std::u16string& getElementNamespace() const override; - const std::u16string& getElementName() const override; - - const_iterator beginAttributes() const override; - const_iterator endAttributes() const override; - size_t getAttributeCount() const override; - -private: - std::shared_ptr<XmlPullParser> mParser; -}; - -} // namespace aapt - -#endif // AAPT_XLIFF_XML_PULL_PARSER_H diff --git a/tools/aapt2/XliffXmlPullParser_test.cpp b/tools/aapt2/XliffXmlPullParser_test.cpp deleted file mode 100644 index f9030724b80b..000000000000 --- a/tools/aapt2/XliffXmlPullParser_test.cpp +++ /dev/null @@ -1,75 +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. - */ - -#include "SourceXmlPullParser.h" -#include "XliffXmlPullParser.h" - -#include <gtest/gtest.h> -#include <sstream> -#include <string> - -namespace aapt { - -TEST(XliffXmlPullParserTest, IgnoreXliffTags) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl - << "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">" << std::endl - << "<string name=\"foo\">" - << "Hey <xliff:g><xliff:it>there</xliff:it></xliff:g> world</string>" << std::endl - << "</resources>" << std::endl; - std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input); - XliffXmlPullParser parser(sourceParser); - EXPECT_EQ(XmlPullParser::Event::kStartDocument, parser.getEvent()); - - EXPECT_EQ(XmlPullParser::Event::kStartNamespace, parser.next()); - EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2"); - EXPECT_EQ(parser.getNamespacePrefix(), u"xliff"); - - EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next()); - EXPECT_EQ(parser.getElementNamespace(), u""); - EXPECT_EQ(parser.getElementName(), u"resources"); - EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace. - - EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next()); - EXPECT_EQ(parser.getElementNamespace(), u""); - EXPECT_EQ(parser.getElementName(), u"string"); - - EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); - EXPECT_EQ(parser.getText(), u"Hey "); - - EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); - EXPECT_EQ(parser.getText(), u"there"); - - EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); - EXPECT_EQ(parser.getText(), u" world"); - - EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next()); - EXPECT_EQ(parser.getElementNamespace(), u""); - EXPECT_EQ(parser.getElementName(), u"string"); - EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace. - - EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next()); - EXPECT_EQ(parser.getElementNamespace(), u""); - EXPECT_EQ(parser.getElementName(), u"resources"); - - EXPECT_EQ(XmlPullParser::Event::kEndNamespace, parser.next()); - EXPECT_EQ(parser.getNamespacePrefix(), u"xliff"); - EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2"); - - EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.next()); -} - -} // namespace aapt diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h deleted file mode 100644 index 69318840445d..000000000000 --- a/tools/aapt2/XmlDom.h +++ /dev/null @@ -1,154 +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. - */ - -#ifndef AAPT_XML_DOM_H -#define AAPT_XML_DOM_H - -#include "Logger.h" -#include "StringPiece.h" - -#include <istream> -#include <libexpat/expat.h> -#include <memory> -#include <string> -#include <vector> - -namespace aapt { -namespace xml { - -struct Visitor; - -/** - * The type of node. Can be used to downcast to the concrete XML node - * class. - */ -enum class NodeType { - kNamespace, - kElement, - kText, -}; - -/** - * Base class for all XML nodes. - */ -struct Node { - NodeType type; - Node* parent; - size_t lineNumber; - size_t columnNumber; - std::u16string comment; - std::vector<std::unique_ptr<Node>> children; - - Node(NodeType type); - void addChild(std::unique_ptr<Node> child); - virtual std::unique_ptr<Node> clone() const = 0; - virtual void accept(Visitor* visitor) = 0; - virtual ~Node() {} -}; - -/** - * Base class that implements the visitor methods for a - * subclass of Node. - */ -template <typename Derived> -struct BaseNode : public Node { - BaseNode(NodeType t); - virtual void accept(Visitor* visitor) override; -}; - -/** - * A Namespace XML node. Can only have one child. - */ -struct Namespace : public BaseNode<Namespace> { - std::u16string namespacePrefix; - std::u16string namespaceUri; - - Namespace(); - virtual std::unique_ptr<Node> clone() const override; -}; - -/** - * An XML attribute. - */ -struct Attribute { - std::u16string namespaceUri; - std::u16string name; - std::u16string value; -}; - -/** - * An Element XML node. - */ -struct Element : public BaseNode<Element> { - std::u16string namespaceUri; - std::u16string name; - std::vector<Attribute> attributes; - - Element(); - virtual std::unique_ptr<Node> clone() const override; - 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 xml::Attribute* reqAttr); - std::vector<xml::Element*> getChildElements(); -}; - -/** - * A Text (CDATA) XML node. Can not have any children. - */ -struct Text : public BaseNode<Text> { - std::u16string text; - - Text(); - virtual std::unique_ptr<Node> clone() const override; -}; - -/** - * 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<Node> inflate(std::istream* in, SourceLogger* logger); - -/** - * 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<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger); - -/** - * A visitor interface for the different XML Node subtypes. - */ -struct Visitor { - virtual void visit(Namespace* node) = 0; - virtual void visit(Element* node) = 0; - virtual void visit(Text* text) = 0; -}; - -// Implementations - -template <typename Derived> -BaseNode<Derived>::BaseNode(NodeType type) : Node(type) { -} - -template <typename Derived> -void BaseNode<Derived>::accept(Visitor* visitor) { - visitor->visit(static_cast<Derived*>(this)); -} - -} // namespace xml -} // namespace aapt - -#endif // AAPT_XML_DOM_H diff --git a/tools/aapt2/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp deleted file mode 100644 index 56b5613d4264..000000000000 --- a/tools/aapt2/XmlFlattener.cpp +++ /dev/null @@ -1,574 +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. - */ - -#include "BigBuffer.h" -#include "Logger.h" -#include "Maybe.h" -#include "Resolver.h" -#include "Resource.h" -#include "ResourceParser.h" -#include "ResourceValues.h" -#include "SdkConstants.h" -#include "Source.h" -#include "StringPool.h" -#include "Util.h" -#include "XmlFlattener.h" - -#include <androidfw/ResourceTypes.h> -#include <limits> -#include <map> -#include <string> -#include <vector> - -namespace aapt { -namespace xml { - -constexpr uint32_t kLowPriority = 0xffffffffu; - -// A vector that maps String refs to their final destination in the out buffer. -using FlatStringRefList = std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>; - -struct XmlFlattener : public Visitor { - XmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs, - const std::u16string& defaultPackage) : - mOut(outBuffer), mPool(pool), mStringRefs(stringRefs), - mDefaultPackage(defaultPackage) { - } - - // No copying. - XmlFlattener(const XmlFlattener&) = delete; - XmlFlattener& operator=(const XmlFlattener&) = delete; - - void writeNamespace(Namespace* node, uint16_t type) { - const size_t startIndex = mOut->size(); - android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>(); - android::ResXMLTree_namespaceExt* flatNs = - mOut->nextBlock<android::ResXMLTree_namespaceExt>(); - mOut->align4(); - - flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) }; - flatNode->lineNumber = node->lineNumber; - flatNode->comment.index = -1; - addString(node->namespacePrefix, kLowPriority, &flatNs->prefix); - addString(node->namespaceUri, kLowPriority, &flatNs->uri); - } - - virtual void visit(Namespace* node) override { - // Extract the package/prefix from this namespace node. - Maybe<std::u16string> package = util::extractPackageFromNamespace(node->namespaceUri); - if (package) { - mPackageAliases.emplace_back( - node->namespacePrefix, - package.value().empty() ? mDefaultPackage : package.value()); - } - - writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE); - for (const auto& child : node->children) { - child->accept(this); - } - writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE); - - if (package) { - mPackageAliases.pop_back(); - } - } - - virtual void visit(Text* node) override { - if (util::trimWhitespace(node->text).empty()) { - return; - } - - const size_t startIndex = mOut->size(); - android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>(); - android::ResXMLTree_cdataExt* flatText = mOut->nextBlock<android::ResXMLTree_cdataExt>(); - mOut->align4(); - - const uint16_t type = android::RES_XML_CDATA_TYPE; - flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) }; - flatNode->lineNumber = node->lineNumber; - flatNode->comment.index = -1; - addString(node->text, kLowPriority, &flatText->data); - } - - virtual void visit(Element* node) override { - const size_t startIndex = mOut->size(); - android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>(); - android::ResXMLTree_attrExt* flatElem = mOut->nextBlock<android::ResXMLTree_attrExt>(); - - const uint16_t type = android::RES_XML_START_ELEMENT_TYPE; - flatNode->header = { type, sizeof(*flatNode), 0 }; - flatNode->lineNumber = node->lineNumber; - flatNode->comment.index = -1; - - addString(node->namespaceUri, kLowPriority, &flatElem->ns); - addString(node->name, kLowPriority, &flatElem->name); - flatElem->attributeStart = sizeof(*flatElem); - flatElem->attributeSize = sizeof(android::ResXMLTree_attribute); - flatElem->attributeCount = node->attributes.size(); - - if (!writeAttributes(mOut, node, flatElem)) { - mError = true; - } - - mOut->align4(); - flatNode->header.size = (uint32_t)(mOut->size() - startIndex); - - for (const auto& child : node->children) { - child->accept(this); - } - - const size_t startEndIndex = mOut->size(); - android::ResXMLTree_node* flatEndNode = mOut->nextBlock<android::ResXMLTree_node>(); - android::ResXMLTree_endElementExt* flatEndElem = - mOut->nextBlock<android::ResXMLTree_endElementExt>(); - mOut->align4(); - - const uint16_t endType = android::RES_XML_END_ELEMENT_TYPE; - flatEndNode->header = { endType, sizeof(*flatEndNode), - (uint32_t)(mOut->size() - startEndIndex) }; - flatEndNode->lineNumber = node->lineNumber; - flatEndNode->comment.index = -1; - - addString(node->namespaceUri, kLowPriority, &flatEndElem->ns); - addString(node->name, kLowPriority, &flatEndElem->name); - } - - bool success() const { - return !mError; - } - -protected: - void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) { - if (!str.empty()) { - mStringRefs->emplace_back(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 = -1; - } - } - - void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) { - mStringRefs->emplace_back(ref, dest); - } - - Maybe<std::u16string> getPackageAlias(const std::u16string& prefix) { - const auto endIter = mPackageAliases.rend(); - for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) { - if (iter->first == prefix) { - return iter->second; - } - } - return {}; - } - - const std::u16string& getDefaultPackage() const { - return mDefaultPackage; - } - - /** - * Subclasses override this to deal with attributes. Attributes can be flattened as - * raw values or as resources. - */ - virtual bool writeAttributes(BigBuffer* out, Element* node, - android::ResXMLTree_attrExt* flatElem) = 0; - -private: - BigBuffer* mOut; - StringPool* mPool; - FlatStringRefList* mStringRefs; - std::u16string mDefaultPackage; - bool mError = false; - std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases; -}; - -/** - * Flattens XML, encoding the attributes as raw strings. This is used in the compile phase. - */ -struct CompileXmlFlattener : public XmlFlattener { - CompileXmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs, - const std::u16string& defaultPackage) : - XmlFlattener(outBuffer, pool, stringRefs, defaultPackage) { - } - - virtual bool writeAttributes(BigBuffer* out, Element* node, - android::ResXMLTree_attrExt* flatElem) override { - flatElem->attributeCount = node->attributes.size(); - if (node->attributes.empty()) { - return true; - } - - android::ResXMLTree_attribute* flatAttrs = out->nextBlock<android::ResXMLTree_attribute>( - node->attributes.size()); - for (const Attribute& attr : node->attributes) { - addString(attr.namespaceUri, kLowPriority, &flatAttrs->ns); - addString(attr.name, kLowPriority, &flatAttrs->name); - addString(attr.value, kLowPriority, &flatAttrs->rawValue); - flatAttrs++; - } - return true; - } -}; - -struct AttributeToFlatten { - uint32_t resourceId = 0; - const Attribute* xmlAttr = nullptr; - const ::aapt::Attribute* resourceAttr = nullptr; -}; - -static bool lessAttributeId(const AttributeToFlatten& a, uint32_t id) { - return a.resourceId < id; -} - -/** - * Flattens XML, encoding the attributes as resources. - */ -struct LinkedXmlFlattener : public XmlFlattener { - LinkedXmlFlattener(BigBuffer* outBuffer, StringPool* pool, - std::map<std::u16string, StringPool>* packagePools, - FlatStringRefList* stringRefs, - const std::u16string& defaultPackage, - const std::shared_ptr<IResolver>& resolver, - SourceLogger* logger, - const FlattenOptions& options) : - XmlFlattener(outBuffer, pool, stringRefs, defaultPackage), mResolver(resolver), - mLogger(logger), mPackagePools(packagePools), mOptions(options) { - } - - virtual bool writeAttributes(BigBuffer* out, Element* node, - android::ResXMLTree_attrExt* flatElem) override { - bool error = false; - std::vector<AttributeToFlatten> sortedAttributes; - uint32_t nextAttributeId = 0x80000000u; - - // Sort and filter attributes by their resource ID. - for (const Attribute& attr : node->attributes) { - AttributeToFlatten attrToFlatten; - attrToFlatten.xmlAttr = &attr; - - Maybe<std::u16string> package = util::extractPackageFromNamespace(attr.namespaceUri); - if (package) { - // Find the Attribute object via our Resolver. - ResourceName attrName = { package.value(), ResourceType::kAttr, attr.name }; - if (attrName.package.empty()) { - attrName.package = getDefaultPackage(); - } - - Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName); - if (!result || !result.value().id.isValid() || !result.value().attr) { - error = true; - mLogger->error(node->lineNumber) - << "unresolved attribute '" << attrName << "'." - << std::endl; - } else { - attrToFlatten.resourceId = result.value().id.id; - attrToFlatten.resourceAttr = result.value().attr; - - size_t sdk = findAttributeSdkLevel(attrToFlatten.resourceId); - if (mOptions.maxSdkAttribute && sdk > mOptions.maxSdkAttribute.value()) { - // We need to filter this attribute out. - mSmallestFilteredSdk = std::min(mSmallestFilteredSdk, sdk); - continue; - } - } - } - - if (attrToFlatten.resourceId == 0) { - // Attributes that have no resource ID (because they don't belong to a - // package) should appear after those that do have resource IDs. Assign - // them some integer value that will appear after. - attrToFlatten.resourceId = nextAttributeId++; - } - - // Insert the attribute into the sorted vector. - auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(), - attrToFlatten.resourceId, lessAttributeId); - sortedAttributes.insert(iter, std::move(attrToFlatten)); - } - - flatElem->attributeCount = sortedAttributes.size(); - if (sortedAttributes.empty()) { - return true; - } - - android::ResXMLTree_attribute* flatAttr = out->nextBlock<android::ResXMLTree_attribute>( - sortedAttributes.size()); - - // Now that we have sorted the attributes into their final encoded order, it's time - // to actually write them out. - uint16_t attributeIndex = 1; - for (const AttributeToFlatten& attrToFlatten : sortedAttributes) { - Maybe<std::u16string> package = util::extractPackageFromNamespace( - attrToFlatten.xmlAttr->namespaceUri); - - // Assign the indices for specific attributes. - if (package && package.value() == u"android" && attrToFlatten.xmlAttr->name == u"id") { - flatElem->idIndex = attributeIndex; - } else if (attrToFlatten.xmlAttr->namespaceUri.empty()) { - if (attrToFlatten.xmlAttr->name == u"class") { - flatElem->classIndex = attributeIndex; - } else if (attrToFlatten.xmlAttr->name == u"style") { - flatElem->styleIndex = attributeIndex; - } - } - attributeIndex++; - - // Add the namespaceUri and name to the list of StringRefs to encode. - addString(attrToFlatten.xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns); - flatAttr->rawValue.index = -1; - - if (!attrToFlatten.resourceAttr) { - addString(attrToFlatten.xmlAttr->name, kLowPriority, &flatAttr->name); - } else { - // We've already extracted the package successfully before. - assert(package); - - // 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. - StringPool::Ref nameRef = (*mPackagePools)[package.value()].makeRef( - attrToFlatten.xmlAttr->name, - StringPool::Context{ attrToFlatten.resourceId }); - - // Add it to the list of strings to flatten. - addString(nameRef, &flatAttr->name); - - if (mOptions.keepRawValues) { - // Keep raw values (this is for static libraries). - // TODO(with a smarter inflater for binary XML, we can do without this). - addString(attrToFlatten.xmlAttr->value, kLowPriority, &flatAttr->rawValue); - } - } - - error |= !flattenItem(node, attrToFlatten.xmlAttr->value, attrToFlatten.resourceAttr, - flatAttr); - flatAttr->typedValue.size = sizeof(flatAttr->typedValue); - flatAttr++; - } - return !error; - } - - Maybe<size_t> getSmallestFilteredSdk() const { - if (mSmallestFilteredSdk == std::numeric_limits<size_t>::max()) { - return {}; - } - return mSmallestFilteredSdk; - } - -private: - bool flattenItem(const Node* el, const std::u16string& value, const ::aapt::Attribute* attr, - android::ResXMLTree_attribute* flatAttr) { - std::unique_ptr<Item> item; - if (!attr) { - bool create = false; - item = ResourceParser::tryParseReference(value, &create); - if (!item) { - flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING; - addString(value, kLowPriority, &flatAttr->rawValue); - addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>( - &flatAttr->typedValue.data)); - return true; - } - } else { - item = ResourceParser::parseItemForAttribute(value, *attr); - if (!item) { - if (!(attr->typeMask & android::ResTable_map::TYPE_STRING)) { - mLogger->error(el->lineNumber) - << "'" - << value - << "' is not compatible with attribute '" - << *attr - << "'." - << std::endl; - return false; - } - - flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING; - addString(value, kLowPriority, &flatAttr->rawValue); - addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>( - &flatAttr->typedValue.data)); - return true; - } - } - - assert(item); - - bool error = false; - - // If this is a reference, resolve the name into an ID. - visitFunc<Reference>(*item, [&](Reference& reference) { - // First see if we can convert the package name from a prefix to a real - // package name. - ResourceName realName = reference.name; - if (!realName.package.empty()) { - Maybe<std::u16string> package = getPackageAlias(realName.package); - if (package) { - realName.package = package.value(); - } - } else { - realName.package = getDefaultPackage(); - } - - Maybe<ResourceId> result = mResolver->findId(realName); - if (!result || !result.value().isValid()) { - std::ostream& out = mLogger->error(el->lineNumber) - << "unresolved reference '" - << reference.name - << "'"; - if (realName != reference.name) { - out << " (aka '" << realName << "')"; - } - out << "'." << std::endl; - error = true; - } else { - reference.id = result.value(); - } - }); - - if (error) { - return false; - } - - item->flatten(flatAttr->typedValue); - return true; - } - - std::shared_ptr<IResolver> mResolver; - SourceLogger* mLogger; - std::map<std::u16string, StringPool>* mPackagePools; - FlattenOptions mOptions; - size_t mSmallestFilteredSdk = std::numeric_limits<size_t>::max(); -}; - -/** - * The binary XML file expects the StringPool to appear first, but we haven't collected the - * strings yet. We write to a temporary BigBuffer while parsing the input, adding strings - * we encounter to the StringPool. At the end, we write the StringPool to the given BigBuffer and - * then move the data from the temporary BigBuffer into the given one. This incurs no - * copies as the given BigBuffer simply takes ownership of the data. - */ -static void flattenXml(StringPool* pool, FlatStringRefList* stringRefs, BigBuffer* outBuffer, - BigBuffer&& xmlTreeBuffer) { - // Sort the string pool so that attribute resource IDs show up first. - 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 : *stringRefs) { - refEntry.second->index = refEntry.first.getIndex(); - } - - // Write the XML header. - const size_t beforeXmlTreeIndex = outBuffer->size(); - android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>(); - header->header.type = android::RES_XML_TYPE; - header->header.headerSize = sizeof(*header); - - // Flatten the StringPool. - StringPool::flattenUtf16(outBuffer, *pool); - - // Write the array of resource IDs, indexed by StringPool order. - const size_t beforeResIdMapIndex = outBuffer->size(); - android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>(); - resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE; - resIdMapChunk->headerSize = sizeof(*resIdMapChunk); - for (const auto& str : *pool) { - ResourceId id { str->context.priority }; - if (id.id == kLowPriority || !id.isValid()) { - // When we see the first non-resource ID, - // we're done. - break; - } - - *outBuffer->nextBlock<uint32_t>() = id.id; - } - resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex; - - // Move the temporary BigBuffer into outBuffer. - outBuffer->appendBuffer(std::move(xmlTreeBuffer)); - header->header.size = outBuffer->size() - beforeXmlTreeIndex; -} - -bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer) { - StringPool pool; - - // This will hold the StringRefs and the location in which to write the index. - // Once we sort the StringPool, we can assign the updated indices - // to the correct data locations. - FlatStringRefList stringRefs; - - // Since we don't know the size of the final StringPool, we write to this - // temporary BigBuffer, which we will append to outBuffer later. - BigBuffer out(1024); - - CompileXmlFlattener flattener(&out, &pool, &stringRefs, defaultPackage); - root->accept(&flattener); - - if (!flattener.success()) { - return false; - } - - flattenXml(&pool, &stringRefs, outBuffer, std::move(out)); - return true; -}; - -Maybe<size_t> flattenAndLink(const Source& source, Node* root, - const std::u16string& defaultPackage, - const std::shared_ptr<IResolver>& resolver, - const FlattenOptions& options, BigBuffer* outBuffer) { - SourceLogger logger(source); - StringPool pool; - - // 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. - std::map<std::u16string, StringPool> packagePools; - - FlatStringRefList stringRefs; - - // Since we don't know the size of the final StringPool, we write to this - // temporary BigBuffer, which we will append to outBuffer later. - BigBuffer out(1024); - - LinkedXmlFlattener flattener(&out, &pool, &packagePools, &stringRefs, defaultPackage, resolver, - &logger, options); - root->accept(&flattener); - - if (!flattener.success()) { - return {}; - } - - // Merge the package pools into the main pool. - for (auto& packagePoolEntry : packagePools) { - pool.merge(std::move(packagePoolEntry.second)); - } - - flattenXml(&pool, &stringRefs, outBuffer, std::move(out)); - - if (flattener.getSmallestFilteredSdk()) { - return flattener.getSmallestFilteredSdk(); - } - return 0; -} - -} // namespace xml -} // namespace aapt diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h deleted file mode 100644 index 4ece0a37869d..000000000000 --- a/tools/aapt2/XmlFlattener.h +++ /dev/null @@ -1,69 +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. - */ - -#ifndef AAPT_XML_FLATTENER_H -#define AAPT_XML_FLATTENER_H - -#include "BigBuffer.h" -#include "Maybe.h" -#include "Resolver.h" -#include "Source.h" -#include "XmlDom.h" - -#include <string> - -namespace aapt { -namespace xml { - -/** - * Flattens an XML file into a binary representation parseable by - * the Android resource system. - */ -bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer); - -/** - * Options for flattenAndLink. - */ -struct FlattenOptions { - /** - * Keep attribute raw string values along with typed values. - */ - bool keepRawValues = false; - - /** - * If set, any attribute introduced in a later SDK will not be encoded. - */ - Maybe<size_t> maxSdkAttribute; -}; - -/** - * Like flatten(Node*,BigBuffer*), but references to resources are checked - * and string values are transformed to typed data where possible. - * - * `defaultPackage` is used when a reference has no package or the namespace URI - * "http://schemas.android.com/apk/res-auto" is used. - * - * `resolver` is used to resolve references to resources. - */ -Maybe<size_t> flattenAndLink(const Source& source, Node* root, - const std::u16string& defaultPackage, - const std::shared_ptr<IResolver>& resolver, - const FlattenOptions& options, BigBuffer* outBuffer); - -} // namespace xml -} // namespace aapt - -#endif // AAPT_XML_FLATTENER_H diff --git a/tools/aapt2/XmlFlattener_test.cpp b/tools/aapt2/XmlFlattener_test.cpp deleted file mode 100644 index 8915d2478b64..000000000000 --- a/tools/aapt2/XmlFlattener_test.cpp +++ /dev/null @@ -1,232 +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. - */ - -#include "MockResolver.h" -#include "ResourceTable.h" -#include "ResourceValues.h" -#include "Util.h" -#include "XmlFlattener.h" - -#include <androidfw/AssetManager.h> -#include <androidfw/ResourceTypes.h> -#include <gtest/gtest.h> -#include <sstream> -#include <string> - -using namespace android; - -namespace aapt { -namespace xml { - -constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; - -class XmlFlattenerTest : public ::testing::Test { -public: - virtual void SetUp() override { - mResolver = std::make_shared<MockResolver>( - std::make_shared<ResourceTable>(), - std::map<ResourceName, ResourceId>({ - { ResourceName{ u"android", ResourceType::kAttr, u"attr" }, - ResourceId{ 0x01010000u } }, - { ResourceName{ u"android", ResourceType::kId, u"id" }, - ResourceId{ 0x01020000u } }, - { ResourceName{ u"com.lib", ResourceType::kAttr, u"attr" }, - ResourceId{ 0x01010001u } }, - { ResourceName{ u"com.lib", ResourceType::kId, u"id" }, - ResourceId{ 0x01020001u } }})); - } - - ::testing::AssertionResult testFlatten(const std::string& in, ResXMLTree* outTree) { - std::stringstream input(kXmlPreamble); - input << in << std::endl; - - SourceLogger logger(Source{ "test.xml" }); - std::unique_ptr<Node> root = inflate(&input, &logger); - if (!root) { - return ::testing::AssertionFailure(); - } - - BigBuffer outBuffer(1024); - if (!flattenAndLink(Source{ "test.xml" }, root.get(), std::u16string(u"android"), - mResolver, {}, &outBuffer)) { - return ::testing::AssertionFailure(); - } - - std::unique_ptr<uint8_t[]> data = util::copy(outBuffer); - if (outTree->setTo(data.get(), outBuffer.size(), true) != NO_ERROR) { - return ::testing::AssertionFailure(); - } - return ::testing::AssertionSuccess(); - } - - std::shared_ptr<IResolver> mResolver; -}; - -TEST_F(XmlFlattenerTest, ParseSimpleView) { - std::string input = R"EOF( - <View xmlns:android="http://schemas.android.com/apk/res/android" - android:attr="@id/id" - class="str" - style="@id/id"> - </View> - )EOF"; - ResXMLTree tree; - ASSERT_TRUE(testFlatten(input, &tree)); - - while (tree.next() != ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); - ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); - } - - const StringPiece16 androidNs = u"http://schemas.android.com/apk/res/android"; - const StringPiece16 attrName = u"attr"; - ssize_t idx = tree.indexOfAttribute(androidNs.data(), androidNs.size(), attrName.data(), - attrName.size()); - ASSERT_GE(idx, 0); - EXPECT_EQ(tree.getAttributeNameResID(idx), 0x01010000u); - EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE); - - const StringPiece16 class16 = u"class"; - idx = tree.indexOfAttribute(nullptr, 0, class16.data(), class16.size()); - ASSERT_GE(idx, 0); - EXPECT_EQ(tree.getAttributeNameResID(idx), 0u); - EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_STRING); - EXPECT_EQ(tree.getAttributeData(idx), tree.getAttributeValueStringID(idx)); - - const StringPiece16 style16 = u"style"; - idx = tree.indexOfAttribute(nullptr, 0, style16.data(), style16.size()); - ASSERT_GE(idx, 0); - EXPECT_EQ(tree.getAttributeNameResID(idx), 0u); - EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE); - EXPECT_EQ((uint32_t) tree.getAttributeData(idx), 0x01020000u); - EXPECT_EQ(tree.getAttributeValueStringID(idx), -1); - - while (tree.next() != ResXMLTree::END_DOCUMENT) { - ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); - } -} - -TEST_F(XmlFlattenerTest, ParseViewWithPackageAlias) { - std::string input = "<View xmlns:ns1=\"http://schemas.android.com/apk/res/android\"\n" - " xmlns:ns2=\"http://schemas.android.com/apk/res/android\"\n" - " ns1:attr=\"@ns2:id/id\">\n" - "</View>"; - ResXMLTree tree; - ASSERT_TRUE(testFlatten(input, &tree)); - - while (tree.next() != ResXMLTree::END_DOCUMENT) { - ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); - } -} - -::testing::AssertionResult attributeNameAndValueEquals(ResXMLTree* tree, size_t index, - ResourceId nameId, ResourceId valueId) { - if (index >= tree->getAttributeCount()) { - return ::testing::AssertionFailure() << "index " << index << " is out of bounds (" - << tree->getAttributeCount() << ")"; - } - - if (tree->getAttributeNameResID(index) != nameId.id) { - return ::testing::AssertionFailure() - << "attribute at index " << index << " has ID " - << ResourceId{ (uint32_t) tree->getAttributeNameResID(index) } - << ". Expected ID " << nameId; - } - - if (tree->getAttributeDataType(index) != Res_value::TYPE_REFERENCE) { - return ::testing::AssertionFailure() << "attribute at index " << index << " has value of " - << "type " << std::hex - << tree->getAttributeDataType(index) << std::dec - << ". Expected reference (" << std::hex - << Res_value::TYPE_REFERENCE << std::dec << ")"; - } - - if ((uint32_t) tree->getAttributeData(index) != valueId.id) { - return ::testing::AssertionFailure() - << "attribute at index " << index << " has value " << "with ID " - << ResourceId{ (uint32_t) tree->getAttributeData(index) } - << ". Expected ID " << valueId; - } - return ::testing::AssertionSuccess(); -} - -TEST_F(XmlFlattenerTest, ParseViewWithShadowedPackageAlias) { - std::string input = "<View xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" - " app:attr=\"@app:id/id\">\n" - " <View xmlns:app=\"http://schemas.android.com/apk/res/com.lib\"\n" - " app:attr=\"@app:id/id\"/>\n" - "</View>"; - ResXMLTree tree; - ASSERT_TRUE(testFlatten(input, &tree)); - - while (tree.next() != ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); - } - - ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010000u }, - ResourceId{ 0x01020000u })); - - while (tree.next() != ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); - } - - ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u }, - ResourceId{ 0x01020001u })); -} - -TEST_F(XmlFlattenerTest, ParseViewWithLocalPackageAndAliasOfTheSameName) { - std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/com.lib\"\n" - " android:attr=\"@id/id\"/>"; - ResXMLTree tree; - ASSERT_TRUE(testFlatten(input, &tree)); - - while (tree.next() != ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); - } - - // We expect the 'android:attr' to be converted to 'com.lib:attr' due to the namespace - // assignment. - // However, we didn't give '@id/id' a package, so it should use the default package - // 'android', and not be converted from 'android' to 'com.lib'. - ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u }, - ResourceId{ 0x01020000u })); -} - -/* - * The device ResXMLParser in libandroidfw differentiates between empty namespace and null - * namespace. - */ -TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { - std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" - " package=\"android\"/>"; - - ResXMLTree tree; - ASSERT_TRUE(testFlatten(input, &tree)); - - while (tree.next() != ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); - } - - const StringPiece16 kPackage = u"package"; - EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); -} - -} // namespace xml -} // namespace aapt diff --git a/tools/aapt2/ZipEntry.cpp b/tools/aapt2/ZipEntry.cpp deleted file mode 100644 index 891b4e1e2d3c..000000000000 --- a/tools/aapt2/ZipEntry.cpp +++ /dev/null @@ -1,745 +0,0 @@ -/* - * Copyright (C) 2006 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. - */ - -// -// Access to entries in a Zip archive. -// - -#define LOG_TAG "zip" - -#include "ZipEntry.h" -#include <utils/Log.h> - -#include <stdio.h> -#include <string.h> -#include <assert.h> - -namespace aapt { - -using namespace android; - -/* - * Initialize a new ZipEntry structure from a FILE* positioned at a - * CentralDirectoryEntry. - * - * On exit, the file pointer will be at the start of the next CDE or - * at the EOCD. - */ -status_t ZipEntry::initFromCDE(FILE* fp) -{ - status_t result; - long posn; - bool hasDD; - - //ALOGV("initFromCDE ---\n"); - - /* read the CDE */ - result = mCDE.read(fp); - if (result != NO_ERROR) { - ALOGD("mCDE.read failed\n"); - return result; - } - - //mCDE.dump(); - - /* using the info in the CDE, go load up the LFH */ - posn = ftell(fp); - if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) { - ALOGD("local header seek failed (%ld)\n", - mCDE.mLocalHeaderRelOffset); - return UNKNOWN_ERROR; - } - - result = mLFH.read(fp); - if (result != NO_ERROR) { - ALOGD("mLFH.read failed\n"); - return result; - } - - if (fseek(fp, posn, SEEK_SET) != 0) - return UNKNOWN_ERROR; - - //mLFH.dump(); - - /* - * We *might* need to read the Data Descriptor at this point and - * integrate it into the LFH. If this bit is set, the CRC-32, - * compressed size, and uncompressed size will be zero. In practice - * these seem to be rare. - */ - hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0; - if (hasDD) { - // do something clever - //ALOGD("+++ has data descriptor\n"); - } - - /* - * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr" - * flag is set, because the LFH is incomplete. (Not a problem, since we - * prefer the CDE values.) - */ - if (!hasDD && !compareHeaders()) { - ALOGW("warning: header mismatch\n"); - // keep going? - } - - /* - * If the mVersionToExtract is greater than 20, we may have an - * issue unpacking the record -- could be encrypted, compressed - * with something we don't support, or use Zip64 extensions. We - * can defer worrying about that to when we're extracting data. - */ - - return NO_ERROR; -} - -/* - * Initialize a new entry. Pass in the file name and an optional comment. - * - * Initializes the CDE and the LFH. - */ -void ZipEntry::initNew(const char* fileName, const char* comment) -{ - assert(fileName != NULL && *fileName != '\0'); // name required - - /* most fields are properly initialized by constructor */ - mCDE.mVersionMadeBy = kDefaultMadeBy; - mCDE.mVersionToExtract = kDefaultVersion; - mCDE.mCompressionMethod = kCompressStored; - mCDE.mFileNameLength = strlen(fileName); - if (comment != NULL) - mCDE.mFileCommentLength = strlen(comment); - mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does - - if (mCDE.mFileNameLength > 0) { - mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; - strcpy((char*) mCDE.mFileName, fileName); - } - if (mCDE.mFileCommentLength > 0) { - /* TODO: stop assuming null-terminated ASCII here? */ - mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; - strcpy((char*) mCDE.mFileComment, comment); - } - - copyCDEtoLFH(); -} - -/* - * Initialize a new entry, starting with the ZipEntry from a different - * archive. - * - * Initializes the CDE and the LFH. - */ -status_t ZipEntry::initFromExternal(const ZipFile* /* pZipFile */, - const ZipEntry* pEntry, const char* storageName) -{ - mCDE = pEntry->mCDE; - if (storageName && *storageName != 0) { - mCDE.mFileNameLength = strlen(storageName); - mCDE.mFileName = new unsigned char[mCDE.mFileNameLength + 1]; - strcpy((char*) mCDE.mFileName, storageName); - } - - // Check whether we got all the memory needed. - if ((mCDE.mFileNameLength > 0 && mCDE.mFileName == NULL) || - (mCDE.mFileCommentLength > 0 && mCDE.mFileComment == NULL) || - (mCDE.mExtraFieldLength > 0 && mCDE.mExtraField == NULL)) { - return NO_MEMORY; - } - - /* construct the LFH from the CDE */ - copyCDEtoLFH(); - - /* - * The LFH "extra" field is independent of the CDE "extra", so we - * handle it here. - */ - assert(mLFH.mExtraField == NULL); - mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength; - if (mLFH.mExtraFieldLength > 0) { - mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1]; - if (mLFH.mExtraField == NULL) - return NO_MEMORY; - memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField, - mLFH.mExtraFieldLength+1); - } - - return NO_ERROR; -} - -/* - * Insert pad bytes in the LFH by tweaking the "extra" field. This will - * potentially confuse something that put "extra" data in here earlier, - * but I can't find an actual problem. - */ -status_t ZipEntry::addPadding(int padding) -{ - if (padding <= 0) - return INVALID_OPERATION; - - //ALOGI("HEY: adding %d pad bytes to existing %d in %s\n", - // padding, mLFH.mExtraFieldLength, mCDE.mFileName); - - if (mLFH.mExtraFieldLength > 0) { - /* extend existing field */ - unsigned char* newExtra; - - newExtra = new unsigned char[mLFH.mExtraFieldLength + padding]; - if (newExtra == NULL) - return NO_MEMORY; - memset(newExtra + mLFH.mExtraFieldLength, 0, padding); - memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength); - - delete[] mLFH.mExtraField; - mLFH.mExtraField = newExtra; - mLFH.mExtraFieldLength += padding; - } else { - /* create new field */ - mLFH.mExtraField = new unsigned char[padding]; - memset(mLFH.mExtraField, 0, padding); - mLFH.mExtraFieldLength = padding; - } - - return NO_ERROR; -} - -/* - * Set the fields in the LFH equal to the corresponding fields in the CDE. - * - * This does not touch the LFH "extra" field. - */ -void ZipEntry::copyCDEtoLFH(void) -{ - mLFH.mVersionToExtract = mCDE.mVersionToExtract; - mLFH.mGPBitFlag = mCDE.mGPBitFlag; - mLFH.mCompressionMethod = mCDE.mCompressionMethod; - mLFH.mLastModFileTime = mCDE.mLastModFileTime; - mLFH.mLastModFileDate = mCDE.mLastModFileDate; - mLFH.mCRC32 = mCDE.mCRC32; - mLFH.mCompressedSize = mCDE.mCompressedSize; - mLFH.mUncompressedSize = mCDE.mUncompressedSize; - mLFH.mFileNameLength = mCDE.mFileNameLength; - // the "extra field" is independent - - delete[] mLFH.mFileName; - if (mLFH.mFileNameLength > 0) { - mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1]; - strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName); - } else { - mLFH.mFileName = NULL; - } -} - -/* - * Set some information about a file after we add it. - */ -void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32, - int compressionMethod) -{ - mCDE.mCompressionMethod = compressionMethod; - mCDE.mCRC32 = crc32; - mCDE.mCompressedSize = compLen; - mCDE.mUncompressedSize = uncompLen; - mCDE.mCompressionMethod = compressionMethod; - if (compressionMethod == kCompressDeflated) { - mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used - } - copyCDEtoLFH(); -} - -/* - * See if the data in mCDE and mLFH match up. This is mostly useful for - * debugging these classes, but it can be used to identify damaged - * archives. - * - * Returns "false" if they differ. - */ -bool ZipEntry::compareHeaders(void) const -{ - if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) { - ALOGV("cmp: VersionToExtract\n"); - return false; - } - if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) { - ALOGV("cmp: GPBitFlag\n"); - return false; - } - if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) { - ALOGV("cmp: CompressionMethod\n"); - return false; - } - if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) { - ALOGV("cmp: LastModFileTime\n"); - return false; - } - if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) { - ALOGV("cmp: LastModFileDate\n"); - return false; - } - if (mCDE.mCRC32 != mLFH.mCRC32) { - ALOGV("cmp: CRC32\n"); - return false; - } - if (mCDE.mCompressedSize != mLFH.mCompressedSize) { - ALOGV("cmp: CompressedSize\n"); - return false; - } - if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) { - ALOGV("cmp: UncompressedSize\n"); - return false; - } - if (mCDE.mFileNameLength != mLFH.mFileNameLength) { - ALOGV("cmp: FileNameLength\n"); - return false; - } -#if 0 // this seems to be used for padding, not real data - if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) { - ALOGV("cmp: ExtraFieldLength\n"); - return false; - } -#endif - if (mCDE.mFileName != NULL) { - if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) { - ALOGV("cmp: FileName\n"); - return false; - } - } - - return true; -} - - -/* - * Convert the DOS date/time stamp into a UNIX time stamp. - */ -time_t ZipEntry::getModWhen(void) const -{ - struct tm parts; - - parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1; - parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5; - parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11; - parts.tm_mday = (mCDE.mLastModFileDate & 0x001f); - parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1; - parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80; - parts.tm_wday = parts.tm_yday = 0; - parts.tm_isdst = -1; // DST info "not available" - - return mktime(&parts); -} - -/* - * Set the CDE/LFH timestamp from UNIX time. - */ -void ZipEntry::setModWhen(time_t when) -{ -#if !defined(_WIN32) - struct tm tmResult; -#endif - time_t even; - unsigned short zdate, ztime; - - struct tm* ptm; - - /* round up to an even number of seconds */ - even = (time_t)(((unsigned long)(when) + 1) & (~1)); - - /* expand */ -#if !defined(_WIN32) - ptm = localtime_r(&even, &tmResult); -#else - ptm = localtime(&even); -#endif - - int year; - year = ptm->tm_year; - if (year < 80) - year = 80; - - zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday; - ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1; - - mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime; - mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate; -} - - -/* - * =========================================================================== - * ZipEntry::LocalFileHeader - * =========================================================================== - */ - -/* - * Read a local file header. - * - * On entry, "fp" points to the signature at the start of the header. - * On exit, "fp" points to the start of data. - */ -status_t ZipEntry::LocalFileHeader::read(FILE* fp) -{ - status_t result = NO_ERROR; - unsigned char buf[kLFHLen]; - - assert(mFileName == NULL); - assert(mExtraField == NULL); - - if (fread(buf, 1, kLFHLen, fp) != kLFHLen) { - result = UNKNOWN_ERROR; - goto bail; - } - - if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { - ALOGD("whoops: didn't find expected signature\n"); - result = UNKNOWN_ERROR; - goto bail; - } - - mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]); - mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]); - mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]); - mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]); - mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]); - mCRC32 = ZipEntry::getLongLE(&buf[0x0e]); - mCompressedSize = ZipEntry::getLongLE(&buf[0x12]); - mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]); - mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]); - mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]); - - // TODO: validate sizes - - /* grab filename */ - if (mFileNameLength != 0) { - mFileName = new unsigned char[mFileNameLength+1]; - if (mFileName == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mFileName[mFileNameLength] = '\0'; - } - - /* grab extra field */ - if (mExtraFieldLength != 0) { - mExtraField = new unsigned char[mExtraFieldLength+1]; - if (mExtraField == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mExtraField[mExtraFieldLength] = '\0'; - } - -bail: - return result; -} - -/* - * Write a local file header. - */ -status_t ZipEntry::LocalFileHeader::write(FILE* fp) -{ - unsigned char buf[kLFHLen]; - - ZipEntry::putLongLE(&buf[0x00], kSignature); - ZipEntry::putShortLE(&buf[0x04], mVersionToExtract); - ZipEntry::putShortLE(&buf[0x06], mGPBitFlag); - ZipEntry::putShortLE(&buf[0x08], mCompressionMethod); - ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime); - ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate); - ZipEntry::putLongLE(&buf[0x0e], mCRC32); - ZipEntry::putLongLE(&buf[0x12], mCompressedSize); - ZipEntry::putLongLE(&buf[0x16], mUncompressedSize); - ZipEntry::putShortLE(&buf[0x1a], mFileNameLength); - ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength); - - if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen) - return UNKNOWN_ERROR; - - /* write filename */ - if (mFileNameLength != 0) { - if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) - return UNKNOWN_ERROR; - } - - /* write "extra field" */ - if (mExtraFieldLength != 0) { - if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - - -/* - * Dump the contents of a LocalFileHeader object. - */ -void ZipEntry::LocalFileHeader::dump(void) const -{ - ALOGD(" LocalFileHeader contents:\n"); - ALOGD(" versToExt=%u gpBits=0x%04x compression=%u\n", - mVersionToExtract, mGPBitFlag, mCompressionMethod); - ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", - mLastModFileTime, mLastModFileDate, mCRC32); - ALOGD(" compressedSize=%lu uncompressedSize=%lu\n", - mCompressedSize, mUncompressedSize); - ALOGD(" filenameLen=%u extraLen=%u\n", - mFileNameLength, mExtraFieldLength); - if (mFileName != NULL) - ALOGD(" filename: '%s'\n", mFileName); -} - - -/* - * =========================================================================== - * ZipEntry::CentralDirEntry - * =========================================================================== - */ - -/* - * Read the central dir entry that appears next in the file. - * - * On entry, "fp" should be positioned on the signature bytes for the - * entry. On exit, "fp" will point at the signature word for the next - * entry or for the EOCD. - */ -status_t ZipEntry::CentralDirEntry::read(FILE* fp) -{ - status_t result = NO_ERROR; - unsigned char buf[kCDELen]; - - /* no re-use */ - assert(mFileName == NULL); - assert(mExtraField == NULL); - assert(mFileComment == NULL); - - if (fread(buf, 1, kCDELen, fp) != kCDELen) { - result = UNKNOWN_ERROR; - goto bail; - } - - if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { - ALOGD("Whoops: didn't find expected signature\n"); - result = UNKNOWN_ERROR; - goto bail; - } - - mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]); - mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]); - mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]); - mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]); - mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]); - mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]); - mCRC32 = ZipEntry::getLongLE(&buf[0x10]); - mCompressedSize = ZipEntry::getLongLE(&buf[0x14]); - mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]); - mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]); - mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]); - mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]); - mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]); - mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]); - mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]); - mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]); - - // TODO: validate sizes and offsets - - /* grab filename */ - if (mFileNameLength != 0) { - mFileName = new unsigned char[mFileNameLength+1]; - if (mFileName == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mFileName[mFileNameLength] = '\0'; - } - - /* read "extra field" */ - if (mExtraFieldLength != 0) { - mExtraField = new unsigned char[mExtraFieldLength+1]; - if (mExtraField == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mExtraField[mExtraFieldLength] = '\0'; - } - - - /* grab comment, if any */ - if (mFileCommentLength != 0) { - mFileComment = new unsigned char[mFileCommentLength+1]; - if (mFileComment == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) - { - result = UNKNOWN_ERROR; - goto bail; - } - mFileComment[mFileCommentLength] = '\0'; - } - -bail: - return result; -} - -/* - * Write a central dir entry. - */ -status_t ZipEntry::CentralDirEntry::write(FILE* fp) -{ - unsigned char buf[kCDELen]; - - ZipEntry::putLongLE(&buf[0x00], kSignature); - ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy); - ZipEntry::putShortLE(&buf[0x06], mVersionToExtract); - ZipEntry::putShortLE(&buf[0x08], mGPBitFlag); - ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod); - ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime); - ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate); - ZipEntry::putLongLE(&buf[0x10], mCRC32); - ZipEntry::putLongLE(&buf[0x14], mCompressedSize); - ZipEntry::putLongLE(&buf[0x18], mUncompressedSize); - ZipEntry::putShortLE(&buf[0x1c], mFileNameLength); - ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength); - ZipEntry::putShortLE(&buf[0x20], mFileCommentLength); - ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart); - ZipEntry::putShortLE(&buf[0x24], mInternalAttrs); - ZipEntry::putLongLE(&buf[0x26], mExternalAttrs); - ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset); - - if (fwrite(buf, 1, kCDELen, fp) != kCDELen) - return UNKNOWN_ERROR; - - /* write filename */ - if (mFileNameLength != 0) { - if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) - return UNKNOWN_ERROR; - } - - /* write "extra field" */ - if (mExtraFieldLength != 0) { - if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) - return UNKNOWN_ERROR; - } - - /* write comment */ - if (mFileCommentLength != 0) { - if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - -/* - * Dump the contents of a CentralDirEntry object. - */ -void ZipEntry::CentralDirEntry::dump(void) const -{ - ALOGD(" CentralDirEntry contents:\n"); - ALOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n", - mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod); - ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", - mLastModFileTime, mLastModFileDate, mCRC32); - ALOGD(" compressedSize=%lu uncompressedSize=%lu\n", - mCompressedSize, mUncompressedSize); - ALOGD(" filenameLen=%u extraLen=%u commentLen=%u\n", - mFileNameLength, mExtraFieldLength, mFileCommentLength); - ALOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n", - mDiskNumberStart, mInternalAttrs, mExternalAttrs, - mLocalHeaderRelOffset); - - if (mFileName != NULL) - ALOGD(" filename: '%s'\n", mFileName); - if (mFileComment != NULL) - ALOGD(" comment: '%s'\n", mFileComment); -} - -/* - * Copy-assignment operator for CentralDirEntry. - */ -ZipEntry::CentralDirEntry& ZipEntry::CentralDirEntry::operator=(const ZipEntry::CentralDirEntry& src) { - if (this == &src) { - return *this; - } - - // Free up old data. - delete[] mFileName; - delete[] mExtraField; - delete[] mFileComment; - - // Copy scalars. - mVersionMadeBy = src.mVersionMadeBy; - mVersionToExtract = src.mVersionToExtract; - mGPBitFlag = src.mGPBitFlag; - mCompressionMethod = src.mCompressionMethod; - mLastModFileTime = src.mLastModFileTime; - mLastModFileDate = src.mLastModFileDate; - mCRC32 = src.mCRC32; - mCompressedSize = src.mCompressedSize; - mUncompressedSize = src.mUncompressedSize; - mFileNameLength = src.mFileNameLength; - mExtraFieldLength = src.mExtraFieldLength; - mFileCommentLength = src.mFileCommentLength; - mDiskNumberStart = src.mDiskNumberStart; - mInternalAttrs = src.mInternalAttrs; - mExternalAttrs = src.mExternalAttrs; - mLocalHeaderRelOffset = src.mLocalHeaderRelOffset; - - // Copy strings, if necessary. - if (mFileNameLength > 0) { - mFileName = new unsigned char[mFileNameLength + 1]; - if (mFileName != NULL) - strcpy((char*)mFileName, (char*)src.mFileName); - } else { - mFileName = NULL; - } - if (mFileCommentLength > 0) { - mFileComment = new unsigned char[mFileCommentLength + 1]; - if (mFileComment != NULL) - strcpy((char*)mFileComment, (char*)src.mFileComment); - } else { - mFileComment = NULL; - } - if (mExtraFieldLength > 0) { - /* we null-terminate this, though it may not be a string */ - mExtraField = new unsigned char[mExtraFieldLength + 1]; - if (mExtraField != NULL) - memcpy(mExtraField, src.mExtraField, mExtraFieldLength + 1); - } else { - mExtraField = NULL; - } - - return *this; -} - -} // namespace aapt diff --git a/tools/aapt2/ZipEntry.h b/tools/aapt2/ZipEntry.h deleted file mode 100644 index 2745a4386278..000000000000 --- a/tools/aapt2/ZipEntry.h +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright (C) 2006 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. - */ - -// -// Zip archive entries. -// -// The ZipEntry class is tightly meshed with the ZipFile class. -// -#ifndef __LIBS_ZIPENTRY_H -#define __LIBS_ZIPENTRY_H - -#include <utils/Errors.h> - -#include <stdlib.h> -#include <stdio.h> - -namespace aapt { - -using android::status_t; - -class ZipFile; - -/* - * ZipEntry objects represent a single entry in a Zip archive. - * - * You can use one of these to get or set information about an entry, but - * there are no functions here for accessing the data itself. (We could - * tuck a pointer to the ZipFile in here for convenience, but that raises - * the likelihood of using ZipEntry objects after discarding the ZipFile.) - * - * File information is stored in two places: next to the file data (the Local - * File Header, and possibly a Data Descriptor), and at the end of the file - * (the Central Directory Entry). The two must be kept in sync. - */ -class ZipEntry { -public: - friend class ZipFile; - - ZipEntry(void) - : mDeleted(false), mMarked(false) - {} - ~ZipEntry(void) {} - - /* - * Returns "true" if the data is compressed. - */ - bool isCompressed(void) const { - return mCDE.mCompressionMethod != kCompressStored; - } - int getCompressionMethod(void) const { return mCDE.mCompressionMethod; } - - /* - * Return the uncompressed length. - */ - off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; } - - /* - * Return the compressed length. For uncompressed data, this returns - * the same thing as getUncompresesdLen(). - */ - off_t getCompressedLen(void) const { return mCDE.mCompressedSize; } - - /* - * Return the offset of the local file header. - */ - off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; } - - /* - * Return the absolute file offset of the start of the compressed or - * uncompressed data. - */ - off_t getFileOffset(void) const { - return mCDE.mLocalHeaderRelOffset + - LocalFileHeader::kLFHLen + - mLFH.mFileNameLength + - mLFH.mExtraFieldLength; - } - - /* - * Return the data CRC. - */ - unsigned long getCRC32(void) const { return mCDE.mCRC32; } - - /* - * Return file modification time in UNIX seconds-since-epoch. - */ - time_t getModWhen(void) const; - - /* - * Return the archived file name. - */ - const char* getFileName(void) const { return (const char*) mCDE.mFileName; } - - /* - * Application-defined "mark". Can be useful when synchronizing the - * contents of an archive with contents on disk. - */ - bool getMarked(void) const { return mMarked; } - void setMarked(bool val) { mMarked = val; } - - /* - * Some basic functions for raw data manipulation. "LE" means - * Little Endian. - */ - static inline unsigned short getShortLE(const unsigned char* buf) { - return buf[0] | (buf[1] << 8); - } - static inline unsigned long getLongLE(const unsigned char* buf) { - return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); - } - static inline void putShortLE(unsigned char* buf, short val) { - buf[0] = (unsigned char) val; - buf[1] = (unsigned char) (val >> 8); - } - static inline void putLongLE(unsigned char* buf, long val) { - buf[0] = (unsigned char) val; - buf[1] = (unsigned char) (val >> 8); - buf[2] = (unsigned char) (val >> 16); - buf[3] = (unsigned char) (val >> 24); - } - - /* defined for Zip archives */ - enum { - kCompressStored = 0, // no compression - // shrunk = 1, - // reduced 1 = 2, - // reduced 2 = 3, - // reduced 3 = 4, - // reduced 4 = 5, - // imploded = 6, - // tokenized = 7, - kCompressDeflated = 8, // standard deflate - // Deflate64 = 9, - // lib imploded = 10, - // reserved = 11, - // bzip2 = 12, - }; - - /* - * Deletion flag. If set, the entry will be removed on the next - * call to "flush". - */ - bool getDeleted(void) const { return mDeleted; } - -protected: - /* - * Initialize the structure from the file, which is pointing at - * our Central Directory entry. - */ - status_t initFromCDE(FILE* fp); - - /* - * Initialize the structure for a new file. We need the filename - * and comment so that we can properly size the LFH area. The - * filename is mandatory, the comment is optional. - */ - void initNew(const char* fileName, const char* comment); - - /* - * Initialize the structure with the contents of a ZipEntry from - * another file. If fileName is non-NULL, override the name with fileName. - */ - status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry, - const char* fileName); - - /* - * Add some pad bytes to the LFH. We do this by adding or resizing - * the "extra" field. - */ - status_t addPadding(int padding); - - /* - * Set information about the data for this entry. - */ - void setDataInfo(long uncompLen, long compLen, unsigned long crc32, - int compressionMethod); - - /* - * Set the modification date. - */ - void setModWhen(time_t when); - - /* - * Set the offset of the local file header, relative to the start of - * the current file. - */ - void setLFHOffset(off_t offset) { - mCDE.mLocalHeaderRelOffset = (long) offset; - } - - /* mark for deletion; used by ZipFile::remove() */ - void setDeleted(void) { mDeleted = true; } - -private: - /* these are private and not defined */ - ZipEntry(const ZipEntry& src); - ZipEntry& operator=(const ZipEntry& src); - - /* returns "true" if the CDE and the LFH agree */ - bool compareHeaders(void) const; - void copyCDEtoLFH(void); - - bool mDeleted; // set if entry is pending deletion - bool mMarked; // app-defined marker - - /* - * Every entry in the Zip archive starts off with one of these. - */ - class LocalFileHeader { - public: - LocalFileHeader(void) : - mVersionToExtract(0), - mGPBitFlag(0), - mCompressionMethod(0), - mLastModFileTime(0), - mLastModFileDate(0), - mCRC32(0), - mCompressedSize(0), - mUncompressedSize(0), - mFileNameLength(0), - mExtraFieldLength(0), - mFileName(NULL), - mExtraField(NULL) - {} - virtual ~LocalFileHeader(void) { - delete[] mFileName; - delete[] mExtraField; - } - - status_t read(FILE* fp); - status_t write(FILE* fp); - - // unsigned long mSignature; - unsigned short mVersionToExtract; - unsigned short mGPBitFlag; - unsigned short mCompressionMethod; - unsigned short mLastModFileTime; - unsigned short mLastModFileDate; - unsigned long mCRC32; - unsigned long mCompressedSize; - unsigned long mUncompressedSize; - unsigned short mFileNameLength; - unsigned short mExtraFieldLength; - unsigned char* mFileName; - unsigned char* mExtraField; - - enum { - kSignature = 0x04034b50, - kLFHLen = 30, // LocalFileHdr len, excl. var fields - }; - - void dump(void) const; - }; - - /* - * Every entry in the Zip archive has one of these in the "central - * directory" at the end of the file. - */ - class CentralDirEntry { - public: - CentralDirEntry(void) : - mVersionMadeBy(0), - mVersionToExtract(0), - mGPBitFlag(0), - mCompressionMethod(0), - mLastModFileTime(0), - mLastModFileDate(0), - mCRC32(0), - mCompressedSize(0), - mUncompressedSize(0), - mFileNameLength(0), - mExtraFieldLength(0), - mFileCommentLength(0), - mDiskNumberStart(0), - mInternalAttrs(0), - mExternalAttrs(0), - mLocalHeaderRelOffset(0), - mFileName(NULL), - mExtraField(NULL), - mFileComment(NULL) - {} - virtual ~CentralDirEntry(void) { - delete[] mFileName; - delete[] mExtraField; - delete[] mFileComment; - } - - status_t read(FILE* fp); - status_t write(FILE* fp); - - CentralDirEntry& operator=(const CentralDirEntry& src); - - // unsigned long mSignature; - unsigned short mVersionMadeBy; - unsigned short mVersionToExtract; - unsigned short mGPBitFlag; - unsigned short mCompressionMethod; - unsigned short mLastModFileTime; - unsigned short mLastModFileDate; - unsigned long mCRC32; - unsigned long mCompressedSize; - unsigned long mUncompressedSize; - unsigned short mFileNameLength; - unsigned short mExtraFieldLength; - unsigned short mFileCommentLength; - unsigned short mDiskNumberStart; - unsigned short mInternalAttrs; - unsigned long mExternalAttrs; - unsigned long mLocalHeaderRelOffset; - unsigned char* mFileName; - unsigned char* mExtraField; - unsigned char* mFileComment; - - void dump(void) const; - - enum { - kSignature = 0x02014b50, - kCDELen = 46, // CentralDirEnt len, excl. var fields - }; - }; - - enum { - //kDataDescriptorSignature = 0x08074b50, // currently unused - kDataDescriptorLen = 16, // four 32-bit fields - - kDefaultVersion = 20, // need deflate, nothing much else - kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3 - kUsesDataDescr = 0x0008, // GPBitFlag bit 3 - }; - - LocalFileHeader mLFH; - CentralDirEntry mCDE; -}; - -}; // namespace aapt - -#endif // __LIBS_ZIPENTRY_H diff --git a/tools/aapt2/ZipFile.cpp b/tools/aapt2/ZipFile.cpp deleted file mode 100644 index 268c15efd9b9..000000000000 --- a/tools/aapt2/ZipFile.cpp +++ /dev/null @@ -1,1306 +0,0 @@ -/* - * Copyright (C) 2006 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. - */ - -// -// Access to Zip archives. -// - -#define LOG_TAG "zip" - -#include <androidfw/ZipUtils.h> -#include <utils/Log.h> - -#include "ZipFile.h" -#include "Util.h" - -#include <zlib.h> -#define DEF_MEM_LEVEL 8 // normally in zutil.h? - -#include <memory.h> -#include <sys/stat.h> -#include <errno.h> -#include <assert.h> - -namespace aapt { - -using namespace android; - -/* - * Some environments require the "b", some choke on it. - */ -#define FILE_OPEN_RO "rb" -#define FILE_OPEN_RW "r+b" -#define FILE_OPEN_RW_CREATE "w+b" - -/* should live somewhere else? */ -static status_t errnoToStatus(int err) -{ - if (err == ENOENT) - return NAME_NOT_FOUND; - else if (err == EACCES) - return PERMISSION_DENIED; - else - return UNKNOWN_ERROR; -} - -/* - * Open a file and parse its guts. - */ -status_t ZipFile::open(const char* zipFileName, int flags) -{ - bool newArchive = false; - - assert(mZipFp == NULL); // no reopen - - if ((flags & kOpenTruncate)) - flags |= kOpenCreate; // trunc implies create - - if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite)) - return INVALID_OPERATION; // not both - if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite))) - return INVALID_OPERATION; // not neither - if ((flags & kOpenCreate) && !(flags & kOpenReadWrite)) - return INVALID_OPERATION; // create requires write - - if (flags & kOpenTruncate) { - newArchive = true; - } else { - newArchive = (access(zipFileName, F_OK) != 0); - if (!(flags & kOpenCreate) && newArchive) { - /* not creating, must already exist */ - ALOGD("File %s does not exist", zipFileName); - return NAME_NOT_FOUND; - } - } - - /* open the file */ - const char* openflags; - if (flags & kOpenReadWrite) { - if (newArchive) - openflags = FILE_OPEN_RW_CREATE; - else - openflags = FILE_OPEN_RW; - } else { - openflags = FILE_OPEN_RO; - } - mZipFp = fopen(zipFileName, openflags); - if (mZipFp == NULL) { - int err = errno; - ALOGD("fopen failed: %d\n", err); - return errnoToStatus(err); - } - - status_t result; - if (!newArchive) { - /* - * Load the central directory. If that fails, then this probably - * isn't a Zip archive. - */ - result = readCentralDir(); - } else { - /* - * Newly-created. The EndOfCentralDir constructor actually - * sets everything to be the way we want it (all zeroes). We - * set mNeedCDRewrite so that we create *something* if the - * caller doesn't add any files. (We could also just unlink - * the file if it's brand new and nothing was added, but that's - * probably doing more than we really should -- the user might - * have a need for empty zip files.) - */ - mNeedCDRewrite = true; - result = NO_ERROR; - } - - if (flags & kOpenReadOnly) - mReadOnly = true; - else - assert(!mReadOnly); - - return result; -} - -/* - * Return the Nth entry in the archive. - */ -ZipEntry* ZipFile::getEntryByIndex(int idx) const -{ - if (idx < 0 || idx >= (int) mEntries.size()) - return NULL; - - return mEntries[idx]; -} - -/* - * Find an entry by name. - */ -ZipEntry* ZipFile::getEntryByName(const char* fileName) const -{ - /* - * Do a stupid linear string-compare search. - * - * There are various ways to speed this up, especially since it's rare - * to intermingle changes to the archive with "get by name" calls. We - * don't want to sort the mEntries vector itself, however, because - * it's used to recreate the Central Directory. - * - * (Hash table works, parallel list of pointers in sorted order is good.) - */ - int idx; - - for (idx = mEntries.size()-1; idx >= 0; idx--) { - ZipEntry* pEntry = mEntries[idx]; - if (!pEntry->getDeleted() && - strcmp(fileName, pEntry->getFileName()) == 0) - { - return pEntry; - } - } - - return NULL; -} - -/* - * Empty the mEntries vector. - */ -void ZipFile::discardEntries(void) -{ - int count = mEntries.size(); - - while (--count >= 0) - delete mEntries[count]; - - mEntries.clear(); -} - - -/* - * Find the central directory and read the contents. - * - * The fun thing about ZIP archives is that they may or may not be - * readable from start to end. In some cases, notably for archives - * that were written to stdout, the only length information is in the - * central directory at the end of the file. - * - * Of course, the central directory can be followed by a variable-length - * comment field, so we have to scan through it backwards. The comment - * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff - * itself, plus apparently sometimes people throw random junk on the end - * just for the fun of it. - * - * This is all a little wobbly. If the wrong value ends up in the EOCD - * area, we're hosed. This appears to be the way that everbody handles - * it though, so we're in pretty good company if this fails. - */ -status_t ZipFile::readCentralDir(void) -{ - status_t result = NO_ERROR; - unsigned char* buf = NULL; - off_t fileLength, seekStart; - long readAmount; - int i; - - fseek(mZipFp, 0, SEEK_END); - fileLength = ftell(mZipFp); - rewind(mZipFp); - - /* too small to be a ZIP archive? */ - if (fileLength < EndOfCentralDir::kEOCDLen) { - ALOGD("Length is %ld -- too small\n", (long)fileLength); - result = INVALID_OPERATION; - goto bail; - } - - buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch]; - if (buf == NULL) { - ALOGD("Failure allocating %d bytes for EOCD search", - EndOfCentralDir::kMaxEOCDSearch); - result = NO_MEMORY; - goto bail; - } - - if (fileLength > EndOfCentralDir::kMaxEOCDSearch) { - seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch; - readAmount = EndOfCentralDir::kMaxEOCDSearch; - } else { - seekStart = 0; - readAmount = (long) fileLength; - } - if (fseek(mZipFp, seekStart, SEEK_SET) != 0) { - ALOGD("Failure seeking to end of zip at %ld", (long) seekStart); - result = UNKNOWN_ERROR; - goto bail; - } - - /* read the last part of the file into the buffer */ - if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) { - ALOGD("short file? wanted %ld\n", readAmount); - result = UNKNOWN_ERROR; - goto bail; - } - - /* find the end-of-central-dir magic */ - for (i = readAmount - 4; i >= 0; i--) { - if (buf[i] == 0x50 && - ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature) - { - ALOGV("+++ Found EOCD at buf+%d\n", i); - break; - } - } - if (i < 0) { - ALOGD("EOCD not found, not Zip\n"); - result = INVALID_OPERATION; - goto bail; - } - - /* extract eocd values */ - result = mEOCD.readBuf(buf + i, readAmount - i); - if (result != NO_ERROR) { - ALOGD("Failure reading %ld bytes of EOCD values", readAmount - i); - goto bail; - } - //mEOCD.dump(); - - if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 || - mEOCD.mNumEntries != mEOCD.mTotalNumEntries) - { - ALOGD("Archive spanning not supported\n"); - result = INVALID_OPERATION; - goto bail; - } - - /* - * So far so good. "mCentralDirSize" is the size in bytes of the - * central directory, so we can just seek back that far to find it. - * We can also seek forward mCentralDirOffset bytes from the - * start of the file. - * - * We're not guaranteed to have the rest of the central dir in the - * buffer, nor are we guaranteed that the central dir will have any - * sort of convenient size. We need to skip to the start of it and - * read the header, then the other goodies. - * - * The only thing we really need right now is the file comment, which - * we're hoping to preserve. - */ - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { - ALOGD("Failure seeking to central dir offset %ld\n", - mEOCD.mCentralDirOffset); - result = UNKNOWN_ERROR; - goto bail; - } - - /* - * Loop through and read the central dir entries. - */ - ALOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries); - int entry; - for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) { - ZipEntry* pEntry = new ZipEntry; - - result = pEntry->initFromCDE(mZipFp); - if (result != NO_ERROR) { - ALOGD("initFromCDE failed\n"); - delete pEntry; - goto bail; - } - - mEntries.push_back(pEntry); - } - - - /* - * If all went well, we should now be back at the EOCD. - */ - { - unsigned char checkBuf[4]; - if (fread(checkBuf, 1, 4, mZipFp) != 4) { - ALOGD("EOCD check read failed\n"); - result = INVALID_OPERATION; - goto bail; - } - if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) { - ALOGD("EOCD read check failed\n"); - result = UNKNOWN_ERROR; - goto bail; - } - ALOGV("+++ EOCD read check passed\n"); - } - -bail: - delete[] buf; - return result; -} - -status_t ZipFile::add(const BigBuffer& buffer, const char* storageName, int compressionMethod, - ZipEntry** ppEntry) { - std::unique_ptr<uint8_t[]> data = util::copy(buffer); - return add(data.get(), buffer.size(), storageName, compressionMethod, ppEntry); -} - - -/* - * Add a new file to the archive. - * - * This requires creating and populating a ZipEntry structure, and copying - * the data into the file at the appropriate position. The "appropriate - * position" is the current location of the central directory, which we - * casually overwrite (we can put it back later). - * - * If we were concerned about safety, we would want to make all changes - * in a temp file and then overwrite the original after everything was - * safely written. Not really a concern for us. - */ -status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size, - const char* storageName, int sourceType, int compressionMethod, - ZipEntry** ppEntry) -{ - ZipEntry* pEntry = NULL; - status_t result = NO_ERROR; - long lfhPosn, startPosn, endPosn, uncompressedLen; - FILE* inputFp = NULL; - unsigned long crc; - time_t modWhen; - - if (mReadOnly) - return INVALID_OPERATION; - - assert(compressionMethod == ZipEntry::kCompressDeflated || - compressionMethod == ZipEntry::kCompressStored); - - /* make sure we're in a reasonable state */ - assert(mZipFp != NULL); - assert(mEntries.size() == mEOCD.mTotalNumEntries); - - /* make sure it doesn't already exist */ - if (getEntryByName(storageName) != NULL) - return ALREADY_EXISTS; - - if (!data) { - inputFp = fopen(fileName, FILE_OPEN_RO); - if (inputFp == NULL) - return errnoToStatus(errno); - } - - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { - result = UNKNOWN_ERROR; - goto bail; - } - - pEntry = new ZipEntry; - pEntry->initNew(storageName, NULL); - - /* - * From here on out, failures are more interesting. - */ - mNeedCDRewrite = true; - - /* - * Write the LFH, even though it's still mostly blank. We need it - * as a place-holder. In theory the LFH isn't necessary, but in - * practice some utilities demand it. - */ - lfhPosn = ftell(mZipFp); - pEntry->mLFH.write(mZipFp); - startPosn = ftell(mZipFp); - - /* - * Copy the data in, possibly compressing it as we go. - */ - if (sourceType == ZipEntry::kCompressStored) { - if (compressionMethod == ZipEntry::kCompressDeflated) { - bool failed = false; - result = compressFpToFp(mZipFp, inputFp, data, size, &crc); - if (result != NO_ERROR) { - ALOGD("compression failed, storing\n"); - failed = true; - } else { - /* - * Make sure it has compressed "enough". This probably ought - * to be set through an API call, but I don't expect our - * criteria to change over time. - */ - long src = inputFp ? ftell(inputFp) : size; - long dst = ftell(mZipFp) - startPosn; - if (dst + (dst / 10) > src) { - ALOGD("insufficient compression (src=%ld dst=%ld), storing\n", - src, dst); - failed = true; - } - } - - if (failed) { - compressionMethod = ZipEntry::kCompressStored; - if (inputFp) rewind(inputFp); - fseek(mZipFp, startPosn, SEEK_SET); - /* fall through to kCompressStored case */ - } - } - /* handle "no compression" request, or failed compression from above */ - if (compressionMethod == ZipEntry::kCompressStored) { - if (inputFp) { - result = copyFpToFp(mZipFp, inputFp, &crc); - } else { - result = copyDataToFp(mZipFp, data, size, &crc); - } - if (result != NO_ERROR) { - // don't need to truncate; happens in CDE rewrite - ALOGD("failed copying data in\n"); - goto bail; - } - } - - // currently seeked to end of file - uncompressedLen = inputFp ? ftell(inputFp) : size; - } else if (sourceType == ZipEntry::kCompressDeflated) { - /* we should support uncompressed-from-compressed, but it's not - * important right now */ - assert(compressionMethod == ZipEntry::kCompressDeflated); - - bool scanResult; - int method; - long compressedLen; - - scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen, - &compressedLen, &crc); - if (!scanResult || method != ZipEntry::kCompressDeflated) { - ALOGD("this isn't a deflated gzip file?"); - result = UNKNOWN_ERROR; - goto bail; - } - - result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL); - if (result != NO_ERROR) { - ALOGD("failed copying gzip data in\n"); - goto bail; - } - } else { - assert(false); - result = UNKNOWN_ERROR; - goto bail; - } - - /* - * We could write the "Data Descriptor", but there doesn't seem to - * be any point since we're going to go back and write the LFH. - * - * Update file offsets. - */ - endPosn = ftell(mZipFp); // seeked to end of compressed data - - /* - * Success! Fill out new values. - */ - pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc, - compressionMethod); - modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp)); - pEntry->setModWhen(modWhen); - pEntry->setLFHOffset(lfhPosn); - mEOCD.mNumEntries++; - mEOCD.mTotalNumEntries++; - mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() - mEOCD.mCentralDirOffset = endPosn; - - /* - * Go back and write the LFH. - */ - if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) { - result = UNKNOWN_ERROR; - goto bail; - } - pEntry->mLFH.write(mZipFp); - - /* - * Add pEntry to the list. - */ - mEntries.push_back(pEntry); - if (ppEntry != NULL) - *ppEntry = pEntry; - pEntry = NULL; - -bail: - if (inputFp != NULL) - fclose(inputFp); - delete pEntry; - return result; -} - -/* - * Add an entry by copying it from another zip file. If "padding" is - * nonzero, the specified number of bytes will be added to the "extra" - * field in the header. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ -status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, - const char* storageName, int padding, ZipEntry** ppEntry) -{ - ZipEntry* pEntry = NULL; - status_t result; - long lfhPosn, endPosn; - - if (mReadOnly) - return INVALID_OPERATION; - - /* make sure we're in a reasonable state */ - assert(mZipFp != NULL); - assert(mEntries.size() == mEOCD.mTotalNumEntries); - - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { - result = UNKNOWN_ERROR; - goto bail; - } - - pEntry = new ZipEntry; - if (pEntry == NULL) { - result = NO_MEMORY; - goto bail; - } - - result = pEntry->initFromExternal(pSourceZip, pSourceEntry, storageName); - if (result != NO_ERROR) { - goto bail; - } - if (padding != 0) { - result = pEntry->addPadding(padding); - if (result != NO_ERROR) - goto bail; - } - - /* - * From here on out, failures are more interesting. - */ - mNeedCDRewrite = true; - - /* - * Write the LFH. Since we're not recompressing the data, we already - * have all of the fields filled out. - */ - lfhPosn = ftell(mZipFp); - pEntry->mLFH.write(mZipFp); - - /* - * Copy the data over. - * - * If the "has data descriptor" flag is set, we want to copy the DD - * fields as well. This is a fixed-size area immediately following - * the data. - */ - if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) - { - result = UNKNOWN_ERROR; - goto bail; - } - - off_t copyLen; - copyLen = pSourceEntry->getCompressedLen(); - if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0) - copyLen += ZipEntry::kDataDescriptorLen; - - if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL) - != NO_ERROR) - { - ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName); - result = UNKNOWN_ERROR; - goto bail; - } - - /* - * Update file offsets. - */ - endPosn = ftell(mZipFp); - - /* - * Success! Fill out new values. - */ - pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset - mEOCD.mNumEntries++; - mEOCD.mTotalNumEntries++; - mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() - mEOCD.mCentralDirOffset = endPosn; - - /* - * Add pEntry to the list. - */ - mEntries.push_back(pEntry); - if (ppEntry != NULL) - *ppEntry = pEntry; - pEntry = NULL; - - result = NO_ERROR; - -bail: - delete pEntry; - return result; -} - -/* - * Copy all of the bytes in "src" to "dst". - * - * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" - * will be seeked immediately past the data. - */ -status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32) -{ - unsigned char tmpBuf[32768]; - size_t count; - - *pCRC32 = crc32(0L, Z_NULL, 0); - - while (1) { - count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp); - if (ferror(srcFp) || ferror(dstFp)) - return errnoToStatus(errno); - if (count == 0) - break; - - *pCRC32 = crc32(*pCRC32, tmpBuf, count); - - if (fwrite(tmpBuf, 1, count, dstFp) != count) { - ALOGD("fwrite %d bytes failed\n", (int) count); - return UNKNOWN_ERROR; - } - } - - return NO_ERROR; -} - -/* - * Copy all of the bytes in "src" to "dst". - * - * On exit, "dstFp" will be seeked immediately past the data. - */ -status_t ZipFile::copyDataToFp(FILE* dstFp, - const void* data, size_t size, unsigned long* pCRC32) -{ - *pCRC32 = crc32(0L, Z_NULL, 0); - if (size > 0) { - *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size); - if (fwrite(data, 1, size, dstFp) != size) { - ALOGD("fwrite %d bytes failed\n", (int) size); - return UNKNOWN_ERROR; - } - } - - return NO_ERROR; -} - -/* - * Copy some of the bytes in "src" to "dst". - * - * If "pCRC32" is NULL, the CRC will not be computed. - * - * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" - * will be seeked immediately past the data just written. - */ -status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, - unsigned long* pCRC32) -{ - unsigned char tmpBuf[32768]; - size_t count; - - if (pCRC32 != NULL) - *pCRC32 = crc32(0L, Z_NULL, 0); - - while (length) { - long readSize; - - readSize = sizeof(tmpBuf); - if (readSize > length) - readSize = length; - - count = fread(tmpBuf, 1, readSize, srcFp); - if ((long) count != readSize) { // error or unexpected EOF - ALOGD("fread %d bytes failed\n", (int) readSize); - return UNKNOWN_ERROR; - } - - if (pCRC32 != NULL) - *pCRC32 = crc32(*pCRC32, tmpBuf, count); - - if (fwrite(tmpBuf, 1, count, dstFp) != count) { - ALOGD("fwrite %d bytes failed\n", (int) count); - return UNKNOWN_ERROR; - } - - length -= readSize; - } - - return NO_ERROR; -} - -/* - * Compress all of the data in "srcFp" and write it to "dstFp". - * - * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" - * will be seeked immediately past the compressed data. - */ -status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp, - const void* data, size_t size, unsigned long* pCRC32) -{ - status_t result = NO_ERROR; - const size_t kBufSize = 32768; - unsigned char* inBuf = NULL; - unsigned char* outBuf = NULL; - z_stream zstream; - bool atEof = false; // no feof() aviailable yet - unsigned long crc; - int zerr; - - /* - * Create an input buffer and an output buffer. - */ - inBuf = new unsigned char[kBufSize]; - outBuf = new unsigned char[kBufSize]; - if (inBuf == NULL || outBuf == NULL) { - result = NO_MEMORY; - goto bail; - } - - /* - * Initialize the zlib stream. - */ - memset(&zstream, 0, sizeof(zstream)); - zstream.zalloc = Z_NULL; - zstream.zfree = Z_NULL; - zstream.opaque = Z_NULL; - zstream.next_in = NULL; - zstream.avail_in = 0; - zstream.next_out = outBuf; - zstream.avail_out = kBufSize; - zstream.data_type = Z_UNKNOWN; - - zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION, - Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); - if (zerr != Z_OK) { - result = UNKNOWN_ERROR; - if (zerr == Z_VERSION_ERROR) { - ALOGE("Installed zlib is not compatible with linked version (%s)\n", - ZLIB_VERSION); - } else { - ALOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr); - } - goto bail; - } - - crc = crc32(0L, Z_NULL, 0); - - /* - * Loop while we have data. - */ - do { - size_t getSize; - int flush; - - /* only read if the input buffer is empty */ - if (zstream.avail_in == 0 && !atEof) { - ALOGV("+++ reading %d bytes\n", (int)kBufSize); - if (data) { - getSize = size > kBufSize ? kBufSize : size; - memcpy(inBuf, data, getSize); - data = ((const char*)data) + getSize; - size -= getSize; - } else { - getSize = fread(inBuf, 1, kBufSize, srcFp); - if (ferror(srcFp)) { - ALOGD("deflate read failed (errno=%d)\n", errno); - goto z_bail; - } - } - if (getSize < kBufSize) { - ALOGV("+++ got %d bytes, EOF reached\n", - (int)getSize); - atEof = true; - } - - crc = crc32(crc, inBuf, getSize); - - zstream.next_in = inBuf; - zstream.avail_in = getSize; - } - - if (atEof) - flush = Z_FINISH; /* tell zlib that we're done */ - else - flush = Z_NO_FLUSH; /* more to come! */ - - zerr = deflate(&zstream, flush); - if (zerr != Z_OK && zerr != Z_STREAM_END) { - ALOGD("zlib deflate call failed (zerr=%d)\n", zerr); - result = UNKNOWN_ERROR; - goto z_bail; - } - - /* write when we're full or when we're done */ - if (zstream.avail_out == 0 || - (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize)) - { - ALOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf)); - if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) != - (size_t)(zstream.next_out - outBuf)) - { - ALOGD("write %d failed in deflate\n", - (int) (zstream.next_out - outBuf)); - goto z_bail; - } - - zstream.next_out = outBuf; - zstream.avail_out = kBufSize; - } - } while (zerr == Z_OK); - - assert(zerr == Z_STREAM_END); /* other errors should've been caught */ - - *pCRC32 = crc; - -z_bail: - deflateEnd(&zstream); /* free up any allocated structures */ - -bail: - delete[] inBuf; - delete[] outBuf; - - return result; -} - -/* - * Mark an entry as deleted. - * - * We will eventually need to crunch the file down, but if several files - * are being removed (perhaps as part of an "update" process) we can make - * things considerably faster by deferring the removal to "flush" time. - */ -status_t ZipFile::remove(ZipEntry* pEntry) -{ - /* - * Should verify that pEntry is actually part of this archive, and - * not some stray ZipEntry from a different file. - */ - - /* mark entry as deleted, and mark archive as dirty */ - pEntry->setDeleted(); - mNeedCDRewrite = true; - return NO_ERROR; -} - -/* - * Flush any pending writes. - * - * In particular, this will crunch out deleted entries, and write the - * Central Directory and EOCD if we have stomped on them. - */ -status_t ZipFile::flush(void) -{ - status_t result = NO_ERROR; - long eocdPosn; - int i, count; - - if (mReadOnly) - return INVALID_OPERATION; - if (!mNeedCDRewrite) - return NO_ERROR; - - assert(mZipFp != NULL); - - result = crunchArchive(); - if (result != NO_ERROR) - return result; - - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) - return UNKNOWN_ERROR; - - count = mEntries.size(); - for (i = 0; i < count; i++) { - ZipEntry* pEntry = mEntries[i]; - pEntry->mCDE.write(mZipFp); - } - - eocdPosn = ftell(mZipFp); - mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset; - - mEOCD.write(mZipFp); - - /* - * If we had some stuff bloat up during compression and get replaced - * with plain files, or if we deleted some entries, there's a lot - * of wasted space at the end of the file. Remove it now. - */ - if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) { - ALOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno)); - // not fatal - } - - /* should we clear the "newly added" flag in all entries now? */ - - mNeedCDRewrite = false; - return NO_ERROR; -} - -/* - * Crunch deleted files out of an archive by shifting the later files down. - * - * Because we're not using a temp file, we do the operation inside the - * current file. - */ -status_t ZipFile::crunchArchive(void) -{ - status_t result = NO_ERROR; - int i, count; - long delCount, adjust; - -#if 0 - printf("CONTENTS:\n"); - for (i = 0; i < (int) mEntries.size(); i++) { - printf(" %d: lfhOff=%ld del=%d\n", - i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted()); - } - printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset); -#endif - - /* - * Roll through the set of files, shifting them as appropriate. We - * could probably get a slight performance improvement by sliding - * multiple files down at once (because we could use larger reads - * when operating on batches of small files), but it's not that useful. - */ - count = mEntries.size(); - delCount = adjust = 0; - for (i = 0; i < count; i++) { - ZipEntry* pEntry = mEntries[i]; - long span; - - if (pEntry->getLFHOffset() != 0) { - long nextOffset; - - /* Get the length of this entry by finding the offset - * of the next entry. Directory entries don't have - * file offsets, so we need to find the next non-directory - * entry. - */ - nextOffset = 0; - for (int ii = i+1; nextOffset == 0 && ii < count; ii++) - nextOffset = mEntries[ii]->getLFHOffset(); - if (nextOffset == 0) - nextOffset = mEOCD.mCentralDirOffset; - span = nextOffset - pEntry->getLFHOffset(); - - assert(span >= ZipEntry::LocalFileHeader::kLFHLen); - } else { - /* This is a directory entry. It doesn't have - * any actual file contents, so there's no need to - * move anything. - */ - span = 0; - } - - //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n", - // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count); - - if (pEntry->getDeleted()) { - adjust += span; - delCount++; - - delete pEntry; - mEntries.erase(mEntries.begin() + i); - - /* adjust loop control */ - count--; - i--; - } else if (span != 0 && adjust > 0) { - /* shuffle this entry back */ - //printf("+++ Shuffling '%s' back %ld\n", - // pEntry->getFileName(), adjust); - result = filemove(mZipFp, pEntry->getLFHOffset() - adjust, - pEntry->getLFHOffset(), span); - if (result != NO_ERROR) { - /* this is why you use a temp file */ - ALOGE("error during crunch - archive is toast\n"); - return result; - } - - pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust); - } - } - - /* - * Fix EOCD info. We have to wait until the end to do some of this - * because we use mCentralDirOffset to determine "span" for the - * last entry. - */ - mEOCD.mCentralDirOffset -= adjust; - mEOCD.mNumEntries -= delCount; - mEOCD.mTotalNumEntries -= delCount; - mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() - - assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries); - assert(mEOCD.mNumEntries == count); - - return result; -} - -/* - * Works like memmove(), but on pieces of a file. - */ -status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n) -{ - if (dst == src || n <= 0) - return NO_ERROR; - - unsigned char readBuf[32768]; - - if (dst < src) { - /* shift stuff toward start of file; must read from start */ - while (n != 0) { - size_t getSize = sizeof(readBuf); - if (getSize > n) - getSize = n; - - if (fseek(fp, (long) src, SEEK_SET) != 0) { - ALOGD("filemove src seek %ld failed\n", (long) src); - return UNKNOWN_ERROR; - } - - if (fread(readBuf, 1, getSize, fp) != getSize) { - ALOGD("filemove read %ld off=%ld failed\n", - (long) getSize, (long) src); - return UNKNOWN_ERROR; - } - - if (fseek(fp, (long) dst, SEEK_SET) != 0) { - ALOGD("filemove dst seek %ld failed\n", (long) dst); - return UNKNOWN_ERROR; - } - - if (fwrite(readBuf, 1, getSize, fp) != getSize) { - ALOGD("filemove write %ld off=%ld failed\n", - (long) getSize, (long) dst); - return UNKNOWN_ERROR; - } - - src += getSize; - dst += getSize; - n -= getSize; - } - } else { - /* shift stuff toward end of file; must read from end */ - assert(false); // write this someday, maybe - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - - -/* - * Get the modification time from a file descriptor. - */ -time_t ZipFile::getModTime(int fd) -{ - struct stat sb; - - if (fstat(fd, &sb) < 0) { - ALOGD("HEY: fstat on fd %d failed\n", fd); - return (time_t) -1; - } - - return sb.st_mtime; -} - - -#if 0 /* this is a bad idea */ -/* - * Get a copy of the Zip file descriptor. - * - * We don't allow this if the file was opened read-write because we tend - * to leave the file contents in an uncertain state between calls to - * flush(). The duplicated file descriptor should only be valid for reads. - */ -int ZipFile::getZipFd(void) const -{ - if (!mReadOnly) - return INVALID_OPERATION; - assert(mZipFp != NULL); - - int fd; - fd = dup(fileno(mZipFp)); - if (fd < 0) { - ALOGD("didn't work, errno=%d\n", errno); - } - - return fd; -} -#endif - - -#if 0 -/* - * Expand data. - */ -bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const -{ - return false; -} -#endif - -// free the memory when you're done -void* ZipFile::uncompress(const ZipEntry* entry) -{ - size_t unlen = entry->getUncompressedLen(); - size_t clen = entry->getCompressedLen(); - - void* buf = malloc(unlen); - if (buf == NULL) { - return NULL; - } - - fseek(mZipFp, 0, SEEK_SET); - - off_t offset = entry->getFileOffset(); - if (fseek(mZipFp, offset, SEEK_SET) != 0) { - goto bail; - } - - switch (entry->getCompressionMethod()) - { - case ZipEntry::kCompressStored: { - ssize_t amt = fread(buf, 1, unlen, mZipFp); - if (amt != (ssize_t)unlen) { - goto bail; - } -#if 0 - printf("data...\n"); - const unsigned char* p = (unsigned char*)buf; - const unsigned char* end = p+unlen; - for (int i=0; i<32 && p < end; i++) { - printf("0x%08x ", (int)(offset+(i*0x10))); - for (int j=0; j<0x10 && p < end; j++) { - printf(" %02x", *p); - p++; - } - printf("\n"); - } -#endif - - } - break; - case ZipEntry::kCompressDeflated: { - if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) { - goto bail; - } - } - break; - default: - goto bail; - } - return buf; - -bail: - free(buf); - return NULL; -} - - -/* - * =========================================================================== - * ZipFile::EndOfCentralDir - * =========================================================================== - */ - -/* - * Read the end-of-central-dir fields. - * - * "buf" should be positioned at the EOCD signature, and should contain - * the entire EOCD area including the comment. - */ -status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len) -{ - /* don't allow re-use */ - assert(mComment == NULL); - - if (len < kEOCDLen) { - /* looks like ZIP file got truncated */ - ALOGD(" Zip EOCD: expected >= %d bytes, found %d\n", - kEOCDLen, len); - return INVALID_OPERATION; - } - - /* this should probably be an assert() */ - if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) - return UNKNOWN_ERROR; - - mDiskNumber = ZipEntry::getShortLE(&buf[0x04]); - mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]); - mNumEntries = ZipEntry::getShortLE(&buf[0x08]); - mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]); - mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]); - mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]); - mCommentLen = ZipEntry::getShortLE(&buf[0x14]); - - // TODO: validate mCentralDirOffset - - if (mCommentLen > 0) { - if (kEOCDLen + mCommentLen > len) { - ALOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n", - kEOCDLen, mCommentLen, len); - return UNKNOWN_ERROR; - } - mComment = new unsigned char[mCommentLen]; - memcpy(mComment, buf + kEOCDLen, mCommentLen); - } - - return NO_ERROR; -} - -/* - * Write an end-of-central-directory section. - */ -status_t ZipFile::EndOfCentralDir::write(FILE* fp) -{ - unsigned char buf[kEOCDLen]; - - ZipEntry::putLongLE(&buf[0x00], kSignature); - ZipEntry::putShortLE(&buf[0x04], mDiskNumber); - ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir); - ZipEntry::putShortLE(&buf[0x08], mNumEntries); - ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries); - ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize); - ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset); - ZipEntry::putShortLE(&buf[0x14], mCommentLen); - - if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen) - return UNKNOWN_ERROR; - if (mCommentLen > 0) { - assert(mComment != NULL); - if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen) - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - -/* - * Dump the contents of an EndOfCentralDir object. - */ -void ZipFile::EndOfCentralDir::dump(void) const -{ - ALOGD(" EndOfCentralDir contents:\n"); - ALOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n", - mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries); - ALOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n", - mCentralDirSize, mCentralDirOffset, mCommentLen); -} - -} // namespace aapt diff --git a/tools/aapt2/ZipFile.h b/tools/aapt2/ZipFile.h deleted file mode 100644 index 9de92ddc0872..000000000000 --- a/tools/aapt2/ZipFile.h +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (C) 2006 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. - */ - -// -// General-purpose Zip archive access. This class allows both reading and -// writing to Zip archives, including deletion of existing entries. -// -#ifndef __LIBS_ZIPFILE_H -#define __LIBS_ZIPFILE_H - -#include "BigBuffer.h" -#include "ZipEntry.h" - -#include <stdio.h> -#include <utils/Errors.h> -#include <vector> - -namespace aapt { - -using android::status_t; - -/* - * Manipulate a Zip archive. - * - * Some changes will not be visible in the until until "flush" is called. - * - * The correct way to update a file archive is to make all changes to a - * copy of the archive in a temporary file, and then unlink/rename over - * the original after everything completes. Because we're only interested - * in using this for packaging, we don't worry about such things. Crashing - * after making changes and before flush() completes could leave us with - * an unusable Zip archive. - */ -class ZipFile { -public: - ZipFile(void) - : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false) - {} - ~ZipFile(void) { - if (!mReadOnly) - flush(); - if (mZipFp != NULL) - fclose(mZipFp); - discardEntries(); - } - - /* - * Open a new or existing archive. - */ - enum { - kOpenReadOnly = 0x01, - kOpenReadWrite = 0x02, - kOpenCreate = 0x04, // create if it doesn't exist - kOpenTruncate = 0x08, // if it exists, empty it - }; - status_t open(const char* zipFileName, int flags); - - /* - * Add a file to the end of the archive. Specify whether you want the - * library to try to store it compressed. - * - * If "storageName" is specified, the archive will use that instead - * of "fileName". - * - * If there is already an entry with the same name, the call fails. - * Existing entries with the same name must be removed first. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t add(const char* fileName, int compressionMethod, - ZipEntry** ppEntry) - { - return add(fileName, fileName, compressionMethod, ppEntry); - } - status_t add(const char* fileName, const char* storageName, - int compressionMethod, ZipEntry** ppEntry) - { - return addCommon(fileName, NULL, 0, storageName, - ZipEntry::kCompressStored, - compressionMethod, ppEntry); - } - - /* - * Add a file that is already compressed with gzip. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t addGzip(const char* fileName, const char* storageName, - ZipEntry** ppEntry) - { - return addCommon(fileName, NULL, 0, storageName, - ZipEntry::kCompressDeflated, - ZipEntry::kCompressDeflated, ppEntry); - } - - /* - * Add a file from an in-memory data buffer. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t add(const void* data, size_t size, const char* storageName, - int compressionMethod, ZipEntry** ppEntry) - { - return addCommon(NULL, data, size, storageName, - ZipEntry::kCompressStored, - compressionMethod, ppEntry); - } - - status_t add(const BigBuffer& data, const char* storageName, - int compressionMethod, ZipEntry** ppEntry); - - /* - * Add an entry by copying it from another zip file. If storageName is - * non-NULL, the entry will be inserted with the name storageName, otherwise - * it will have the same name as the source entry. If "padding" is - * nonzero, the specified number of bytes will be added to the "extra" - * field in the header. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, - const char* storageName, int padding, ZipEntry** ppEntry); - - /* - * Mark an entry as having been removed. It is not actually deleted - * from the archive or our internal data structures until flush() is - * called. - */ - status_t remove(ZipEntry* pEntry); - - /* - * Flush changes. If mNeedCDRewrite is set, this writes the central dir. - */ - status_t flush(void); - - /* - * Expand the data into the buffer provided. The buffer must hold - * at least <uncompressed len> bytes. Variation expands directly - * to a file. - * - * Returns "false" if an error was encountered in the compressed data. - */ - //bool uncompress(const ZipEntry* pEntry, void* buf) const; - //bool uncompress(const ZipEntry* pEntry, FILE* fp) const; - void* uncompress(const ZipEntry* pEntry); - - /* - * Get an entry, by name. Returns NULL if not found. - * - * Does not return entries pending deletion. - */ - ZipEntry* getEntryByName(const char* fileName) const; - - /* - * Get the Nth entry in the archive. - * - * This will return an entry that is pending deletion. - */ - int getNumEntries(void) const { return mEntries.size(); } - ZipEntry* getEntryByIndex(int idx) const; - -private: - /* these are private and not defined */ - ZipFile(const ZipFile& src); - ZipFile& operator=(const ZipFile& src); - - class EndOfCentralDir { - public: - EndOfCentralDir(void) : - mDiskNumber(0), - mDiskWithCentralDir(0), - mNumEntries(0), - mTotalNumEntries(0), - mCentralDirSize(0), - mCentralDirOffset(0), - mCommentLen(0), - mComment(NULL) - {} - virtual ~EndOfCentralDir(void) { - delete[] mComment; - } - - status_t readBuf(const unsigned char* buf, int len); - status_t write(FILE* fp); - - //unsigned long mSignature; - unsigned short mDiskNumber; - unsigned short mDiskWithCentralDir; - unsigned short mNumEntries; - unsigned short mTotalNumEntries; - unsigned long mCentralDirSize; - unsigned long mCentralDirOffset; // offset from first disk - unsigned short mCommentLen; - unsigned char* mComment; - - enum { - kSignature = 0x06054b50, - kEOCDLen = 22, // EndOfCentralDir len, excl. comment - - kMaxCommentLen = 65535, // longest possible in ushort - kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen, - - }; - - void dump(void) const; - }; - - - /* read all entries in the central dir */ - status_t readCentralDir(void); - - /* crunch deleted entries out */ - status_t crunchArchive(void); - - /* clean up mEntries */ - void discardEntries(void); - - /* common handler for all "add" functions */ - status_t addCommon(const char* fileName, const void* data, size_t size, - const char* storageName, int sourceType, int compressionMethod, - ZipEntry** ppEntry); - - /* copy all of "srcFp" into "dstFp" */ - status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32); - /* copy all of "data" into "dstFp" */ - status_t copyDataToFp(FILE* dstFp, - const void* data, size_t size, unsigned long* pCRC32); - /* copy some of "srcFp" into "dstFp" */ - status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, - unsigned long* pCRC32); - /* like memmove(), but on parts of a single file */ - status_t filemove(FILE* fp, off_t dest, off_t src, size_t n); - /* compress all of "srcFp" into "dstFp", using Deflate */ - status_t compressFpToFp(FILE* dstFp, FILE* srcFp, - const void* data, size_t size, unsigned long* pCRC32); - - /* get modification date from a file descriptor */ - time_t getModTime(int fd); - - /* - * We use stdio FILE*, which gives us buffering but makes dealing - * with files >2GB awkward. Until we support Zip64, we're fine. - */ - FILE* mZipFp; // Zip file pointer - - /* one of these per file */ - EndOfCentralDir mEOCD; - - /* did we open this read-only? */ - bool mReadOnly; - - /* set this when we trash the central dir */ - bool mNeedCDRewrite; - - /* - * One ZipEntry per entry in the zip file. I'm using pointers instead - * of objects because it's easier than making operator= work for the - * classes and sub-classes. - */ - std::vector<ZipEntry*> mEntries; -}; - -}; // namespace aapt - -#endif // __LIBS_ZIPFILE_H diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp new file mode 100644 index 000000000000..2452a1d29410 --- /dev/null +++ b/tools/aapt2/compile/Compile.cpp @@ -0,0 +1,578 @@ +/* + * 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 "ConfigDescription.h" +#include "Diagnostics.h" +#include "Flags.h" +#include "ResourceParser.h" +#include "ResourceTable.h" +#include "compile/IdAssigner.h" +#include "compile/Png.h" +#include "compile/PseudolocaleGenerator.h" +#include "compile/XmlIdCollector.h" +#include "flatten/Archive.h" +#include "flatten/XmlFlattener.h" +#include "proto/ProtoSerialize.h" +#include "util/Files.h" +#include "util/Maybe.h" +#include "util/Util.h" +#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 <dirent.h> +#include <fstream> +#include <string> + +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; +}; + +/** + * 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 + }; +} + +struct CompileOptions { + std::string outputPath; + Maybe<std::string> resDir; + bool pseudolocalize = false; + bool legacyMode = 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 bool isHidden(const StringPiece& filename) { + return util::stringStartsWith<char>(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; + } + + outPathData->push_back(std::move(pathData.value())); + } + } + 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; + + // 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 (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; + } + } + + // 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(outputPath, 0)) { + context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open"); + 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; + } + } + + if (!writer->finishEntry()) { + context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to finish entry"); + return false; + } + return true; +} + +static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath, 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; +} + +static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, 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; + } + + // 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; + } + } + + if (!writer->finishEntry()) { + diag->error(DiagMessage(outputPath) << "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) { + + 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; + } + + xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source); + + fin.close(); + } + + if (!xmlRes) { + return false; + } + + // Collect IDs that are defined here. + XmlIdCollector collector; + if (!collector.consume(context, xmlRes.get())) { + return false; + } + + xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); + xmlRes->file.config = pathData.config; + xmlRes->file.source = pathData.source; + + BigBuffer buffer(1024); + XmlFlattenerOptions xmlFlattenerOptions; + xmlFlattenerOptions.keepRawValues = true; + XmlFlattener flattener(&buffer, xmlFlattenerOptions); + if (!flattener.consume(context, xmlRes.get())) { + return false; + } + + if (!writeHeaderAndBufferToWriter(outputPath, xmlRes->file, buffer, writer, + context->getDiagnostics())) { + 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; + } + } + + if (!writeHeaderAndBufferToWriter(outputPath, resFile, 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; +} + +class CompileContext : public IAaptContext { +public: + void setVerbose(bool val) { + mVerbose = val; + } + + bool verbose() override { + return mVerbose; + } + + IDiagnostics* getDiagnostics() override { + return &mDiagnostics; + } + + NameMangler* getNameMangler() override { + abort(); + return nullptr; + } + + const std::u16string& getCompilationPackage() override { + static std::u16string empty; + return empty; + } + + uint8_t getPackageId() override { + return 0x0; + } + + SymbolTable* getExternalSymbols() override { + abort(); + return nullptr; + } + +private: + StdErrDiagnostics mDiagnostics; + bool mVerbose = false; + +}; + +/** + * 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)) { + return 1; + } + + context.setVerbose(verbose); + + std::unique_ptr<IArchiveWriter> archiveWriter; + + 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; + } + + if (!loadInputFilesFromDir(&context, options, &inputData)) { + return 1; + } + + archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath); + + } 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; + } + } + + 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 { + 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; + } + } + } + + if (error) { + return 1; + } + return 0; +} + +} // namespace aapt diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp new file mode 100644 index 000000000000..aa4a5803b8df --- /dev/null +++ b/tools/aapt2/compile/IdAssigner.cpp @@ -0,0 +1,110 @@ +/* + * 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 "ResourceTable.h" + +#include "compile/IdAssigner.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; + + for (auto& package : table->packages) { + assert(package->id && "packages must have manually assigned IDs"); + + usedTypeIds.reset(); + + // Type ID 0 is invalid, reserve it. + usedTypeIds.set(0); + + // Collect used type IDs. + for (auto& type : package->types) { + if (type->id) { + usedEntryIds.clear(); + + 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; + } + + // Mark the type ID as taken. + usedTypeIds.set(type->id.value()); + } + + // 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; + } + } + } + + // 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++; + } + } + } + + // 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++; + } + } + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/compile/IdAssigner.h b/tools/aapt2/compile/IdAssigner.h new file mode 100644 index 000000000000..514df3ad3861 --- /dev/null +++ b/tools/aapt2/compile/IdAssigner.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#ifndef AAPT_COMPILE_IDASSIGNER_H +#define AAPT_COMPILE_IDASSIGNER_H + +#include "process/IResourceTableConsumer.h" + +namespace aapt { + +/** + * 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; +}; + +} // namespace aapt + +#endif /* AAPT_COMPILE_IDASSIGNER_H */ diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp new file mode 100644 index 000000000000..e25a17ab125e --- /dev/null +++ b/tools/aapt2/compile/IdAssigner_test.cpp @@ -0,0 +1,123 @@ +/* + * 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 "compile/IdAssigner.h" + +#include "test/Context.h" +#include "test/Builders.h" + +#include <gtest/gtest.h> + +namespace aapt { + +::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())); +} + +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())); +} + +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; + + ASSERT_FALSE(assigner.consume(context.get(), table.get())); +} + +::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"; + } + + 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; + } + } + + 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> 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; + } + } + } + } + return ::testing::AssertionSuccess() << "all IDs are unique and assigned"; +} + +} // namespace aapt diff --git a/tools/aapt2/Png.cpp b/tools/aapt2/compile/Png.cpp index 4e9b68e8c95f..bbf7f411d07e 100644 --- a/tools/aapt2/Png.cpp +++ b/tools/aapt2/compile/Png.cpp @@ -14,11 +14,10 @@ * limitations under the License. */ -#include "BigBuffer.h" -#include "Logger.h" +#include "util/BigBuffer.h" #include "Png.h" #include "Source.h" -#include "Util.h" +#include "util/Util.h" #include <androidfw/ResourceTypes.h> #include <iostream> @@ -95,15 +94,14 @@ static void flushDataToStream(png_structp /*writePtr*/) { } static void logWarning(png_structp readPtr, png_const_charp warningMessage) { - SourceLogger* logger = reinterpret_cast<SourceLogger*>(png_get_error_ptr(readPtr)); - logger->warn() << warningMessage << "." << std::endl; + IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr)); + diag->warn(DiagMessage() << warningMessage); } -static bool readPng(png_structp readPtr, png_infop infoPtr, PngInfo* outInfo, - std::string* outError) { +static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) { if (setjmp(png_jmpbuf(readPtr))) { - *outError = "failed reading png"; + diag->error(DiagMessage() << "failed reading png"); return false; } @@ -229,7 +227,7 @@ 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(SourceLogger* logger, const PngInfo& imageInfo, int grayscaleTolerance, +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) { @@ -363,9 +361,9 @@ static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int gr *colorType = PNG_COLOR_TYPE_PALETTE; } else { if (maxGrayDeviation <= grayscaleTolerance) { - logger->note() << "forcing image to gray (max deviation = " << maxGrayDeviation - << ")." - << std::endl; + 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; @@ -411,10 +409,10 @@ static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int gr } } -static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info, - int grayScaleTolerance, SourceLogger* logger, std::string* outError) { +static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info, + int grayScaleTolerance) { if (setjmp(png_jmpbuf(writePtr))) { - *outError = "failed to write png"; + diag->error(DiagMessage() << "failed to write png"); return false; } @@ -442,9 +440,9 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info, png_set_compression_level(writePtr, Z_BEST_COMPRESSION); if (kDebug) { - logger->note() << "writing image: w = " << info->width - << ", h = " << info->height - << std::endl; + diag->note(DiagMessage() + << "writing image: w = " << info->width + << ", h = " << info->height); } png_color rgbPalette[256]; @@ -452,7 +450,7 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info, bool hasTransparency; int paletteEntries; - analyze_image(logger, *info, grayScaleTolerance, rgbPalette, alphaPalette, + 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 @@ -465,22 +463,22 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info, if (kDebug) { switch (colorType) { case PNG_COLOR_TYPE_PALETTE: - logger->note() << "has " << paletteEntries - << " colors" << (hasTransparency ? " (with alpha)" : "") - << ", using PNG_COLOR_TYPE_PALLETTE." - << std::endl; + diag->note(DiagMessage() + << "has " << paletteEntries + << " colors" << (hasTransparency ? " (with alpha)" : "") + << ", using PNG_COLOR_TYPE_PALLETTE"); break; case PNG_COLOR_TYPE_GRAY: - logger->note() << "is opaque gray, using PNG_COLOR_TYPE_GRAY." << std::endl; + diag->note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY"); break; case PNG_COLOR_TYPE_GRAY_ALPHA: - logger->note() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA." << std::endl; + diag->note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA"); break; case PNG_COLOR_TYPE_RGB: - logger->note() << "is opaque RGB, using PNG_COLOR_TYPE_RGB." << std::endl; + diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB"); break; case PNG_COLOR_TYPE_RGB_ALPHA: - logger->note() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA." << std::endl; + diag->note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA"); break; } } @@ -511,7 +509,7 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info, // base 9 patch data if (kDebug) { - logger->note() << "adding 9-patch info..." << std::endl; + diag->note(DiagMessage() << "adding 9-patch info.."); } strcpy((char*)unknowns[pIndex].name, "npTc"); unknowns[pIndex].data = (png_byte*) info->serialize9Patch(); @@ -587,10 +585,10 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info, &compressionType, nullptr); if (kDebug) { - logger->note() << "image written: w = " << width << ", h = " << height - << ", d = " << bitDepth << ", colors = " << colorType - << ", inter = " << interlaceType << ", comp = " << compressionType - << std::endl; + diag->note(DiagMessage() + << "image written: w = " << width << ", h = " << height + << ", d = " << bitDepth << ", colors = " << colorType + << ", inter = " << interlaceType << ", comp = " << compressionType); } return true; } @@ -1177,7 +1175,7 @@ getout: if (errorMsg) { std::stringstream err; err << "9-patch malformed: " << errorMsg; - if (!errorEdge) { + if (errorEdge) { err << "." << std::endl; if (errorPixel >= 0) { err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge"; @@ -1192,23 +1190,22 @@ getout: } -bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffer, - const Options& options, std::string* outError) { +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)) { - *outError = strerror(errno); + 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) { - *outError = "not a valid png file"; + mDiag->error(DiagMessage() << "not a valid png file"); return false; } - SourceLogger logger(source); bool result = false; png_structp readPtr = nullptr; png_infop infoPtr = nullptr; @@ -1218,40 +1215,42 @@ bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffe readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); if (!readPtr) { - *outError = "failed to allocate read ptr"; + mDiag->error(DiagMessage() << "failed to allocate read ptr"); goto bail; } infoPtr = png_create_info_struct(readPtr); if (!infoPtr) { - *outError = "failed to allocate info ptr"; + mDiag->error(DiagMessage() << "failed to allocate info ptr"); goto bail; } - png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(&logger), nullptr, logWarning); + 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); + png_set_read_fn(readPtr, (png_voidp) input, readDataFromStream); - if (!readPng(readPtr, infoPtr, &pngInfo, outError)) { + if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) { goto bail; } if (util::stringEndsWith<char>(source.path, ".9.png")) { - if (!do9Patch(&pngInfo, outError)) { + 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) { - *outError = "failed to allocate write ptr"; + mDiag->error(DiagMessage() << "failed to allocate write ptr"); goto bail; } writeInfoPtr = png_create_info_struct(writePtr); if (!writeInfoPtr) { - *outError = "failed to allocate write info ptr"; + mDiag->error(DiagMessage() << "failed to allocate write info ptr"); goto bail; } @@ -1260,8 +1259,7 @@ bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffe // Set the write function to write to std::ostream. png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream); - if (!writePng(writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance, &logger, - outError)) { + if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance)) { goto bail; } diff --git a/tools/aapt2/Png.h b/tools/aapt2/compile/Png.h index 4577ab89d2d9..345ff6c56870 100644 --- a/tools/aapt2/Png.h +++ b/tools/aapt2/compile/Png.h @@ -17,7 +17,8 @@ #ifndef AAPT_PNG_H #define AAPT_PNG_H -#include "BigBuffer.h" +#include "util/BigBuffer.h" +#include "Diagnostics.h" #include "Source.h" #include <iostream> @@ -25,13 +26,20 @@ namespace aapt { -struct Png { - struct Options { - int grayScaleTolerance = 0; - }; +struct PngOptions { + int grayScaleTolerance = 0; +}; + +class Png { +public: + Png(IDiagnostics* diag) : mDiag(diag) { + } + + bool process(const Source& source, std::istream* input, BigBuffer* outBuffer, + const PngOptions& options); - bool process(const Source& source, std::istream& input, BigBuffer* outBuffer, - const Options& options, std::string* outError); +private: + IDiagnostics* mDiag; }; } // namespace aapt diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp new file mode 100644 index 000000000000..99c20778c816 --- /dev/null +++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp @@ -0,0 +1,261 @@ +/* + * 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 "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 }); + } + + // 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 }); + } + } + + 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(); + } + + if (ranges[i].updateEnd) { + *ranges[i].updateEnd = localized.str.size(); + } + + localized.str += localizer.text(originalText.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; +} + +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(Array* array) override { + std::unique_ptr<Array> localized = util::make_unique<Array>(); + localized->items.resize(array->items.size()); + for (size_t i = 0; i < array->items.size(); i++) { + Visitor subVisitor(mPool, mMethod); + array->items[i]->accept(&subVisitor); + if (subVisitor.mItem) { + localized->items[i] = std::move(subVisitor.mItem); + } else { + localized->items[i] = std::unique_ptr<Item>(array->items[i]->clone(mPool)); + } + } + localized->setSource(array->getSource()); + localized->setWeak(true); + mValue = std::move(localized); + } + + 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)); + } + } + } + localized->setSource(plural->getSource()); + localized->setWeak(true); + mValue = std::move(localized); + } + + void visit(String* string) override { + if (!string->isTranslateable()) { + return; + } + + 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 { + if (!string->isTranslateable()) { + return; + } + + mItem = pseudolocalizeStyledString(string, mMethod, mPool); + mItem->setWeak(true); + } +}; + +ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base, + Pseudolocalizer::Method 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; + + case Pseudolocalizer::Method::kBidi: + modified.language[0] = 'a'; + modified.language[1] = 'r'; + modified.country[0] = 'X'; + modified.country[1] = 'B'; + break; + default: + 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); + } +} + +} // 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->findAllValues( + ConfigDescription::defaultConfig()); + for (ResourceConfigValue* value : values) { + pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, + &table->stringPool, entry.get()); + pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, + &table->stringPool, entry.get()); + } + } + } + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/compile/PseudolocaleGenerator.h b/tools/aapt2/compile/PseudolocaleGenerator.h new file mode 100644 index 000000000000..4fbc51607595 --- /dev/null +++ b/tools/aapt2/compile/PseudolocaleGenerator.h @@ -0,0 +1,36 @@ +/* + * 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_PSEUDOLOCALEGENERATOR_H +#define AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H + +#include "StringPool.h" +#include "compile/Pseudolocalizer.h" +#include "process/IResourceTableConsumer.h" + +namespace aapt { + +std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string, + Pseudolocalizer::Method method, + StringPool* pool); + +struct PseudolocaleGenerator : public IResourceTableConsumer { + bool consume(IAaptContext* context, ResourceTable* table) override; +}; + +} // namespace aapt + +#endif /* AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H */ diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp new file mode 100644 index 000000000000..4cb6ea2db565 --- /dev/null +++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp @@ -0,0 +1,123 @@ +/* + * 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/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> + +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 } }; + + std::unique_ptr<StyledString> newString = pseudolocalizeStyledString( + util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(), + Pseudolocalizer::Method::kNone, &pool); + + EXPECT_EQ(originalStyle.str, *newString->value->str); + ASSERT_EQ(originalStyle.spans.size(), newString->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(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(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); + + originalStyle.spans.push_back(Span{ u"em", 0, 11u }); + + newString = pseudolocalizeStyledString( + util::make_unique<StyledString>(pool.makeRef(originalStyle)).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(3u, newString->value->spans[0].firstChar); + EXPECT_EQ(4u, newString->value->spans[0].lastChar); + + EXPECT_EQ(7u, newString->value->spans[1].firstChar); + EXPECT_EQ(8u, newString->value->spans[1].lastChar); + + EXPECT_EQ(2u, newString->value->spans[2].firstChar); + EXPECT_EQ(11u, newString->value->spans[2].lastChar); + + EXPECT_EQ(1u, newString->value->spans[3].firstChar); + EXPECT_EQ(12u, newString->value->spans[3].lastChar); +} + +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"))); + +} + +} // namespace aapt + diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp new file mode 100644 index 000000000000..eae52d778744 --- /dev/null +++ b/tools/aapt2/compile/Pseudolocalizer.cpp @@ -0,0 +1,394 @@ +/* + * 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 "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"; + +// 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"; + +// Placeholder marks +static const std::u16string k_placeholder_open = u"\u00bb"; +static const std::u16string k_placeholder_close = u"\u00ab"; + +static const char16_t k_arg_start = u'{'; +static const char16_t k_arg_end = u'}'; + +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(); } +}; + +class PseudoMethodBidi : public PseudoMethodImpl { +public: + std::u16string text(const StringPiece16& text) override; + std::u16string placeholder(const StringPiece16& 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; +}; + +Pseudolocalizer::Pseudolocalizer(Method method) : mLastDepth(0) { + setMethod(method); +} + +void Pseudolocalizer::setMethod(Method method) { + switch (method) { + case Method::kNone: + mImpl = util::make_unique<PseudoMethodNone>(); + break; + case Method::kAccent: + mImpl = util::make_unique<PseudoMethodAccent>(); + break; + case Method::kBidi: + mImpl = 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; + } + + if (c == k_arg_start) { + depth++; + } else if (c == k_arg_end && 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; + } + } + 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 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 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); + } + return result; +} + +std::u16string PseudoMethodAccent::start() { + std::u16string result; + if (mDepth == 0) { + result = u"["; + } + mWordCount = mLength = 0; + mDepth++; + 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; +} + +/** + * Converts characters so they look like they've been localized. + * + * 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]; + } + } 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); + } + // Count only pseudolocalizable chars and delimiters + mLength++; + } + } + 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 (lastspace && !space) { + // Word start + result += k_rlm + k_rlo; + } else if (!lastspace && space) { + // Word end + result += k_pdf + k_rlm; + } + lastspace = space; + result.append(&c, 1); + } + if (!lastspace) { + // End of last word + result += k_pdf + k_rlm; + } + 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; +} + +} // namespace aapt diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h new file mode 100644 index 000000000000..8818c1725617 --- /dev/null +++ b/tools/aapt2/compile/Pseudolocalizer.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#ifndef AAPT_COMPILE_PSEUDOLOCALIZE_H +#define AAPT_COMPILE_PSEUDOLOCALIZE_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; +}; + +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; +}; + +} // namespace aapt + +#endif /* AAPT_COMPILE_PSEUDOLOCALIZE_H */ diff --git a/tools/aapt2/compile/Pseudolocalizer_test.cpp b/tools/aapt2/compile/Pseudolocalizer_test.cpp new file mode 100644 index 000000000000..b0bc2c10fbe0 --- /dev/null +++ b/tools/aapt2/compile/Pseudolocalizer_test.cpp @@ -0,0 +1,227 @@ +/* + * 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 "compile/Pseudolocalizer.h" +#include "util/Util.h" + +#include <androidfw/ResourceTypes.h> +#include <gtest/gtest.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, + 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(); +} + +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(); +} + +TEST(PseudolocalizerTest, NoPseudolocalization) { + 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)); +} + +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)); +} + +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)); +} + +TEST(PseudolocalizerTest, SimpleICU) { + // 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(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)); +} + +TEST(PseudolocalizerTest, Escaping) { + // 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)); +} + +TEST(PseudolocalizerTest, PluralsAndSelects) { + 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)); +} + +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)); +} + +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)); +} + +} // namespace aapt diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp new file mode 100644 index 000000000000..f40689eaeb47 --- /dev/null +++ b/tools/aapt2/compile/XmlIdCollector.cpp @@ -0,0 +1,70 @@ +/* + * 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 "ResourceUtils.h" +#include "ResourceValues.h" +#include "compile/XmlIdCollector.h" +#include "xml/XmlDom.h" + +#include <algorithm> +#include <vector> + +namespace aapt { + +namespace { + +static bool cmpName(const SourcedResourceName& a, const ResourceNameRef& b) { + return a.name < b; +} + +struct IdCollector : public xml::Visitor { + using xml::Visitor::visit; + + std::vector<SourcedResourceName>* mOutSymbols; + + 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::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); + } +}; + +} // namespace + +bool XmlIdCollector::consume(IAaptContext* context, xml::XmlResource* xmlRes) { + xmlRes->file.exportedSymbols.clear(); + IdCollector collector(&xmlRes->file.exportedSymbols); + xmlRes->root->accept(&collector); + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/compile/XmlIdCollector.h b/tools/aapt2/compile/XmlIdCollector.h new file mode 100644 index 000000000000..1b149449de2c --- /dev/null +++ b/tools/aapt2/compile/XmlIdCollector.h @@ -0,0 +1,31 @@ +/* + * 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. + */ + +#ifndef AAPT_XMLIDCOLLECTOR_H +#define AAPT_XMLIDCOLLECTOR_H + +#include "process/IResourceTableConsumer.h" +#include "xml/XmlDom.h" + +namespace aapt { + +struct XmlIdCollector : public IXmlResourceConsumer { + bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override; +}; + +} // namespace aapt + +#endif /* AAPT_XMLIDCOLLECTOR_H */ diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp new file mode 100644 index 000000000000..a37ea86c317f --- /dev/null +++ b/tools/aapt2/compile/XmlIdCollector_test.cpp @@ -0,0 +1,61 @@ +/* + * 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 "compile/XmlIdCollector.h" +#include "test/Builders.h" +#include "test/Context.h" + +#include <algorithm> +#include <gtest/gtest.h> + +namespace aapt { + +TEST(XmlIdCollectorTest, CollectsIds) { + 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" + android:id="@+id/foo" + text="@+id/bar"> + <SubView android:id="@+id/car" + class="@+id/bar"/> + </View>)EOF"); + + 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.exportedSymbols.begin(), doc->file.exportedSymbols.end(), + SourcedResourceName{ test::parseNameOrDie(u"@id/bar"), 3u })); + + EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(), + SourcedResourceName{ test::parseNameOrDie(u"@id/car"), 6u })); +} + +TEST(XmlIdCollectorTest, DontCollectNonIds) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>"); + + XmlIdCollector collector; + ASSERT_TRUE(collector.consume(context.get(), doc.get())); + + EXPECT_TRUE(doc->file.exportedSymbols.empty()); +} + +} // namespace aapt diff --git a/tools/aapt2/data/AndroidManifest.xml b/tools/aapt2/data/AndroidManifest.xml deleted file mode 100644 index 8533c28c24bb..000000000000 --- a/tools/aapt2/data/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.app"> - <application - android:name=".Activity"> - </application> -</manifest> diff --git a/tools/aapt2/data/Makefile b/tools/aapt2/data/Makefile deleted file mode 100644 index 91ff5fee6477..000000000000 --- a/tools/aapt2/data/Makefile +++ /dev/null @@ -1,83 +0,0 @@ -## -# Environment dependent variables -## - -AAPT := aapt2 -ZIPALIGN := zipalign -f 4 -FRAMEWORK := ../../../../../out/target/common/obj/APPS/framework-res_intermediates/package-export.apk - -## -# Project depenedent variables -## - -LOCAL_PACKAGE := com.android.app -LOCAL_RESOURCE_DIR := res -LOCAL_LIBS := lib/out/package.apk -LOCAL_OUT := out -LOCAL_GEN := out/gen -LOCAL_PROGUARD := out/proguard.rule - -## -# AAPT2 custom rules. -## - -PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk -PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk - -# Eg: framework.apk, etc. -PRIVATE_INCLUDES := $(FRAMEWORK) -$(info PRIVATE_INCLUDES = $(PRIVATE_INCLUDES)) - -# Eg: gen/com/android/app/R.java -PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java -$(info PRIVATE_R_JAVA = $(PRIVATE_R_JAVA)) - -# Eg: res/drawable/icon.png, res/values/styles.xml -PRIVATE_RESOURCES := $(shell find $(LOCAL_RESOURCE_DIR) -mindepth 1 -maxdepth 2 -type f) -$(info PRIVATE_RESOURCES = $(PRIVATE_RESOURCES)) - -# Eg: drawable, values, layouts -PRIVATE_RESOURCE_TYPES := \ - $(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES)))) -$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES)) - -# Eg: out/values-v4.apk, out/drawable-xhdpi.apk -PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES)) -$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES)) - -# Generates rules for collect phase. -# $1: Resource type (values-v4) -# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml -define make-collect-rule -$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES)) - $(AAPT) compile -o $$@ $$^ -endef - -# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml -$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d))) - -# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk -$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_INCLUDES) $(LOCAL_LIBS) AndroidManifest.xml - $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS) --proguard $(LOCAL_PROGUARD) -v - -# R.java: gen/com/android/app/R.java <- out/resources.arsc -# No action since R.java is generated when out/resources.arsc is. -$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED) - -# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/* -$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED) - $(ZIPALIGN) $< $@ - -# Create the out directory if needed. -dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT)) - -.PHONY: java -java: $(PRIVATE_R_JAVA) - -.PHONY: assemble -assemble: $(PRIVATE_APK_ALIGNED) - -.PHONY: all -all: assemble java - -.DEFAULT_GOAL := all diff --git a/tools/aapt2/data/lib/AndroidManifest.xml b/tools/aapt2/data/lib/AndroidManifest.xml deleted file mode 100644 index 08b468ee6229..000000000000 --- a/tools/aapt2/data/lib/AndroidManifest.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.appcompat"> - - <uses-feature android:name="bloooop" /> -</manifest> diff --git a/tools/aapt2/data/lib/Makefile b/tools/aapt2/data/lib/Makefile deleted file mode 100644 index 741be9a0e75a..000000000000 --- a/tools/aapt2/data/lib/Makefile +++ /dev/null @@ -1,81 +0,0 @@ -## -# Environment dependent variables -## - -AAPT := aapt2 -ZIPALIGN := zipalign -f 4 -FRAMEWORK := ../../../../../../out/target/common/obj/APPS/framework-res_intermediates/package-export.apk - -## -# Project depenedent variables -## - -LOCAL_PACKAGE := android.appcompat -LOCAL_RESOURCE_DIR := res -LOCAL_OUT := out -LOCAL_GEN := out/gen - -## -# AAPT2 custom rules. -## - -PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk -PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk - -# Eg: framework.apk, etc. -PRIVATE_LIBS := $(FRAMEWORK) -$(info PRIVATE_LIBS = $(PRIVATE_LIBS)) - -# Eg: gen/com/android/app/R.java -PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java -$(info PRIVATE_R_JAVA = $(PRIVATE_R_JAVA)) - -# Eg: res/drawable/icon.png, res/values/styles.xml -PRIVATE_RESOURCES := $(shell find $(LOCAL_RESOURCE_DIR) -mindepth 1 -maxdepth 2 -type f) -$(info PRIVATE_RESOURCES = $(PRIVATE_RESOURCES)) - -# Eg: drawable, values, layouts -PRIVATE_RESOURCE_TYPES := \ - $(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES)))) -$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES)) - -# Eg: out/values-v4.apk, out/drawable-xhdpi.apk -PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES)) -$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES)) - -# Generates rules for collect phase. -# $1: Resource type (values-v4) -# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml -define make-collect-rule -$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES)) - $(AAPT) compile -o $$@ $$^ -endef - -# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml -$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d))) - -# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk -$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_LIBS) AndroidManifest.xml - $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_LIBS)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) --static-lib - -# R.java: gen/com/android/app/R.java <- out/resources.arsc -# No action since R.java is generated when out/resources.arsc is. -$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED) - -# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/* -$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED) - $(ZIPALIGN) $< $@ - -# Create the out directory if needed. -dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT)) - -.PHONY: java -java: $(PRIVATE_R_JAVA) - -.PHONY: assemble -assemble: $(PRIVATE_APK_ALIGNED) - -.PHONY: all -all: assemble java - -.DEFAULT_GOAL := all diff --git a/tools/aapt2/data/lib/res/layout/main.xml b/tools/aapt2/data/lib/res/layout/main.xml deleted file mode 100644 index 187ed2d75da3..000000000000 --- a/tools/aapt2/data/lib/res/layout/main.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"/> diff --git a/tools/aapt2/data/lib/res/raw/hello.txt b/tools/aapt2/data/lib/res/raw/hello.txt deleted file mode 100644 index 44fc22bebb39..000000000000 --- a/tools/aapt2/data/lib/res/raw/hello.txt +++ /dev/null @@ -1 +0,0 @@ -Oh howdy there diff --git a/tools/aapt2/data/lib/res/values/styles.xml b/tools/aapt2/data/lib/res/values/styles.xml deleted file mode 100644 index 4ce633394063..000000000000 --- a/tools/aapt2/data/lib/res/values/styles.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <style name="Platform.AppCompat" parent="@android:style/Theme"> - <item name="android:windowNoTitle">true</item> - </style> - - <bool name="allow">true</bool> -</resources> diff --git a/tools/aapt2/data/res/drawable/image.xml b/tools/aapt2/data/res/drawable/image.xml deleted file mode 100644 index 9b387390ea82..000000000000 --- a/tools/aapt2/data/res/drawable/image.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<vector /> diff --git a/tools/aapt2/data/res/values-v4/styles.xml b/tools/aapt2/data/res/values-v4/styles.xml deleted file mode 100644 index 979a82a06df9..000000000000 --- a/tools/aapt2/data/res/values-v4/styles.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <style name="App" parent="android:Theme.Material"> - <item name="android:colorAccent">@color/accent</item> - <item name="android:text">Hey</item> - </style> -</resources> diff --git a/tools/aapt2/data/res/values/colors.xml b/tools/aapt2/data/res/values/colors.xml deleted file mode 100644 index 89db5fb3ec45..000000000000 --- a/tools/aapt2/data/res/values/colors.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <color name="primary">#f44336</color> - <color name="primary_dark">#b71c1c</color> - <color name="accent">#fdd835</color> -</resources> diff --git a/tools/aapt2/data/res/values/test.xml b/tools/aapt2/data/res/values/test.xml deleted file mode 100644 index d3ead34d043c..000000000000 --- a/tools/aapt2/data/res/values/test.xml +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="hooha"><font bgcolor="#ffffff">Hey guys!</font> <xliff:g>My</xliff:g> name is <b>Adam</b>. How <b><i>are</i></b> you?</string> - <public name="hooha" type="string" id="0x7f020001"/> - <string name="wow">@android:string/ok</string> - <public name="image" type="drawable" id="0x7f060000" /> - <attr name="layout_width" format="boolean" /> - <attr name="flags"> - <flag name="complex" value="1" /> - <flag name="pub" value="2" /> - <flag name="weak" value="4" /> - </attr> -</resources> diff --git a/tools/aapt2/data/resources.arsc b/tools/aapt2/data/resources.arsc Binary files differdeleted file mode 100644 index 6a416df0a96f..000000000000 --- a/tools/aapt2/data/resources.arsc +++ /dev/null diff --git a/tools/aapt2/data/resources_base.arsc b/tools/aapt2/data/resources_base.arsc Binary files differdeleted file mode 100644 index f9d06107b0e0..000000000000 --- a/tools/aapt2/data/resources_base.arsc +++ /dev/null diff --git a/tools/aapt2/data/resources_hdpi.arsc b/tools/aapt2/data/resources_hdpi.arsc Binary files differdeleted file mode 100644 index 97232a3317ba..000000000000 --- a/tools/aapt2/data/resources_hdpi.arsc +++ /dev/null diff --git a/tools/aapt2/dump/Dump.cpp b/tools/aapt2/dump/Dump.cpp new file mode 100644 index 000000000000..56b9f9a3e081 --- /dev/null +++ b/tools/aapt2/dump/Dump.cpp @@ -0,0 +1,170 @@ +/* + * 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 "Debug.h" +#include "Diagnostics.h" +#include "Flags.h" +#include "io/ZipArchive.h" +#include "process/IResourceTableConsumer.h" +#include "proto/ProtoSerialize.h" +#include "util/Files.h" +#include "util/StringPiece.h" + +#include <vector> + +namespace aapt { + +//struct DumpOptions { +// +//}; + +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) { + return; + } + + std::cout << "Resource: " << file->name << "\n" + << "Config: " << file->config << "\n" + << "Source: " << file->source << "\n"; +} + +void dumpCompiledTable(const pb::ResourceTable& pbTable, const Source& source, + IAaptContext* context) { + std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, + context->getDiagnostics()); + if (!table) { + return; + } + + Debug::printTable(table.get()); +} + +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); + } + } + return; + } + + Maybe<android::FileMap> file = file::mmapPath(filePath, &err); + if (!file) { + context->getDiagnostics()->error(DiagMessage(filePath) << err); + return; + } + + android::FileMap* fileMap = &file.value(); + + // Try as a compiled table. + pb::ResourceTable pbTable; + if (pbTable.ParseFromArray(fileMap->getDataPtr(), fileMap->getDataLength())) { + dumpCompiledTable(pbTable, Source(filePath), context); + return; + } + + // 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; + } +} + +class DumpContext : public IAaptContext { +public: + IDiagnostics* getDiagnostics() override { + return &mDiagnostics; + } + + NameMangler* getNameMangler() override { + abort(); + return nullptr; + } + + const std::u16string& getCompilationPackage() override { + static std::u16string empty; + return empty; + } + + uint8_t getPackageId() override { + return 0; + } + + SymbolTable* getExternalSymbols() override { + abort(); + return nullptr; + } + + bool verbose() override { + return mVerbose; + } + + void setVerbose(bool val) { + mVerbose = val; + } + +private: + StdErrDiagnostics mDiagnostics; + bool mVerbose = 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; +} + +} // namespace aapt diff --git a/tools/aapt2/filter/ConfigFilter.cpp b/tools/aapt2/filter/ConfigFilter.cpp new file mode 100644 index 000000000000..68a017d247f6 --- /dev/null +++ b/tools/aapt2/filter/ConfigFilter.cpp @@ -0,0 +1,77 @@ +/* + * 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 "ConfigDescription.h" +#include "filter/ConfigFilter.h" + +#include <androidfw/ResourceTypes.h> + +namespace aapt { + +void AxisConfigFilter::addConfig(ConfigDescription config) { + uint32_t diffMask = ConfigDescription::defaultConfig().diff(config); + + // Ignore the version + diffMask &= ~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; + } + + mConfigs.insert(std::make_pair(config, diffMask)); + mConfigMask |= diffMask; +} + +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; + } + + 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; + } + } + } + return matchedAxis == (mConfigMask & mask); +} + +} // namespace aapt diff --git a/tools/aapt2/filter/ConfigFilter.h b/tools/aapt2/filter/ConfigFilter.h new file mode 100644 index 000000000000..36e9c44255e4 --- /dev/null +++ b/tools/aapt2/filter/ConfigFilter.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 AAPT_FILTER_CONFIGFILTER_H +#define AAPT_FILTER_CONFIGFILTER_H + +#include "ConfigDescription.h" + +#include <set> +#include <utility> + +namespace aapt { + +/** + * Matches ConfigDescriptions based on some pattern. + */ +class IConfigFilter { +public: + virtual ~IConfigFilter() = default; + + /** + * 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 + * accepted. + * + * Used when handling "-c" options. + */ +class AxisConfigFilter : public IConfigFilter { +public: + void addConfig(ConfigDescription config); + + bool match(const ConfigDescription& config) const override; + +private: + std::set<std::pair<ConfigDescription, uint32_t>> mConfigs; + uint32_t mConfigMask = 0; +}; + +} // namespace aapt + +#endif /* AAPT_FILTER_CONFIGFILTER_H */ diff --git a/tools/aapt2/filter/ConfigFilter_test.cpp b/tools/aapt2/filter/ConfigFilter_test.cpp new file mode 100644 index 000000000000..f6b49557306d --- /dev/null +++ b/tools/aapt2/filter/ConfigFilter_test.cpp @@ -0,0 +1,112 @@ +/* + * 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 "filter/ConfigFilter.h" +#include "test/Common.h" + +#include <gtest/gtest.h> + +namespace aapt { + +TEST(ConfigFilterTest, EmptyFilterMatchesAnything) { + AxisConfigFilter filter; + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi"))); + EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithUnrelatedAxis) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithSameValueAxis) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithSameValueAxisAndOtherUnrelatedAxis) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); + + 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")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("en"))); +} + +TEST(ConfigFilterTest, DoesNotMatchConfigWithDifferentValueAxis) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); + + 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"))); +} + +TEST(ConfigFilterTest, MatchesSmallestWidthWhenSmaller) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("sw600dp")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-sw320dp-v13"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithSameLanguageButNoRegionSpecified) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("de-rDE")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("de"))); +} + +TEST(ConfigFilterTest, IgnoresVersion) { + 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"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithRegion) { + 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"))); +} + +} // namespace aapt diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp new file mode 100644 index 000000000000..3a244c05efec --- /dev/null +++ b/tools/aapt2/flatten/Archive.cpp @@ -0,0 +1,184 @@ +/* + * 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 "flatten/Archive.h" +#include "util/Files.h" +#include "util/StringPiece.h" + +#include <cstdio> +#include <memory> +#include <string> +#include <vector> +#include <ziparchive/zip_writer.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; + } +}; + +struct ZipFileWriter : public IArchiveWriter { + std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose }; + std::unique_ptr<ZipWriter> mWriter; + + 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 startEntry(const StringPiece& path, uint32_t flags) override { + if (!mWriter) { + return false; + } + + size_t zipFlags = 0; + if (flags & ArchiveEntry::kCompress) { + zipFlags |= ZipWriter::kCompress; + } + + if (flags & ArchiveEntry::kAlign) { + zipFlags |= ZipWriter::kAlign32; + } + + int32_t result = mWriter->StartEntry(path.data(), zipFlags); + if (result != 0) { + return false; + } + 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 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; + } + + bool finishEntry() override { + int32_t result = mWriter->FinishEntry(); + if (result != 0) { + return false; + } + return true; + } + + virtual ~ZipFileWriter() { + if (mWriter) { + mWriter->Finish(); + } + } +}; + +} // 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); +} + +} // namespace aapt diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h new file mode 100644 index 000000000000..34c10ad40365 --- /dev/null +++ b/tools/aapt2/flatten/Archive.h @@ -0,0 +1,66 @@ +/* + * 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. + */ + +#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> + +namespace aapt { + +struct ArchiveEntry { + enum : uint32_t { + kCompress = 0x01, + kAlign = 0x02, + }; + + std::string path; + uint32_t flags; + size_t uncompressedSize; +}; + +struct IArchiveWriter : public google::protobuf::io::CopyingOutputStream { + 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; + + // 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> createZipFileArchiveWriter(IDiagnostics* diag, + const StringPiece& path); + +} // namespace aapt + +#endif /* AAPT_FLATTEN_ARCHIVE_H */ diff --git a/tools/aapt2/flatten/ChunkWriter.h b/tools/aapt2/flatten/ChunkWriter.h new file mode 100644 index 000000000000..de1d87a57e6d --- /dev/null +++ b/tools/aapt2/flatten/ChunkWriter.h @@ -0,0 +1,87 @@ +/* + * 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. + */ + +#ifndef AAPT_FLATTEN_CHUNKWRITER_H +#define AAPT_FLATTEN_CHUNKWRITER_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; + } +}; + +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; +} + +} // namespace aapt + +#endif /* AAPT_FLATTEN_CHUNKWRITER_H */ diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h new file mode 100644 index 000000000000..3e20ad643eb6 --- /dev/null +++ b/tools/aapt2/flatten/ResourceTypeExtensions.h @@ -0,0 +1,36 @@ +/* + * 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. + */ + +#ifndef AAPT_RESOURCE_TYPE_EXTENSIONS_H +#define AAPT_RESOURCE_TYPE_EXTENSIONS_H + +#include <androidfw/ResourceTypes.h> + +namespace aapt { + +/** + * 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; +}; + +} // namespace aapt + +#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp new file mode 100644 index 000000000000..28a792820de3 --- /dev/null +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -0,0 +1,504 @@ +/* + * 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 "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 { + +namespace { + +template <typename T> +static bool cmpIds(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; +} + +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; +} + +struct FlatEntry { + ResourceEntry* entry; + Value* value; + + // The entry string pool index to the entry's name. + uint32_t entryKey; +}; + +class MapFlattenVisitor : public RawValueVisitor { +public: + using RawValueVisitor::visit; + + MapFlattenVisitor(ResTable_entry_ext* outEntry, BigBuffer* buffer) : + mOutEntry(outEntry), mBuffer(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(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()); + } + } + + void visit(Styleable* styleable) override { + for (auto& attrRef : styleable->entries) { + BinaryPrimitive val(Res_value{}); + flattenEntry(&attrRef, &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++; + } + } + + 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()); + } + } + + /** + * 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); + } + +private: + void flattenKey(Reference* key, ResTable_map* outEntry) { + assert(key->id && "key has no ID"); + outEntry->name.ident = util::hostToDevice32(key->id.value().id); + } + + void flattenValue(Item* value, ResTable_map* outEntry) { + bool result = value->flatten(&outEntry->value); + assert(result && "flatten failed"); + } + + 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++; + } + + ResTable_entry_ext* mOutEntry; + BigBuffer* mBuffer; + size_t mEntryCount = 0; +}; + +class PackageFlattener { +public: + PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package) : + mDiag(diag), mPackage(package) { + } + + 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()); + + if (mPackage->name.size() >= arraysize(pkgHeader->name)) { + mDiag->error(DiagMessage() << + "package name '" << mPackage->name << "' is too long"); + return false; + } + + // Copy the package name in device endianness. + strcpy16_htod(pkgHeader->name, arraysize(pkgHeader->name), mPackage->name); + + // 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); + + pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size()); + StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool); + + pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size()); + StringPool::flattenUtf16(pkgWriter.getBuffer(), mKeyPool); + + // Append the types. + buffer->appendBuffer(std::move(typeBuffer)); + + pkgWriter.finish(); + return true; + } + +private: + IDiagnostics* mDiag; + ResourceTablePackage* mPackage; + StringPool mTypePool; + StringPool mKeyPool; + + 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* 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; + } + + outEntry->flags = util::hostToDevice16(outEntry->flags); + outEntry->key.index = util::hostToDevice32(entry->entryKey); + outEntry->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>(); + 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; + } + + 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; + } + + 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; + } + + assert(type->id && "type must have an ID set"); + + sortedTypes.push_back(type.get()); + } + std::sort(sortedTypes.begin(), sortedTypes.end(), cmpIds<ResourceTableType>); + return sortedTypes; + } + + 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; + } + + 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; + + specHeader->entryCount = util::hostToDevice32(numEntries); + + // 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); + + const size_t actualNumEntries = sortedEntries->size(); + for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) { + ResourceEntry* entry = sortedEntries->at(entryIndex); + + // Populate the config masks for this entry. + + if (entry->symbolStatus.state == SymbolState::kPublic) { + configMasks[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)); + } + } + } + typeSpecWriter.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*> 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; + } + } + } + return true; + } +}; + +} // 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 { + int diff = a.context.priority - b.context.priority; + if (diff < 0) return true; + if (diff > 0) return false; + diff = a.context.config.compare(b.context.config); + if (diff < 0) return true; + if (diff > 0) return false; + return a.value < b.value; + }); + table->stringPool.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()); + + // Flatten the values string pool. + StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool); + + BigBuffer packageBuffer(1024); + + // Flatten each package. + for (auto& package : table->packages) { + PackageFlattener flattener(context->getDiagnostics(), package.get()); + if (!flattener.flattenPackage(&packageBuffer)) { + return false; + } + } + + // Finally merge all the packages into the main buffer. + tableWriter.getBuffer()->appendBuffer(std::move(packageBuffer)); + tableWriter.finish(); + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/ManifestParser.h b/tools/aapt2/flatten/TableFlattener.h index f2e43d4bb695..0ab01974044b 100644 --- a/tools/aapt2/ManifestParser.h +++ b/tools/aapt2/flatten/TableFlattener.h @@ -14,32 +14,27 @@ * limitations under the License. */ -#ifndef AAPT_MANIFEST_PARSER_H -#define AAPT_MANIFEST_PARSER_H +#ifndef AAPT_FLATTEN_TABLEFLATTENER_H +#define AAPT_FLATTEN_TABLEFLATTENER_H -#include "AppInfo.h" -#include "Logger.h" -#include "Source.h" -#include "XmlPullParser.h" +#include "process/IResourceTableConsumer.h" namespace aapt { -/* - * Parses an AndroidManifest.xml file and fills in an AppInfo structure with - * app data. - */ -class ManifestParser { +class BigBuffer; +class ResourceTable; + +class TableFlattener : public IResourceTableConsumer { public: - ManifestParser() = default; - ManifestParser(const ManifestParser&) = delete; + TableFlattener(BigBuffer* buffer) : mBuffer(buffer) { + } - bool parse(const Source& source, std::shared_ptr<XmlPullParser> parser, AppInfo* outInfo); + bool consume(IAaptContext* context, ResourceTable* table) override; private: - bool parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser, - AppInfo* outInfo); + BigBuffer* mBuffer; }; } // namespace aapt -#endif // AAPT_MANIFEST_PARSER_H +#endif /* AAPT_FLATTEN_TABLEFLATTENER_H */ diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp new file mode 100644 index 000000000000..39c4fd318508 --- /dev/null +++ b/tools/aapt2/flatten/TableFlattener_test.cpp @@ -0,0 +1,231 @@ +/* + * 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 "flatten/TableFlattener.h" +#include "test/Builders.h" +#include "test/Context.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(); + } + + ::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(); + } + + ::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(); + } + + ::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(); + } + +private: + std::unique_ptr<IAaptContext> mContext; +}; + +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)); +} + +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)); +} + +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); +} + +} // namespace aapt diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp new file mode 100644 index 000000000000..570cd9635de3 --- /dev/null +++ b/tools/aapt2/flatten/XmlFlattener.cpp @@ -0,0 +1,315 @@ +/* + * 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 "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 <vector> + +using namespace android; + +namespace aapt { + +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) { + } + + 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); + } + } + + void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) { + mStringRefs.push_back(StringFlattenDest{ ref, dest }); + } + + void writeNamespace(xml::Namespace* node, uint16_t type) { + ChunkWriter writer(mBuffer); + + ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type); + flatNode->lineNumber = util::hostToDevice32(node->lineNumber); + flatNode->comment.index = util::hostToDevice32(-1); + + ResXMLTree_namespaceExt* flatNs = writer.nextBlock<ResXMLTree_namespaceExt>(); + addString(node->namespacePrefix, kLowPriority, &flatNs->prefix); + addString(node->namespaceUri, kLowPriority, &flatNs->uri); + + writer.finish(); + } + + 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; + } + + 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* flatText = writer.nextBlock<ResXMLTree_cdataExt>(); + addString(node->text, kLowPriority, &flatText->data); + + 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); + + 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)); + + writeAttributes(node, flatElem, &startWriter); + + startWriter.finish(); + } + + xml::Visitor::visit(node); + + { + 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); + + ResXMLTree_endElementExt* flatEndElem = endWriter.nextBlock<ResXMLTree_endElementExt>(); + addString(node->namespaceUri, kLowPriority, &flatEndElem->ns); + addString(node->name, kLowPriority, &flatEndElem->name); + + endWriter.finish(); + } + } + + 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; + } + + 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); + } + + if (mFilteredAttrs.empty()) { + return; + } + + 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++; + } + } +}; + +} // namespace + +bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) { + BigBuffer nodeBuffer(1024); + XmlFlattenerVisitor visitor(&nodeBuffer, mOptions); + node->accept(&visitor); + + // Merge the package pools into the main pool. + for (auto& packagePoolEntry : visitor.mPackagePools) { + visitor.mPool.merge(std::move(packagePoolEntry.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 { + 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()); + } + + // Write the XML header. + ChunkWriter xmlHeaderWriter(mBuffer); + xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE); + + // 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; +} + +bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) { + if (!resource->root) { + return false; + } + return flatten(context, resource->root.get()); +} + +} // namespace aapt diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h new file mode 100644 index 000000000000..a688ac965b0d --- /dev/null +++ b/tools/aapt2/flatten/XmlFlattener.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#ifndef AAPT_FLATTEN_XMLFLATTENER_H +#define AAPT_FLATTEN_XMLFLATTENER_H + +#include "process/IResourceTableConsumer.h" +#include "util/BigBuffer.h" +#include "xml/XmlDom.h" + +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; +}; + +class XmlFlattener : public IXmlResourceConsumer { +public: + XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) : + mBuffer(buffer), mOptions(options) { + } + + bool consume(IAaptContext* context, xml::XmlResource* resource) override; + +private: + BigBuffer* mBuffer; + XmlFlattenerOptions mOptions; + + bool flatten(IAaptContext* context, xml::Node* node); +}; + +} // namespace aapt + +#endif /* AAPT_FLATTEN_XMLFLATTENER_H */ diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp new file mode 100644 index 000000000000..4e6eb811e572 --- /dev/null +++ b/tools/aapt2/flatten/XmlFlattener_test.cpp @@ -0,0 +1,210 @@ +/* + * 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 "flatten/XmlFlattener.h" +#include "link/Linkers.h" +#include "test/Builders.h" +#include "test/Context.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(); + } + + ::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(); + } + +protected: + std::unique_ptr<IAaptContext> mContext; +}; + +TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) { + 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)); + + ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE); + + size_t len; + const char16_t* namespacePrefix = tree.getNamespacePrefix(&len); + EXPECT_EQ(StringPiece16(namespacePrefix, len), u"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.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* 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())); + + ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); + + 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* attrNamespace = tree.getAttributeNamespace(0, &len); + EXPECT_EQ(StringPiece16(attrNamespace, len), u"http://com.test"); + + 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.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::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); + tagName = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tagName, len), u"View"); + + ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE); + namespacePrefix = tree.getNamespacePrefix(&len); + EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test"); + + namespaceUri = tree.getNamespaceUri(&len); + ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test"); + + ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT); +} + +TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) { + 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); + + android::ResXMLTree tree; + XmlFlattenerOptions options; + options.maxSdkLevel = 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); + } + + ASSERT_EQ(1u, tree.getAttributeCount()); + EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0)); +} + +TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) { + 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)); + + 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); +} + +/* + * 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\"/>"); + + 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"; + EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); +} + +} // namespace aapt diff --git a/tools/aapt2/integration-tests/Android.mk b/tools/aapt2/integration-tests/Android.mk new file mode 100644 index 000000000000..6361f9b8ae7d --- /dev/null +++ b/tools/aapt2/integration-tests/Android.mk @@ -0,0 +1,2 @@ +LOCAL_PATH := $(call my-dir) +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/aapt2/integration-tests/AppOne/Android.mk b/tools/aapt2/integration-tests/AppOne/Android.mk new file mode 100644 index 000000000000..bc40a6269382 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/Android.mk @@ -0,0 +1,28 @@ +# +# 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. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true +LOCAL_PACKAGE_NAME := AaptTestAppOne +LOCAL_MODULE_TAGS := tests +LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_STATIC_ANDROID_LIBRARIES := \ + AaptTestStaticLibOne \ + AaptTestStaticLibTwo +LOCAL_AAPT_FLAGS := --no-version-vectors +include $(BUILD_PACKAGE) diff --git a/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml b/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml new file mode 100644 index 000000000000..b6d8f2d1b748 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml @@ -0,0 +1,17 @@ +<?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. +--> + +<manifest package="com.android.aapt.app.one" /> diff --git a/tools/aapt2/data/res/drawable/icon.png b/tools/aapt2/integration-tests/AppOne/res/drawable/icon.png Binary files differindex 4bff9b900643..4bff9b900643 100644 --- a/tools/aapt2/data/res/drawable/icon.png +++ b/tools/aapt2/integration-tests/AppOne/res/drawable/icon.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/image.xml b/tools/aapt2/integration-tests/AppOne/res/drawable/image.xml new file mode 100644 index 000000000000..6132a75d85d0 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/drawable/image.xml @@ -0,0 +1,17 @@ +<?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 /> diff --git a/tools/aapt2/data/res/drawable/test.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/test.9.png Binary files differindex 33daa117ea9d..33daa117ea9d 100644 --- a/tools/aapt2/data/res/drawable/test.9.png +++ b/tools/aapt2/integration-tests/AppOne/res/drawable/test.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/layout-v21/main.xml b/tools/aapt2/integration-tests/AppOne/res/layout-v21/main.xml new file mode 100644 index 000000000000..9f5a4a85cbcf --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/layout-v21/main.xml @@ -0,0 +1,22 @@ +<?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" + xmlns:support="http://schemas.android.com/apk/res/android.appcompat" + android:id="@+id/view" + android:layout_width="match_parent" + android:layout_height="wrap_content"> +</LinearLayout> diff --git a/tools/aapt2/data/res/layout/main.xml b/tools/aapt2/integration-tests/AppOne/res/layout/main.xml index 50a51d99ad0a..ab1a251a7d56 100644 --- a/tools/aapt2/data/res/layout/main.xml +++ b/tools/aapt2/integration-tests/AppOne/res/layout/main.xml @@ -1,4 +1,19 @@ <?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" xmlns:support="http://schemas.android.com/apk/res/android.appcompat" android:id="@+id/view" @@ -14,8 +29,8 @@ android:layout_width="1dp" android:onClick="doClick" android:text="@{user.name}" + android:background="#ffffff" android:layout_height="match_parent" - app:layout_width="@support:bool/allow" app:flags="complex|weak" android:colorAccent="#ffffff"/> </LinearLayout> diff --git a/tools/aapt2/integration-tests/AppOne/res/raw/test.txt b/tools/aapt2/integration-tests/AppOne/res/raw/test.txt new file mode 100644 index 000000000000..b14df6442ea5 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/raw/test.txt @@ -0,0 +1 @@ +Hi diff --git a/tools/aapt2/integration-tests/AppOne/res/values-v4/styles.xml b/tools/aapt2/integration-tests/AppOne/res/values-v4/styles.xml new file mode 100644 index 000000000000..d8c11e210eda --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/values-v4/styles.xml @@ -0,0 +1,22 @@ +<?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. +--> + +<resources> + <style name="App" parent="android:Theme.Material"> + <item name="android:colorAccent">@color/accent</item> + <item name="android:text">Hey</item> + </style> +</resources> diff --git a/tools/aapt2/integration-tests/AppOne/res/values/colors.xml b/tools/aapt2/integration-tests/AppOne/res/values/colors.xml new file mode 100644 index 000000000000..4df5077051d2 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/values/colors.xml @@ -0,0 +1,21 @@ +<?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. +--> + +<resources> + <color name="primary">#f44336</color> + <color name="primary_dark">#b71c1c</color> + <color name="accent">#fdd835</color> +</resources> diff --git a/tools/aapt2/data/res/values/styles.xml b/tools/aapt2/integration-tests/AppOne/res/values/styles.xml index d0b19a3a881b..f05845cfab28 100644 --- a/tools/aapt2/data/res/values/styles.xml +++ b/tools/aapt2/integration-tests/AppOne/res/values/styles.xml @@ -1,6 +1,21 @@ <?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. +--> + <resources xmlns:lib="http://schemas.android.com/apk/res/android.appcompat"> - <style name="App" parent="android.appcompat:Platform.AppCompat"> + <style name="App"> <item name="android:background">@color/primary</item> <item name="android:colorPrimary">@color/primary</item> <item name="android:colorPrimaryDark">@color/primary_dark</item> @@ -8,8 +23,8 @@ </style> <attr name="custom" format="reference" /> <style name="Pop"> - <item name="custom">@drawable/image</item> - <item name="android:focusable">@lib:bool/allow</item> + <item name="custom">@android:drawable/btn_default</item> + <item name="android:focusable">true</item> </style> <string name="yo">@string/wow</string> diff --git a/tools/aapt2/integration-tests/AppOne/res/values/test.xml b/tools/aapt2/integration-tests/AppOne/res/values/test.xml new file mode 100644 index 000000000000..f4b7471aefae --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/values/test.xml @@ -0,0 +1,32 @@ +<?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. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Reference the two static libraries --> + <string name="AppFooBar">@string/FooBar</string> + <string name="AppFoo">@string/Foo</string> + + <string name="hooha"><font bgcolor="#ffffff">Hey guys!</font> <xliff:g>My</xliff:g> name is <b>Adam</b>. How <b><i>are</i></b> you?</string> + <public name="hooha" type="string" id="0x7f020001"/> + <string name="wow">@android:string/ok</string> + <public name="layout_width" type="attr" /> + <attr name="layout_width" format="boolean" /> + <attr name="flags"> + <flag name="complex" value="1" /> + <flag name="pub" value="2" /> + <flag name="weak" value="4" /> + </attr> +</resources> diff --git a/tools/aapt2/integration-tests/AppOne/src/com/android/aapt/app/one/AppOne.java b/tools/aapt2/integration-tests/AppOne/src/com/android/aapt/app/one/AppOne.java new file mode 100644 index 000000000000..472b35a781fe --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/src/com/android/aapt/app/one/AppOne.java @@ -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. + */ +package com.android.aapt.app.one; + +public class AppOne { + // IDs from StaticLibOne + public static int FooId = com.android.aapt.staticlib.one.R.string.Foo; + public static int LayoutId = com.android.aapt.staticlib.one.R.layout.layout; + + // IDs from StaticLibTwo + public static int FooBarId = com.android.aapt.staticlib.two.R.string.FooBar; +} + diff --git a/tools/aapt2/integration-tests/StaticLibOne/Android.mk b/tools/aapt2/integration-tests/StaticLibOne/Android.mk new file mode 100644 index 000000000000..0b7129aa0d38 --- /dev/null +++ b/tools/aapt2/integration-tests/StaticLibOne/Android.mk @@ -0,0 +1,28 @@ +# +# 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. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true +LOCAL_MODULE := AaptTestStaticLibOne +LOCAL_MODULE_TAGS := tests +LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +# We need this to compile the Java sources of AaptTestStaticLibTwo using javac. +LOCAL_JAR_EXCLUDE_FILES := none +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/tools/aapt2/integration-tests/StaticLibOne/AndroidManifest.xml b/tools/aapt2/integration-tests/StaticLibOne/AndroidManifest.xml new file mode 100644 index 000000000000..705047e71300 --- /dev/null +++ b/tools/aapt2/integration-tests/StaticLibOne/AndroidManifest.xml @@ -0,0 +1,17 @@ +<?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. +--> + +<manifest package="com.android.aapt.staticlib.one" /> diff --git a/tools/aapt2/integration-tests/StaticLibOne/res/layout/layout.xml b/tools/aapt2/integration-tests/StaticLibOne/res/layout/layout.xml new file mode 100644 index 000000000000..683c91cd9cf5 --- /dev/null +++ b/tools/aapt2/integration-tests/StaticLibOne/res/layout/layout.xml @@ -0,0 +1,18 @@ +<?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. +--> + +<View xmlns:android="http://schemas.android.com/apk/res/android" + android:text="@string/Foo" /> diff --git a/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml b/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml new file mode 100644 index 000000000000..d09a4851f7b4 --- /dev/null +++ b/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml @@ -0,0 +1,27 @@ +<?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. +--> + +<resources> + <!-- An attribute from StaticLibOne --> + <attr name="StaticLibOne_attr" format="string" /> + + <string name="Foo">Foo</string> + <string name="Foo" product="tablet">Bar</string> + + <declare-styleable name="Widget"> + <attr name="StaticLibOne_attr" /> + </declare-styleable> +</resources> diff --git a/tools/aapt2/integration-tests/StaticLibOne/src/com/android/aapt/staticlib/one/StaticLibOne.java b/tools/aapt2/integration-tests/StaticLibOne/src/com/android/aapt/staticlib/one/StaticLibOne.java new file mode 100644 index 000000000000..cf48f67056cf --- /dev/null +++ b/tools/aapt2/integration-tests/StaticLibOne/src/com/android/aapt/staticlib/one/StaticLibOne.java @@ -0,0 +1,22 @@ +/* + * 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.aapt.staticlib.one; + +public class StaticLibOne { + // IDs from StaticLibOne + public static int FooId = com.android.aapt.staticlib.one.R.string.Foo; + public static int LayoutId = com.android.aapt.staticlib.one.R.layout.layout; +} diff --git a/tools/aapt2/integration-tests/StaticLibTwo/Android.mk b/tools/aapt2/integration-tests/StaticLibTwo/Android.mk new file mode 100644 index 000000000000..8b6eb41b08cd --- /dev/null +++ b/tools/aapt2/integration-tests/StaticLibTwo/Android.mk @@ -0,0 +1,27 @@ +# +# 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. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true +LOCAL_MODULE := AaptTestStaticLibTwo +LOCAL_MODULE_TAGS := tests +LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res +LOCAL_SHARED_ANDROID_LIBRARIES := AaptTestStaticLibOne +include $(BUILD_STATIC_JAVA_LIBRARY) + diff --git a/tools/aapt2/integration-tests/StaticLibTwo/AndroidManifest.xml b/tools/aapt2/integration-tests/StaticLibTwo/AndroidManifest.xml new file mode 100644 index 000000000000..28f069932452 --- /dev/null +++ b/tools/aapt2/integration-tests/StaticLibTwo/AndroidManifest.xml @@ -0,0 +1,17 @@ +<?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. +--> + +<manifest package="com.android.aapt.staticlib.two" /> diff --git a/tools/aapt2/integration-tests/StaticLibTwo/res/drawable/vector.xml b/tools/aapt2/integration-tests/StaticLibTwo/res/drawable/vector.xml new file mode 100644 index 000000000000..dd5979f7e838 --- /dev/null +++ b/tools/aapt2/integration-tests/StaticLibTwo/res/drawable/vector.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:pathData="1123"/> diff --git a/tools/aapt2/integration-tests/StaticLibTwo/res/layout/layout_two.xml b/tools/aapt2/integration-tests/StaticLibTwo/res/layout/layout_two.xml new file mode 100644 index 000000000000..ba9830708eb0 --- /dev/null +++ b/tools/aapt2/integration-tests/StaticLibTwo/res/layout/layout_two.xml @@ -0,0 +1,18 @@ +<?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. +--> + +<View xmlns:custom="http://schemas.android.com/apk/res-auto" + custom:StaticLibOne_attr="@string/FooBar" /> diff --git a/tools/aapt2/integration-tests/StaticLibTwo/res/values/values.xml b/tools/aapt2/integration-tests/StaticLibTwo/res/values/values.xml new file mode 100644 index 000000000000..97bb2a53d9f6 --- /dev/null +++ b/tools/aapt2/integration-tests/StaticLibTwo/res/values/values.xml @@ -0,0 +1,19 @@ +<?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. +--> + +<resources> + <string name="FooBar">@string/Foo</string> +</resources> diff --git a/tools/aapt2/integration-tests/StaticLibTwo/src/com/android/aapt/staticlib/two/StaticLibTwo.java b/tools/aapt2/integration-tests/StaticLibTwo/src/com/android/aapt/staticlib/two/StaticLibTwo.java new file mode 100644 index 000000000000..7110dcdd017a --- /dev/null +++ b/tools/aapt2/integration-tests/StaticLibTwo/src/com/android/aapt/staticlib/two/StaticLibTwo.java @@ -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. + */ +package com.android.aapt.staticlib.two; + +public class StaticLibTwo { + // IDs from StaticLibOne + public static int FooId = com.android.aapt.staticlib.one.R.string.Foo; + public static int LayoutId = com.android.aapt.staticlib.one.R.layout.layout; + + // IDs from StaticLibTwo + public static int FooBarId = com.android.aapt.staticlib.two.R.string.FooBar; +} diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h new file mode 100644 index 000000000000..467e60464a68 --- /dev/null +++ b/tools/aapt2/io/Data.h @@ -0,0 +1,100 @@ +/* + * 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. + */ + +#ifndef AAPT_IO_DATA_H +#define AAPT_IO_DATA_H + +#include <utils/FileMap.h> + +#include <memory> + +namespace aapt { +namespace io { + +/** + * Interface for a block of contiguous memory. An instance of this interface owns the data. + */ +class IData { +public: + virtual ~IData() = default; + + virtual const void* data() const = 0; + virtual size_t size() const = 0; +}; + +/** + * 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)) { + } + + const void* data() const override { + return mMap.getDataPtr(); + } + + size_t size() const override { + return mMap.getDataLength(); + } + +private: + android::FileMap mMap; +}; + +/** + * 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; +}; + +/** + * 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; + } +}; + +} // namespace io +} // namespace aapt + +#endif /* AAPT_IO_DATA_H */ diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h new file mode 100644 index 000000000000..b4d49719aa3e --- /dev/null +++ b/tools/aapt2/io/File.h @@ -0,0 +1,78 @@ +/* + * 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. + */ + +#ifndef AAPT_IO_FILE_H +#define AAPT_IO_FILE_H + +#include "Source.h" +#include "io/Data.h" + +#include <memory> +#include <vector> + +namespace aapt { +namespace io { + +/** + * 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; +}; + +class IFileCollectionIterator { +public: + virtual ~IFileCollectionIterator() = default; + + virtual bool hasNext() = 0; + virtual IFile* next() = 0; +}; + +/** + * 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; + + virtual IFile* findFile(const StringPiece& path) = 0; + virtual std::unique_ptr<IFileCollectionIterator> iterator() = 0; +}; + +} // namespace io +} // namespace aapt + +#endif /* AAPT_IO_FILE_H */ diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp new file mode 100644 index 000000000000..e758d8a421e1 --- /dev/null +++ b/tools/aapt2/io/FileSystem.cpp @@ -0,0 +1,78 @@ +/* + * 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 "Source.h" +#include "io/FileSystem.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) { +} + +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>(); + } + return {}; +} + +const Source& RegularFile::getSource() const { + return mSource; +} + +FileCollectionIterator::FileCollectionIterator(FileCollection* collection) : + mCurrent(collection->mFiles.begin()), mEnd(collection->mFiles.end()) { +} + +bool FileCollectionIterator::hasNext() { + return mCurrent != mEnd; +} + +IFile* FileCollectionIterator::next() { + IFile* result = mCurrent->second.get(); + ++mCurrent; + return result; +} + +IFile* FileCollection::insertFile(const StringPiece& path) { + return (mFiles[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; +} + +std::unique_ptr<IFileCollectionIterator> FileCollection::iterator() { + return util::make_unique<FileCollectionIterator>(this); +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h new file mode 100644 index 000000000000..f0559c03a8b8 --- /dev/null +++ b/tools/aapt2/io/FileSystem.h @@ -0,0 +1,74 @@ +/* + * 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. + */ + +#ifndef AAPT_IO_FILESYSTEM_H +#define AAPT_IO_FILESYSTEM_H + +#include "io/File.h" + +#include <map> + +namespace aapt { +namespace io { + +/** + * A regular file from the file system. Uses mmap to open the data. + */ +class RegularFile : public IFile { +public: + RegularFile(const Source& source); + + std::unique_ptr<IData> openAsData() override; + const Source& getSource() const override; + +private: + Source mSource; +}; + +class FileCollection; + +class FileCollectionIterator : public IFileCollectionIterator { +public: + FileCollectionIterator(FileCollection* collection); + + bool hasNext() override; + io::IFile* next() override; + +private: + std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd; +}; + +/** + * 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; + +private: + friend class FileCollectionIterator; + std::map<std::string, std::unique_ptr<IFile>> mFiles; +}; + +} // namespace io +} // namespace aapt + +#endif // AAPT_IO_FILESYSTEM_H diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp new file mode 100644 index 000000000000..b3e7a02102e4 --- /dev/null +++ b/tools/aapt2/io/ZipArchive.cpp @@ -0,0 +1,142 @@ +/* + * 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 "Source.h" +#include "io/ZipArchive.h" +#include "util/Util.h" + +#include <utils/FileMap.h> +#include <ziparchive/zip_archive.h> + +namespace aapt { +namespace io { + +ZipFile::ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source) : + mZipHandle(handle), mZipEntry(entry), mSource(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); + } +} + +const Source& ZipFile::getSource() const { + return mSource; +} + +ZipFileCollectionIterator::ZipFileCollectionIterator(ZipFileCollection* collection) : + mCurrent(collection->mFiles.begin()), mEnd(collection->mFiles.end()) { +} + +bool ZipFileCollectionIterator::hasNext() { + return mCurrent != mEnd; +} + +IFile* ZipFileCollectionIterator::next() { + IFile* result = mCurrent->second.get(); + ++mCurrent; + return result; +} + +ZipFileCollection::ZipFileCollection() : mHandle(nullptr) { +} + +std::unique_ptr<ZipFileCollection> ZipFileCollection::create(const StringPiece& path, + std::string* outError) { + constexpr static const int32_t kEmptyArchive = -6; + + std::unique_ptr<ZipFileCollection> collection = std::unique_ptr<ZipFileCollection>( + new ZipFileCollection()); + + 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 {}; + } + + void* cookie = nullptr; + result = StartIteration(collection->mHandle, &cookie, nullptr, nullptr); + if (result != 0) { + if (outError) *outError = ErrorCodeString(result); + return {}; + } + + 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; +} + +IFile* ZipFileCollection::findFile(const StringPiece& path) { + auto iter = mFiles.find(path.toString()); + if (iter != mFiles.end()) { + return iter->second.get(); + } + return nullptr; +} + +std::unique_ptr<IFileCollectionIterator> ZipFileCollection::iterator() { + return util::make_unique<ZipFileCollectionIterator>(this); +} + +ZipFileCollection::~ZipFileCollection() { + if (mHandle) { + CloseArchive(mHandle); + } +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h new file mode 100644 index 000000000000..5ad119d1d6d4 --- /dev/null +++ b/tools/aapt2/io/ZipArchive.h @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#ifndef AAPT_IO_ZIPARCHIVE_H +#define AAPT_IO_ZIPARCHIVE_H + +#include "io/File.h" +#include "util/StringPiece.h" + +#include <map> +#include <ziparchive/zip_archive.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. + */ +class ZipFile : public IFile { +public: + ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source); + + std::unique_ptr<IData> openAsData() override; + const Source& getSource() const override; + +private: + ZipArchiveHandle mZipHandle; + ZipEntry mZipEntry; + Source mSource; +}; + +class ZipFileCollection; + +class ZipFileCollectionIterator : public IFileCollectionIterator { +public: + ZipFileCollectionIterator(ZipFileCollection* collection); + + bool hasNext() override; + io::IFile* next() override; + +private: + std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd; +}; + +/** + * 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); + + io::IFile* findFile(const StringPiece& path) override; + std::unique_ptr<IFileCollectionIterator> iterator() override; + + ~ZipFileCollection() override; + +private: + friend class ZipFileCollectionIterator; + ZipFileCollection(); + + ZipArchiveHandle mHandle; + std::map<std::string, std::unique_ptr<IFile>> mFiles; +}; + +} // namespace io +} // namespace aapt + +#endif /* AAPT_IO_ZIPARCHIVE_H */ diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp new file mode 100644 index 000000000000..b7e7f903a2b1 --- /dev/null +++ b/tools/aapt2/java/AnnotationProcessor.cpp @@ -0,0 +1,93 @@ +/* + * 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 "java/AnnotationProcessor.h" +#include "util/Util.h" + +#include <algorithm> + +namespace aapt { + +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; + } + + 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()); + } + + if (util::trimWhitespace(comment).empty()) { + return; + } + + if (!mHasComments) { + mHasComments = true; + mComment << "/**"; + } + + mComment << "\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) { + 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::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"; + } + + if (mAnnotationBitMask & kDeprecated) { + *out << prefix << "@Deprecated\n"; + } + + if (mAnnotationBitMask & kSystemApi) { + *out << prefix << "@android.annotation.SystemApi\n"; + } +} + +} // namespace aapt diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h new file mode 100644 index 000000000000..8309dd978175 --- /dev/null +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -0,0 +1,86 @@ +/* + * 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. + */ + +#ifndef AAPT_JAVA_ANNOTATIONPROCESSOR_H +#define AAPT_JAVA_ANNOTATIONPROCESSOR_H + +#include "util/StringPiece.h" + +#include <sstream> +#include <string> + +namespace aapt { + +/** + * Builds a JavaDoc comment from a set of XML comments. + * This will also look for instances of @SystemApi and convert them to + * actual Java annotations. + * + * Example: + * + * Input XML: + * + * <!-- This is meant to be hidden because + * It is system api. Also it is @deprecated + * @SystemApi + * --> + * + * Output JavaDoc: + * + * /\* + * * This is meant to be hidden because + * * It is system api. Also it is @deprecated + * *\/ + * + * Output Annotations: + * + * @Deprecated + * @android.annotation.SystemApi + * + */ +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); + + 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; + +private: + enum : uint32_t { + kDeprecated = 0x01, + kSystemApi = 0x02, + }; + + std::stringstream mComment; + std::stringstream mAnnotations; + bool mHasComments = false; + uint32_t mAnnotationBitMask = 0; + + void appendCommentLine(std::string& line); +}; + +} // namespace aapt + +#endif /* AAPT_JAVA_ANNOTATIONPROCESSOR_H */ diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp new file mode 100644 index 000000000000..5a39add48fbd --- /dev/null +++ b/tools/aapt2/java/AnnotationProcessor_test.cpp @@ -0,0 +1,52 @@ +/* + * 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 "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! }"; + + AnnotationProcessor processor; + processor.appendComment(comment); + + std::stringstream result; + processor.writeToStream(&result, ""); + std::string annotations = result.str(); + + EXPECT_NE(std::string::npos, annotations.find("@Deprecated")); +} + +TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) { + AnnotationProcessor processor; + processor.appendComment("@SystemApi This is a system API"); + + 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")); +} + +} // namespace aapt + + diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp new file mode 100644 index 000000000000..08f2c8b9805c --- /dev/null +++ b/tools/aapt2/java/ClassDefinition.cpp @@ -0,0 +1,75 @@ +/* + * 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 "java/ClassDefinition.h" +#include "util/StringPiece.h" + +#include <ostream> + +namespace aapt { + +bool ClassDefinition::empty() const { + for (const std::unique_ptr<ClassMember>& member : mMembers) { + if (!member->empty()) { + return false; + } + } + return true; +} + +void ClassDefinition::writeToStream(const StringPiece& prefix, bool final, + std::ostream* out) const { + if (mMembers.empty() && !mCreateIfEmpty) { + return; + } + + ClassMember::writeToStream(prefix, final, out); + + *out << prefix << "public "; + if (mQualifier == ClassQualifier::Static) { + *out << "static "; + } + *out << "final class " << mName << " {\n"; + + std::string newPrefix = prefix.toString(); + newPrefix.append(kIndent); + + for (const std::unique_ptr<ClassMember>& member : mMembers) { + member->writeToStream(newPrefix, final, out); + *out << "\n"; + } + + *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"; + +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); +} + +} // namespace aapt diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h new file mode 100644 index 000000000000..d45328fedba2 --- /dev/null +++ b/tools/aapt2/java/ClassDefinition.h @@ -0,0 +1,188 @@ +/* + * 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. + */ + +#ifndef AAPT_JAVA_CLASSDEFINITION_H +#define AAPT_JAVA_CLASSDEFINITION_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. +constexpr static size_t kAttribsPerLine = 4; +constexpr static const char* kIndent = " "; + +class ClassMember { +public: + virtual ~ClassMember() = default; + + AnnotationProcessor* getCommentBuilder() { + return &mProcessor; + } + + virtual bool empty() const = 0; + + virtual void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const { + mProcessor.writeToStream(out, prefix); + } + +private: + AnnotationProcessor mProcessor; +}; + +template <typename T> +class PrimitiveMember : public ClassMember { +public: + PrimitiveMember(const StringPiece& name, const T& val) : + mName(name.toString()), mVal(val) { + } + + bool empty() const override { + return false; + } + + 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 << ";"; + } + +private: + std::string mName; + T mVal; + + DISALLOW_COPY_AND_ASSIGN(PrimitiveMember); +}; + +/** + * Specialization for strings so they get the right type and are quoted with "". + */ +template <> +class PrimitiveMember<std::string> : public ClassMember { +public: + PrimitiveMember(const StringPiece& name, const std::string& val) : + mName(name.toString()), mVal(val) { + } + + bool empty() const override { + return false; + } + + 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 << "\";"; + } + +private: + std::string mName; + std::string mVal; + + DISALLOW_COPY_AND_ASSIGN(PrimitiveMember); +}; + +using IntMember = PrimitiveMember<uint32_t>; +using ResourceMember = PrimitiveMember<ResourceId>; +using StringMember = PrimitiveMember<std::string>; + +template <typename T> +class PrimitiveArrayMember : public ClassMember { +public: + PrimitiveArrayMember(const StringPiece& name) : + mName(name.toString()) { + } + + void addElement(const T& val) { + mElements.push_back(val); + } + + bool empty() const override { + return false; + } + + void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override { + ClassMember::writeToStream(prefix, final, out); + + *out << prefix << "public static final int[] " << mName << "={"; + + 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; + } + + *out << *current; + if (std::distance(current, end) > 1) { + *out << ", "; + } + } + *out << "\n" << prefix << kIndent <<"};"; + } + +private: + std::string mName; + std::vector<T> mElements; + + DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember); +}; + +using ResourceArrayMember = PrimitiveArrayMember<ResourceId>; + +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); +}; + +} // namespace aapt + +#endif /* AAPT_JAVA_CLASSDEFINITION_H */ diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp new file mode 100644 index 000000000000..84df0b429fc5 --- /dev/null +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -0,0 +1,557 @@ +/* + * 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 "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(); +} + +/* + * 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 = '_'; + } + } + return output; +} + +/** + * Transforms an attribute in a styleable to the Java field name: + * + * <declare-styleable name="Foo"> + * <attr name="android:bar" /> + * <attr name="bar" /> + * </declare-styleable> + * + * 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 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>\"."); + } + + 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;"); + } + + if (typeMask & android::ResTable_map::TYPE_INTEGER) { + processor->appendComment("<p>May be an integer value, such as \"<code>100</code>\"."); + } + + 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>\"."); + } + + 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>\"."); + } + + if (typeMask & android::ResTable_map::TYPE_FLOAT) { + processor->appendComment( + "<p>May be a floating point value, such as \"<code>1.2</code>\"."); + } + + 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)."); + } + + 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 (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>"); + } +} + +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; + } + 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(); + } +} + +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(); + } + + if (Maybe<ResourceName> mangledName = + mContext->getNameMangler()->mangleName(mangledReference.name.value())) { + mangledReference.name = mangledName; + } + + // 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)); + } + + // 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"; + } + + 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"; + } + + styleableArrayDef->getCommentBuilder()->appendComment(styleableComment.str()); + } + + // Add the ResourceIds to the array member. + for (const StyleableAttr& styleableAttr : sortedAttributes) { + styleableArrayDef->addElement( + styleableAttr.attrRef->id ? styleableAttr.attrRef->id.value() : ResourceId(0)); + } + + // Add the Styleable array to the Styleable class. + outStyleableClassDef->addMember(std::move(styleableArrayDef)); + + // Now we emit the indices into the array. + for (size_t i = 0; i < attrCount; i++) { + const StyleableAttr& styleableAttr = sortedAttributes[i]; + + if (!styleableAttr.symbol) { + continue; + } + + if (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic && + !styleableAttr.symbol->isPublic) { + // Don't write entries for non-public attributes. + continue; + } + + StringPiece16 comment = styleableAttr.attrRef->getComment(); + if (styleableAttr.symbol->attribute && comment.empty()) { + comment = styleableAttr.symbol->attribute->getComment(); + } + + 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; + } + + const ResourceName& attrName = styleableAttr.attrRef->name.value(); + + StringPiece16 packageName = attrName.package; + if (packageName.empty()) { + packageName = mContext->getCompilationPackage(); + } + + 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()); + } + + attrProcessor->appendNewLine(); + + addAttributeFormatDoc(attrProcessor, styleableAttr.symbol->attribute.get()); + attrProcessor->appendNewLine(); + + std::stringstream doclavaName; + doclavaName << "@attr name " << packageName << ":" << attrName.entry;; + attrProcessor->appendComment(doclavaName.str()); + + outStyleableClassDef->addMember(std::move(indexMember)); + } +} + +bool JavaClassGenerator::addMembersToTypeClass(const StringPiece16& packageNameToGenerate, + const ResourceTablePackage* package, + const ResourceTableType* type, + ClassDefinition* outTypeClassDef) { + + for (const auto& entry : type->entries) { + if (skipSymbol(entry->symbolStatus.state)) { + continue; + } + + ResourceId id; + if (package->id && type->id && entry->id) { + id = ResourceId(package->id.value(), type->id.value(), entry->id.value()); + } + + 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; + } + + if (!isValidSymbol(unmangledName)) { + ResourceNameRef resourceName(packageNameToGenerate, type->type, unmangledName); + std::stringstream err; + err << "invalid symbol name '" << resourceName << "'"; + mError = err.str(); + return false; + } + + 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)); + } + } + return true; +} + +bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, std::ostream* out) { + return generate(packageNameToGenerate, packageNameToGenerate, 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); + } +} + +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)); + } + } + + appendJavaDocAnnotations(mOptions.javadocAnnotations, rClass.getCommentBuilder()); + + if (!ClassDefinition::writeJavaFile(&rClass, util::utf16ToUtf8(outPackageName), + mOptions.useFinal, out)) { + return false; + } + + out->flush(); + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h new file mode 100644 index 000000000000..77e0ed76143a --- /dev/null +++ b/tools/aapt2/java/JavaClassGenerator.h @@ -0,0 +1,102 @@ +/* + * 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. + */ + +#ifndef AAPT_JAVA_CLASS_GENERATOR_H +#define AAPT_JAVA_CLASS_GENERATOR_H + +#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; +}; + +/* + * 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; +}; + +inline const std::string& JavaClassGenerator::getError() const { + return mError; +} + +} // namespace aapt + +#endif // AAPT_JAVA_CLASS_GENERATOR_H diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp new file mode 100644 index 000000000000..46266b3f3e89 --- /dev/null +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -0,0 +1,325 @@ +/* + * 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 "java/JavaClassGenerator.h" +#include "test/Test.h" +#include "util/Util.h" + +#include <sstream> +#include <string> + +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)); +} + +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;")); + + 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;")); +} + +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(), {}); + std::stringstream out; + ASSERT_TRUE(generator.generate(u"android", u"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")); +} + +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(), {}); + 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 class attr")); + EXPECT_EQ(std::string::npos, output.find("public static final class ^attr-private")); +} + +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;")); + } +} + +/* + * TODO(adamlesinski): Re-enable this once we get merging working again. + * TEST(JavaClassGeneratorTest, EmitPackageMangledSymbols) { + ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"foo" }, + 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(mTable->merge(std::move(table))); + + Linker linker(mTable, + std::make_shared<MockResolver>(mTable, std::map<ResourceName, ResourceId>()), + {}); + ASSERT_TRUE(linker.linkAndValidate()); + + JavaClassGenerator generator(mTable, {}); + + std::stringstream out; + EXPECT_TRUE(generator.generate(mTable->getPackage(), out)); + std::string output = out.str(); + EXPECT_NE(std::string::npos, output.find("int foo =")); + EXPECT_EQ(std::string::npos, output.find("int test =")); + + out.str(""); + EXPECT_TRUE(generator.generate(u"com.lib", out)); + output = out.str(); + EXPECT_NE(std::string::npos, output.find("int test =")); + EXPECT_EQ(std::string::npos, output.find("int foo =")); +}*/ + +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=")); +} + +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(/** + * This is a comment + * @deprecated + */ + @Deprecated + public static final int foo=0x01010000;)EOF"; + + 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, 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)); +} + +} // namespace aapt diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp new file mode 100644 index 000000000000..be8955ecdf83 --- /dev/null +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -0,0 +1,124 @@ +/* + * 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 "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 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; +} + +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 != 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; +} + +} // namespace aapt diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h new file mode 100644 index 000000000000..f565289393fb --- /dev/null +++ b/tools/aapt2/java/ManifestClassGenerator.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#ifndef AAPT_JAVA_MANIFESTCLASSGENERATOR_H +#define AAPT_JAVA_MANIFESTCLASSGENERATOR_H + +#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); + +} // namespace aapt + +#endif /* AAPT_JAVA_MANIFESTCLASSGENERATOR_H */ diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp new file mode 100644 index 000000000000..d3bca7068cb2 --- /dev/null +++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp @@ -0,0 +1,136 @@ +/* + * 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 "java/ManifestClassGenerator.h" +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.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(); +} + +TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) { + 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" /> + <permission android:name="com.test.sample.permission.HUH" /> + <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); +} + +TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { + 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. --> + <permission android:name="android.permission.ACCESS_INTERNET" /> + <!-- @deprecated This permission is for playing outside. --> + <permission android:name="android.permission.PLAY_OUTSIDE" /> + <!-- This is a private permission for system only! + @hide + @SystemApi --> + <permission android:name="android.permission.SECRET" /> + </manifest>)EOF"); + + std::string actual; + ASSERT_TRUE(getManifestClassText(context.get(), manifest.get(), &actual)); + + const char* expectedAccessInternet = +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)); + + const char* expectedPlayOutside = +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)); + + const char* expectedSecret = +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)); +} + +} // namespace aapt diff --git a/tools/aapt2/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index e89fb7c8bd3d..c610bb0f2ff2 100644 --- a/tools/aapt2/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -14,9 +14,9 @@ * limitations under the License. */ -#include "ProguardRules.h" -#include "Util.h" -#include "XmlDom.h" +#include "java/ProguardRules.h" +#include "util/Util.h" +#include "xml/XmlDom.h" #include <memory> #include <string> @@ -24,8 +24,6 @@ namespace aapt { namespace proguard { -constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; - class BaseVisitor : public xml::Visitor { public: BaseVisitor(const Source& source, KeepSet* keepSet) : mSource(source), mKeepSet(keepSet) { @@ -41,11 +39,11 @@ public: virtual void visit(xml::Element* node) override { if (!node->namespaceUri.empty()) { - Maybe<std::u16string> maybePackage = util::extractPackageFromNamespace( + 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() + u"." + node->name; + std::u16string package = maybePackage.value().package + u"." + node->name; if (util::isJavaClassName(package)) { addClass(node->lineNumber, package); } @@ -61,11 +59,11 @@ public: protected: void addClass(size_t lineNumber, const std::u16string& className) { - mKeepSet->addClass(mSource.line(lineNumber), className); + mKeepSet->addClass(Source(mSource.path, lineNumber), className); } void addMethod(size_t lineNumber, const std::u16string& methodName) { - mKeepSet->addMethod(mSource.line(lineNumber), methodName); + mKeepSet->addMethod(Source(mSource.path, lineNumber), methodName); } private: @@ -82,7 +80,7 @@ struct LayoutVisitor : public BaseVisitor { bool checkName = false; if (node->namespaceUri.empty()) { checkClass = node->name == u"view" || node->name == u"fragment"; - } else if (node->namespaceUri == kSchemaAndroid) { + } else if (node->namespaceUri == xml::kSchemaAndroid) { checkName = node->name == u"fragment"; } @@ -90,10 +88,10 @@ struct LayoutVisitor : public BaseVisitor { if (checkClass && attr.namespaceUri.empty() && attr.name == u"class" && util::isJavaClassName(attr.value)) { addClass(node->lineNumber, attr.value); - } else if (checkName && attr.namespaceUri == kSchemaAndroid && attr.name == u"name" && - util::isJavaClassName(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 == kSchemaAndroid && attr.name == u"onClick") { + } else if (attr.namespaceUri == xml::kSchemaAndroid && attr.name == u"onClick") { addMethod(node->lineNumber, attr.value); } } @@ -113,7 +111,7 @@ struct XmlResourceVisitor : public BaseVisitor { } if (checkFragment) { - xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"fragment"); + xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"fragment"); if (attr && util::isJavaClassName(attr->value)) { addClass(node->lineNumber, attr->value); } @@ -155,7 +153,7 @@ struct ManifestVisitor : public BaseVisitor { } } else if (node->name == u"application") { getName = true; - xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"backupAgent"); + xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"backupAgent"); if (attr) { Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage, attr->value); @@ -170,7 +168,7 @@ struct ManifestVisitor : public BaseVisitor { } if (getName) { - xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"name"); + xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"name"); if (attr) { Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage, attr->value); @@ -186,30 +184,37 @@ struct ManifestVisitor : public BaseVisitor { std::u16string mPackage; }; -bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet) { +bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, + KeepSet* keepSet) { ManifestVisitor visitor(source, keepSet); - node->accept(&visitor); - return true; + if (res->root) { + res->root->accept(&visitor); + return true; + } + return false; } -bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node, - KeepSet* keepSet) { - switch (type) { +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); - node->accept(&visitor); + res->root->accept(&visitor); break; } case ResourceType::kXml: { XmlResourceVisitor visitor(source, keepSet); - node->accept(&visitor); + res->root->accept(&visitor); break; } case ResourceType::kTransition: { TransitionVisitor visitor(source, keepSet); - node->accept(&visitor); + res->root->accept(&visitor); break; } @@ -221,15 +226,15 @@ bool collectProguardRules(ResourceType type, const Source& source, xml::Node* no bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) { for (const auto& entry : keepSet.mKeepSet) { - for (const SourceLine& source : entry.second) { - *out << "// Referenced at " << source << "\n"; + 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 SourceLine& source : entry.second) { - *out << "// Referenced at " << source << "\n"; + for (const Source& source : entry.second) { + *out << "# Referenced at " << source << "\n"; } *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl; } diff --git a/tools/aapt2/ProguardRules.h b/tools/aapt2/java/ProguardRules.h index bbb3e64e6758..aafffd39d84e 100644 --- a/tools/aapt2/ProguardRules.h +++ b/tools/aapt2/java/ProguardRules.h @@ -19,7 +19,7 @@ #include "Resource.h" #include "Source.h" -#include "XmlDom.h" +#include "xml/XmlDom.h" #include <map> #include <ostream> @@ -31,24 +31,23 @@ namespace proguard { class KeepSet { public: - inline void addClass(const SourceLine& source, const std::u16string& className) { + inline void addClass(const Source& source, const std::u16string& className) { mKeepSet[className].insert(source); } - inline void addMethod(const SourceLine& source, const std::u16string& methodName) { + inline void addMethod(const Source& source, const std::u16string& methodName) { mKeepMethodSet[methodName].insert(source); } private: friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet); - std::map<std::u16string, std::set<SourceLine>> mKeepSet; - std::map<std::u16string, std::set<SourceLine>> mKeepMethodSet; + std::map<std::u16string, std::set<Source>> mKeepSet; + std::map<std::u16string, std::set<Source>> mKeepMethodSet; }; -bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet); -bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node, - KeepSet* keepSet); +bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keepSet); +bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet); bool writeKeepSet(std::ostream* out, const KeepSet& keepSet); diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp new file mode 100644 index 000000000000..459c330cfbdc --- /dev/null +++ b/tools/aapt2/link/AutoVersioner.cpp @@ -0,0 +1,139 @@ +/* + * 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 "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; + } + } + + // 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; + } + } + + // 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) { + continue; + } + + 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); + } + } + } + } + } + } + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp new file mode 100644 index 000000000000..9b3a87c4eed0 --- /dev/null +++ b/tools/aapt2/link/AutoVersioner_test.cpp @@ -0,0 +1,125 @@ +/* + * 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 "ConfigDescription.h" +#include "link/Linkers.h" +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.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)); +} + +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)); +} + +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")); +} + +} // namespace aapt diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp new file mode 100644 index 000000000000..49971201fb3c --- /dev/null +++ b/tools/aapt2/link/Link.cpp @@ -0,0 +1,1589 @@ +/* + * 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 "AppInfo.h" +#include "Debug.h" +#include "Flags.h" +#include "Locale.h" +#include "NameMangler.h" +#include "ResourceUtils.h" +#include "compile/IdAssigner.h" +#include "filter/ConfigFilter.h" +#include "flatten/Archive.h" +#include "flatten/TableFlattener.h" +#include "flatten/XmlFlattener.h" +#include "io/FileSystem.h" +#include "io/ZipArchive.h" +#include "java/JavaClassGenerator.h" +#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/TableMerger.h" +#include "process/IResourceTableConsumer.h" +#include "process/SymbolTable.h" +#include "proto/ProtoSerialize.h" +#include "split/TableSplitter.h" +#include "unflatten/BinaryResourceParser.h" +#include "util/Files.h" +#include "util/StringPiece.h" +#include "xml/XmlDom.h" + +#include <google/protobuf/io/coded_stream.h> + +#include <fstream> +#include <sys/stat.h> +#include <vector> + +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; +}; + +class LinkContext : public IAaptContext { +public: + LinkContext() : mNameMangler({}) { + } + + IDiagnostics* getDiagnostics() override { + return &mDiagnostics; + } + + NameMangler* getNameMangler() override { + return &mNameMangler; + } + + void setNameManglerPolicy(const NameManglerPolicy& policy) { + mNameMangler = NameMangler(policy); + } + + const std::u16string& getCompilationPackage() override { + return mCompilationPackage; + } + + void setCompilationPackage(const StringPiece16& packageName) { + mCompilationPackage = packageName.toString(); + } + + uint8_t getPackageId() override { + return mPackageId; + } + + void setPackageId(uint8_t id) { + mPackageId = id; + } + + SymbolTable* getExternalSymbols() override { + return &mSymbols; + } + + bool verbose() override { + return mVerbose; + } + + void setVerbose(bool val) { + mVerbose = val; + } + +private: + StdErrDiagnostics mDiagnostics; + NameMangler mNameMangler; + std::u16string mCompilationPackage; + uint8_t mPackageId = 0x0; + SymbolTable mSymbols; + bool mVerbose = false; +}; + +static bool copyFileToArchive(io::IFile* file, const std::string& outPath, + uint32_t compressionFlags, + 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; + } + + const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data()); + size_t bufferSize = 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 (writer->startEntry(outPath, compressionFlags)) { + if (writer->writeEntry(buffer, bufferSize)) { + if (writer->finishEntry()) { + return true; + } + } + } + + context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath); + 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; + } + + if (context->verbose()) { + DiagMessage msg; + msg << "writing " << path << " to archive"; + if (maxSdkLevel) { + msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues; + } + context->getDiagnostics()->note(msg); + } + + 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; +} + +/*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, + 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; +} + +/** + * 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> 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(); + + std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source); + if (!xmlRes) { + return {}; + } + return xmlRes; +} + +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 {}; + } + + std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag); + if (!resFile) { + return {}; + } + return resFile; +} + +struct ResourceFileFlattenerOptions { + bool noAutoVersion = false; + bool noVersionVectors = false; + bool keepRawValues = false; + bool doNotCompressAnything = false; + std::vector<std::string> extensionsToNotCompress; +}; + +class ResourceFileFlattener { +public: + ResourceFileFlattener(const ResourceFileFlattenerOptions& options, + IAaptContext* context, proguard::KeepSet* keepSet) : + mOptions(options), mContext(context), mKeepSet(keepSet) { + } + + bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter); + +private: + struct FileOperation { + io::IFile* fileToCopy; + std::unique_ptr<xml::XmlResource> xmlToFlatten; + std::string dstPath; + bool skipVersion = false; + }; + + uint32_t getCompressionFlags(const StringPiece& str); + + bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc, + io::IFile* file, ResourceTable* table, FileOperation* outFileOp); + + ResourceFileFlattenerOptions mOptions; + IAaptContext* mContext; + proguard::KeepSet* mKeepSet; +}; + +uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) { + if (mOptions.doNotCompressAnything) { + return 0; + } + + for (const std::string& extension : mOptions.extensionsToNotCompress) { + if (util::stringEndsWith<char>(str, extension)) { + return 0; + } + } + 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); + } + + std::unique_ptr<io::IData> data = file->openAsData(); + if (!data) { + mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); + return false; + } + + 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()); + } + + if (!outFileOp->xmlToFlatten) { + return false; + } + + // Copy the the file description header. + outFileOp->xmlToFlatten->file = fileDesc; + + XmlReferenceLinker xmlLinker; + if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) { + return false; + } + + if (!proguard::collectProguardRules(outFileOp->xmlToFlatten->file.source, + outFileOp->xmlToFlatten.get(), mKeepSet)) { + return false; + } + + 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; + } + } + } + + // 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; + } + } + } + return true; +} + +/** + * 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); + } + } + + if (error) { + return false; + } + + // 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; + } + } + } + } + } + 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"); + } + + // 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; + } + + if (!assetSource->addAssetPath(path)) { + mContext->getDiagnostics()->error( + DiagMessage(path) << "failed to load include path"); + return false; + } + } + + mContext->getExternalSymbols()->appendSource(std::move(assetSource)); + return true; + } + + 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 {}; + } + + /** + * 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; + } + + std::unique_ptr<IArchiveWriter> makeArchiveWriter() { + if (mOptions.outputToDirectory) { + return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); + } else { + return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); + } + } + + bool flattenTable(ResourceTable* table, IArchiveWriter* writer) { + BigBuffer buffer(1024); + TableFlattener flattener(&buffer); + if (!flattener.consume(mContext, table)) { + return false; + } + + if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) { + if (writer->writeEntry(buffer)) { + if (writer->finishEntry()) { + return true; + } + } + } + + mContext->getDiagnostics()->error( + DiagMessage() << "failed to write resources.arsc to archive"); + 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; + } + + 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)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed to write"); + return false; + } + } + + if (!writer->finishEntry()) { + mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry"); + return false; + } + return true; + } + + bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate, + const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) { + if (!mOptions.generateJavaClassPath) { + return true; + } + + 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; + } + + file::appendPath(&outPath, "R.java"); + + std::ofstream fout(outPath, std::ofstream::binary); + if (!fout) { + mContext->getDiagnostics()->error( + DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); + return false; + } + + JavaClassGenerator generator(mContext, table, javaOptions); + if (!generator.generate(packageNameToGenerate, outPackage, &fout)) { + mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError()); + return false; + } + + if (!fout) { + mContext->getDiagnostics()->error( + DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); + } + return true; + } + + bool writeManifestJavaFile(xml::XmlResource* manifestXml) { + if (!mOptions.generateJavaClassPath) { + return true; + } + + std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass( + mContext->getDiagnostics(), manifestXml); + + if (!manifestClass) { + // Something bad happened, but we already logged it, so exit. + return false; + } + + if (manifestClass->empty()) { + // Empty Manifest class, no need to generate it. + return true; + } + + // Add any JavaDoc annotations to the generated class. + for (const std::string& annotation : mOptions.javadocAnnotations) { + std::string properAnnotation = "@"; + properAnnotation += annotation; + manifestClass->getCommentBuilder()->appendComment(properAnnotation); + } + + const std::string packageUtf8 = util::utf16ToUtf8(mContext->getCompilationPackage()); + + std::string outPath = mOptions.generateJavaClassPath.value(); + file::appendPath(&outPath, file::packageToPath(packageUtf8)); + + if (!file::mkdirs(outPath)) { + mContext->getDiagnostics()->error( + DiagMessage() << "failed to create directory '" << outPath << "'"); + return false; + } + + file::appendPath(&outPath, "Manifest.java"); + + std::ofstream fout(outPath, std::ofstream::binary); + if (!fout) { + mContext->getDiagnostics()->error( + DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); + return false; + } + + if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) { + mContext->getDiagnostics()->error( + DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); + return false; + } + return true; + } + + bool writeProguardFile(const proguard::KeepSet& keepSet) { + if (!mOptions.generateProguardRulesPath) { + return true; + } + + 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; + } + + proguard::writeKeepSet(&fout, keepSet); + if (!fout) { + mContext->getDiagnostics()->error( + DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); + return false; + } + return true; + } + + 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()); + } + + std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) { + io::IFile* file = collection->findFile("resources.arsc.flat"); + if (!file) { + return {}; + } + + std::unique_ptr<io::IData> data = file->openAsData(); + return loadTableFromPb(file->getSource(), data->data(), data->size(), + mContext->getDiagnostics()); + } + + bool mergeStaticLibrary(const std::string& input, bool override) { + if (mContext->verbose()) { + mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input); + } + + std::string errorStr; + std::unique_ptr<io::ZipFileCollection> collection = + io::ZipFileCollection::create(input, &errorStr); + if (!collection) { + mContext->getDiagnostics()->error(DiagMessage(input) << errorStr); + return false; + } + + std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get()); + if (!table) { + mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library"); + return false; + } + + ResourceTablePackage* pkg = table->findPackageById(0x7f); + if (!pkg) { + mContext->getDiagnostics()->error(DiagMessage(input) + << "static library has no package"); + return false; + } + + bool result; + if (mOptions.noStaticLibPackages) { + // Merge all resources as if they were in the compilation package. This is the old + // behaviour 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()) { + mOptions.extraJavaPackages.insert(pkg->name); + } + + pkg->name = u""; + if (override) { + result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get()); + } else { + result = mTableMerger->merge(Source(input), table.get(), collection.get()); + } + + } 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()); + } + + if (!result) { + return false; + } + + // Make sure to move the collection into the set of IFileCollections. + mCollections.push_back(std::move(collection)); + return true; + } + + bool mergeResourceTable(io::IFile* file, bool override) { + if (mContext->verbose()) { + mContext->getDiagnostics()->note(DiagMessage() << "merging resource table " + << file->getSource()); + } + + std::unique_ptr<io::IData> data = file->openAsData(); + if (!data) { + mContext->getDiagnostics()->error(DiagMessage(file->getSource()) + << "failed to open file"); + return false; + } + + std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(), + data->data(), data->size(), + mContext->getDiagnostics()); + if (!table) { + return false; + } + + bool result = false; + if (override) { + result = mTableMerger->mergeOverlay(file->getSource(), table.get()); + } else { + result = mTableMerger->merge(file->getSource(), table.get()); + } + return result; + } + + 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 = mTableMerger->mergeFileOverlay(*fileDesc, file); + } else { + result = mTableMerger->mergeFile(*fileDesc, file); + } + + if (!result) { + return false; + } + + // Add the exports of this file to the table. + for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) { + if (exportedSymbol.name.package.empty()) { + exportedSymbol.name.package = mContext->getCompilationPackage(); + } + + ResourceNameRef resName = exportedSymbol.name; + + Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName( + exportedSymbol.name); + if (mangledName) { + resName = mangledName.value(); + } + + 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; + } + + /** + * 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); + } + + std::string errorStr; + std::unique_ptr<io::ZipFileCollection> collection = + io::ZipFileCollection::create(input, &errorStr); + if (!collection) { + mContext->getDiagnostics()->error(DiagMessage(input) << errorStr); + return false; + } + + bool error = false; + for (auto iter = collection->iterator(); iter->hasNext(); ) { + if (!mergeFile(iter->next(), override)) { + error = 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); + } + + 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; + } + + std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader( + src, data->data(), data->size(), mContext->getDiagnostics()); + if (resourceFile) { + return mergeCompiledFile(file, resourceFile.get(), override); + } + return false; + } + + // Ignore non .flat files. This could be classes.dex or something else that happens + // to be in an archive. + return true; + } + + 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; + } + + 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; + } + + if (!util::isJavaPackageName(mContext->getCompilationPackage())) { + mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) + << "invalid package name '" + << mContext->getCompilationPackage() + << "'"); + return 1; + } + + mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() }); + + if (mContext->getCompilationPackage() == u"android") { + mContext->setPackageId(0x01); + } else { + mContext->setPackageId(0x7f); + } + + if (!loadSymbolsFromIncludePaths()) { + return 1; + } + + TableMergerOptions tableMergerOptions; + tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay; + mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions); + + if (mContext->verbose()) { + mContext->getDiagnostics()->note( + DiagMessage() << "linking package '" << mContext->getCompilationPackage() + << "' with package ID " << std::hex + << (int) mContext->getPackageId()); + } + + + for (const std::string& input : inputFiles) { + if (!mergePath(input, false)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input"); + return 1; + } + } + + for (const std::string& input : mOptions.overlayFiles) { + if (!mergePath(input, true)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays"); + return 1; + } + } + + if (!verifyNoExternalPackages()) { + return 1; + } + + if (!mOptions.staticLib) { + PrivateAttributeMover mover; + if (!mover.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error( + DiagMessage() << "failed moving private attributes"); + return 1; + } + } + + 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 the names to mangle based on our source merge earlier. + mContext->setNameManglerPolicy(NameManglerPolicy{ + mContext->getCompilationPackage(), mTableMerger->getMergedPackages() }); + + // Add our table to the symbol table. + mContext->getExternalSymbols()->prependSource( + util::make_unique<ResourceTableSymbolSource>(&mFinalTable)); + + { + ReferenceLinker linker; + if (!linker.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed linking references"); + 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); + } + } + + proguard::KeepSet proguardKeepSet; + + std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(); + if (!archiveWriter) { + mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive"); + return 1; + } + + bool error = false; + { + ManifestFixer manifestFixer(mOptions.manifestFixerOptions); + if (!manifestFixer.consume(mContext, manifestXml.get())) { + error = true; + } + + // 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 (error) { + mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest"); + return 1; + } + + 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; + } + + if (!mOptions.noAutoVersion) { + AutoVersioner versioner; + if (!versioner.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles"); + return 1; + } + } + + 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; + } + } + + 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 (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. + + options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; + if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(), + outputPackage, options)) { + return 1; + } + + options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; + outputPackage = mOptions.privateSymbols.value(); + } + + if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) { + return 1; + } + + for (const std::u16string& extraPackage : mOptions.extraJavaPackages) { + if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) { + return 1; + } + } + } + + if (mOptions.generateProguardRulesPath) { + if (!writeProguardFile(proguardKeepSet)) { + return 1; + } + } + + if (mContext->verbose()) { + DebugPrintTableOptions debugPrintTableOptions; + debugPrintTableOptions.showSources = true; + Debug::printTable(&mFinalTable, debugPrintTableOptions); + } + return 0; + } + +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; + } + + // 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 (verbose) { + context.setVerbose(verbose); + } + + if (privateSymbolsPackage) { + options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value()); + } + + if (minSdkVersion) { + options.manifestFixerOptions.minSdkVersionDefault = + util::utf8ToUtf16(minSdkVersion.value()); + } + + if (targetSdkVersion) { + options.manifestFixerOptions.targetSdkVersionDefault = + util::utf8ToUtf16(targetSdkVersion.value()); + } + + if (renameManifestPackage) { + options.manifestFixerOptions.renameManifestPackage = + util::utf8ToUtf16(renameManifestPackage.value()); + } + + if (renameInstrumentationTargetPackage) { + options.manifestFixerOptions.renameInstrumentationTargetPackage = + util::utf8ToUtf16(renameInstrumentationTargetPackage.value()); + } + + if (versionCode) { + options.manifestFixerOptions.versionCodeDefault = util::utf8ToUtf16(versionCode.value()); + } + + if (versionName) { + options.manifestFixerOptions.versionNameDefault = util::utf8ToUtf16(versionName.value()); + } + + if (customJavaPackage) { + options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value()); + } + + // 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 (productList) { + for (StringPiece product : util::tokenize<char>(productList.value(), ',')) { + if (product != "" && product != "default") { + options.products.insert(product.toString()); + } + } + } + + 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); + } + } + + options.tableSplitterOptions.configFilter = &filter; + } + + if (preferredDensity) { + ConfigDescription preferredDensityConfig; + if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) { + context.getDiagnostics()->error(DiagMessage() << "invalid density '" + << preferredDensity.value() + << "' for --preferred-density option"); + return 1; + } + + // Clear the version that can be automatically added. + preferredDensityConfig.sdkVersion = 0; + + 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; + } + + // Turn off auto versioning for static-libs. + if (options.staticLib) { + options.noAutoVersion = true; + options.noVersionVectors = true; + } + + LinkCommand cmd(&context, options); + return cmd.run(argList); +} + +} // namespace aapt diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h new file mode 100644 index 000000000000..ec532aba465f --- /dev/null +++ b/tools/aapt2/link/Linkers.h @@ -0,0 +1,103 @@ +/* + * 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. + */ + +#ifndef AAPT_LINKER_LINKERS_H +#define AAPT_LINKER_LINKERS_H + +#include "Resource.h" +#include "process/IResourceTableConsumer.h" +#include "xml/XmlDom.h" + +#include <set> + +namespace aapt { + +class ResourceTable; +class ResourceEntry; +struct ConfigDescription; + +/** + * Defines the location in which a value exists. This determines visibility of other + * package's private symbols. + */ +struct CallSite { + ResourceNameRef resource; +}; + +/** + * 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); + +struct AutoVersioner : public IResourceTableConsumer { + bool consume(IAaptContext* context, ResourceTable* table) override; +}; + +struct XmlAutoVersioner : public IXmlResourceConsumer { + bool consume(IAaptContext* context, xml::XmlResource* resource) override; +}; + +/** + * 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' + * IDs. + * + * 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 + * unexpected types. + * + * 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; +}; + +/** + * 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; + } +}; + +} // namespace aapt + +#endif /* AAPT_LINKER_LINKERS_H */ diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp new file mode 100644 index 000000000000..953e87e104be --- /dev/null +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -0,0 +1,292 @@ +/* + * 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 "ResourceUtils.h" +#include "link/ManifestFixer.h" +#include "util/Util.h" +#include "xml/XmlActionExecutor.h" +#include "xml/XmlDom.h" + +namespace aapt { + +/** + * This is how PackageManager builds class names from AndroidManifest.xml entries. + */ +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::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, 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, u"name")) { + return nameIsJavaClassName(el, attr, diag); + } + diag->error(DiagMessage(el->lineNumber) + << "<" << el->name << "> is missing attribute 'android:name'"); + return false; +} + +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; + } + 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; + } + } + + if (mOptions.renameInstrumentationTargetPackage) { + if (!util::isJavaPackageName(mOptions.renameInstrumentationTargetPackage.value())) { + diag->error(DiagMessage() << "invalid instrumentation target package override '" + << mOptions.renameInstrumentationTargetPackage.value() << "'"); + return false; + } + } + + // 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 (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() }); + } + + 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; + }); + + // Instrumentation actions. + manifestAction[u"instrumentation"].action([&](xml::Element* el) -> bool { + if (!mOptions.renameInstrumentationTargetPackage) { + return true; + } + + 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"uses-library"]; + 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); + + // 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; +} + +class FullyQualifiedClassNameVisitor : public xml::Visitor { +public: + using xml::Visitor::visit; + + FullyQualifiedClassNameVisitor(const StringPiece16& package) : mPackage(package) { + } + + 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()); + } + } + + // Super implementation to iterate over the children. + xml::Visitor::visit(el); + } + +private: + StringPiece16 mPackage; +}; + +static bool renameManifestPackage(const StringPiece16& packageOverride, xml::Element* manifestEl) { + xml::Attribute* attr = manifestEl->findAttribute({}, u"package"); + + // We've already verified that the manifest element is present, with a package name specified. + assert(attr); + + std::u16string originalPackage = std::move(attr->value); + attr->value = packageOverride.toString(); + + FullyQualifiedClassNameVisitor visitor(originalPackage); + manifestEl->accept(&visitor); + 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; + } + + 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)); + } + + xml::XmlActionExecutor executor; + if (!buildRules(&executor, context->getDiagnostics())) { + return false; + } + + if (!executor.execute(xml::XmlActionExecutorPolicy::Whitelist, context->getDiagnostics(), + doc)) { + return false; + } + + 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; + } + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h new file mode 100644 index 000000000000..4d9356a933c2 --- /dev/null +++ b/tools/aapt2/link/ManifestFixer.h @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#ifndef AAPT_LINK_MANIFESTFIXER_H +#define AAPT_LINK_MANIFESTFIXER_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; +}; + +/** + * Verifies that the manifest is correctly formed and inserts defaults + * where specified with ManifestFixerOptions. + */ +class ManifestFixer : public IXmlResourceConsumer { +public: + ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) { + } + + bool consume(IAaptContext* context, xml::XmlResource* doc) override; + +private: + bool buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag); + + ManifestFixerOptions mOptions; +}; + +} // namespace aapt + +#endif /* AAPT_LINK_MANIFESTFIXER_H */ diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp new file mode 100644 index 000000000000..f993720b9566 --- /dev/null +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -0,0 +1,256 @@ +/* + * 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 "link/ManifestFixer.h" +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.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 {}; + } +}; + +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>")); +} + +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\" />")); +} + +TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { + ManifestFixerOptions options = { std::u16string(u"8"), std::u16string(u"22") }; + + 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 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 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 xmlns:android="http://schemas.android.com/apk/res/android" + package="android" />)EOF", options); + ASSERT_NE(nullptr, doc); + + el = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, el); + el = el->findChild({}, u"uses-sdk"); + ASSERT_NE(nullptr, el); + attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(u"8", attr->value); + attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(u"22", attr->value); +} + +TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) { + ManifestFixerOptions options; + options.renameManifestPackage = std::u16string(u"com.android"); + + 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); + + xml::Element* manifestEl = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, manifestEl); + + xml::Attribute* attr = nullptr; + + attr = manifestEl->findAttribute({}, u"package"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::u16string(u"com.android"), attr->value); + + xml::Element* applicationEl = manifestEl->findChild({}, u"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({}, u"text"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::u16string(u"hello"), attr->value); + + xml::Element* el; + el = applicationEl->findChild({}, u"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); + + el = applicationEl->findChild({}, u"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); +} + +TEST_F(ManifestFixerTest, RenameManifestInstrumentationPackageAndFullyQualifyTarget) { + ManifestFixerOptions options; + options.renameInstrumentationTargetPackage = std::u16string(u"com.android"); + + 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); + + xml::Element* manifestEl = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, manifestEl); + + xml::Element* instrumentationEl = manifestEl->findChild({}, u"instrumentation"); + ASSERT_NE(nullptr, instrumentationEl); + + xml::Attribute* attr = instrumentationEl->findAttribute(xml::kSchemaAndroid, u"targetPackage"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::u16string(u"com.android"), attr->value); +} + +TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) { + ManifestFixerOptions options; + options.versionNameDefault = std::u16string(u"Beta"); + options.versionCodeDefault = std::u16string(u"0x10000000"); + + 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); + + xml::Element* manifestEl = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, manifestEl); + + xml::Attribute* attr = manifestEl->findAttribute(xml::kSchemaAndroid, u"versionName"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::u16string(u"Beta"), attr->value); + + attr = manifestEl->findAttribute(xml::kSchemaAndroid, u"versionCode"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::u16string(u"0x10000000"), attr->value); +} + +} // namespace aapt diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp new file mode 100644 index 000000000000..3c8af4f81ffe --- /dev/null +++ b/tools/aapt2/link/PrivateAttributeMover.cpp @@ -0,0 +1,80 @@ +/* + * 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 "ResourceTable.h" +#include "link/Linkers.h" + +#include <algorithm> +#include <iterator> + +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; + } + + *result = std::move(*newEnd); + + auto first = newEnd; + ++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; + } + } + + inputContainer.erase(newEnd, last); + return result; +} + +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; + } + + ResourceTableType* privAttrType = package->findOrCreateType(ResourceType::kAttrPrivate); + assert(privAttrType->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; +} + +} // namespace aapt diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp new file mode 100644 index 000000000000..dbe0c92253c1 --- /dev/null +++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp @@ -0,0 +1,78 @@ +/* + * 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 "link/Linkers.h" +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.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); +} + +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(); + + 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); + + type = package->findType(ResourceType::kAttrPrivate); + ASSERT_EQ(type, nullptr); +} + +} // namespace aapt diff --git a/tools/aapt2/link/ProductFilter.cpp b/tools/aapt2/link/ProductFilter.cpp new file mode 100644 index 000000000000..8784e891b293 --- /dev/null +++ b/tools/aapt2/link/ProductFilter.cpp @@ -0,0 +1,118 @@ +/* + * 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/ProductFilter.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; + } + + // 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); + + ResourceConfigValue* previouslyDefaultConfigValue = defaultProductIter->get(); + diag->note(DiagMessage(previouslyDefaultConfigValue->value->getSource()) + << "default product also defined here"); + return end; + } + + // Mark the default. + defaultProductIter = iter; + } + } + + if (defaultProductIter == end) { + diag->error(DiagMessage() << "no default product defined for resource " << name); + return end; + } + + if (selectedProductIter == end) { + selectedProductIter = defaultProductIter; + } + return selectedProductIter; +} + +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); + } + } + } + return !error; +} + +} // namespace aapt diff --git a/tools/aapt2/link/ProductFilter.h b/tools/aapt2/link/ProductFilter.h new file mode 100644 index 000000000000..d2edd38289dc --- /dev/null +++ b/tools/aapt2/link/ProductFilter.h @@ -0,0 +1,49 @@ +/* + * 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 new file mode 100644 index 000000000000..f4f756ae4519 --- /dev/null +++ b/tools/aapt2/link/ProductFilter_test.cpp @@ -0,0 +1,136 @@ +/* + * 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/ProductFilter.h" +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.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")); +} + +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")); +} + +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)); +} + +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)); +} + +} // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp new file mode 100644 index 000000000000..66eb0df048db --- /dev/null +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -0,0 +1,334 @@ +/* + * 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 "Diagnostics.h" +#include "ReferenceLinker.h" +#include "ResourceTable.h" +#include "ResourceUtils.h" +#include "ResourceValues.h" +#include "ValueVisitor.h" +#include "link/Linkers.h" +#include "process/IResourceTableConsumer.h" +#include "process/SymbolTable.h" +#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 + * 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) { + } + + 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()); + } + + 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; + } + } + } + + bool hasError() { + return mError; + } + +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; + } +}; + +} // namespace + +/** + * 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()); + } else { + return nullptr; + } +} + +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::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; +} + +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 {}; + } + + if (!symbol->attribute) { + if (outError) *outError = "is not an attribute"; + return {}; + } + return xml::AaptAttribute{ symbol->id, *symbol->attribute }; +} + +void ReferenceLinker::writeResourceName(DiagMessage* outMsg, const Reference& orig, + const Reference& transformed) { + assert(outMsg); + + if (orig.name) { + *outMsg << orig.name.value(); + if (transformed.name.value() != orig.name.value()) { + *outMsg << " (aka " << transformed.name.value() << ")"; + } + } else { + *outMsg << 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; +} + +namespace { + +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 */ }; + } + 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; + } + } + } + } + return !error; +} + +} // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h new file mode 100644 index 000000000000..7993aaf39e47 --- /dev/null +++ b/tools/aapt2/link/ReferenceLinker.h @@ -0,0 +1,106 @@ +/* + * 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. + */ + +#ifndef AAPT_LINKER_REFERENCELINKER_H +#define AAPT_LINKER_REFERENCELINKER_H + +#include "Resource.h" +#include "ResourceValues.h" +#include "ValueVisitor.h" +#include "link/Linkers.h" +#include "process/IResourceTableConsumer.h" +#include "process/SymbolTable.h" +#include "xml/XmlDom.h" + +#include <cassert> + +namespace aapt { + +/** + * 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. + */ +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; +}; + +} // namespace aapt + +#endif /* AAPT_LINKER_REFERENCELINKER_H */ diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp new file mode 100644 index 000000000000..76b23098a35c --- /dev/null +++ b/tools/aapt2/link/ReferenceLinker_test.cpp @@ -0,0 +1,227 @@ +/* + * 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 "link/ReferenceLinker.h" +#include "test/Test.h" + +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)); +} + +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"); + 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)); +} + +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())); +} + +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())); +} + +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())); +} + +} // namespace aapt diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp new file mode 100644 index 000000000000..7471e15db41a --- /dev/null +++ b/tools/aapt2/link/TableMerger.cpp @@ -0,0 +1,321 @@ +/* + * 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 "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"); +} + +bool TableMerger::merge(const Source& src, ResourceTable* table, + io::IFileCollection* collection) { + return mergeImpl(src, table, collection, false /* overlay */, true /* allow new */); +} + +bool TableMerger::mergeOverlay(const Source& src, ResourceTable* table, + io::IFileCollection* collection) { + return mergeImpl(src, table, collection, true /* overlay */, mOptions.autoAddOverlay); +} + +/** + * 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; + }; + } + + // 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); + } + } + 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 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; + } + + newFile->file = f; + return true; + }; + + error |= !doMerge(src, table, package.get(), + mangle, false /* overlay */, true /* allow new */, callback); + } + return !error; +} + +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; + } + + dstType->symbolStatus = std::move(srcType->symbolStatus); + dstType->id = srcType->id; + } + + 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); + } + } + + 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; + } + + 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); + } + } + + 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)); + } + } + } + } + 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)); +} + +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::mergeFile(const ResourceFile& fileDesc, io::IFile* file) { + return mergeFileImpl(fileDesc, file, false /* overlay */); +} + +bool TableMerger::mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file) { + return mergeFileImpl(fileDesc, file, true /* overlay */); +} + +} // namespace aapt diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h new file mode 100644 index 000000000000..80c2a5e69b66 --- /dev/null +++ b/tools/aapt2/link/TableMerger.h @@ -0,0 +1,126 @@ +/* + * 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. + */ + +#ifndef AAPT_TABLEMERGER_H +#define AAPT_TABLEMERGER_H + +#include "Resource.h" +#include "ResourceTable.h" +#include "ResourceValues.h" +#include "filter/ConfigFilter.h" +#include "io/File.h" +#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; +}; + +/** + * 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 + * 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 + * 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); +}; + +} // namespace aapt + +#endif /* AAPT_TABLEMERGER_H */ diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp new file mode 100644 index 000000000000..4a80d3f48777 --- /dev/null +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -0,0 +1,220 @@ +/* + * 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 "filter/ConfigFilter.h" +#include "io/FileSystem.h" +#include "link/TableMerger.h" +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.h> + +namespace aapt { + +struct TableMergerTest : public ::testing::Test { + std::unique_ptr<IAaptContext> mContext; + + void SetUp() override { + mContext = test::ContextBuilder() + // We are compiling this package. + .setCompilationPackage(u"com.app.a") + + // 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" } }) + + .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"))); +} + +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); +} + +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)); +} + +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); +} + +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); +} + +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())); +} + +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())); +} + +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())); +} + +} // namespace aapt diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp new file mode 100644 index 000000000000..568bc74895c7 --- /dev/null +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -0,0 +1,173 @@ +/* + * 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 "Diagnostics.h" +#include "ResourceUtils.h" +#include "SdkConstants.h" +#include "link/Linkers.h" +#include "link/ReferenceLinker.h" +#include "process/IResourceTableConsumer.h" +#include "process/SymbolTable.h" +#include "util/Util.h" +#include "xml/XmlDom.h" + +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 + * 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) { + } + + void visit(Reference* ref) override { + if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mDecls, mCallSite)) { + mError = true; + } + } + + bool hasError() const { + return mError; + } + +private: + IAaptContext* mContext; + SymbolTable* mSymbols; + xml::IPackageDeclStack* mDecls; + CallSite* mCallSite; + bool mError; +}; + +/** + * 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); + } + + if (attr.compiledValue) { + // With a compiledValue, we must resolve the reference and assign it an ID. + attr.compiledValue->setSource(source); + attr.compiledValue->accept(&mReferenceVisitor); + } + } + + // Call the super implementation. + xml::PackageAwareVisitor::visit(el); + } + + bool hasError() { + return mError || mReferenceVisitor.hasError(); + } + +private: + IAaptContext* mContext; + SymbolTable* mSymbols; + Source mSource; + std::set<int>* mSdkLevelsFound; + CallSite* mCallSite; + ReferenceVisitor mReferenceVisitor; + bool mError = false; +}; + +} // namespace + +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; +} + +} // namespace aapt diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp new file mode 100644 index 000000000000..af9098b9e483 --- /dev/null +++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp @@ -0,0 +1,260 @@ +/* + * 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 <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; +}; + +TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) { + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.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); +} + +TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) { + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.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())); +} + +TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) { + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.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())); +} + +TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) { + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.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); +} + +TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) { + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.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); +} + +TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) { + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.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)); +} + +TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.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)); +} + +TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) { + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.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)); +} + +} // namespace aapt diff --git a/tools/aapt2/process.dot b/tools/aapt2/process.dot deleted file mode 100644 index 4741952d2332..000000000000 --- a/tools/aapt2/process.dot +++ /dev/null @@ -1,108 +0,0 @@ -digraph aapt { - out_package [label="out/default/package.apk"]; - out_fr_package [label="out/fr/package.apk"]; - out_table_aligned [label="out/default/resources-aligned.arsc"]; - out_table_fr_aligned [label="out/fr/resources-aligned.arsc"]; - out_res_layout_main_xml [label="out/res/layout/main.xml"]; - out_res_layout_v21_main_xml [color=red,label="out/res/layout-v21/main.xml"]; - out_res_layout_fr_main_xml [label="out/res/layout-fr/main.xml"]; - out_res_layout_fr_v21_main_xml [color=red,label="out/res/layout-fr-v21/main.xml"]; - out_table [label="out/default/resources.arsc"]; - out_fr_table [label="out/fr/resources.arsc"]; - out_values_table [label="out/values/resources.arsc"]; - out_layout_table [label="out/layout/resources.arsc"]; - out_values_fr_table [label="out/values-fr/resources.arsc"]; - out_layout_fr_table [label="out/layout-fr/resources.arsc"]; - res_values_strings_xml [label="res/values/strings.xml"]; - res_values_attrs_xml [label="res/values/attrs.xml"]; - res_layout_main_xml [label="res/layout/main.xml"]; - res_layout_fr_main_xml [label="res/layout-fr/main.xml"]; - res_values_fr_strings_xml [label="res/values-fr/strings.xml"]; - - lib_apk_resources_arsc [label="lib.apk:resources.arsc",color=green]; - lib_apk_res_layout_main_xml [label="lib.apk:res/layout/main.xml",color=green]; - lib_apk_res_drawable_icon_png [label="lib.apk:res/drawable/icon.png",color=green]; - lib_apk_fr_res_layout_main_xml [label="lib.apk:res/layout-fr/main.xml",color=green]; - lib_apk_fr_res_drawable_icon_png [label="lib.apk:res/drawable-fr/icon.png",color=green]; - out_res_layout_lib_main_xml [label="out/res/layout/lib-main.xml"]; - - out_package -> package_default; - out_fr_package -> package_fr; - - package_default [shape=box,label="Assemble",color=blue]; - package_default -> out_table_aligned; - package_default -> out_res_layout_main_xml; - package_default -> out_res_layout_v21_main_xml [color=red]; - package_default -> out_res_layout_lib_main_xml; - - package_fr [shape=box,label="Assemble",color=blue]; - package_fr -> out_table_fr_aligned; - package_fr -> out_res_layout_fr_main_xml; - package_fr -> out_res_layout_fr_v21_main_xml [color=red]; - - out_table_aligned -> align_tables; - out_table_fr_aligned -> align_tables; - - align_tables [shape=box,label="Align",color=blue]; - align_tables -> out_table; - align_tables -> out_fr_table; - - out_table -> link_tables; - - link_tables [shape=box,label="Link",color=blue]; - link_tables -> out_values_table; - link_tables -> out_layout_table; - link_tables -> lib_apk_resources_arsc; - - out_values_table -> compile_values; - - compile_values [shape=box,label="Collect",color=blue]; - compile_values -> res_values_strings_xml; - compile_values -> res_values_attrs_xml; - - out_layout_table -> collect_xml; - - collect_xml [shape=box,label="Collect",color=blue]; - collect_xml -> res_layout_main_xml; - - out_fr_table -> link_fr_tables; - - link_fr_tables [shape=box,label="Link",color=blue]; - link_fr_tables -> out_values_fr_table; - link_fr_tables -> out_layout_fr_table; - link_fr_tables -> lib_apk_resources_arsc; - - out_values_fr_table -> compile_values_fr; - - compile_values_fr [shape=box,label="Collect",color=blue]; - compile_values_fr -> res_values_fr_strings_xml; - - out_layout_fr_table -> collect_xml_fr; - - collect_xml_fr [shape=box,label="Collect",color=blue]; - collect_xml_fr -> res_layout_fr_main_xml; - - compile_res_layout_main_xml [shape=box,label="Compile",color=blue]; - - out_res_layout_main_xml -> compile_res_layout_main_xml; - - out_res_layout_v21_main_xml -> compile_res_layout_main_xml [color=red]; - - compile_res_layout_main_xml -> res_layout_main_xml; - compile_res_layout_main_xml -> out_table_aligned; - - compile_res_layout_fr_main_xml [shape=box,label="Compile",color=blue]; - - out_res_layout_fr_main_xml -> compile_res_layout_fr_main_xml; - - out_res_layout_fr_v21_main_xml -> compile_res_layout_fr_main_xml [color=red]; - - compile_res_layout_fr_main_xml -> res_layout_fr_main_xml; - compile_res_layout_fr_main_xml -> out_table_fr_aligned; - - out_res_layout_lib_main_xml -> compile_res_layout_lib_main_xml; - - compile_res_layout_lib_main_xml [shape=box,label="Compile",color=blue]; - compile_res_layout_lib_main_xml -> out_table_aligned; - compile_res_layout_lib_main_xml -> lib_apk_res_layout_main_xml; -} diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h new file mode 100644 index 000000000000..9affb836340c --- /dev/null +++ b/tools/aapt2/process/IResourceTableConsumer.h @@ -0,0 +1,64 @@ +/* + * 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. + */ + +#ifndef AAPT_PROCESS_IRESOURCETABLECONSUMER_H +#define AAPT_PROCESS_IRESOURCETABLECONSUMER_H + +#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 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; +}; + +struct IResourceTableConsumer { + virtual ~IResourceTableConsumer() = default; + + virtual bool consume(IAaptContext* context, ResourceTable* table) = 0; +}; + +namespace xml { +struct XmlResource; +} + +struct IXmlResourceConsumer { + virtual ~IXmlResourceConsumer() = default; + + virtual bool consume(IAaptContext* context, xml::XmlResource* resource) = 0; +}; + +} // namespace aapt + +#endif /* AAPT_PROCESS_IRESOURCETABLECONSUMER_H */ diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp new file mode 100644 index 000000000000..eaaf06f7e530 --- /dev/null +++ b/tools/aapt2/process/SymbolTable.cpp @@ -0,0 +1,310 @@ +/* + * 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 "ConfigDescription.h" +#include "Resource.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)); + + // 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)); + + // We must clear the cache in case we did a lookup before adding this resource. + mCache.clear(); +} + +const SymbolTable::Symbol* SymbolTable::findByName(const ResourceName& name) { + if (const std::shared_ptr<Symbol>& s = mCache.get(name)) { + return s.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; +} + +const SymbolTable::Symbol* SymbolTable::findById(ResourceId id) { + if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) { + return s.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; +} + +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; +} + +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)); + } + return {}; + } + + ResourceTable::SearchResult sr = result.value(); + + std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>(); + symbol->isPublic = (sr.entry->symbolStatus.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* 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 {}; + } + } + } + return symbol; +} + +bool AssetManagerSymbolSource::addAssetPath(const StringPiece& path) { + int32_t cookie = 0; + return mAssets.addAssetPath(android::String8(path.data(), path.size()), &cookie); +} + +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->typeMask = entry[i].map.value.data; + break; + } + } + + 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)); + } + } + 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> s; + if (name.type == ResourceType::kAttr) { + s = lookupAttributeInTable(table, resId); + } else { + s = util::make_unique<SymbolTable::Symbol>(); + s->id = resId; + } + + if (s) { + s->isPublic = (typeSpecFlags & 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; +} + +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 {}; + } + + uint32_t typeSpecFlags = 0; + table.getResourceFlags(id.id, &typeSpecFlags); + + 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; + } + + if (s) { + s->isPublic = (typeSpecFlags & 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 {}; +} + +} // namespace aapt diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h new file mode 100644 index 000000000000..e684bb06f1f5 --- /dev/null +++ b/tools/aapt2/process/SymbolTable.h @@ -0,0 +1,172 @@ +/* + * 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. + */ + +#ifndef AAPT_PROCESS_SYMBOLTABLE_H +#define AAPT_PROCESS_SYMBOLTABLE_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; +} + +inline android::hash_t hash_type(const ResourceId& id) { + return android::hash_type(id.id); +} + +class ISymbolSource; + +class SymbolTable { +public: + struct Symbol { + Symbol() : Symbol(Maybe<ResourceId>{}) { + } + + 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, bool pub) : + id(i), attribute(attr), isPublic(pub) { + } + + 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; + }; + + SymbolTable() : mCache(200), mIdCache(200) { + } + + 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); + + /** + * 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; + + // 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; + + DISALLOW_COPY_AND_ASSIGN(SymbolTable); +}; + +/** + * 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 {}; + } +}; + +/** + * Exposes the resources in a ResourceTable as symbols for SymbolTable. + * Instances of this class must outlive the encompassed ResourceTable. + * Lookups by ID are ignored. + */ +class ResourceTableSymbolSource : public ISymbolSource { +public: + explicit ResourceTableSymbolSource(ResourceTable* table) : mTable(table) { + } + + std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) override; + + std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override { + return {}; + } + +private: + ResourceTable* mTable; + + DISALLOW_COPY_AND_ASSIGN(ResourceTableSymbolSource); +}; + +class AssetManagerSymbolSource : public ISymbolSource { +public: + AssetManagerSymbolSource() = default; + + 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; + +private: + android::AssetManager mAssets; + + DISALLOW_COPY_AND_ASSIGN(AssetManagerSymbolSource); +}; + +} // namespace aapt + +#endif /* AAPT_PROCESS_SYMBOLTABLE_H */ diff --git a/tools/aapt2/process/SymbolTable_test.cpp b/tools/aapt2/process/SymbolTable_test.cpp new file mode 100644 index 000000000000..34f31be3d0db --- /dev/null +++ b/tools/aapt2/process/SymbolTable_test.cpp @@ -0,0 +1,53 @@ +/* + * 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 "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); +} + +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); +} + +} // namespace aapt diff --git a/tools/aapt2/proto/ProtoHelpers.cpp b/tools/aapt2/proto/ProtoHelpers.cpp new file mode 100644 index 000000000000..99981c52e26f --- /dev/null +++ b/tools/aapt2/proto/ProtoHelpers.cpp @@ -0,0 +1,137 @@ +/* + * 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 "proto/ProtoHelpers.h" + +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 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 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(); + } + + if (pbSource.has_line_no()) { + outSource->line = static_cast<size_t>(pbSource.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; +} + +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; +} + +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)); +} + +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; +} + +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; +} + +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; +} + +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 diff --git a/tools/aapt2/proto/ProtoHelpers.h b/tools/aapt2/proto/ProtoHelpers.h new file mode 100644 index 000000000000..02e67f17c80c --- /dev/null +++ b/tools/aapt2/proto/ProtoHelpers.h @@ -0,0 +1,52 @@ +/* + * 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_PROTO_PROTOHELPERS_H +#define AAPT_PROTO_PROTOHELPERS_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 serializeSourceToPb(const Source& source, StringPool* srcPool, pb::Source* outPbSource); +void deserializeSourceFromPb(const pb::Source& pbSource, const android::ResStringPool& srcPool, + Source* outSource); + +pb::SymbolStatus_Visibility serializeVisibilityToPb(SymbolState state); +SymbolState deserializeVisibilityFromPb(pb::SymbolStatus_Visibility pbVisibility); + +void serializeConfig(const ConfigDescription& config, pb::ConfigDescription* outPbConfig); +bool deserializeConfigDescriptionFromPb(const pb::ConfigDescription& pbConfig, + ConfigDescription* outConfig); + +pb::Reference_Type serializeReferenceTypeToPb(Reference::Type type); +Reference::Type deserializeReferenceTypeFromPb(pb::Reference_Type pbType); + +pb::Plural_Arity serializePluralEnumToPb(size_t pluralIdx); +size_t deserializePluralEnumFromPb(pb::Plural_Arity arity); + +} // namespace aapt + +#endif /* AAPT_PROTO_PROTOHELPERS_H */ diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h new file mode 100644 index 000000000000..6e224ab00af4 --- /dev/null +++ b/tools/aapt2/proto/ProtoSerialize.h @@ -0,0 +1,77 @@ +/* + * 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_FLATTEN_TABLEPROTOSERIALIZER_H +#define AAPT_FLATTEN_TABLEPROTOSERIALIZER_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 google::protobuf::io::CopyingOutputStream { +public: + CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out, + pb::CompiledFile* pbFile); + bool Write(const void* data, int size) override; + bool Finish(); + +private: + bool ensureFileWritten(); + + google::protobuf::io::CodedOutputStream mOut; + pb::CompiledFile* mPbFile; + + DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream); +}; + +class CompiledFileInputStream { +public: + CompiledFileInputStream(const void* data, size_t size); + + const pb::CompiledFile* CompiledFile(); + + const void* data(); + size_t size(); + +private: + google::protobuf::io::CodedInputStream mIn; + std::unique_ptr<pb::CompiledFile> mPbFile; + const uint8_t* mData; + size_t mSize; + + DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream); +}; + +} // namespace aapt + +#endif /* AAPT_FLATTEN_TABLEPROTOSERIALIZER_H */ diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp new file mode 100644 index 000000000000..82e4fb0146ab --- /dev/null +++ b/tools/aapt2/proto/TableProtoDeserializer.cpp @@ -0,0 +1,520 @@ +/* + * 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 "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); + } + + 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(); + } + } + +private: + const std::map<ResourceId, ResourceNameRef>* mMapping; +}; + +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: + 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; + + 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 {}; + } + + 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 {}; + } + } + } + } + + 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"); + } + return {}; + } + + std::unique_ptr<Value> deserializeValueFromPb(const pb::Value& pbValue, + const ConfigDescription& config, + StringPool* pool) { + const bool isWeak = pbValue.has_weak() ? pbValue.weak() : false; + + std::unique_ptr<Value> value; + if (pbValue.has_item()) { + value = deserializeItemFromPb(pbValue.item(), config, pool); + if (!value) { + 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(); + 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"); + return {}; + } + + assert(value && "forgot to set value"); + + value->setWeak(isWeak); + deserializeItemCommon(pbValue, value.get()); + return value; + } + + bool deserializeReferenceFromPb(const pb::Reference& pbRef, Reference* outRef) { + outRef->referenceType = deserializeReferenceTypeFromPb(pbRef.type()); + outRef->privateReference = pbRef.private_(); + + if (!pbRef.has_id() && !pbRef.has_symbol_idx()) { + return false; + } + + if (pbRef.has_id()) { + outRef->id = ResourceId(pbRef.id()); + } + + 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; + } + + outRef->name = nameRef.toResourceName(); + } + return true; + } + + 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)); + } + + if (pbItem.has_comment()) { + outValue->setComment(util::utf8ToUtf16(pbItem.comment())); + } + } + +private: + const android::ResStringPool* mValuePool; + const android::ResStringPool* mSourcePool; + const android::ResStringPool* mSymbolPool; + const Source mSource; + IDiagnostics* mDiag; +}; + +} // namespace + +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; + + std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); + + if (!pbTable.has_string_pool()) { + diag->error(DiagMessage(source) << "no string pool found"); + return {}; + } + + 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 {}; + } + + 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 {}; + } + } + + 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 {}; + } + } + + PackagePbDeserializer packagePbDeserializer(&valuePool, &sourcePool, &symbolPool, source, diag); + for (const pb::Package& pbPackage : pbTable.packages()) { + if (!packagePbDeserializer.deserializeFromPb(pbPackage, table.get())) { + return {}; + } + } + 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; + + // 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() }); + } + 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); + } + return mPbFile.get(); +} + +const void* CompiledFileInputStream::data() { + if (!mPbFile) { + if (!CompiledFile()) { + return nullptr; + } + } + return mData; +} + +size_t CompiledFileInputStream::size() { + if (!mPbFile) { + if (!CompiledFile()) { + return 0; + } + } + return mSize; +} + +} // namespace aapt diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp new file mode 100644 index 000000000000..5d1b72b0ebbd --- /dev/null +++ b/tools/aapt2/proto/TableProtoSerializer.cpp @@ -0,0 +1,322 @@ +/* + * 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 "Resource.h" +#include "ResourceTable.h" +#include "StringPool.h" +#include "ValueVisitor.h" +#include "proto/ProtoHelpers.h" +#include "proto/ProtoSerialize.h" +#include "util/BigBuffer.h" + +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) { + } + + void visit(Reference* ref) override { + serializeReferenceToPb(*ref, getPbItem()->mutable_ref()); + } + + void visit(String* str) override { + getPbItem()->mutable_str()->set_idx(str->value.getIndex()); + } + + void visit(StyledString* str) override { + getPbItem()->mutable_str()->set_idx(str->value.getIndex()); + } + + void visit(FileReference* file) override { + getPbItem()->mutable_file()->set_path_idx(file->path.getIndex()); + } + + 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(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(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()); + + pb::Item* pbItem = pbEntry->mutable_item(); + serializeItemCommonToPb(entry.key, pbEntry); + PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbItem); + entry.value->accept(&subVisitor); + } + } + + 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()); + } + } + + 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 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); + } + } + +private: + pb::Item* getPbItem() { + if (mOutPbValue) { + return mOutPbValue->mutable_item(); + } + return mOutPbItem; + } + + 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; +}; + +} // 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 { + int diff = a.context.priority - b.context.priority; + if (diff < 0) return true; + if (diff > 0) return false; + diff = a.context.config.compare(b.context.config); + if (diff < 0) return true; + if (diff > 0) return false; + return a.value < b.value; + }); + table->stringPool.prune(); + + std::unique_ptr<pb::ResourceTable> pbTable = util::make_unique<pb::ResourceTable>(); + serializeStringPoolToPb(table->stringPool, pbTable->mutable_string_pool()); + + StringPool sourcePool, symbolPool; + + for (auto& package : table->packages) { + pb::Package* pbPackage = pbTable->add_packages(); + if (package->id) { + pbPackage->set_package_id(package->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); + } + } + } + } + + serializeStringPoolToPb(sourcePool, pbTable->mutable_source_pool()); + serializeStringPoolToPb(symbolPool, pbTable->mutable_symbol_pool()); + return pbTable; +} + +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()); + + 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(google::protobuf::io::ZeroCopyOutputStream* out, + pb::CompiledFile* pbFile) : + mOut(out), mPbFile(pbFile) { +} + +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(); +} + +bool CompiledFileOutputStream::Write(const void* data, int size) { + if (!ensureFileWritten()) { + return false; + } + mOut.WriteRaw(data, size); + return !mOut.HadError(); +} + +bool CompiledFileOutputStream::Finish() { + return ensureFileWritten(); +} + +} // namespace aapt diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp new file mode 100644 index 000000000000..dd995d858d77 --- /dev/null +++ b/tools/aapt2/proto/TableProtoSerializer_test.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 "ResourceTable.h" +#include "proto/ProtoSerialize.h" +#include "test/Builders.h" +#include "test/Common.h" +#include "test/Context.h" + +#include <gtest/gtest.h> + +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()); +} + +TEST(TableProtoSerializer, SerializeFileHeader) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + + 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 }); + + const std::string expectedData = "1234"; + + std::unique_ptr<pb::CompiledFile> pbFile = serializeCompiledFileToPb(f); + + std::string outputStr; + { + google::protobuf::io::StringOutputStream outStream(&outputStr); + CompiledFileOutputStream outFileStream(&outStream, pbFile.get()); + + ASSERT_TRUE(outFileStream.Write(expectedData.data(), expectedData.size())); + ASSERT_TRUE(outFileStream.Finish()); + } + + CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size()); + const pb::CompiledFile* newPbFile = inFileStream.CompiledFile(); + ASSERT_NE(nullptr, newPbFile); + + std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(*newPbFile, Source{ "test" }, + context->getDiagnostics()); + ASSERT_NE(nullptr, file); + + 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->exportedSymbols.size()); + EXPECT_EQ(test::parseNameOrDie(u"@+id/unchecked"), file->exportedSymbols[0].name); +} + +TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) { + ResourceFile f; + std::unique_ptr<pb::CompiledFile> pbFile = serializeCompiledFileToPb(f); + + const std::string expectedData = "1234"; + + std::string outputStr; + { + google::protobuf::io::StringOutputStream outStream(&outputStr); + CompiledFileOutputStream outFileStream(&outStream, pbFile.get()); + + ASSERT_TRUE(outFileStream.Write(expectedData.data(), expectedData.size())); + ASSERT_TRUE(outFileStream.Finish()); + } + + outputStr[0] = 0xff; + + CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size()); + EXPECT_EQ(nullptr, inFileStream.CompiledFile()); + EXPECT_EQ(nullptr, inFileStream.data()); + EXPECT_EQ(0u, inFileStream.size()); +} + +} // namespace aapt diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp new file mode 100644 index 000000000000..4bfdb1205e19 --- /dev/null +++ b/tools/aapt2/split/TableSplitter.cpp @@ -0,0 +1,265 @@ +/* + * 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 "ConfigDescription.h" +#include "ResourceTable.h" +#include "split/TableSplitter.h" + +#include <algorithm> +#include <map> +#include <set> +#include <unordered_map> +#include <vector> + +namespace aapt { + +using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>; +using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>; + +static ConfigDescription copyWithoutDensity(const ConfigDescription& config) { + ConfigDescription withoutDensity = config; + withoutDensity.density = 0; + return withoutDensity; +} + +/** + * 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; + } + } + } + + std::vector<ResourceConfigValue*> selectValues(const ConfigDensityGroups& densityGroups, + ConfigClaimedMap* claimedValues) { + 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); + + // 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; + } + + // 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); + } + } + return selected; + } + +private: + std::set<ConfigDescription> mDensityIndependentConfigs; + std::map<ConfigDescription, uint16_t> mDensityDependentConfigToDensityMap; +}; + +/** + * 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; + + 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); + } +} + +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; + } + } + } + } + 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; + } + + 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(); + } + } + } + + // 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; + + if (configValue->config.density != 0) { + // Create a bucket for this density-dependent config. + densityGroups[copyWithoutDensity(configValue->config)] + .push_back(configValue.get()); + } + } + } + + // 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(); + + // Select the values we want from this entry for this split. + SplitValueSelector selector(splitConstraint); + std::vector<ResourceConfigValue*> selectedValues = + selector.selectValues(densityGroups, &configClaimedMap); + + // 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; + } + + ResourceEntry* splitEntry = splitType->findOrCreateEntry(entry->name); + if (!splitEntry->id) { + splitEntry->id = entry->id; + splitEntry->symbolStatus = entry->symbolStatus; + } + + // 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)); + } + } + } + + if (mPreferredDensity) { + markNonPreferredDensitiesAsClaimed(mPreferredDensity.value(), + densityGroups, + &configClaimedMap); + } + + // 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(); + } + } + + // Now erase all nullptrs. + entry->values.erase( + std::remove(entry->values.begin(), entry->values.end(), nullptr), + entry->values.end()); + } + } + } +} + +} // namespace aapt diff --git a/tools/aapt2/split/TableSplitter.h b/tools/aapt2/split/TableSplitter.h new file mode 100644 index 000000000000..15e0764c4259 --- /dev/null +++ b/tools/aapt2/split/TableSplitter.h @@ -0,0 +1,78 @@ +/* + * 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_SPLIT_TABLESPLITTER_H +#define AAPT_SPLIT_TABLESPLITTER_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; +}; + +struct TableSplitterOptions { + /** + * The preferred density to keep in the table, stripping out all others. + */ + Maybe<uint16_t> preferredDensity; + + /** + * Configuration filter that determines which resource configuration values end up in + * the final table. + */ + IConfigFilter* configFilter = 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>()); + } + } + + bool verifySplitConstraints(IAaptContext* context); + + void splitTable(ResourceTable* originalTable); + + const std::vector<std::unique_ptr<ResourceTable>>& getSplits() { + return mSplits; + } + +private: + std::vector<SplitConstraints> mSplitConstraints; + std::vector<std::unique_ptr<ResourceTable>> mSplits; + Maybe<uint16_t> mPreferredDensity; + IConfigFilter* mConfigFilter; + + 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 new file mode 100644 index 000000000000..74ca32e04a30 --- /dev/null +++ b/tools/aapt2/split/TableSplitter_test.cpp @@ -0,0 +1,107 @@ +/* + * 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 "split/TableSplitter.h" +#include "test/Builders.h" +#include "test/Common.h" + +#include <gtest/gtest.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")); +} + +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"))); +} + +} // namespace aapt diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h new file mode 100644 index 000000000000..8eb4bc88168d --- /dev/null +++ b/tools/aapt2/test/Builders.h @@ -0,0 +1,256 @@ +/* + * 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. + */ + +#ifndef AAPT_TEST_BUILDERS_H +#define AAPT_TEST_BUILDERS_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); + } +}; + +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<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); + } +}; + +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); + } +}; + +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); + } +}; + +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); + } +}; + +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> 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 + +#endif /* AAPT_TEST_BUILDERS_H */ diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h new file mode 100644 index 000000000000..faccd47783ff --- /dev/null +++ b/tools/aapt2/test/Common.h @@ -0,0 +1,121 @@ +/* + * 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. + */ + +#ifndef AAPT_TEST_COMMON_H +#define AAPT_TEST_COMMON_H + +#include "ConfigDescription.h" +#include "Debug.h" +#include "ResourceTable.h" +#include "ResourceUtils.h" +#include "ValueVisitor.h" +#include "io/File.h" +#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. +// +#define AAPT_ASSERT_TRUE(v) ASSERT_TRUE(bool(v)) +#define AAPT_ASSERT_FALSE(v) ASSERT_FALSE(bool(v)) +#define AAPT_EXPECT_TRUE(v) EXPECT_TRUE(bool(v)) +#define AAPT_EXPECT_FALSE(v) EXPECT_FALSE(bool(v)) + +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; + } + } +}; + +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 ConfigDescription parseConfigOrDie(const StringPiece& str) { + ConfigDescription config; + bool result = ConfigDescription::parse(str, &config); + assert(result && "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()); + } + } + 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* getValue(ResourceTable* table, const StringPiece16& resName) { + return getValueForConfig<T>(table, resName, {}); +} + +class TestFile : public io::IFile { +private: + Source mSource; + +public: + TestFile(const StringPiece& path) : mSource(path) {} + + std::unique_ptr<io::IData> openAsData() override { + return {}; + } + + const Source& getSource() const override { + return mSource; + } +}; + +} // namespace test +} // namespace aapt + +#endif /* AAPT_TEST_COMMON_H */ diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h new file mode 100644 index 000000000000..96752d33dd9a --- /dev/null +++ b/tools/aapt2/test/Context.h @@ -0,0 +1,175 @@ +/* + * 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. + */ + +#ifndef AAPT_TEST_CONTEXT_H +#define AAPT_TEST_CONTEXT_H + +#include "NameMangler.h" +#include "util/Util.h" + +#include "process/IResourceTableConsumer.h" +#include "process/SymbolTable.h" +#include "test/Common.h" + +#include <cassert> +#include <list> + +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(); + } + + NameMangler* getNameMangler() override { + return &mNameMangler; + } + + bool verbose() override { + return false; + } + +private: + friend class ContextBuilder; + + Context() : mNameMangler({}) { + } + + Maybe<std::u16string> mCompilationPackage; + Maybe<uint8_t> mPackageId; + StdErrDiagnostics mDiagnostics; + SymbolTable mSymbols; + NameMangler mNameMangler; +}; + +class ContextBuilder { +private: + std::unique_ptr<Context> mContext = std::unique_ptr<Context>(new Context()); + +public: + ContextBuilder& setCompilationPackage(const StringPiece16& package) { + mContext->mCompilationPackage = package.toString(); + return *this; + } + + ContextBuilder& setPackageId(uint8_t id) { + mContext->mPackageId = id; + return *this; + } + + ContextBuilder& setNameManglerPolicy(NameManglerPolicy policy) { + mContext->mNameMangler = NameMangler(policy); + return *this; + } + + ContextBuilder& addSymbolSource(std::unique_ptr<ISymbolSource> src) { + mContext->getExternalSymbols()->appendSource(std::move(src)); + return *this; + } + + std::unique_ptr<Context> build() { + return std::move(mContext); + } +}; + +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; + } + + 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<ISymbolSource> build() { + return std::move(mSymbolSource); + } + +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>(); +}; + +} // namespace test +} // namespace aapt + +#endif /* AAPT_TEST_CONTEXT_H */ diff --git a/tools/aapt2/Compat_test.cpp b/tools/aapt2/test/Test.h index 96aee44c6c95..d4845cfc19b7 100644 --- a/tools/aapt2/Compat_test.cpp +++ b/tools/aapt2/test/Test.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * 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. @@ -14,20 +14,19 @@ * limitations under the License. */ -#include <gtest/gtest.h> - -namespace aapt { - -TEST(CompatTest, VersionAttributesInStyle) { -} +#ifndef AAPT_TEST_TEST_H +#define AAPT_TEST_TEST_H -TEST(CompatTest, VersionAttributesInXML) { -} +#include "test/Builders.h" +#include "test/Common.h" +#include "test/Context.h" -TEST(CompatTest, DoNotOverrideExistingVersionedFiles) { -} +#include <gtest/gtest.h> -TEST(CompatTest, VersionAttributesInStyleWithCorrectPrecedence) { -} +namespace aapt { +namespace test { +} // namespace test } // namespace aapt + +#endif // AAPT_TEST_TEST_H diff --git a/tools/aapt2/todo.txt b/tools/aapt2/todo.txt deleted file mode 100644 index acc8bfbcc9e5..000000000000 --- a/tools/aapt2/todo.txt +++ /dev/null @@ -1,29 +0,0 @@ -XML Files -X Collect declared IDs -X Build StringPool -X Flatten - -Resource Table Operations -X Build Resource Table (with StringPool) from XML. -X Modify Resource Table. -X - Copy and transform resources. -X - Pre-17/21 attr correction. -X Perform analysis of types. -X Flatten. -X Assign resource IDs. -X Assign public resource IDs. -X Merge resource tables -- Assign private attributes to different typespace. -- Align resource tables - -Splits -- Collect all resources (ids from layouts). -- Generate resource table from base resources. -- Generate resource table from individual resources of the required type. -- Align resource tables (same type/name = same ID). - -Fat Apk -X Collect all resources (ids from layouts). -X Generate resource tables for all configurations. -- Align individual resource tables. -- Merge resource tables. diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp new file mode 100644 index 000000000000..ec4675167676 --- /dev/null +++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp @@ -0,0 +1,573 @@ +/* + * 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 "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; + +namespace { + +/* + * Visitor that converts a reference's resource ID to a resource name, + * given a mapping from resource ID to resource name. + */ +class ReferenceIdToNameVisitor : public ValueVisitor { +private: + const std::map<ResourceId, ResourceName>* mMapping; + +public: + using ValueVisitor::visit; + + ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping) : + mMapping(mapping) { + assert(mMapping); + } + + 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 = {}; + } + } +}; + +} // namespace + +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; + } + } + + if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "corrupt resource table: " + << parser.getLastError()); + return false; + } + return !error; +} + +/** + * 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; + } + + 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; + } + } + + 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; + } + } + + 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; +} + +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 << "'"); + return false; + } + + if (!mTable->addResourceAllowMangled(name, config, {}, std::move(resourceValue), + mContext->getDiagnostics())) { + return false; + } + + 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; + } + } + + // 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 }); + } + } + 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>(); + } + + 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})); + } + } + + 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); + } + + // Treat this as a raw binary primitive. + return util::make_unique<BinaryPrimitive>(*value); +} + +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<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<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<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<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 {}; + } + + 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; +} + +} // namespace aapt diff --git a/tools/aapt2/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h index 3aab301ec199..12bc13db38f2 100644 --- a/tools/aapt2/BinaryResourceParser.h +++ b/tools/aapt2/unflatten/BinaryResourceParser.h @@ -17,11 +17,13 @@ #ifndef AAPT_BINARY_RESOURCE_PARSER_H #define AAPT_BINARY_RESOURCE_PARSER_H -#include "Resolver.h" #include "ResourceTable.h" #include "ResourceValues.h" #include "Source.h" +#include "process/IResourceTableConsumer.h" +#include "util/Util.h" + #include <androidfw/ResourceTypes.h> #include <string> @@ -42,11 +44,8 @@ public: * Creates a parser, which will read `len` bytes from `data`, and * add any resources parsed to `table`. `source` is for logging purposes. */ - BinaryResourceParser(const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<IResolver>& resolver, - const Source& source, - const std::u16string& defaultPackage, - const void* data, size_t len); + BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source, + const void* data, size_t dataLen); BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy. @@ -56,71 +55,47 @@ public: bool parse(); private: - // Helper method to retrieve the symbol name for a given table offset specified - // as a pointer. - bool getSymbol(const void* data, ResourceNameRef* outSymbol); - bool parseTable(const android::ResChunk_header* chunk); - bool parseSymbolTable(const android::ResChunk_header* chunk); - - // Looks up the resource ID in the reference and converts it to a name if available. - bool idToName(Reference* reference); - bool parsePackage(const android::ResChunk_header* chunk); - bool parsePublic(const android::ResChunk_header* chunk); bool parseTypeSpec(const android::ResChunk_header* chunk); - bool parseType(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<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); + 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<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); + 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<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<Styleable> parseStyleable(const ResourceNameRef& name, - const ConfigDescription& config, const android::ResTable_map_entry* map); + const ConfigDescription& config, + const android::ResTable_map_entry* map); - std::shared_ptr<ResourceTable> mTable; + /** + * 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); - std::shared_ptr<IResolver> mResolver; + IAaptContext* mContext; + ResourceTable* mTable; const Source mSource; - // The package name of the resource table. - std::u16string mDefaultPackage; - const void* mData; const size_t mDataLen; - // The array of symbol entries. Each element points to an offset - // in the table and an index into the symbol table string pool. - const SymbolTable_entry* mSymbolEntries = nullptr; - - // Number of symbol entries. - size_t mSymbolEntryCount = 0; - - // The symbol table string pool. Holds the names of symbols - // referenced in this table but not defined nor resolved to an - // ID. - android::ResStringPool mSymbolPool; - - // The source string pool. Resource entries may have an extra - // field that points into this string pool, which denotes where - // the resource was parsed from originally. - android::ResStringPool mSourcePool; - // The standard value string pool for resource values. android::ResStringPool mValuePool; @@ -146,13 +121,11 @@ namespace android { */ inline const ResTable_map* begin(const ResTable_map_entry* map) { - return reinterpret_cast<const ResTable_map*>( - reinterpret_cast<const uint8_t*>(map) + 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 reinterpret_cast<const ResTable_map*>( - reinterpret_cast<const uint8_t*>(map) + map->size) + map->count; + return begin(map) + aapt::util::deviceToHost32(map->count); } } // namespace android diff --git a/tools/aapt2/ResChunkPullParser.cpp b/tools/aapt2/unflatten/ResChunkPullParser.cpp index 78ea60e795fc..6f8bb1b29b62 100644 --- a/tools/aapt2/ResChunkPullParser.cpp +++ b/tools/aapt2/unflatten/ResChunkPullParser.cpp @@ -14,7 +14,8 @@ * limitations under the License. */ -#include "ResChunkPullParser.h" +#include "unflatten/ResChunkPullParser.h" +#include "util/Util.h" #include <androidfw/ResourceTypes.h> #include <cstddef> @@ -31,12 +32,11 @@ ResChunkPullParser::Event ResChunkPullParser::next() { if (mEvent == Event::StartDocument) { mCurrentChunk = mData; } else { - mCurrentChunk = reinterpret_cast<const ResChunk_header*>( - reinterpret_cast<const char*>(mCurrentChunk) + mCurrentChunk->size); + mCurrentChunk = (const ResChunk_header*) + (((const char*) mCurrentChunk) + util::deviceToHost32(mCurrentChunk->size)); } - const std::ptrdiff_t diff = reinterpret_cast<const char*>(mCurrentChunk) - - reinterpret_cast<const char*>(mData); + 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); @@ -49,15 +49,16 @@ ResChunkPullParser::Event ResChunkPullParser::next() { return (mEvent = Event::BadDocument); } - if (mCurrentChunk->headerSize < sizeof(ResChunk_header)) { + if (util::deviceToHost16(mCurrentChunk->headerSize) < sizeof(ResChunk_header)) { mLastError = "chunk has too small header"; mCurrentChunk = nullptr; return (mEvent = Event::BadDocument); - } else if (mCurrentChunk->size < mCurrentChunk->headerSize) { + } 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 + mCurrentChunk->size > mLen) { + } 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); diff --git a/tools/aapt2/ResChunkPullParser.h b/tools/aapt2/unflatten/ResChunkPullParser.h index 1426ed23a5c7..a51d5bfdc9b3 100644 --- a/tools/aapt2/ResChunkPullParser.h +++ b/tools/aapt2/unflatten/ResChunkPullParser.h @@ -17,6 +17,8 @@ #ifndef AAPT_RES_CHUNK_PULL_PARSER_H #define AAPT_RES_CHUNK_PULL_PARSER_H +#include "util/Util.h" + #include <androidfw/ResourceTypes.h> #include <string> @@ -76,18 +78,18 @@ private: template <typename T> inline static const T* convertTo(const android::ResChunk_header* chunk) { - if (chunk->headerSize < sizeof(T)) { + 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) + 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 size_t getChunkDataLen(const android::ResChunk_header& chunk) { - return chunk.size - chunk.headerSize; +inline static uint32_t getChunkDataLen(const android::ResChunk_header* chunk) { + return util::deviceToHost32(chunk->size) - util::deviceToHost16(chunk->headerSize); } // diff --git a/tools/aapt2/BigBuffer.cpp b/tools/aapt2/util/BigBuffer.cpp index 8f571728d729..c88e3c102415 100644 --- a/tools/aapt2/BigBuffer.cpp +++ b/tools/aapt2/util/BigBuffer.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "BigBuffer.h" +#include "util/BigBuffer.h" #include <algorithm> #include <memory> diff --git a/tools/aapt2/BigBuffer.h b/tools/aapt2/util/BigBuffer.h index 8b6569c6a8d6..cad2a2e63be1 100644 --- a/tools/aapt2/BigBuffer.h +++ b/tools/aapt2/util/BigBuffer.h @@ -20,6 +20,7 @@ #include <cassert> #include <cstring> #include <memory> +#include <type_traits> #include <vector> namespace aapt { @@ -124,6 +125,7 @@ inline size_t BigBuffer::size() const { 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)); } diff --git a/tools/aapt2/BigBuffer_test.cpp b/tools/aapt2/util/BigBuffer_test.cpp index 01ee8d7e9ad5..2a24f123e18e 100644 --- a/tools/aapt2/BigBuffer_test.cpp +++ b/tools/aapt2/util/BigBuffer_test.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "BigBuffer.h" +#include "util/BigBuffer.h" #include <gtest/gtest.h> diff --git a/tools/aapt2/Files.cpp b/tools/aapt2/util/Files.cpp index 8484148f29d3..f5e49f11c082 100644 --- a/tools/aapt2/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -14,20 +14,24 @@ * limitations under the License. */ -#include "Files.h" -#include "Util.h" +#include "util/Files.h" +#include "util/Util.h" +#include <algorithm> +#include <android-base/file.h> #include <cerrno> +#include <cstdio> #include <dirent.h> #include <string> #include <sys/stat.h> -#ifdef HAVE_MS_C_RUNTIME +#ifdef _WIN32 // Windows includes. #include <direct.h> #endif namespace aapt { +namespace file { FileType getFileType(const StringPiece& path) { struct stat sb; @@ -61,15 +65,15 @@ FileType getFileType(const StringPiece& path) { } } -std::vector<std::string> listFiles(const StringPiece& root) { +std::vector<std::string> listFiles(const StringPiece& root, std::string* outError) { DIR* dir = opendir(root.data()); if (dir == nullptr) { - Logger::error(Source{ root.toString() }) - << "unable to open file: " - << strerror(errno) - << "." - << std::endl; - return {}; + if (outError) { + std::stringstream errorStr; + errorStr << "unable to open file: " << strerror(errno); + *outError = errorStr.str(); + return {}; + } } std::vector<std::string> files; @@ -83,7 +87,7 @@ std::vector<std::string> listFiles(const StringPiece& root) { } inline static int mkdirImpl(const StringPiece& path) { -#ifdef HAVE_MS_C_RUNTIME +#ifdef _WIN32 return _mkdir(path.toString().c_str()); #else return mkdir(path.toString().c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP); @@ -94,7 +98,7 @@ 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) { + if (*current == sDirSep && current != start) { StringPiece parentPath(start, current - start); int result = mkdirImpl(parentPath); if (result < 0 && errno != EEXIST) { @@ -105,17 +109,105 @@ bool mkdirs(const StringPiece& path) { return mkdirImpl(path) == 0 || errno == EEXIST; } -std::string getStem(const StringPiece& path) { +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 std::string(start, current - start); + return StringPiece(start, current - start); + } + } + 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; } } + return StringPiece(lastDirSep, end - lastDirSep); +} + +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()); +} + +std::string packageToPath(const StringPiece& package) { + std::string outPath; + for (StringPiece part : util::tokenize<char>(package, '.')) { + appendPath(&outPath, part); + } + return outPath; +} + +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 {}; + } + + int fd = fileno(f.get()); + + struct stat fileStats = {}; + if (fstat(fd, &fileStats) != 0) { + if (outError) *outError = strerror(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); + } + + if (!fileMap.create(path.data(), fd, 0, fileStats.st_size, true)) { + if (outError) *outError = strerror(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; + } + + for (StringPiece line : util::tokenize<char>(contents, ' ')) { + line = util::trimWhitespace(line); + if (!line.empty()) { + outArgList->push_back(line.toString()); + } + } + return true; +} + bool FileFilter::setPattern(const StringPiece& pattern) { mPatternTokens = util::splitAndLowercase(pattern, ':'); return true; @@ -169,14 +261,10 @@ bool FileFilter::operator()(const std::string& filename, FileType type) const { if (ignore) { if (chatty) { - Logger::warn() - << "skipping " << - (type == FileType::kDirectory ? "dir '" : "file '") - << filename - << "' due to ignore pattern '" - << token - << "'." - << std::endl; + mDiag->warn(DiagMessage() << "skipping " + << (type == FileType::kDirectory ? "dir '" : "file '") + << filename << "' due to ignore pattern '" + << token << "'"); } return false; } @@ -184,5 +272,5 @@ bool FileFilter::operator()(const std::string& filename, FileType type) const { return true; } - +} // namespace file } // namespace aapt diff --git a/tools/aapt2/Files.h b/tools/aapt2/util/Files.h index 844fd2b07189..4d8a1feb63b1 100644 --- a/tools/aapt2/Files.h +++ b/tools/aapt2/util/Files.h @@ -17,15 +17,20 @@ #ifndef AAPT_FILES_H #define AAPT_FILES_H -#include "Logger.h" +#include "Diagnostics.h" +#include "Maybe.h" #include "Source.h" -#include "StringPiece.h" +#include "util/StringPiece.h" + +#include <utils/FileMap.h> #include <cassert> +#include <memory> #include <string> #include <vector> namespace aapt { +namespace file { #ifdef _WIN32 constexpr const char sDirSep = '\\'; @@ -56,14 +61,7 @@ std::vector<std::string> listFiles(const StringPiece& root); /* * Appends a path to `base`, separated by the directory separator. */ -void appendPath(std::string* base, const StringPiece& part); - -/* - * Appends a series of paths to `base`, separated by the - * system directory separator. - */ -template <typename... Ts > -void appendPath(std::string* base, const StringPiece& part, const Ts&... parts); +void appendPath(std::string* base, StringPiece part); /* * Makes all the directories in `path`. The last element in the path @@ -74,7 +72,34 @@ bool mkdirs(const StringPiece& path); /** * Returns all but the last part of the path. */ -std::string getStem(const StringPiece& path); +StringPiece getStem(const StringPiece& path); + +/** + * Returns the last part of the path with extension. + */ +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); + +/** + * Converts a package name (com.android.app) to a path: com/android/app + */ +std::string packageToPath(const StringPiece& package); + +/** + * Creates a FileMap for the file at path. + */ +Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError); + +/** + * 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); /* * Filter that determines which resource files/directories are @@ -84,6 +109,9 @@ std::string getStem(const StringPiece& path); */ class FileFilter { public: + FileFilter(IDiagnostics* diag) : mDiag(diag) { + } + /* * Patterns syntax: * - Delimiter is : @@ -106,23 +134,11 @@ public: bool operator()(const std::string& filename, FileType type) const; private: + IDiagnostics* mDiag; std::vector<std::string> mPatternTokens; }; -inline void appendPath(std::string* base, const StringPiece& part) { - assert(base); - *base += sDirSep; - base->append(part.data(), part.size()); -} - -template <typename... Ts > -void appendPath(std::string* base, const StringPiece& part, const Ts&... parts) { - assert(base); - *base += sDirSep; - base->append(part.data(), part.size()); - appendPath(base, parts...); -} - +} // namespace file } // namespace aapt #endif // AAPT_FILES_H diff --git a/tools/aapt2/util/Files_test.cpp b/tools/aapt2/util/Files_test.cpp new file mode 100644 index 000000000000..efb04593ff82 --- /dev/null +++ b/tools/aapt2/util/Files_test.cpp @@ -0,0 +1,58 @@ +/* + * 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 "test/Test.h" +#include "util/Files.h" + +#include <sstream> + +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; +}; + +TEST_F(FilesTest, appendPath) { + std::string base = "hello"; + appendPath(&base, "there"); + EXPECT_EQ(mExpectedPath, base); +} + +TEST_F(FilesTest, appendPathWithLeadingOrTrailingSeparators) { + std::string base = "hello/"; + appendPath(&base, "there"); + EXPECT_EQ(mExpectedPath, base); + + base = "hello"; + appendPath(&base, "/there"); + EXPECT_EQ(mExpectedPath, base); + + base = "hello/"; + appendPath(&base, "/there"); + EXPECT_EQ(mExpectedPath, base); +} + +} // namespace files +} // namespace aapt diff --git a/tools/aapt2/util/ImmutableMap.h b/tools/aapt2/util/ImmutableMap.h new file mode 100644 index 000000000000..b1f9e9d2fb57 --- /dev/null +++ b/tools/aapt2/util/ImmutableMap.h @@ -0,0 +1,84 @@ +/* + * 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. + */ + +#ifndef AAPT_UTIL_IMMUTABLEMAP_H +#define AAPT_UTIL_IMMUTABLEMAP_H + +#include "util/TypeTraits.h" + +#include <utility> +#include <vector> + +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)) { + } + +public: + using const_iterator = typename decltype(mData)::const_iterator; + + ImmutableMap(ImmutableMap&&) = default; + ImmutableMap& operator=(ImmutableMap&&) = default; + + ImmutableMap(const ImmutableMap&) = delete; + ImmutableMap& operator=(const ImmutableMap&) = delete; + + 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 endIter = end(); + auto iter = std::lower_bound(mData.begin(), endIter, key, cmp); + if (iter == endIter || iter->first == key) { + return iter; + } + return endIter; + } + + const_iterator begin() const { + return mData.begin(); + } + + const_iterator end() const { + return mData.end(); + } +}; + +} // namespace aapt + +#endif /* AAPT_UTIL_IMMUTABLEMAP_H */ diff --git a/tools/aapt2/Maybe.h b/tools/aapt2/util/Maybe.h index ff6625f4bb5e..595db960d5e5 100644 --- a/tools/aapt2/Maybe.h +++ b/tools/aapt2/util/Maybe.h @@ -17,6 +17,8 @@ #ifndef AAPT_MAYBE_H #define AAPT_MAYBE_H +#include "util/TypeTraits.h" + #include <cassert> #include <type_traits> #include <utility> @@ -72,7 +74,7 @@ public: * True if this holds a value, false if * it holds Nothing. */ - operator bool() const; + explicit operator bool() const; /** * Gets the value if one exists, or else @@ -275,6 +277,35 @@ inline Maybe<T> make_nothing() { 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 + * 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; +} + +/** + * 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); +} + } // namespace aapt #endif // AAPT_MAYBE_H diff --git a/tools/aapt2/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp index 71bbb940beda..5d42dc3ac3ab 100644 --- a/tools/aapt2/Maybe_test.cpp +++ b/tools/aapt2/util/Maybe_test.cpp @@ -14,11 +14,12 @@ * limitations under the License. */ +#include "test/Common.h" +#include "util/Maybe.h" + #include <gtest/gtest.h> #include <string> -#include "Maybe.h" - namespace aapt { struct Dummy { @@ -85,22 +86,22 @@ struct Dummy { TEST(MaybeTest, MakeNothing) { Maybe<int> val = make_nothing<int>(); - EXPECT_FALSE(val); + AAPT_EXPECT_FALSE(val); Maybe<std::string> val2 = make_nothing<std::string>(); - EXPECT_FALSE(val2); + AAPT_EXPECT_FALSE(val2); val2 = make_nothing<std::string>(); - EXPECT_FALSE(val2); + AAPT_EXPECT_FALSE(val2); } TEST(MaybeTest, MakeSomething) { Maybe<int> val = make_value(23); - ASSERT_TRUE(val); + AAPT_ASSERT_TRUE(val); EXPECT_EQ(23, val.value()); Maybe<std::string> val2 = make_value(std::string("hey")); - ASSERT_TRUE(val2); + AAPT_ASSERT_TRUE(val2); EXPECT_EQ(std::string("hey"), val2.value()); } @@ -118,4 +119,17 @@ TEST(MaybeTest, MoveAssign) { } } +TEST(MaybeTest, Equality) { + Maybe<int> a = 1; + Maybe<int> b = 1; + Maybe<int> c; + + Maybe<int> emptyA, emptyB; + + EXPECT_EQ(a, b); + EXPECT_EQ(b, a); + EXPECT_NE(a, c); + EXPECT_EQ(emptyA, emptyB); +} + } // namespace aapt diff --git a/tools/aapt2/StringPiece.h b/tools/aapt2/util/StringPiece.h index e2a1597caeda..f91bccc93019 100644 --- a/tools/aapt2/StringPiece.h +++ b/tools/aapt2/util/StringPiece.h @@ -36,6 +36,7 @@ template <typename TChar> class BasicStringPiece { public: using const_iterator = const TChar*; + using difference_type = size_t; BasicStringPiece(); BasicStringPiece(const BasicStringPiece<TChar>& str); @@ -56,6 +57,7 @@ public: 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; @@ -163,6 +165,17 @@ inline std::basic_string<TChar> BasicStringPiece<TChar>::toString() const { } 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; +} + +template <> inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const { const char nullStr = '\0'; const char* b1 = mData != nullptr ? mData : &nullStr; @@ -184,6 +197,16 @@ inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<ch 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; +} template <> inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const { @@ -229,4 +252,9 @@ inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<ch } // 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()); +} + #endif // AAPT_STRING_PIECE_H diff --git a/tools/aapt2/StringPiece_test.cpp b/tools/aapt2/util/StringPiece_test.cpp index 43f7a370d23c..853a9a46fde8 100644 --- a/tools/aapt2/StringPiece_test.cpp +++ b/tools/aapt2/util/StringPiece_test.cpp @@ -19,7 +19,7 @@ #include <string> #include <vector> -#include "StringPiece.h" +#include "util/StringPiece.h" namespace aapt { @@ -59,4 +59,36 @@ TEST(StringPieceTest, PiecesHaveCorrectSortOrderUtf8) { 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."); + + 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)); + + 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."); + + 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)); +} + } // namespace aapt diff --git a/tools/aapt2/util/TypeTraits.h b/tools/aapt2/util/TypeTraits.h new file mode 100644 index 000000000000..76c13d615e41 --- /dev/null +++ b/tools/aapt2/util/TypeTraits.h @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#ifndef AAPT_UTIL_TYPETRAITS_H +#define AAPT_UTIL_TYPETRAITS_H + +#include <type_traits> + +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_HAS_BINARY_OP_TRAIT(has_eq_op, ==); +DEFINE_HAS_BINARY_OP_TRAIT(has_lt_op, <); + +/** + * Type trait that checks if two types can be equated (==) and compared (<). + */ +template <typename T, typename U> +struct is_comparable { + static constexpr bool value = has_eq_op<T, U>::value && has_lt_op<T, U>::value; +}; + +} // namespace aapt + +#endif /* AAPT_UTIL_TYPETRAITS_H */ diff --git a/tools/aapt2/Util.cpp b/tools/aapt2/util/Util.cpp index 03ecd1aca310..5a87c334c59e 100644 --- a/tools/aapt2/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -14,10 +14,10 @@ * limitations under the License. */ -#include "BigBuffer.h" -#include "Maybe.h" -#include "StringPiece.h" -#include "Util.h" +#include "util/BigBuffer.h" +#include "util/Maybe.h" +#include "util/StringPiece.h" +#include "util/Util.h" #include <algorithm> #include <ostream> @@ -28,9 +28,6 @@ namespace aapt { namespace util { -constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto"; -constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/"; - static std::vector<std::string> splitAndTransform(const StringPiece& str, char sep, const std::function<char(char)>& f) { std::vector<std::string> parts; @@ -76,6 +73,25 @@ StringPiece16 trimWhitespace(const StringPiece16& str) { return StringPiece16(start, end - start); } +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(); + + while (start != end && isspace(*start)) { + start++; + } + + while (end != start && isspace(*(end - 1))) { + end--; + } + + return StringPiece(start, end - start); +} + StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16& str, const StringPiece16& allowedChars) { const auto endIter = str.end(); @@ -122,6 +138,29 @@ bool isJavaClassName(const StringPiece16& str) { return pieces >= 2; } +bool isJavaPackageName(const StringPiece16& str) { + if (str.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; + } + + if (findNonAlphaNumericAndNotInSet(piece, u"_") != piece.end()) { + return false; + } + } + return pieces >= 1; +} + Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package, const StringPiece16& className) { if (className.empty()) { @@ -136,10 +175,11 @@ Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package, return {}; } - std::u16string result(package.data(), package.size()); if (className.data()[0] != u'.') { - result += u'.'; + return {}; } + + std::u16string result(package.data(), package.size()); result.append(className.data(), className.size()); if (!isJavaClassName(result)) { return {}; @@ -147,6 +187,105 @@ Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package, 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); +} + +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; + } + } + } + + if (c != end) { + c++; + } + } + + 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; + } + 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)++) { @@ -175,7 +314,51 @@ StringBuilder& StringBuilder::append(const StringPiece16& str) { const char16_t* start = str.begin(); const char16_t* current = start; while (current != end) { - if (*current == u'"') { + 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 @@ -208,52 +391,7 @@ StringBuilder& StringBuilder::append(const StringPiece16& str) { } mStr.append(start, current - start); start = current + 1; - - current++; - if (current != end) { - 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; - } - start = current + 1; - } + mLastCharWasEscape = true; } else if (!mQuote) { // This is not quoted text, so look for whitespace. if (isspace16(*current)) { @@ -303,8 +441,10 @@ std::string utf16ToUtf8(const StringPiece16& utf16) { } 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); - utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin()); return utf8; } @@ -327,16 +467,28 @@ std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer) { return data; } -Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri) { - if (stringStartsWith<char16_t>(namespaceUri, kSchemaPrefix)) { - StringPiece16 schemaPrefix = kSchemaPrefix; - StringPiece16 package = namespaceUri; - return package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size()) - .toString(); - } else if (namespaceUri == kSchemaAuto) { - return std::u16string(); +bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix, + StringPiece16* outEntry, StringPiece16* outSuffix) { + if (!stringStartsWith<char16_t>(path, u"res/")) { + return false; + } + + StringPiece16::const_iterator lastOccurence = path.end(); + for (auto iter = path.begin() + StringPiece16(u"res/").size(); iter != path.end(); ++iter) { + if (*iter == u'/') { + lastOccurence = iter; + } + } + + if (lastOccurence == path.end()) { + return false; } - return {}; + + 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; } } // namespace util diff --git a/tools/aapt2/Util.h b/tools/aapt2/util/Util.h index 9cdb152bf41f..0dacbd773488 100644 --- a/tools/aapt2/Util.h +++ b/tools/aapt2/util/Util.h @@ -17,9 +17,9 @@ #ifndef AAPT_UTIL_H #define AAPT_UTIL_H -#include "BigBuffer.h" -#include "Maybe.h" -#include "StringPiece.h" +#include "util/BigBuffer.h" +#include "util/Maybe.h" +#include "util/StringPiece.h" #include <androidfw/ResourceTypes.h> #include <functional> @@ -62,6 +62,8 @@ bool stringEndsWith(const BasicStringPiece<T>& str, const BasicStringPiece<T>& s */ StringPiece16 trimWhitespace(const StringPiece16& str); +StringPiece trimWhitespace(const StringPiece& str); + /** * UTF-16 isspace(). It basically checks for lower range characters that are * whitespace. @@ -83,6 +85,11 @@ StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16 bool isJavaClassName(const StringPiece16& str); /** + * Tests that the string is a valid Java package name. + */ +bool isJavaPackageName(const StringPiece16& str); + +/** * Converts the class name to a fully qualified class name from the given `package`. Ex: * * asdf --> package.asdf @@ -151,6 +158,23 @@ inline StringPiece16 getString(const android::ResStringPool& pool, size_t idx) { return StringPiece16(); } +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(); +} + +/** + * 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); + class StringBuilder { public: StringBuilder& append(const StringPiece16& str); @@ -162,6 +186,7 @@ private: std::u16string mStr; bool mQuote = false; bool mTrailingSpace = false; + bool mLastCharWasEscape = false; std::string mError; }; @@ -213,11 +238,12 @@ public: private: friend class Tokenizer<Char>; - iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok); + iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok, bool end); - BasicStringPiece<Char> str; - Char separator; - BasicStringPiece<Char> token; + BasicStringPiece<Char> mStr; + Char mSeparator; + BasicStringPiece<Char> mToken; + bool mEnd; }; Tokenizer(BasicStringPiece<Char> str, Char sep); @@ -236,36 +262,38 @@ inline Tokenizer<Char> tokenize(BasicStringPiece<Char> str, Char sep) { template <typename Char> typename Tokenizer<Char>::iterator& Tokenizer<Char>::iterator::operator++() { - const Char* start = token.end(); - const Char* end = str.end(); + const Char* start = mToken.end(); + const Char* end = mStr.end(); if (start == end) { - token.assign(token.end(), 0); + mEnd = true; + mToken.assign(mToken.end(), 0); return *this; } start += 1; const Char* current = start; while (current != end) { - if (*current == separator) { - token.assign(start, current - start); + if (*current == mSeparator) { + mToken.assign(start, current - start); return *this; } ++current; } - token.assign(start, end - start); + mToken.assign(start, end - start); return *this; } template <typename Char> inline BasicStringPiece<Char> Tokenizer<Char>::iterator::operator*() { - return token; + return mToken; } 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 token.begin() == rhs.token.begin() && token.end() == rhs.token.end(); + return mToken.begin() == rhs.mToken.begin() && mToken.end() == rhs.mToken.end() && + mEnd == rhs.mEnd; } template <typename Char> @@ -275,8 +303,8 @@ inline bool Tokenizer<Char>::iterator::operator!=(const iterator& rhs) const { template <typename Char> inline Tokenizer<Char>::iterator::iterator(BasicStringPiece<Char> s, Char sep, - BasicStringPiece<Char> tok) : - str(s), separator(sep), token(tok) { + BasicStringPiece<Char> tok, bool end) : + mStr(s), mSeparator(sep), mToken(tok), mEnd(end) { } template <typename Char> @@ -291,18 +319,37 @@ inline typename Tokenizer<Char>::iterator Tokenizer<Char>::end() { template <typename Char> inline Tokenizer<Char>::Tokenizer(BasicStringPiece<Char> str, Char sep) : - mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0))), - mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0)) { + mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0), false)), + mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0), true) { +} + +inline uint16_t hostToDevice16(uint16_t value) { + return htods(value); +} + +inline uint32_t hostToDevice32(uint32_t value) { + return htodl(value); +} + +inline uint16_t deviceToHost16(uint16_t value) { + return dtohs(value); +} + +inline uint32_t deviceToHost32(uint32_t value) { + return dtohl(value); } /** - * Returns a package name if the namespace URI is of the form: - * http://schemas.android.com/apk/res/<package> + * Given a path like: res/xml-sw600dp/foo.xml + * + * Extracts "res/xml-sw600dp/" into outPrefix. + * Extracts "foo" into outEntry. + * Extracts ".xml" into outSuffix. * - * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto, - * returns an empty package name. + * Returns true if successful. */ -Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri); +bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix, + StringPiece16* outEntry, StringPiece16* outSuffix); } // namespace util diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp new file mode 100644 index 000000000000..1e0c7fa9152d --- /dev/null +++ b/tools/aapt2/util/Util_test.cpp @@ -0,0 +1,204 @@ +/* + * 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 "test/Common.h" +#include "util/StringPiece.h" +#include "util/Util.h" + +#include <gtest/gtest.h> +#include <string> + +namespace aapt { + +TEST(UtilTest, TrimOnlyWhitespace) { + const std::u16string full = u"\n "; + + StringPiece16 trimmed = util::trimWhitespace(full); + EXPECT_TRUE(trimmed.empty()); + EXPECT_EQ(0u, trimmed.size()); +} + +TEST(UtilTest, StringEndsWith) { + EXPECT_TRUE(util::stringEndsWith<char>("hello.xml", ".xml")); +} + +TEST(UtilTest, StringStartsWith) { + EXPECT_TRUE(util::stringStartsWith<char>("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()); +} + +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(StringPiece16(u" wow, so many \t spaces. what?"), + util::StringBuilder().append(u" \" wow, so many \t ") + .append(u"spaces. \"what? ") + .str()); + + EXPECT_EQ(StringPiece16(u"where is the pie?"), + util::StringBuilder().append(u" where \t ") + .append(u" \nis the "" pie?") + .str()); +} + +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(StringPiece16(u"@?#\\\'"), + util::StringBuilder().append(u"\\@\\?\\#\\\\\\'") + .str()); +} + +TEST(UtilTest, StringBuilderMisplacedQuote) { + util::StringBuilder builder{}; + EXPECT_FALSE(builder.append(u"they're coming!")); +} + +TEST(UtilTest, StringBuilderUnicodeCodes) { + EXPECT_EQ(StringPiece16(u"\u00AF\u0AF0 woah"), + util::StringBuilder().append(u"\\u00AF\\u0AF0 woah") + .str()); + + EXPECT_FALSE(util::StringBuilder().append(u"\\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); +} + +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); +} + +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()); +} + +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")); +} + +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"..")); +} + +TEST(UtilTest, FullyQualifiedClassName) { + Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf"); + AAPT_ASSERT_FALSE(res); + + res = util::getFullyQualifiedClassName(u"android", u".asdf"); + AAPT_ASSERT_TRUE(res); + EXPECT_EQ(res.value(), u"android.asdf"); + + res = util::getFullyQualifiedClassName(u"android", u".a.b"); + AAPT_ASSERT_TRUE(res); + EXPECT_EQ(res.value(), u"android.a.b"); + + res = util::getFullyQualifiedClassName(u"android", u"a.b"); + AAPT_ASSERT_TRUE(res); + EXPECT_EQ(res.value(), u"a.b"); + + res = util::getFullyQualifiedClassName(u"", u"a.b"); + AAPT_ASSERT_TRUE(res); + EXPECT_EQ(res.value(), u"a.b"); + + res = util::getFullyQualifiedClassName(u"", u""); + AAPT_ASSERT_FALSE(res); + + res = util::getFullyQualifiedClassName(u"android", u"./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"); + + ASSERT_TRUE(util::extractResFilePathParts(u"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_FALSE(util::extractResFilePathParts(u"AndroidManifest.xml", &prefix, &entry, &suffix)); + EXPECT_FALSE(util::extractResFilePathParts(u"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"."); +} + +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")); +} + +} // namespace aapt diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp new file mode 100644 index 000000000000..0ef67eaf3dc5 --- /dev/null +++ b/tools/aapt2/xml/XmlActionExecutor.cpp @@ -0,0 +1,112 @@ +/* + * 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 "xml/XmlActionExecutor.h" + +namespace aapt { +namespace xml { + +static bool wrapperOne(XmlNodeAction::ActionFunc& f, Element* el, SourcePathDiagnostics*) { + return f(el); +} + +static bool wrapperTwo(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::ActionFuncWithDiag f) { + mActions.emplace_back(std::bind(wrapperTwo, 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 << ">"; +} + +bool XmlNodeAction::execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag, + Element* el) const { + bool error = false; + for (const ActionFuncWithDiag& action : mActions) { + 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; + } + } + + if (policy == XmlActionExecutorPolicy::Whitelist) { + DiagMessage errorMsg(childEl->lineNumber); + errorMsg << "unknown element "; + printElementToDiagMessage(childEl, &errorMsg); + errorMsg << " found"; + diag->error(errorMsg); + error = true; + } + } + return !error; +} + +bool XmlActionExecutor::execute(XmlActionExecutorPolicy policy, IDiagnostics* diag, + XmlResource* doc) const { + SourcePathDiagnostics sourceDiag(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; + } + + 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 (policy == XmlActionExecutorPolicy::Whitelist) { + DiagMessage errorMsg(el->lineNumber); + errorMsg << "unknown element "; + printElementToDiagMessage(el, &errorMsg); + errorMsg << " found"; + sourceDiag.error(errorMsg); + return false; + } + return true; +} + +} // namespace xml +} // namespace aapt diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h new file mode 100644 index 000000000000..36b94dbfde05 --- /dev/null +++ b/tools/aapt2/xml/XmlActionExecutor.h @@ -0,0 +1,108 @@ +/* + * 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_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> + +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, +}; + +/** + * 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; +}; + +/** + * 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); +}; + +} // 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 new file mode 100644 index 000000000000..ebf287a251f2 --- /dev/null +++ b/tools/aapt2/xml/XmlActionExecutor_test.cpp @@ -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. + */ + +#include "test/Test.h" +#include "xml/XmlActionExecutor.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); +} + +TEST(XmlActionExecutorTest, FailsWhenUndefinedHierarchyExists) { + XmlActionExecutor executor; + executor[u"manifest"][u"application"]; + + std::unique_ptr<XmlResource> doc = test::buildXmlDom( + "<manifest><application /><activity /></manifest>"); + StdErrDiagnostics diag; + ASSERT_FALSE(executor.execute(XmlActionExecutorPolicy::Whitelist, &diag, doc.get())); +} + +} // namespace xml +} // namespace aapt diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp index 763029fd4157..0ce333af3115 100644 --- a/tools/aapt2/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -14,12 +14,12 @@ * limitations under the License. */ -#include "Logger.h" -#include "Util.h" #include "XmlDom.h" #include "XmlPullParser.h" +#include "util/Util.h" #include <cassert> +#include <expat.h> #include <memory> #include <stack> #include <string> @@ -65,7 +65,7 @@ static void addToStack(Stack* stack, XML_Parser parser, std::unique_ptr<Node> no stack->root = std::move(node); } - if (thisNode->type != NodeType::kText) { + if (!nodeCast<Text>(thisNode)) { stack->nodeStack.push(thisNode); } } @@ -126,7 +126,7 @@ static void XMLCALL endElementHandler(void* userData, const char* name) { Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); assert(!stack->nodeStack.empty()); - stack->nodeStack.top()->comment = std::move(stack->pendingComment); + //stack->nodeStack.top()->comment = std::move(stack->pendingComment); stack->nodeStack.pop(); } @@ -143,8 +143,7 @@ static void XMLCALL characterDataHandler(void* userData, const char* s, int len) Node* currentParent = stack->nodeStack.top(); if (!currentParent->children.empty()) { Node* lastChild = currentParent->children.back().get(); - if (lastChild->type == NodeType::kText) { - Text* text = static_cast<Text*>(lastChild); + if (Text* text = nodeCast<Text>(lastChild)) { text->text += util::utf8ToUtf16(StringPiece(s, len)); return; } @@ -166,7 +165,7 @@ static void XMLCALL commentDataHandler(void* userData, const char* comment) { stack->pendingComment += util::utf8ToUtf16(comment); } -std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger) { +std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source) { Stack stack; XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep); @@ -182,20 +181,23 @@ std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger) { in->read(buffer, sizeof(buffer) / sizeof(buffer[0])); if (in->bad() && !in->eof()) { stack.root = {}; - logger->error() << strerror(errno) << std::endl; + diag->error(DiagMessage(source) << strerror(errno)); break; } if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == XML_STATUS_ERROR) { stack.root = {}; - logger->error(XML_GetCurrentLineNumber(parser)) - << XML_ErrorString(XML_GetErrorCode(parser)) << std::endl; + diag->error(DiagMessage(source.withLine(XML_GetCurrentLineNumber(parser))) + << XML_ErrorString(XML_GetErrorCode(parser))); break; } } XML_ParserFree(parser); - return std::move(stack.root); + if (stack.root) { + return util::make_unique<XmlResource>(ResourceFile{ {}, {}, source }, std::move(stack.root)); + } + return {}; } static void copyAttributes(Element* el, android::ResXMLParser* parser) { @@ -224,21 +226,26 @@ static void copyAttributes(Element* el, android::ResXMLParser* parser) { } } -std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger) { +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<Node> root; std::stack<Node*> nodeStack; - android::ResXMLTree tree; - if (tree.setTo(data, dataLen) != android::NO_ERROR) { + ResXMLTree tree; + if (tree.setTo(data, dataLen) != NO_ERROR) { return {}; } - android::ResXMLParser::event_code_t code; - while ((code = tree.next()) != android::ResXMLParser::BAD_DOCUMENT && - code != android::ResXMLParser::END_DOCUMENT) { + ResXMLParser::event_code_t code; + while ((code = tree.next()) != ResXMLParser::BAD_DOCUMENT && + code != ResXMLParser::END_DOCUMENT) { std::unique_ptr<Node> newNode; switch (code) { - case android::ResXMLParser::START_NAMESPACE: { + case ResXMLParser::START_NAMESPACE: { std::unique_ptr<Namespace> node = util::make_unique<Namespace>(); size_t len; const char16_t* str16 = tree.getNamespacePrefix(&len); @@ -254,7 +261,7 @@ std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* lo break; } - case android::ResXMLParser::START_TAG: { + case ResXMLParser::START_TAG: { std::unique_ptr<Element> node = util::make_unique<Element>(); size_t len; const char16_t* str16 = tree.getElementNamespace(&len); @@ -273,7 +280,7 @@ std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* lo break; } - case android::ResXMLParser::TEXT: { + case ResXMLParser::TEXT: { std::unique_ptr<Text> node = util::make_unique<Text>(); size_t len; const char16_t* str16 = tree.getText(&len); @@ -284,8 +291,8 @@ std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* lo break; } - case android::ResXMLParser::END_NAMESPACE: - case android::ResXMLParser::END_TAG: + case ResXMLParser::END_NAMESPACE: + case ResXMLParser::END_TAG: assert(!nodeStack.empty()); nodeStack.pop(); break; @@ -307,53 +314,37 @@ std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* lo nodeStack.top()->addChild(std::move(newNode)); } - if (thisNode->type != NodeType::kText) { + if (!nodeCast<Text>(thisNode)) { nodeStack.push(thisNode); } } } - return std::move(root); -} - -Node::Node(NodeType type) : type(type), parent(nullptr), lineNumber(0), columnNumber(0) { -} - -void Node::addChild(std::unique_ptr<Node> child) { - child->parent = this; - children.push_back(std::move(child)); + return util::make_unique<XmlResource>(ResourceFile{}, std::move(root)); } -Namespace::Namespace() : BaseNode(NodeType::kNamespace) { +Element* findRootElement(XmlResource* doc) { + return findRootElement(doc->root.get()); } -std::unique_ptr<Node> Namespace::clone() const { - Namespace* ns = new Namespace(); - ns->lineNumber = lineNumber; - ns->columnNumber = columnNumber; - ns->comment = comment; - ns->namespacePrefix = namespacePrefix; - ns->namespaceUri = namespaceUri; - for (auto& child : children) { - ns->addChild(child->clone()); +Element* findRootElement(Node* node) { + if (!node) { + return nullptr; } - return std::unique_ptr<Node>(ns); -} -Element::Element() : BaseNode(NodeType::kElement) { + 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(); + } + return el; } -std::unique_ptr<Node> Element::clone() const { - Element* el = new Element(); - el->lineNumber = lineNumber; - el->columnNumber = columnNumber; - el->comment = comment; - el->namespaceUri = namespaceUri; - el->name = name; - el->attributes = attributes; - for (auto& child : children) { - el->addChild(child->clone()); - } - return std::unique_ptr<Node>(el); +void Node::addChild(std::unique_ptr<Node> child) { + child->parent = this; + children.push_back(std::move(child)); } Attribute* Element::findAttribute(const StringPiece16& ns, const StringPiece16& name) { @@ -366,29 +357,29 @@ Attribute* Element::findAttribute(const StringPiece16& ns, const StringPiece16& } Element* Element::findChild(const StringPiece16& ns, const StringPiece16& name) { - return findChildWithAttribute(ns, name, nullptr); + return findChildWithAttribute(ns, name, {}, {}, {}); } Element* Element::findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name, - const Attribute* reqAttr) { + const StringPiece16& attrNs, const StringPiece16& attrName, + const StringPiece16& attrValue) { for (auto& childNode : children) { Node* child = childNode.get(); - while (child->type == NodeType::kNamespace) { + while (nodeCast<Namespace>(child)) { if (child->children.empty()) { break; } child = child->children[0].get(); } - if (child->type == NodeType::kElement) { - Element* el = static_cast<Element*>(child); + if (Element* el = nodeCast<Element>(child)) { if (ns == el->namespaceUri && name == el->name) { - if (!reqAttr) { + if (attrNs.empty() && attrName.empty()) { return el; } - Attribute* attrName = el->findAttribute(reqAttr->namespaceUri, reqAttr->name); - if (attrName && attrName->value == reqAttr->value) { + Attribute* attr = el->findAttribute(attrNs, attrName); + if (attr && attrValue == attr->value) { return el; } } @@ -401,30 +392,52 @@ std::vector<Element*> Element::getChildElements() { std::vector<Element*> elements; for (auto& childNode : children) { Node* child = childNode.get(); - while (child->type == NodeType::kNamespace) { + while (nodeCast<Namespace>(child)) { if (child->children.empty()) { break; } child = child->children[0].get(); } - if (child->type == NodeType::kElement) { - elements.push_back(static_cast<Element*>(child)); + if (Element* el = nodeCast<Element>(child)) { + elements.push_back(el); } } return elements; } -Text::Text() : BaseNode(NodeType::kText) { +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; + } + + Visitor::visit(ns); + + if (added) { + mPackageDecls.pop_back(); + } } -std::unique_ptr<Node> Text::clone() const { - Text* el = new Text(); - el->lineNumber = lineNumber; - el->columnNumber = columnNumber; - el->comment = comment; - el->text = text; - return std::unique_ptr<Node>(el); +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 {}; } } // namespace xml diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h new file mode 100644 index 000000000000..b374d20039a5 --- /dev/null +++ b/tools/aapt2/xml/XmlDom.h @@ -0,0 +1,220 @@ +/* + * 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. + */ + +#ifndef AAPT_XML_DOM_H +#define AAPT_XML_DOM_H + +#include "Diagnostics.h" +#include "Resource.h" +#include "ResourceValues.h" +#include "util/StringPiece.h" +#include "util/Util.h" +#include "xml/XmlUtil.h" + +#include <istream> +#include <memory> +#include <string> +#include <vector> + +namespace aapt { +namespace xml { + +struct 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; +}; + +/** + * Base class that implements the visitor methods for a + * subclass of Node. + */ +template <typename Derived> +struct BaseNode : public Node { + 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; +}; + +struct AaptAttribute { + Maybe<ResourceId> id; + aapt::Attribute attribute; +}; + +/** + * An XML attribute. + */ +struct Attribute { + std::u16string namespaceUri; + std::u16string name; + std::u16string value; + + Maybe<AaptAttribute> compiledAttribute; + std::unique_ptr<Item> compiledValue; +}; + +/** + * 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(); +}; + +/** + * A Text (CDATA) XML node. Can not have any children. + */ +struct Text : public BaseNode<Text> { + std::u16string text; +}; + +/** + * An XML resource with a source, name, and XML tree. + */ +struct XmlResource { + 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); + +/** + * 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); + +Element* findRootElement(XmlResource* doc); +Element* findRootElement(Node* node); + +/** + * A visitor interface for the different XML Node subtypes. This will not traverse into + * children. Use Visitor for that. + */ +struct RawVisitor { + virtual ~RawVisitor() = default; + + 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; + + void visit(Namespace* node) override { + visitChildren(node); + } + + void visit(Element* node) override { + visitChildren(node); + } + + void visit(Text* text) override { + visitChildren(text); + } + + 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; + }; + + std::vector<PackageDecl> mPackageDecls; + +public: + using Visitor::visit; + + void visit(Namespace* ns) override; + Maybe<ExtractedPackage> transformPackageAlias( + const StringPiece16& alias, const StringPiece16& localPackage) const override; +}; + +// Implementations + +template <typename Derived> +void BaseNode<Derived>::accept(RawVisitor* visitor) { + visitor->visit(static_cast<Derived*>(this)); +} + +template <typename T> +struct NodeCastImpl : public RawVisitor { + using RawVisitor::visit; + + T* value = nullptr; + + void visit(T* v) override { + value = v; + } +}; + +template <typename T> +T* nodeCast(Node* node) { + NodeCastImpl<T> visitor; + node->accept(&visitor); + return visitor.value; +} + +} // namespace xml +} // namespace aapt + +#endif // AAPT_XML_DOM_H diff --git a/tools/aapt2/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp index 021714410e75..431ee2c8fb46 100644 --- a/tools/aapt2/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "XmlDom.h" +#include "xml/XmlDom.h" #include <gtest/gtest.h> #include <sstream> @@ -36,12 +36,13 @@ TEST(XmlDomTest, Inflate) { </Layout> )EOF"; - SourceLogger logger(Source{ "/test/path" }); - std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger); - ASSERT_NE(root, nullptr); + const Source source = { "test.xml" }; + StdErrDiagnostics diag; + std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, source); + ASSERT_NE(doc, nullptr); - EXPECT_EQ(root->type, xml::NodeType::kNamespace); - xml::Namespace* ns = static_cast<xml::Namespace*>(root.get()); + 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"); } diff --git a/tools/aapt2/SourceXmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp index 8099044f616d..323ec05b5f2c 100644 --- a/tools/aapt2/SourceXmlPullParser.cpp +++ b/tools/aapt2/xml/XmlPullParser.cpp @@ -14,18 +14,20 @@ * limitations under the License. */ +#include "util/Maybe.h" +#include "util/Util.h" +#include "xml/XmlPullParser.h" +#include "xml/XmlUtil.h" + #include <iostream> #include <string> -#include "Maybe.h" -#include "SourceXmlPullParser.h" -#include "Util.h" - namespace aapt { +namespace xml { constexpr char kXmlNamespaceSep = 1; -SourceXmlPullParser::SourceXmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) { +XmlPullParser::XmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) { mParser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep); XML_SetUserData(mParser, this); XML_SetElementHandler(mParser, startElementHandler, endElementHandler); @@ -35,11 +37,11 @@ SourceXmlPullParser::SourceXmlPullParser(std::istream& in) : mIn(in), mEmpty(), mEventQueue.push(EventData{ Event::kStartDocument, 0, mDepth++ }); } -SourceXmlPullParser::~SourceXmlPullParser() { +XmlPullParser::~XmlPullParser() { XML_ParserFree(mParser); } -SourceXmlPullParser::Event SourceXmlPullParser::next() { +XmlPullParser::Event XmlPullParser::next() { const Event currentEvent = getEvent(); if (currentEvent == Event::kBadDocument || currentEvent == Event::kEndDocument) { return currentEvent; @@ -72,14 +74,14 @@ SourceXmlPullParser::Event SourceXmlPullParser::next() { // 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<std::u16string> result = util::extractPackageFromNamespace(getNamespaceUri()); + Maybe<ExtractedPackage> result = extractPackageFromNamespace(getNamespaceUri()); if (event == Event::kStartNamespace) { if (result) { - mPackageAliases.emplace_back(getNamespacePrefix(), result.value()); + mPackageAliases.emplace_back( + PackageDecl{ getNamespacePrefix(), std::move(result.value()) }); } } else { if (result) { - assert(mPackageAliases.back().second == result.value()); mPackageAliases.pop_back(); } } @@ -88,34 +90,34 @@ SourceXmlPullParser::Event SourceXmlPullParser::next() { return event; } -SourceXmlPullParser::Event SourceXmlPullParser::getEvent() const { +XmlPullParser::Event XmlPullParser::getEvent() const { return mEventQueue.front().event; } -const std::string& SourceXmlPullParser::getLastError() const { +const std::string& XmlPullParser::getLastError() const { return mLastError; } -const std::u16string& SourceXmlPullParser::getComment() const { - return mEventQueue.front().comment; +const std::u16string& XmlPullParser::getComment() const { + return mEventQueue.front().data1; } -size_t SourceXmlPullParser::getLineNumber() const { +size_t XmlPullParser::getLineNumber() const { return mEventQueue.front().lineNumber; } -size_t SourceXmlPullParser::getDepth() const { +size_t XmlPullParser::getDepth() const { return mEventQueue.front().depth; } -const std::u16string& SourceXmlPullParser::getText() const { +const std::u16string& XmlPullParser::getText() const { if (getEvent() != Event::kText) { return mEmpty; } return mEventQueue.front().data1; } -const std::u16string& SourceXmlPullParser::getNamespacePrefix() const { +const std::u16string& XmlPullParser::getNamespacePrefix() const { const Event currentEvent = getEvent(); if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) { return mEmpty; @@ -123,7 +125,7 @@ const std::u16string& SourceXmlPullParser::getNamespacePrefix() const { return mEventQueue.front().data1; } -const std::u16string& SourceXmlPullParser::getNamespaceUri() const { +const std::u16string& XmlPullParser::getNamespaceUri() const { const Event currentEvent = getEvent(); if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) { return mEmpty; @@ -131,23 +133,26 @@ const std::u16string& SourceXmlPullParser::getNamespaceUri() const { return mEventQueue.front().data2; } -bool SourceXmlPullParser::applyPackageAlias(std::u16string* package, - const std::u16string& defaultPackage) const { +Maybe<ExtractedPackage> XmlPullParser::transformPackageAlias( + const StringPiece16& alias, const StringPiece16& localPackage) const { + if (alias.empty()) { + return ExtractedPackage{ localPackage.toString(), false /* private */ }; + } + const auto endIter = mPackageAliases.rend(); for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) { - if (iter->first == *package) { - if (iter->second.empty()) { - *package = defaultPackage; - } else { - *package = iter->second; + if (alias == iter->prefix) { + if (iter->package.package.empty()) { + return ExtractedPackage{ localPackage.toString(), + iter->package.privateNamespace }; } - return true; + return iter->package; } } - return false; + return {}; } -const std::u16string& SourceXmlPullParser::getElementNamespace() const { +const std::u16string& XmlPullParser::getElementNamespace() const { const Event currentEvent = getEvent(); if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) { return mEmpty; @@ -155,7 +160,7 @@ const std::u16string& SourceXmlPullParser::getElementNamespace() const { return mEventQueue.front().data1; } -const std::u16string& SourceXmlPullParser::getElementName() const { +const std::u16string& XmlPullParser::getElementName() const { const Event currentEvent = getEvent(); if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) { return mEmpty; @@ -163,15 +168,15 @@ const std::u16string& SourceXmlPullParser::getElementName() const { return mEventQueue.front().data2; } -XmlPullParser::const_iterator SourceXmlPullParser::beginAttributes() const { +XmlPullParser::const_iterator XmlPullParser::beginAttributes() const { return mEventQueue.front().attributes.begin(); } -XmlPullParser::const_iterator SourceXmlPullParser::endAttributes() const { +XmlPullParser::const_iterator XmlPullParser::endAttributes() const { return mEventQueue.front().attributes.end(); } -size_t SourceXmlPullParser::getAttributeCount() const { +size_t XmlPullParser::getAttributeCount() const { if (getEvent() != Event::kStartElement) { return 0; } @@ -196,9 +201,9 @@ static void splitName(const char* name, std::u16string& outNs, std::u16string& o } } -void XMLCALL SourceXmlPullParser::startNamespaceHandler(void* userData, const char* prefix, +void XMLCALL XmlPullParser::startNamespaceHandler(void* userData, const char* prefix, const char* uri) { - SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData); + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); std::u16string namespaceUri = uri != nullptr ? util::utf8ToUtf16(uri) : std::u16string(); parser->mNamespaceUris.push(namespaceUri); parser->mEventQueue.push(EventData{ @@ -210,9 +215,9 @@ void XMLCALL SourceXmlPullParser::startNamespaceHandler(void* userData, const ch }); } -void XMLCALL SourceXmlPullParser::startElementHandler(void* userData, const char* name, +void XMLCALL XmlPullParser::startElementHandler(void* userData, const char* name, const char** attrs) { - SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData); + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); EventData data = { Event::kStartElement, XML_GetCurrentLineNumber(parser->mParser), parser->mDepth++ @@ -233,8 +238,8 @@ void XMLCALL SourceXmlPullParser::startElementHandler(void* userData, const char parser->mEventQueue.push(std::move(data)); } -void XMLCALL SourceXmlPullParser::characterDataHandler(void* userData, const char* s, int len) { - SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData); +void XMLCALL XmlPullParser::characterDataHandler(void* userData, const char* s, int len) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); parser->mEventQueue.push(EventData{ Event::kText, @@ -244,8 +249,8 @@ void XMLCALL SourceXmlPullParser::characterDataHandler(void* userData, const cha }); } -void XMLCALL SourceXmlPullParser::endElementHandler(void* userData, const char* name) { - SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData); +void XMLCALL XmlPullParser::endElementHandler(void* userData, const char* name) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); EventData data = { Event::kEndElement, XML_GetCurrentLineNumber(parser->mParser), --(parser->mDepth) @@ -256,8 +261,8 @@ void XMLCALL SourceXmlPullParser::endElementHandler(void* userData, const char* parser->mEventQueue.push(std::move(data)); } -void XMLCALL SourceXmlPullParser::endNamespaceHandler(void* userData, const char* prefix) { - SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData); +void XMLCALL XmlPullParser::endNamespaceHandler(void* userData, const char* prefix) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); parser->mEventQueue.push(EventData{ Event::kEndNamespace, @@ -269,8 +274,8 @@ void XMLCALL SourceXmlPullParser::endNamespaceHandler(void* userData, const char parser->mNamespaceUris.pop(); } -void XMLCALL SourceXmlPullParser::commentDataHandler(void* userData, const char* comment) { - SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData); +void XMLCALL XmlPullParser::commentDataHandler(void* userData, const char* comment) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); parser->mEventQueue.push(EventData{ Event::kComment, @@ -280,4 +285,24 @@ void XMLCALL SourceXmlPullParser::commentDataHandler(void* userData, const char* }); } +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<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; + } + } + return {}; +} + +} // namespace xml } // namespace aapt diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h index accfd30a4775..7e7070e5e5ea 100644 --- a/tools/aapt2/XmlPullParser.h +++ b/tools/aapt2/xml/XmlPullParser.h @@ -17,16 +17,25 @@ #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 <algorithm> +#include <expat.h> +#include <istream> #include <ostream> +#include <queue> +#include <stack> #include <string> #include <vector> -#include "StringPiece.h" - namespace aapt { +namespace xml { -class XmlPullParser { +class XmlPullParser : public IPackageDeclStack { public: enum class Event { kBadDocument, @@ -41,43 +50,58 @@ public: kComment, }; - static void skipCurrentElement(XmlPullParser* parser); + /** + * 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); - virtual ~XmlPullParser() {} + XmlPullParser(std::istream& in); + ~XmlPullParser(); /** * Returns the current event that is being processed. */ - virtual Event getEvent() const = 0; + Event getEvent() const; - virtual const std::string& getLastError() const = 0; + const std::string& getLastError() const; /** * Note, unlike XmlPullParser, the first call to next() will return * StartElement of the first element. */ - virtual Event next() = 0; + Event next(); // // These are available for all nodes. // - virtual const std::u16string& getComment() const = 0; - virtual size_t getLineNumber() const = 0; - virtual size_t getDepth() const = 0; + const std::u16string& getComment() const; + size_t getLineNumber() const; + size_t getDepth() const; /** * Returns the character data for a Text event. */ - virtual const std::u16string& getText() const = 0; + const std::u16string& getText() const; // // Namespace prefix and URI are available for StartNamespace and EndNamespace. // - virtual const std::u16string& getNamespacePrefix() const = 0; - virtual const std::u16string& getNamespaceUri() const = 0; + 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: @@ -90,15 +114,8 @@ public: * If xmlns:app="http://schemas.android.com/apk/res-auto", then * 'package' will be set to 'defaultPackage'. */ - virtual bool applyPackageAlias(std::u16string* package, - const std::u16string& defaultPackage) const = 0; - - // - // These are available for StartElement and EndElement. - // - - virtual const std::u16string& getElementNamespace() const = 0; - virtual const std::u16string& getElementName() const = 0; + Maybe<ExtractedPackage> transformPackageAlias( + const StringPiece16& alias, const StringPiece16& localPackage) const override; // // Remaining methods are for retrieving information about attributes @@ -121,12 +138,55 @@ public: using const_iterator = std::vector<Attribute>::const_iterator; - virtual const_iterator beginAttributes() const = 0; - virtual const_iterator endAttributes() const = 0; - virtual size_t getAttributeCount() const = 0; + 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; }; +/** + * Finds the attribute in the current element within the global namespace. + */ +Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name); + +/** + * 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); + // // Implementation // @@ -146,13 +206,35 @@ inline ::std::ostream& operator<<(::std::ostream& out, XmlPullParser::Event even return out; } -inline void XmlPullParser::skipCurrentElement(XmlPullParser* parser) { +inline bool XmlPullParser::nextChildNode(XmlPullParser* parser, size_t startDepth) { + Event event; + + // 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(); + } + 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; + return false; case Event::kStartElement: depth++; break; @@ -163,6 +245,7 @@ inline void XmlPullParser::skipCurrentElement(XmlPullParser* parser) { break; } } + return true; } inline bool XmlPullParser::isGoodEvent(XmlPullParser::Event event) { @@ -209,6 +292,7 @@ inline XmlPullParser::const_iterator XmlPullParser::findAttribute(StringPiece16 return endIter; } +} // namespace xml } // namespace aapt #endif // AAPT_XML_PULL_PARSER_H diff --git a/tools/aapt2/xml/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp new file mode 100644 index 000000000000..8fa2c6d274c8 --- /dev/null +++ b/tools/aapt2/xml/XmlPullParser_test.cpp @@ -0,0 +1,55 @@ +/* + * 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 "util/StringPiece.h" +#include "xml/XmlPullParser.h" + +#include <gtest/gtest.h> +#include <sstream> + +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); + + const size_t depthOuter = parser.getDepth(); + ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthOuter)); + + EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent()); + EXPECT_EQ(StringPiece16(u"a"), StringPiece16(parser.getElementName())); + + 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 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())); + + ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB)); + EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent()); + EXPECT_EQ(StringPiece16(u"e"), StringPiece16(parser.getElementName())); + + ASSERT_FALSE(xml::XmlPullParser::nextChildNode(&parser, depthOuter)); + EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.getEvent()); +} + +} // namespace aapt diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp new file mode 100644 index 000000000000..ab9f544d67ea --- /dev/null +++ b/tools/aapt2/xml/XmlUtil.cpp @@ -0,0 +1,67 @@ +/* + * 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 "util/Maybe.h" +#include "util/Util.h" +#include "xml/XmlUtil.h" + +#include <string> + +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 */ }; + + } 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 */ }; + + } else if (namespaceUri == kSchemaAuto) { + return ExtractedPackage{ std::u16string(), true /* isPrivate */ }; + } + 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); + + // If the reference was already private (with a * prefix) and the namespace is public, + // we keep the reference private. + inRef->privateReference |= extractedPackage.privateNamespace; + } + } +} + +} // namespace xml +} // namespace aapt diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h new file mode 100644 index 000000000000..98e5520a6ea2 --- /dev/null +++ b/tools/aapt2/xml/XmlUtil.h @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#ifndef AAPT_XML_XMLUTIL_H +#define AAPT_XML_XMLUTIL_H + +#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"; + +/** + * 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; + + /** + * True if the package's private namespace was declared. This means that private resources + * are made visible. + */ + bool privateNamespace; +}; + +/** + * Returns an ExtractedPackage struct if the namespace URI is of the form: + * http://schemas.android.com/apk/res/<package> or + * http://schemas.android.com/apk/prv/res/<package> + * + * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto, + * returns an empty package name. + */ +Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri); + +/** + * 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; + + /** + * 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; +}; + +/** + * 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); + +} // 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 new file mode 100644 index 000000000000..319e7707d874 --- /dev/null +++ b/tools/aapt2/xml/XmlUtil_test.cpp @@ -0,0 +1,54 @@ +/* + * 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 "test/Common.h" +#include "xml/XmlUtil.h" + +#include <gtest/gtest.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); +} + +} // namespace aapt diff --git a/tools/aidl b/tools/aidl new file mode 100644 index 000000000000..8a42fa0d0a35 --- /dev/null +++ b/tools/aidl @@ -0,0 +1,4 @@ +Where has aidl gone? + + aidl has moved to //system/tools/aidl as part of adding support for + generating bindings in C++. diff --git a/tools/aidl/AST.cpp b/tools/aidl/AST.cpp deleted file mode 100644 index bfa67656b323..000000000000 --- a/tools/aidl/AST.cpp +++ /dev/null @@ -1,912 +0,0 @@ -#include "AST.h" -#include "Type.h" - -void -WriteModifiers(FILE* to, int mod, int mask) -{ - int m = mod & mask; - - if (m & OVERRIDE) { - fprintf(to, "@Override "); - } - - if ((m & SCOPE_MASK) == PUBLIC) { - fprintf(to, "public "); - } - else if ((m & SCOPE_MASK) == PRIVATE) { - fprintf(to, "private "); - } - else if ((m & SCOPE_MASK) == PROTECTED) { - fprintf(to, "protected "); - } - - if (m & STATIC) { - fprintf(to, "static "); - } - - if (m & FINAL) { - fprintf(to, "final "); - } - - if (m & ABSTRACT) { - fprintf(to, "abstract "); - } -} - -void -WriteArgumentList(FILE* to, const vector<Expression*>& arguments) -{ - size_t N = arguments.size(); - for (size_t i=0; i<N; i++) { - arguments[i]->Write(to); - if (i != N-1) { - fprintf(to, ", "); - } - } -} - -ClassElement::ClassElement() -{ -} - -ClassElement::~ClassElement() -{ -} - -Field::Field() - :ClassElement(), - modifiers(0), - variable(NULL) -{ -} - -Field::Field(int m, Variable* v) - :ClassElement(), - modifiers(m), - variable(v) -{ -} - -Field::~Field() -{ -} - -void -Field::GatherTypes(set<Type*>* types) const -{ - types->insert(this->variable->type); -} - -void -Field::Write(FILE* to) -{ - if (this->comment.length() != 0) { - fprintf(to, "%s\n", this->comment.c_str()); - } - WriteModifiers(to, this->modifiers, SCOPE_MASK | STATIC | FINAL | OVERRIDE); - fprintf(to, "%s %s", this->variable->type->QualifiedName().c_str(), - this->variable->name.c_str()); - if (this->value.length() != 0) { - fprintf(to, " = %s", this->value.c_str()); - } - fprintf(to, ";\n"); -} - -Expression::~Expression() -{ -} - -LiteralExpression::LiteralExpression(const string& v) - :value(v) -{ -} - -LiteralExpression::~LiteralExpression() -{ -} - -void -LiteralExpression::Write(FILE* to) -{ - fprintf(to, "%s", this->value.c_str()); -} - -StringLiteralExpression::StringLiteralExpression(const string& v) - :value(v) -{ -} - -StringLiteralExpression::~StringLiteralExpression() -{ -} - -void -StringLiteralExpression::Write(FILE* to) -{ - fprintf(to, "\"%s\"", this->value.c_str()); -} - -Variable::Variable() - :type(NULL), - name(), - dimension(0) -{ -} - -Variable::Variable(Type* t, const string& n) - :type(t), - name(n), - dimension(0) -{ -} - -Variable::Variable(Type* t, const string& n, int d) - :type(t), - name(n), - dimension(d) -{ -} - -Variable::~Variable() -{ -} - -void -Variable::GatherTypes(set<Type*>* types) const -{ - types->insert(this->type); -} - -void -Variable::WriteDeclaration(FILE* to) -{ - string dim; - for (int i=0; i<this->dimension; i++) { - dim += "[]"; - } - fprintf(to, "%s%s %s", this->type->QualifiedName().c_str(), dim.c_str(), - this->name.c_str()); -} - -void -Variable::Write(FILE* to) -{ - fprintf(to, "%s", name.c_str()); -} - -FieldVariable::FieldVariable(Expression* o, const string& n) - :object(o), - clazz(NULL), - name(n) -{ -} - -FieldVariable::FieldVariable(Type* c, const string& n) - :object(NULL), - clazz(c), - name(n) -{ -} - -FieldVariable::~FieldVariable() -{ -} - -void -FieldVariable::Write(FILE* to) -{ - if (this->object != NULL) { - this->object->Write(to); - } - else if (this->clazz != NULL) { - fprintf(to, "%s", this->clazz->QualifiedName().c_str()); - } - fprintf(to, ".%s", name.c_str()); -} - - -Statement::~Statement() -{ -} - -StatementBlock::StatementBlock() -{ -} - -StatementBlock::~StatementBlock() -{ -} - -void -StatementBlock::Write(FILE* to) -{ - fprintf(to, "{\n"); - int N = this->statements.size(); - for (int i=0; i<N; i++) { - this->statements[i]->Write(to); - } - fprintf(to, "}\n"); -} - -void -StatementBlock::Add(Statement* statement) -{ - this->statements.push_back(statement); -} - -void -StatementBlock::Add(Expression* expression) -{ - this->statements.push_back(new ExpressionStatement(expression)); -} - -ExpressionStatement::ExpressionStatement(Expression* e) - :expression(e) -{ -} - -ExpressionStatement::~ExpressionStatement() -{ -} - -void -ExpressionStatement::Write(FILE* to) -{ - this->expression->Write(to); - fprintf(to, ";\n"); -} - -Assignment::Assignment(Variable* l, Expression* r) - :lvalue(l), - rvalue(r), - cast(NULL) -{ -} - -Assignment::Assignment(Variable* l, Expression* r, Type* c) - :lvalue(l), - rvalue(r), - cast(c) -{ -} - -Assignment::~Assignment() -{ -} - -void -Assignment::Write(FILE* to) -{ - this->lvalue->Write(to); - fprintf(to, " = "); - if (this->cast != NULL) { - fprintf(to, "(%s)", this->cast->QualifiedName().c_str()); - } - this->rvalue->Write(to); -} - -MethodCall::MethodCall(const string& n) - :obj(NULL), - clazz(NULL), - name(n) -{ -} - -MethodCall::MethodCall(const string& n, int argc = 0, ...) - :obj(NULL), - clazz(NULL), - name(n) -{ - va_list args; - va_start(args, argc); - init(argc, args); - va_end(args); -} - -MethodCall::MethodCall(Expression* o, const string& n) - :obj(o), - clazz(NULL), - name(n) -{ -} - -MethodCall::MethodCall(Type* t, const string& n) - :obj(NULL), - clazz(t), - name(n) -{ -} - -MethodCall::MethodCall(Expression* o, const string& n, int argc = 0, ...) - :obj(o), - clazz(NULL), - name(n) -{ - va_list args; - va_start(args, argc); - init(argc, args); - va_end(args); -} - -MethodCall::MethodCall(Type* t, const string& n, int argc = 0, ...) - :obj(NULL), - clazz(t), - name(n) -{ - va_list args; - va_start(args, argc); - init(argc, args); - va_end(args); -} - -MethodCall::~MethodCall() -{ -} - -void -MethodCall::init(int n, va_list args) -{ - for (int i=0; i<n; i++) { - Expression* expression = (Expression*)va_arg(args, void*); - this->arguments.push_back(expression); - } -} - -void -MethodCall::Write(FILE* to) -{ - if (this->obj != NULL) { - this->obj->Write(to); - fprintf(to, "."); - } - else if (this->clazz != NULL) { - fprintf(to, "%s.", this->clazz->QualifiedName().c_str()); - } - fprintf(to, "%s(", this->name.c_str()); - WriteArgumentList(to, this->arguments); - fprintf(to, ")"); -} - -Comparison::Comparison(Expression* l, const string& o, Expression* r) - :lvalue(l), - op(o), - rvalue(r) -{ -} - -Comparison::~Comparison() -{ -} - -void -Comparison::Write(FILE* to) -{ - fprintf(to, "("); - this->lvalue->Write(to); - fprintf(to, "%s", this->op.c_str()); - this->rvalue->Write(to); - fprintf(to, ")"); -} - -NewExpression::NewExpression(Type* t) - :type(t) -{ -} - -NewExpression::NewExpression(Type* t, int argc = 0, ...) - :type(t) -{ - va_list args; - va_start(args, argc); - init(argc, args); - va_end(args); -} - -NewExpression::~NewExpression() -{ -} - -void -NewExpression::init(int n, va_list args) -{ - for (int i=0; i<n; i++) { - Expression* expression = (Expression*)va_arg(args, void*); - this->arguments.push_back(expression); - } -} - -void -NewExpression::Write(FILE* to) -{ - fprintf(to, "new %s(", this->type->InstantiableName().c_str()); - WriteArgumentList(to, this->arguments); - fprintf(to, ")"); -} - -NewArrayExpression::NewArrayExpression(Type* t, Expression* s) - :type(t), - size(s) -{ -} - -NewArrayExpression::~NewArrayExpression() -{ -} - -void -NewArrayExpression::Write(FILE* to) -{ - fprintf(to, "new %s[", this->type->QualifiedName().c_str()); - size->Write(to); - fprintf(to, "]"); -} - -Ternary::Ternary() - :condition(NULL), - ifpart(NULL), - elsepart(NULL) -{ -} - -Ternary::Ternary(Expression* a, Expression* b, Expression* c) - :condition(a), - ifpart(b), - elsepart(c) -{ -} - -Ternary::~Ternary() -{ -} - -void -Ternary::Write(FILE* to) -{ - fprintf(to, "(("); - this->condition->Write(to); - fprintf(to, ")?("); - this->ifpart->Write(to); - fprintf(to, "):("); - this->elsepart->Write(to); - fprintf(to, "))"); -} - -Cast::Cast() - :type(NULL), - expression(NULL) -{ -} - -Cast::Cast(Type* t, Expression* e) - :type(t), - expression(e) -{ -} - -Cast::~Cast() -{ -} - -void -Cast::Write(FILE* to) -{ - fprintf(to, "((%s)", this->type->QualifiedName().c_str()); - expression->Write(to); - fprintf(to, ")"); -} - -VariableDeclaration::VariableDeclaration(Variable* l, Expression* r, Type* c) - :lvalue(l), - cast(c), - rvalue(r) -{ -} - -VariableDeclaration::VariableDeclaration(Variable* l) - :lvalue(l), - cast(NULL), - rvalue(NULL) -{ -} - -VariableDeclaration::~VariableDeclaration() -{ -} - -void -VariableDeclaration::Write(FILE* to) -{ - this->lvalue->WriteDeclaration(to); - if (this->rvalue != NULL) { - fprintf(to, " = "); - if (this->cast != NULL) { - fprintf(to, "(%s)", this->cast->QualifiedName().c_str()); - } - this->rvalue->Write(to); - } - fprintf(to, ";\n"); -} - -IfStatement::IfStatement() - :expression(NULL), - statements(new StatementBlock), - elseif(NULL) -{ -} - -IfStatement::~IfStatement() -{ -} - -void -IfStatement::Write(FILE* to) -{ - if (this->expression != NULL) { - fprintf(to, "if ("); - this->expression->Write(to); - fprintf(to, ") "); - } - this->statements->Write(to); - if (this->elseif != NULL) { - fprintf(to, "else "); - this->elseif->Write(to); - } -} - -ReturnStatement::ReturnStatement(Expression* e) - :expression(e) -{ -} - -ReturnStatement::~ReturnStatement() -{ -} - -void -ReturnStatement::Write(FILE* to) -{ - fprintf(to, "return "); - this->expression->Write(to); - fprintf(to, ";\n"); -} - -TryStatement::TryStatement() - :statements(new StatementBlock) -{ -} - -TryStatement::~TryStatement() -{ -} - -void -TryStatement::Write(FILE* to) -{ - fprintf(to, "try "); - this->statements->Write(to); -} - -CatchStatement::CatchStatement(Variable* e) - :statements(new StatementBlock), - exception(e) -{ -} - -CatchStatement::~CatchStatement() -{ -} - -void -CatchStatement::Write(FILE* to) -{ - fprintf(to, "catch "); - if (this->exception != NULL) { - fprintf(to, "("); - this->exception->WriteDeclaration(to); - fprintf(to, ") "); - } - this->statements->Write(to); -} - -FinallyStatement::FinallyStatement() - :statements(new StatementBlock) -{ -} - -FinallyStatement::~FinallyStatement() -{ -} - -void -FinallyStatement::Write(FILE* to) -{ - fprintf(to, "finally "); - this->statements->Write(to); -} - -Case::Case() - :statements(new StatementBlock) -{ -} - -Case::Case(const string& c) - :statements(new StatementBlock) -{ - cases.push_back(c); -} - -Case::~Case() -{ -} - -void -Case::Write(FILE* to) -{ - int N = this->cases.size(); - if (N > 0) { - for (int i=0; i<N; i++) { - string s = this->cases[i]; - if (s.length() != 0) { - fprintf(to, "case %s:\n", s.c_str()); - } else { - fprintf(to, "default:\n"); - } - } - } else { - fprintf(to, "default:\n"); - } - statements->Write(to); -} - -SwitchStatement::SwitchStatement(Expression* e) - :expression(e) -{ -} - -SwitchStatement::~SwitchStatement() -{ -} - -void -SwitchStatement::Write(FILE* to) -{ - fprintf(to, "switch ("); - this->expression->Write(to); - fprintf(to, ")\n{\n"); - int N = this->cases.size(); - for (int i=0; i<N; i++) { - this->cases[i]->Write(to); - } - fprintf(to, "}\n"); -} - -Break::Break() -{ -} - -Break::~Break() -{ -} - -void -Break::Write(FILE* to) -{ - fprintf(to, "break;\n"); -} - -Method::Method() - :ClassElement(), - modifiers(0), - returnType(NULL), // (NULL means constructor) - returnTypeDimension(0), - statements(NULL) -{ -} - -Method::~Method() -{ -} - -void -Method::GatherTypes(set<Type*>* types) const -{ - size_t N, i; - - if (this->returnType) { - types->insert(this->returnType); - } - - N = this->parameters.size(); - for (i=0; i<N; i++) { - this->parameters[i]->GatherTypes(types); - } - - N = this->exceptions.size(); - for (i=0; i<N; i++) { - types->insert(this->exceptions[i]); - } -} - -void -Method::Write(FILE* to) -{ - size_t N, i; - - if (this->comment.length() != 0) { - fprintf(to, "%s\n", this->comment.c_str()); - } - - WriteModifiers(to, this->modifiers, SCOPE_MASK | STATIC | ABSTRACT | FINAL | OVERRIDE); - - if (this->returnType != NULL) { - string dim; - for (i=0; i<this->returnTypeDimension; i++) { - dim += "[]"; - } - fprintf(to, "%s%s ", this->returnType->QualifiedName().c_str(), - dim.c_str()); - } - - fprintf(to, "%s(", this->name.c_str()); - - N = this->parameters.size(); - for (i=0; i<N; i++) { - this->parameters[i]->WriteDeclaration(to); - if (i != N-1) { - fprintf(to, ", "); - } - } - - fprintf(to, ")"); - - N = this->exceptions.size(); - for (i=0; i<N; i++) { - if (i == 0) { - fprintf(to, " throws "); - } else { - fprintf(to, ", "); - } - fprintf(to, "%s", this->exceptions[i]->QualifiedName().c_str()); - } - - if (this->statements == NULL) { - fprintf(to, ";\n"); - } else { - fprintf(to, "\n"); - this->statements->Write(to); - } -} - -Class::Class() - :modifiers(0), - what(CLASS), - type(NULL), - extends(NULL) -{ -} - -Class::~Class() -{ -} - -void -Class::GatherTypes(set<Type*>* types) const -{ - int N, i; - - types->insert(this->type); - if (this->extends != NULL) { - types->insert(this->extends); - } - - N = this->interfaces.size(); - for (i=0; i<N; i++) { - types->insert(this->interfaces[i]); - } - - N = this->elements.size(); - for (i=0; i<N; i++) { - this->elements[i]->GatherTypes(types); - } -} - -void -Class::Write(FILE* to) -{ - size_t N, i; - - if (this->comment.length() != 0) { - fprintf(to, "%s\n", this->comment.c_str()); - } - - WriteModifiers(to, this->modifiers, ALL_MODIFIERS); - - if (this->what == Class::CLASS) { - fprintf(to, "class "); - } else { - fprintf(to, "interface "); - } - - string name = this->type->Name(); - size_t pos = name.rfind('.'); - if (pos != string::npos) { - name = name.c_str() + pos + 1; - } - - fprintf(to, "%s", name.c_str()); - - if (this->extends != NULL) { - fprintf(to, " extends %s", this->extends->QualifiedName().c_str()); - } - - N = this->interfaces.size(); - if (N != 0) { - if (this->what == Class::CLASS) { - fprintf(to, " implements"); - } else { - fprintf(to, " extends"); - } - for (i=0; i<N; i++) { - fprintf(to, " %s", this->interfaces[i]->QualifiedName().c_str()); - } - } - - fprintf(to, "\n"); - fprintf(to, "{\n"); - - N = this->elements.size(); - for (i=0; i<N; i++) { - this->elements[i]->Write(to); - } - - fprintf(to, "}\n"); - -} - -Document::Document() -{ -} - -Document::~Document() -{ -} - -static string -escape_backslashes(const string& str) -{ - string result; - const size_t I=str.length(); - for (size_t i=0; i<I; i++) { - char c = str[i]; - if (c == '\\') { - result += "\\\\"; - } else { - result += c; - } - } - return result; -} - -void -Document::Write(FILE* to) -{ - size_t N, i; - - if (this->comment.length() != 0) { - fprintf(to, "%s\n", this->comment.c_str()); - } - fprintf(to, "/*\n" - " * This file is auto-generated. DO NOT MODIFY.\n" - " * Original file: %s\n" - " */\n", escape_backslashes(this->originalSrc).c_str()); - if (this->package.length() != 0) { - fprintf(to, "package %s;\n", this->package.c_str()); - } - - N = this->classes.size(); - for (i=0; i<N; i++) { - Class* c = this->classes[i]; - c->Write(to); - } -} - diff --git a/tools/aidl/AST.h b/tools/aidl/AST.h deleted file mode 100644 index ead5e7ae3439..000000000000 --- a/tools/aidl/AST.h +++ /dev/null @@ -1,371 +0,0 @@ -#ifndef AIDL_AST_H -#define AIDL_AST_H - -#include <string> -#include <vector> -#include <set> -#include <stdarg.h> -#include <stdio.h> - -using namespace std; - -class Type; - -enum { - PACKAGE_PRIVATE = 0x00000000, - PUBLIC = 0x00000001, - PRIVATE = 0x00000002, - PROTECTED = 0x00000003, - SCOPE_MASK = 0x00000003, - - STATIC = 0x00000010, - FINAL = 0x00000020, - ABSTRACT = 0x00000040, - - OVERRIDE = 0x00000100, - - ALL_MODIFIERS = 0xffffffff -}; - -// Write the modifiers that are set in both mod and mask -void WriteModifiers(FILE* to, int mod, int mask); - -struct ClassElement -{ - ClassElement(); - virtual ~ClassElement(); - - virtual void GatherTypes(set<Type*>* types) const = 0; - virtual void Write(FILE* to) = 0; -}; - -struct Expression -{ - virtual ~Expression(); - virtual void Write(FILE* to) = 0; -}; - -struct LiteralExpression : public Expression -{ - string value; - - LiteralExpression(const string& value); - virtual ~LiteralExpression(); - virtual void Write(FILE* to); -}; - -// TODO: also escape the contents. not needed for now -struct StringLiteralExpression : public Expression -{ - string value; - - StringLiteralExpression(const string& value); - virtual ~StringLiteralExpression(); - virtual void Write(FILE* to); -}; - -struct Variable : public Expression -{ - Type* type; - string name; - int dimension; - - Variable(); - Variable(Type* type, const string& name); - Variable(Type* type, const string& name, int dimension); - virtual ~Variable(); - - virtual void GatherTypes(set<Type*>* types) const; - void WriteDeclaration(FILE* to); - void Write(FILE* to); -}; - -struct FieldVariable : public Expression -{ - Expression* object; - Type* clazz; - string name; - - FieldVariable(Expression* object, const string& name); - FieldVariable(Type* clazz, const string& name); - virtual ~FieldVariable(); - - void Write(FILE* to); -}; - -struct Field : public ClassElement -{ - string comment; - int modifiers; - Variable *variable; - string value; - - Field(); - Field(int modifiers, Variable* variable); - virtual ~Field(); - - virtual void GatherTypes(set<Type*>* types) const; - virtual void Write(FILE* to); -}; - -struct Statement -{ - virtual ~Statement(); - virtual void Write(FILE* to) = 0; -}; - -struct StatementBlock : public Statement -{ - vector<Statement*> statements; - - StatementBlock(); - virtual ~StatementBlock(); - virtual void Write(FILE* to); - - void Add(Statement* statement); - void Add(Expression* expression); -}; - -struct ExpressionStatement : public Statement -{ - Expression* expression; - - ExpressionStatement(Expression* expression); - virtual ~ExpressionStatement(); - virtual void Write(FILE* to); -}; - -struct Assignment : public Expression -{ - Variable* lvalue; - Expression* rvalue; - Type* cast; - - Assignment(Variable* lvalue, Expression* rvalue); - Assignment(Variable* lvalue, Expression* rvalue, Type* cast); - virtual ~Assignment(); - virtual void Write(FILE* to); -}; - -struct MethodCall : public Expression -{ - Expression* obj; - Type* clazz; - string name; - vector<Expression*> arguments; - vector<string> exceptions; - - MethodCall(const string& name); - MethodCall(const string& name, int argc, ...); - MethodCall(Expression* obj, const string& name); - MethodCall(Type* clazz, const string& name); - MethodCall(Expression* obj, const string& name, int argc, ...); - MethodCall(Type* clazz, const string& name, int argc, ...); - virtual ~MethodCall(); - virtual void Write(FILE* to); - -private: - void init(int n, va_list args); -}; - -struct Comparison : public Expression -{ - Expression* lvalue; - string op; - Expression* rvalue; - - Comparison(Expression* lvalue, const string& op, Expression* rvalue); - virtual ~Comparison(); - virtual void Write(FILE* to); -}; - -struct NewExpression : public Expression -{ - Type* type; - vector<Expression*> arguments; - - NewExpression(Type* type); - NewExpression(Type* type, int argc, ...); - virtual ~NewExpression(); - virtual void Write(FILE* to); - -private: - void init(int n, va_list args); -}; - -struct NewArrayExpression : public Expression -{ - Type* type; - Expression* size; - - NewArrayExpression(Type* type, Expression* size); - virtual ~NewArrayExpression(); - virtual void Write(FILE* to); -}; - -struct Ternary : public Expression -{ - Expression* condition; - Expression* ifpart; - Expression* elsepart; - - Ternary(); - Ternary(Expression* condition, Expression* ifpart, Expression* elsepart); - virtual ~Ternary(); - virtual void Write(FILE* to); -}; - -struct Cast : public Expression -{ - Type* type; - Expression* expression; - - Cast(); - Cast(Type* type, Expression* expression); - virtual ~Cast(); - virtual void Write(FILE* to); -}; - -struct VariableDeclaration : public Statement -{ - Variable* lvalue; - Type* cast; - Expression* rvalue; - - VariableDeclaration(Variable* lvalue); - VariableDeclaration(Variable* lvalue, Expression* rvalue, Type* cast = NULL); - virtual ~VariableDeclaration(); - virtual void Write(FILE* to); -}; - -struct IfStatement : public Statement -{ - Expression* expression; - StatementBlock* statements; - IfStatement* elseif; - - IfStatement(); - virtual ~IfStatement(); - virtual void Write(FILE* to); -}; - -struct ReturnStatement : public Statement -{ - Expression* expression; - - ReturnStatement(Expression* expression); - virtual ~ReturnStatement(); - virtual void Write(FILE* to); -}; - -struct TryStatement : public Statement -{ - StatementBlock* statements; - - TryStatement(); - virtual ~TryStatement(); - virtual void Write(FILE* to); -}; - -struct CatchStatement : public Statement -{ - StatementBlock* statements; - Variable* exception; - - CatchStatement(Variable* exception); - virtual ~CatchStatement(); - virtual void Write(FILE* to); -}; - -struct FinallyStatement : public Statement -{ - StatementBlock* statements; - - FinallyStatement(); - virtual ~FinallyStatement(); - virtual void Write(FILE* to); -}; - -struct Case -{ - vector<string> cases; - StatementBlock* statements; - - Case(); - Case(const string& c); - virtual ~Case(); - virtual void Write(FILE* to); -}; - -struct SwitchStatement : public Statement -{ - Expression* expression; - vector<Case*> cases; - - SwitchStatement(Expression* expression); - virtual ~SwitchStatement(); - virtual void Write(FILE* to); -}; - -struct Break : public Statement -{ - Break(); - virtual ~Break(); - virtual void Write(FILE* to); -}; - -struct Method : public ClassElement -{ - string comment; - int modifiers; - Type* returnType; - size_t returnTypeDimension; - string name; - vector<Variable*> parameters; - vector<Type*> exceptions; - StatementBlock* statements; - - Method(); - virtual ~Method(); - - virtual void GatherTypes(set<Type*>* types) const; - virtual void Write(FILE* to); -}; - -struct Class : public ClassElement -{ - enum { - CLASS, - INTERFACE - }; - - string comment; - int modifiers; - int what; // CLASS or INTERFACE - Type* type; - Type* extends; - vector<Type*> interfaces; - vector<ClassElement*> elements; - - Class(); - virtual ~Class(); - - virtual void GatherTypes(set<Type*>* types) const; - virtual void Write(FILE* to); -}; - -struct Document -{ - string comment; - string package; - string originalSrc; - set<Type*> imports; - vector<Class*> classes; - - Document(); - virtual ~Document(); - - virtual void Write(FILE* to); -}; - -#endif // AIDL_AST_H diff --git a/tools/aidl/Android.mk b/tools/aidl/Android.mk deleted file mode 100644 index efd60a2cda99..000000000000 --- a/tools/aidl/Android.mk +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2007 The Android Open Source Project -# -# Copies files into the directory structure described by a manifest - -# This tool is prebuilt if we're doing an app-only build. -ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),) - -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - aidl_language_l.l \ - aidl_language_y.y \ - aidl.cpp \ - aidl_language.cpp \ - options.cpp \ - search_path.cpp \ - AST.cpp \ - Type.cpp \ - generate_java.cpp \ - generate_java_binder.cpp \ - generate_java_rpc.cpp - -LOCAL_CFLAGS := -g -LOCAL_MODULE := aidl - -include $(BUILD_HOST_EXECUTABLE) - -endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK diff --git a/tools/aidl/NOTICE b/tools/aidl/NOTICE deleted file mode 100644 index c5b1efa7aac7..000000000000 --- a/tools/aidl/NOTICE +++ /dev/null @@ -1,190 +0,0 @@ - - Copyright (c) 2005-2008, 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. - - 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. - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - diff --git a/tools/aidl/Type.cpp b/tools/aidl/Type.cpp deleted file mode 100644 index 2267750623ad..000000000000 --- a/tools/aidl/Type.cpp +++ /dev/null @@ -1,1442 +0,0 @@ -#include "Type.h" - -#include <sys/types.h> - -Namespace NAMES; - -Type* VOID_TYPE; -Type* BOOLEAN_TYPE; -Type* BYTE_TYPE; -Type* CHAR_TYPE; -Type* INT_TYPE; -Type* LONG_TYPE; -Type* FLOAT_TYPE; -Type* DOUBLE_TYPE; -Type* STRING_TYPE; -Type* OBJECT_TYPE; -Type* CHAR_SEQUENCE_TYPE; -Type* TEXT_UTILS_TYPE; -Type* REMOTE_EXCEPTION_TYPE; -Type* RUNTIME_EXCEPTION_TYPE; -Type* IBINDER_TYPE; -Type* IINTERFACE_TYPE; -Type* BINDER_NATIVE_TYPE; -Type* BINDER_PROXY_TYPE; -Type* PARCEL_TYPE; -Type* PARCELABLE_INTERFACE_TYPE; -Type* CONTEXT_TYPE; -Type* MAP_TYPE; -Type* LIST_TYPE; -Type* CLASSLOADER_TYPE; -Type* RPC_DATA_TYPE; -Type* RPC_ERROR_TYPE; -Type* EVENT_FAKE_TYPE; - -Expression* NULL_VALUE; -Expression* THIS_VALUE; -Expression* SUPER_VALUE; -Expression* TRUE_VALUE; -Expression* FALSE_VALUE; - -void -register_base_types() -{ - VOID_TYPE = new BasicType("void", - "XXX", "XXX", "XXX", "XXX", "XXX", - "XXX", "XXX", "XXX", "XXX", "XXX"); - NAMES.Add(VOID_TYPE); - - BOOLEAN_TYPE = new BooleanType(); - NAMES.Add(BOOLEAN_TYPE); - - BYTE_TYPE = new BasicType("byte", - "writeByte", "readByte", "writeByteArray", "createByteArray", "readByteArray", - "putByte", "getByte", "putByteArray", "createByteArray", "getByteArray"); - NAMES.Add(BYTE_TYPE); - - CHAR_TYPE = new CharType(); - NAMES.Add(CHAR_TYPE); - - INT_TYPE = new BasicType("int", - "writeInt", "readInt", "writeIntArray", "createIntArray", "readIntArray", - "putInteger", "getInteger", "putIntegerArray", "createIntegerArray", "getIntegerArray"); - NAMES.Add(INT_TYPE); - - LONG_TYPE = new BasicType("long", - "writeLong", "readLong", "writeLongArray", "createLongArray", "readLongArray", - "putLong", "getLong", "putLongArray", "createLongArray", "getLongArray"); - NAMES.Add(LONG_TYPE); - - FLOAT_TYPE = new BasicType("float", - "writeFloat", "readFloat", "writeFloatArray", "createFloatArray", "readFloatArray", - "putFloat", "getFloat", "putFloatArray", "createFloatArray", "getFloatArray"); - NAMES.Add(FLOAT_TYPE); - - DOUBLE_TYPE = new BasicType("double", - "writeDouble", "readDouble", "writeDoubleArray", "createDoubleArray", "readDoubleArray", - "putDouble", "getDouble", "putDoubleArray", "createDoubleArray", "getDoubleArray"); - NAMES.Add(DOUBLE_TYPE); - - STRING_TYPE = new StringType(); - NAMES.Add(STRING_TYPE); - - OBJECT_TYPE = new Type("java.lang", "Object", Type::BUILT_IN, false, false, false); - NAMES.Add(OBJECT_TYPE); - - CHAR_SEQUENCE_TYPE = new CharSequenceType(); - NAMES.Add(CHAR_SEQUENCE_TYPE); - - MAP_TYPE = new MapType(); - NAMES.Add(MAP_TYPE); - - LIST_TYPE = new ListType(); - NAMES.Add(LIST_TYPE); - - TEXT_UTILS_TYPE = new Type("android.text", "TextUtils", Type::BUILT_IN, false, false, false); - NAMES.Add(TEXT_UTILS_TYPE); - - REMOTE_EXCEPTION_TYPE = new RemoteExceptionType(); - NAMES.Add(REMOTE_EXCEPTION_TYPE); - - RUNTIME_EXCEPTION_TYPE = new RuntimeExceptionType(); - NAMES.Add(RUNTIME_EXCEPTION_TYPE); - - IBINDER_TYPE = new IBinderType(); - NAMES.Add(IBINDER_TYPE); - - IINTERFACE_TYPE = new IInterfaceType(); - NAMES.Add(IINTERFACE_TYPE); - - BINDER_NATIVE_TYPE = new BinderType(); - NAMES.Add(BINDER_NATIVE_TYPE); - - BINDER_PROXY_TYPE = new BinderProxyType(); - NAMES.Add(BINDER_PROXY_TYPE); - - PARCEL_TYPE = new ParcelType(); - NAMES.Add(PARCEL_TYPE); - - PARCELABLE_INTERFACE_TYPE = new ParcelableInterfaceType(); - NAMES.Add(PARCELABLE_INTERFACE_TYPE); - - CONTEXT_TYPE = new Type("android.content", "Context", Type::BUILT_IN, false, false, false); - NAMES.Add(CONTEXT_TYPE); - - RPC_DATA_TYPE = new RpcDataType(); - NAMES.Add(RPC_DATA_TYPE); - - RPC_ERROR_TYPE = new UserDataType("android.support.place.rpc", "RpcError", - true, __FILE__, __LINE__); - NAMES.Add(RPC_ERROR_TYPE); - - EVENT_FAKE_TYPE = new Type("event", Type::BUILT_IN, false, false, false); - NAMES.Add(EVENT_FAKE_TYPE); - - CLASSLOADER_TYPE = new ClassLoaderType(); - NAMES.Add(CLASSLOADER_TYPE); - - NULL_VALUE = new LiteralExpression("null"); - THIS_VALUE = new LiteralExpression("this"); - SUPER_VALUE = new LiteralExpression("super"); - TRUE_VALUE = new LiteralExpression("true"); - FALSE_VALUE = new LiteralExpression("false"); - - NAMES.AddGenericType("java.util", "List", 1); - NAMES.AddGenericType("java.util", "Map", 2); -} - -static Type* -make_generic_type(const string& package, const string& name, - const vector<Type*>& args) -{ - if (package == "java.util" && name == "List") { - return new GenericListType("java.util", "List", args); - } - return NULL; - //return new GenericType(package, name, args); -} - -// ================================================================ - -Type::Type(const string& name, int kind, bool canWriteToParcel, bool canWriteToRpcData, - bool canBeOut) - :m_package(), - m_name(name), - m_declFile(""), - m_declLine(-1), - m_kind(kind), - m_canWriteToParcel(canWriteToParcel), - m_canWriteToRpcData(canWriteToRpcData), - m_canBeOut(canBeOut) -{ - m_qualifiedName = name; -} - -Type::Type(const string& package, const string& name, - int kind, bool canWriteToParcel, bool canWriteToRpcData, - bool canBeOut, const string& declFile, int declLine) - :m_package(package), - m_name(name), - m_declFile(declFile), - m_declLine(declLine), - m_kind(kind), - m_canWriteToParcel(canWriteToParcel), - m_canWriteToRpcData(canWriteToRpcData), - m_canBeOut(canBeOut) -{ - if (package.length() > 0) { - m_qualifiedName = package; - m_qualifiedName += '.'; - } - m_qualifiedName += name; -} - -Type::~Type() -{ -} - -bool -Type::CanBeArray() const -{ - return false; -} - -string -Type::ImportType() const -{ - return m_qualifiedName; -} - -string -Type::CreatorName() const -{ - return ""; -} - -string -Type::RpcCreatorName() const -{ - return ""; -} - -string -Type::InstantiableName() const -{ - return QualifiedName(); -} - - -void -Type::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%sn", - __FILE__, __LINE__, m_qualifiedName.c_str()); - addTo->Add(new LiteralExpression("/* WriteToParcel error " - + m_qualifiedName + " */")); -} - -void -Type::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n", - __FILE__, __LINE__, m_qualifiedName.c_str()); - addTo->Add(new LiteralExpression("/* CreateFromParcel error " - + m_qualifiedName + " */")); -} - -void -Type::ReadFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n", - __FILE__, __LINE__, m_qualifiedName.c_str()); - addTo->Add(new LiteralExpression("/* ReadFromParcel error " - + m_qualifiedName + " */")); -} - -void -Type::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n", - __FILE__, __LINE__, m_qualifiedName.c_str()); - addTo->Add(new LiteralExpression("/* WriteArrayToParcel error " - + m_qualifiedName + " */")); -} - -void -Type::CreateArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable**) -{ - fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n", - __FILE__, __LINE__, m_qualifiedName.c_str()); - addTo->Add(new LiteralExpression("/* CreateArrayFromParcel error " - + m_qualifiedName + " */")); -} - -void -Type::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n", - __FILE__, __LINE__, m_qualifiedName.c_str()); - addTo->Add(new LiteralExpression("/* ReadArrayFromParcel error " - + m_qualifiedName + " */")); -} - -void -Type::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags) -{ - fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n", - __FILE__, __LINE__, m_qualifiedName.c_str()); - addTo->Add(new LiteralExpression("/* WriteToRpcData error " - + m_qualifiedName + " */")); -} - -void -Type::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data, - Variable** cl) -{ - fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n", - __FILE__, __LINE__, m_qualifiedName.c_str()); - addTo->Add(new LiteralExpression("/* ReadFromRpcData error " - + m_qualifiedName + " */")); -} - -void -Type::SetQualifiedName(const string& qualified) -{ - m_qualifiedName = qualified; -} - -Expression* -Type::BuildWriteToParcelFlags(int flags) -{ - if (flags == 0) { - return new LiteralExpression("0"); - } - if ((flags&PARCELABLE_WRITE_RETURN_VALUE) != 0) { - return new FieldVariable(PARCELABLE_INTERFACE_TYPE, - "PARCELABLE_WRITE_RETURN_VALUE"); - } - return new LiteralExpression("0"); -} - -// ================================================================ - -BasicType::BasicType(const string& name, const string& marshallParcel, - const string& unmarshallParcel, const string& writeArrayParcel, - const string& createArrayParcel, const string& readArrayParcel, - const string& marshallRpc, const string& unmarshallRpc, - const string& writeArrayRpc, const string& createArrayRpc, const string& readArrayRpc) - :Type(name, BUILT_IN, true, true, false), - m_marshallParcel(marshallParcel), - m_unmarshallParcel(unmarshallParcel), - m_writeArrayParcel(writeArrayParcel), - m_createArrayParcel(createArrayParcel), - m_readArrayParcel(readArrayParcel), - m_marshallRpc(marshallRpc), - m_unmarshallRpc(unmarshallRpc), - m_writeArrayRpc(writeArrayRpc), - m_createArrayRpc(createArrayRpc), - m_readArrayRpc(readArrayRpc) -{ -} - -void -BasicType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - addTo->Add(new MethodCall(parcel, m_marshallParcel, 1, v)); -} - -void -BasicType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - addTo->Add(new Assignment(v, new MethodCall(parcel, m_unmarshallParcel))); -} - -bool -BasicType::CanBeArray() const -{ - return true; -} - -void -BasicType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - addTo->Add(new MethodCall(parcel, m_writeArrayParcel, 1, v)); -} - -void -BasicType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable**) -{ - addTo->Add(new Assignment(v, new MethodCall(parcel, m_createArrayParcel))); -} - -void -BasicType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - addTo->Add(new MethodCall(parcel, m_readArrayParcel, 1, v)); -} - -void -BasicType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags) -{ - addTo->Add(new MethodCall(data, m_marshallRpc, 2, k, v)); -} - -void -BasicType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data, - Variable** cl) -{ - addTo->Add(new Assignment(v, new MethodCall(data, m_unmarshallRpc, 1, k))); -} - -// ================================================================ - -BooleanType::BooleanType() - :Type("boolean", BUILT_IN, true, true, false) -{ -} - -void -BooleanType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - addTo->Add(new MethodCall(parcel, "writeInt", 1, - new Ternary(v, new LiteralExpression("1"), - new LiteralExpression("0")))); -} - -void -BooleanType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - addTo->Add(new Assignment(v, new Comparison(new LiteralExpression("0"), - "!=", new MethodCall(parcel, "readInt")))); -} - -bool -BooleanType::CanBeArray() const -{ - return true; -} - -void -BooleanType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - addTo->Add(new MethodCall(parcel, "writeBooleanArray", 1, v)); -} - -void -BooleanType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable**) -{ - addTo->Add(new Assignment(v, new MethodCall(parcel, "createBooleanArray"))); -} - -void -BooleanType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - addTo->Add(new MethodCall(parcel, "readBooleanArray", 1, v)); -} - -void -BooleanType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags) -{ - addTo->Add(new MethodCall(data, "putBoolean", 2, k, v)); -} - -void -BooleanType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data, - Variable** cl) -{ - addTo->Add(new Assignment(v, new MethodCall(data, "getBoolean", 1, k))); -} - -// ================================================================ - -CharType::CharType() - :Type("char", BUILT_IN, true, true, false) -{ -} - -void -CharType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - addTo->Add(new MethodCall(parcel, "writeInt", 1, - new Cast(INT_TYPE, v))); -} - -void -CharType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - addTo->Add(new Assignment(v, new MethodCall(parcel, "readInt"), this)); -} - -bool -CharType::CanBeArray() const -{ - return true; -} - -void -CharType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - addTo->Add(new MethodCall(parcel, "writeCharArray", 1, v)); -} - -void -CharType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable**) -{ - addTo->Add(new Assignment(v, new MethodCall(parcel, "createCharArray"))); -} - -void -CharType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - addTo->Add(new MethodCall(parcel, "readCharArray", 1, v)); -} - -void -CharType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags) -{ - addTo->Add(new MethodCall(data, "putChar", 2, k, v)); -} - -void -CharType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data, - Variable** cl) -{ - addTo->Add(new Assignment(v, new MethodCall(data, "getChar", 1, k))); -} - -// ================================================================ - -StringType::StringType() - :Type("java.lang", "String", BUILT_IN, true, true, false) -{ -} - -string -StringType::CreatorName() const -{ - return "android.os.Parcel.STRING_CREATOR"; -} - -void -StringType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - addTo->Add(new MethodCall(parcel, "writeString", 1, v)); -} - -void -StringType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - addTo->Add(new Assignment(v, new MethodCall(parcel, "readString"))); -} - -bool -StringType::CanBeArray() const -{ - return true; -} - -void -StringType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - addTo->Add(new MethodCall(parcel, "writeStringArray", 1, v)); -} - -void -StringType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable**) -{ - addTo->Add(new Assignment(v, new MethodCall(parcel, "createStringArray"))); -} - -void -StringType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - addTo->Add(new MethodCall(parcel, "readStringArray", 1, v)); -} - -void -StringType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags) -{ - addTo->Add(new MethodCall(data, "putString", 2, k, v)); -} - -void -StringType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, Variable**) -{ - addTo->Add(new Assignment(v, new MethodCall(data, "getString", 1, k))); -} - -// ================================================================ - -CharSequenceType::CharSequenceType() - :Type("java.lang", "CharSequence", BUILT_IN, true, true, false) -{ -} - -string -CharSequenceType::CreatorName() const -{ - return "android.os.Parcel.STRING_CREATOR"; -} - -void -CharSequenceType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - // if (v != null) { - // parcel.writeInt(1); - // v.writeToParcel(parcel); - // } else { - // parcel.writeInt(0); - // } - IfStatement* elsepart = new IfStatement(); - elsepart->statements->Add(new MethodCall(parcel, "writeInt", 1, - new LiteralExpression("0"))); - IfStatement* ifpart = new IfStatement; - ifpart->expression = new Comparison(v, "!=", NULL_VALUE); - ifpart->elseif = elsepart; - ifpart->statements->Add(new MethodCall(parcel, "writeInt", 1, - new LiteralExpression("1"))); - ifpart->statements->Add(new MethodCall(TEXT_UTILS_TYPE, "writeToParcel", - 3, v, parcel, BuildWriteToParcelFlags(flags))); - - addTo->Add(ifpart); -} - -void -CharSequenceType::CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable**) -{ - // if (0 != parcel.readInt()) { - // v = TextUtils.createFromParcel(parcel) - // } else { - // v = null; - // } - IfStatement* elsepart = new IfStatement(); - elsepart->statements->Add(new Assignment(v, NULL_VALUE)); - - IfStatement* ifpart = new IfStatement(); - ifpart->expression = new Comparison(new LiteralExpression("0"), "!=", - new MethodCall(parcel, "readInt")); - ifpart->elseif = elsepart; - ifpart->statements->Add(new Assignment(v, - new MethodCall(TEXT_UTILS_TYPE, - "CHAR_SEQUENCE_CREATOR.createFromParcel", 1, parcel))); - - addTo->Add(ifpart); -} - - -// ================================================================ - -RemoteExceptionType::RemoteExceptionType() - :Type("android.os", "RemoteException", BUILT_IN, false, false, false) -{ -} - -void -RemoteExceptionType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__); -} - -void -RemoteExceptionType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__); -} - -// ================================================================ - -RuntimeExceptionType::RuntimeExceptionType() - :Type("java.lang", "RuntimeException", BUILT_IN, false, false, false) -{ -} - -void -RuntimeExceptionType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__); -} - -void -RuntimeExceptionType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__); -} - - -// ================================================================ - -IBinderType::IBinderType() - :Type("android.os", "IBinder", BUILT_IN, true, false, false) -{ -} - -void -IBinderType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - addTo->Add(new MethodCall(parcel, "writeStrongBinder", 1, v)); -} - -void -IBinderType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - addTo->Add(new Assignment(v, new MethodCall(parcel, "readStrongBinder"))); -} - -void -IBinderType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - addTo->Add(new MethodCall(parcel, "writeBinderArray", 1, v)); -} - -void -IBinderType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable**) -{ - addTo->Add(new Assignment(v, new MethodCall(parcel, "createBinderArray"))); -} - -void -IBinderType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - addTo->Add(new MethodCall(parcel, "readBinderArray", 1, v)); -} - - -// ================================================================ - -IInterfaceType::IInterfaceType() - :Type("android.os", "IInterface", BUILT_IN, false, false, false) -{ -} - -void -IInterfaceType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__); -} - -void -IInterfaceType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__); -} - - -// ================================================================ - -BinderType::BinderType() - :Type("android.os", "Binder", BUILT_IN, false, false, false) -{ -} - -void -BinderType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__); -} - -void -BinderType::CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable**) -{ - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__); -} - - -// ================================================================ - -BinderProxyType::BinderProxyType() - :Type("android.os", "BinderProxy", BUILT_IN, false, false, false) -{ -} - -void -BinderProxyType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__); -} - -void -BinderProxyType::CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable**) -{ - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__); -} - - -// ================================================================ - -ParcelType::ParcelType() - :Type("android.os", "Parcel", BUILT_IN, false, false, false) -{ -} - -void -ParcelType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__); -} - -void -ParcelType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__); -} - -// ================================================================ - -ParcelableInterfaceType::ParcelableInterfaceType() - :Type("android.os", "Parcelable", BUILT_IN, false, false, false) -{ -} - -void -ParcelableInterfaceType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__); -} - -void -ParcelableInterfaceType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__); -} - -// ================================================================ - -MapType::MapType() - :Type("java.util", "Map", BUILT_IN, true, false, true) -{ -} - -void -MapType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - addTo->Add(new MethodCall(parcel, "writeMap", 1, v)); -} - -static void EnsureClassLoader(StatementBlock* addTo, Variable** cl) -{ - // We don't want to look up the class loader once for every - // collection argument, so ensure we do it at most once per method. - if (*cl == NULL) { - *cl = new Variable(CLASSLOADER_TYPE, "cl"); - addTo->Add(new VariableDeclaration(*cl, - new LiteralExpression("this.getClass().getClassLoader()"), - CLASSLOADER_TYPE)); - } -} - -void -MapType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable** cl) -{ - EnsureClassLoader(addTo, cl); - addTo->Add(new Assignment(v, new MethodCall(parcel, "readHashMap", 1, *cl))); -} - -void -MapType::ReadFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable** cl) -{ - EnsureClassLoader(addTo, cl); - addTo->Add(new MethodCall(parcel, "readMap", 2, v, *cl)); -} - - -// ================================================================ - -ListType::ListType() - :Type("java.util", "List", BUILT_IN, true, true, true) -{ -} - -string -ListType::InstantiableName() const -{ - return "java.util.ArrayList"; -} - -void -ListType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - addTo->Add(new MethodCall(parcel, "writeList", 1, v)); -} - -void -ListType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable** cl) -{ - EnsureClassLoader(addTo, cl); - addTo->Add(new Assignment(v, new MethodCall(parcel, "readArrayList", 1, *cl))); -} - -void -ListType::ReadFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl) -{ - EnsureClassLoader(addTo, cl); - addTo->Add(new MethodCall(parcel, "readList", 2, v, *cl)); -} - -void -ListType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags) -{ - addTo->Add(new MethodCall(data, "putList", 2, k, v)); -} - -void -ListType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data, - Variable** cl) -{ - addTo->Add(new Assignment(v, new MethodCall(data, "getList", 1, k))); -} - -// ================================================================ - -UserDataType::UserDataType(const string& package, const string& name, - bool builtIn, bool canWriteToParcel, bool canWriteToRpcData, - const string& declFile, int declLine) - :Type(package, name, builtIn ? BUILT_IN : USERDATA, canWriteToParcel, canWriteToRpcData, - true, declFile, declLine) -{ -} - -string -UserDataType::CreatorName() const -{ - return QualifiedName() + ".CREATOR"; -} - -string -UserDataType::RpcCreatorName() const -{ - return QualifiedName() + ".RPC_CREATOR"; -} - -void -UserDataType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - // if (v != null) { - // parcel.writeInt(1); - // v.writeToParcel(parcel); - // } else { - // parcel.writeInt(0); - // } - IfStatement* elsepart = new IfStatement(); - elsepart->statements->Add(new MethodCall(parcel, "writeInt", 1, - new LiteralExpression("0"))); - IfStatement* ifpart = new IfStatement; - ifpart->expression = new Comparison(v, "!=", NULL_VALUE); - ifpart->elseif = elsepart; - ifpart->statements->Add(new MethodCall(parcel, "writeInt", 1, - new LiteralExpression("1"))); - ifpart->statements->Add(new MethodCall(v, "writeToParcel", 2, - parcel, BuildWriteToParcelFlags(flags))); - - addTo->Add(ifpart); -} - -void -UserDataType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - // if (0 != parcel.readInt()) { - // v = CLASS.CREATOR.createFromParcel(parcel) - // } else { - // v = null; - // } - IfStatement* elsepart = new IfStatement(); - elsepart->statements->Add(new Assignment(v, NULL_VALUE)); - - IfStatement* ifpart = new IfStatement(); - ifpart->expression = new Comparison(new LiteralExpression("0"), "!=", - new MethodCall(parcel, "readInt")); - ifpart->elseif = elsepart; - ifpart->statements->Add(new Assignment(v, - new MethodCall(v->type, "CREATOR.createFromParcel", 1, parcel))); - - addTo->Add(ifpart); -} - -void -UserDataType::ReadFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable**) -{ - // TODO: really, we don't need to have this extra check, but we - // don't have two separate marshalling code paths - // if (0 != parcel.readInt()) { - // v.readFromParcel(parcel) - // } - IfStatement* ifpart = new IfStatement(); - ifpart->expression = new Comparison(new LiteralExpression("0"), "!=", - new MethodCall(parcel, "readInt")); - ifpart->statements->Add(new MethodCall(v, "readFromParcel", 1, parcel)); - addTo->Add(ifpart); -} - -bool -UserDataType::CanBeArray() const -{ - return true; -} - -void -UserDataType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - addTo->Add(new MethodCall(parcel, "writeTypedArray", 2, v, - BuildWriteToParcelFlags(flags))); -} - -void -UserDataType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable**) -{ - string creator = v->type->QualifiedName() + ".CREATOR"; - addTo->Add(new Assignment(v, new MethodCall(parcel, - "createTypedArray", 1, new LiteralExpression(creator)))); -} - -void -UserDataType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - string creator = v->type->QualifiedName() + ".CREATOR"; - addTo->Add(new MethodCall(parcel, "readTypedArray", 2, - v, new LiteralExpression(creator))); -} - -void -UserDataType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags) -{ - // data.putFlattenable(k, v); - addTo->Add(new MethodCall(data, "putFlattenable", 2, k, v)); -} - -void -UserDataType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, Variable** cl) -{ - // data.getFlattenable(k, CLASS.RPC_CREATOR); - addTo->Add(new Assignment(v, new MethodCall(data, "getFlattenable", 2, k, - new FieldVariable(v->type, "RPC_CREATOR")))); -} - -// ================================================================ - -InterfaceType::InterfaceType(const string& package, const string& name, - bool builtIn, bool oneway, - const string& declFile, int declLine) - :Type(package, name, builtIn ? BUILT_IN : INTERFACE, true, false, false, - declFile, declLine) - ,m_oneway(oneway) -{ -} - -bool -InterfaceType::OneWay() const -{ - return m_oneway; -} - -void -InterfaceType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - // parcel.writeStrongBinder(v != null ? v.asBinder() : null); - addTo->Add(new MethodCall(parcel, "writeStrongBinder", 1, - new Ternary( - new Comparison(v, "!=", NULL_VALUE), - new MethodCall(v, "asBinder"), - NULL_VALUE))); -} - -void -InterfaceType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - // v = Interface.asInterface(parcel.readStrongBinder()); - string type = v->type->QualifiedName(); - type += ".Stub"; - addTo->Add(new Assignment(v, - new MethodCall( NAMES.Find(type), "asInterface", 1, - new MethodCall(parcel, "readStrongBinder")))); -} - - -// ================================================================ - -GenericType::GenericType(const string& package, const string& name, - const vector<Type*>& args) - :Type(package, name, BUILT_IN, true, true, true) -{ - m_args = args; - - m_importName = package + '.' + name; - - string gen = "<"; - int N = args.size(); - for (int i=0; i<N; i++) { - Type* t = args[i]; - gen += t->QualifiedName(); - if (i != N-1) { - gen += ','; - } - } - gen += '>'; - m_genericArguments = gen; - SetQualifiedName(m_importName + gen); -} - -const vector<Type*>& -GenericType::GenericArgumentTypes() const -{ - return m_args; -} - -string -GenericType::GenericArguments() const -{ - return m_genericArguments; -} - -string -GenericType::ImportType() const -{ - return m_importName; -} - -void -GenericType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - fprintf(stderr, "implement GenericType::WriteToParcel\n"); -} - -void -GenericType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - fprintf(stderr, "implement GenericType::CreateFromParcel\n"); -} - -void -GenericType::ReadFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable**) -{ - fprintf(stderr, "implement GenericType::ReadFromParcel\n"); -} - - -// ================================================================ - -GenericListType::GenericListType(const string& package, const string& name, - const vector<Type*>& args) - :GenericType(package, name, args), - m_creator(args[0]->CreatorName()) -{ -} - -string -GenericListType::CreatorName() const -{ - return "android.os.Parcel.arrayListCreator"; -} - -string -GenericListType::InstantiableName() const -{ - return "java.util.ArrayList" + GenericArguments(); -} - -void -GenericListType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) -{ - if (m_creator == STRING_TYPE->CreatorName()) { - addTo->Add(new MethodCall(parcel, "writeStringList", 1, v)); - } else if (m_creator == IBINDER_TYPE->CreatorName()) { - addTo->Add(new MethodCall(parcel, "writeBinderList", 1, v)); - } else { - // parcel.writeTypedListXX(arg); - addTo->Add(new MethodCall(parcel, "writeTypedList", 1, v)); - } -} - -void -GenericListType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**) -{ - if (m_creator == STRING_TYPE->CreatorName()) { - addTo->Add(new Assignment(v, - new MethodCall(parcel, "createStringArrayList", 0))); - } else if (m_creator == IBINDER_TYPE->CreatorName()) { - addTo->Add(new Assignment(v, - new MethodCall(parcel, "createBinderArrayList", 0))); - } else { - // v = _data.readTypedArrayList(XXX.creator); - addTo->Add(new Assignment(v, - new MethodCall(parcel, "createTypedArrayList", 1, - new LiteralExpression(m_creator)))); - } -} - -void -GenericListType::ReadFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable**) -{ - if (m_creator == STRING_TYPE->CreatorName()) { - addTo->Add(new MethodCall(parcel, "readStringList", 1, v)); - } else if (m_creator == IBINDER_TYPE->CreatorName()) { - addTo->Add(new MethodCall(parcel, "readBinderList", 1, v)); - } else { - // v = _data.readTypedList(v, XXX.creator); - addTo->Add(new MethodCall(parcel, "readTypedList", 2, - v, - new LiteralExpression(m_creator))); - } -} - -void -GenericListType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags) -{ - Type* generic = GenericArgumentTypes()[0]; - if (generic == RPC_DATA_TYPE) { - addTo->Add(new MethodCall(data, "putRpcDataList", 2, k, v)); - } else if (generic->RpcCreatorName() != "") { - addTo->Add(new MethodCall(data, "putFlattenableList", 2, k, v)); - } else { - addTo->Add(new MethodCall(data, "putList", 2, k, v)); - } -} - -void -GenericListType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, Variable** cl) -{ - Type* generic = GenericArgumentTypes()[0]; - if (generic == RPC_DATA_TYPE) { - addTo->Add(new Assignment(v, new MethodCall(data, "getRpcDataList", 2, k))); - } else if (generic->RpcCreatorName() != "") { - addTo->Add(new Assignment(v, new MethodCall(data, "getFlattenableList", 2, k, - new LiteralExpression(generic->RpcCreatorName())))); - } else { - string classArg = GenericArgumentTypes()[0]->QualifiedName(); - classArg += ".class"; - addTo->Add(new Assignment(v, new MethodCall(data, "getList", 2, k, - new LiteralExpression(classArg)))); - } -} - - -// ================================================================ - -RpcDataType::RpcDataType() - :UserDataType("android.support.place.rpc", "RpcData", true, true, true) -{ -} - -void -RpcDataType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags) -{ - addTo->Add(new MethodCall(data, "putRpcData", 2, k, v)); -} - -void -RpcDataType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data, - Variable** cl) -{ - addTo->Add(new Assignment(v, new MethodCall(data, "getRpcData", 1, k))); -} - - -// ================================================================ - -ClassLoaderType::ClassLoaderType() - :Type("java.lang", "ClassLoader", BUILT_IN, false, false, false) -{ -} - - -// ================================================================ - -Namespace::Namespace() -{ -} - -Namespace::~Namespace() -{ - int N = m_types.size(); - for (int i=0; i<N; i++) { - delete m_types[i]; - } -} - -void -Namespace::Add(Type* type) -{ - Type* t = Find(type->QualifiedName()); - if (t == NULL) { - m_types.push_back(type); - } -} - -void -Namespace::AddGenericType(const string& package, const string& name, int args) -{ - Generic g; - g.package = package; - g.name = name; - g.qualified = package + '.' + name; - g.args = args; - m_generics.push_back(g); -} - -Type* -Namespace::Find(const string& name) const -{ - int N = m_types.size(); - for (int i=0; i<N; i++) { - if (m_types[i]->QualifiedName() == name) { - return m_types[i]; - } - } - return NULL; -} - -Type* -Namespace::Find(const char* package, const char* name) const -{ - string s; - if (package != NULL) { - s += package; - s += '.'; - } - s += name; - return Find(s); -} - -static string -normalize_generic(const string& s) -{ - string r; - int N = s.size(); - for (int i=0; i<N; i++) { - char c = s[i]; - if (!isspace(c)) { - r += c; - } - } - return r; -} - -Type* -Namespace::Search(const string& name) -{ - // an exact match wins - Type* result = Find(name); - if (result != NULL) { - return result; - } - - // try the class names - // our language doesn't allow you to not specify outer classes - // when referencing an inner class. that could be changed, and this - // would be the place to do it, but I don't think the complexity in - // scoping rules is worth it. - int N = m_types.size(); - for (int i=0; i<N; i++) { - if (m_types[i]->Name() == name) { - return m_types[i]; - } - } - - // we got to here and it's not a generic, give up - if (name.find('<') == name.npos) { - return NULL; - } - - // remove any whitespace - string normalized = normalize_generic(name); - - // find the part before the '<', find a generic for it - ssize_t baseIndex = normalized.find('<'); - string base(normalized.c_str(), baseIndex); - const Generic* g = search_generic(base); - if (g == NULL) { - return NULL; - } - - // For each of the args, do a recursive search on it. We don't allow - // generics within generics like Java does, because we're really limiting - // them to just built-in container classes, at least for now. Our syntax - // ensures this right now as well. - vector<Type*> args; - size_t start = baseIndex + 1; - size_t end = start; - while (normalized[start] != '\0') { - end = normalized.find(',', start); - if (end == normalized.npos) { - end = normalized.find('>', start); - } - string s(normalized.c_str()+start, end-start); - Type* t = this->Search(s); - if (t == NULL) { - // maybe we should print a warning here? - return NULL; - } - args.push_back(t); - start = end+1; - } - - // construct a GenericType, add it to our name set so they always get - // the same object, and return it. - result = make_generic_type(g->package, g->name, args); - if (result == NULL) { - return NULL; - } - - this->Add(result); - return this->Find(result->QualifiedName()); -} - -const Namespace::Generic* -Namespace::search_generic(const string& name) const -{ - int N = m_generics.size(); - - // first exact match - for (int i=0; i<N; i++) { - const Generic& g = m_generics[i]; - if (g.qualified == name) { - return &g; - } - } - - // then name match - for (int i=0; i<N; i++) { - const Generic& g = m_generics[i]; - if (g.name == name) { - return &g; - } - } - - return NULL; -} - -void -Namespace::Dump() const -{ - int n = m_types.size(); - for (int i=0; i<n; i++) { - Type* t = m_types[i]; - printf("type: package=%s name=%s qualifiedName=%s\n", - t->Package().c_str(), t->Name().c_str(), - t->QualifiedName().c_str()); - } -} diff --git a/tools/aidl/Type.h b/tools/aidl/Type.h deleted file mode 100644 index ae12720142e8..000000000000 --- a/tools/aidl/Type.h +++ /dev/null @@ -1,542 +0,0 @@ -#ifndef AIDL_TYPE_H -#define AIDL_TYPE_H - -#include "AST.h" -#include <string> -#include <vector> - -using namespace std; - -class Type -{ -public: - // kinds - enum { - BUILT_IN, - USERDATA, - INTERFACE, - GENERATED - }; - - // WriteToParcel flags - enum { - PARCELABLE_WRITE_RETURN_VALUE = 0x0001 - }; - - Type(const string& name, int kind, bool canWriteToParcel, - bool canWriteToRpcData, bool canBeOut); - Type(const string& package, const string& name, - int kind, bool canWriteToParcel, bool canWriteToRpcData, bool canBeOut, - const string& declFile = "", int declLine = -1); - virtual ~Type(); - - inline string Package() const { return m_package; } - inline string Name() const { return m_name; } - inline string QualifiedName() const { return m_qualifiedName; } - inline int Kind() const { return m_kind; } - inline string DeclFile() const { return m_declFile; } - inline int DeclLine() const { return m_declLine; } - inline bool CanWriteToParcel() const { return m_canWriteToParcel; } - inline bool CanWriteToRpcData() const { return m_canWriteToRpcData; } - inline bool CanBeOutParameter() const { return m_canBeOut; } - - virtual string ImportType() const; - virtual string CreatorName() const; - virtual string RpcCreatorName() const; - virtual string InstantiableName() const; - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - virtual void ReadFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - - virtual bool CanBeArray() const; - - virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - - virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags); - virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, Variable** cl); - -protected: - void SetQualifiedName(const string& qualified); - Expression* BuildWriteToParcelFlags(int flags); - -private: - Type(); - Type(const Type&); - - string m_package; - string m_name; - string m_qualifiedName; - string m_declFile; - int m_declLine; - int m_kind; - bool m_canWriteToParcel; - bool m_canWriteToRpcData; - bool m_canBeOut; -}; - -class BasicType : public Type -{ -public: - BasicType(const string& name, - const string& marshallParcel, - const string& unmarshallParcel, - const string& writeArrayParcel, - const string& createArrayParcel, - const string& readArrayParcel, - const string& marshallRpc, - const string& unmarshallRpc, - const string& writeArrayRpc, - const string& createArrayRpc, - const string& readArrayRpc); - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - - virtual bool CanBeArray() const; - - virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - - virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags); - virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, Variable** cl); - -private: - string m_marshallParcel; - string m_unmarshallParcel; - string m_writeArrayParcel; - string m_createArrayParcel; - string m_readArrayParcel; - string m_marshallRpc; - string m_unmarshallRpc; - string m_writeArrayRpc; - string m_createArrayRpc; - string m_readArrayRpc; -}; - -class BooleanType : public Type -{ -public: - BooleanType(); - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - - virtual bool CanBeArray() const; - - virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - - virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags); - virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, Variable** cl); -}; - -class CharType : public Type -{ -public: - CharType(); - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - - virtual bool CanBeArray() const; - - virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - - virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags); - virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, Variable** cl); -}; - - -class StringType : public Type -{ -public: - StringType(); - - virtual string CreatorName() const; - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - - virtual bool CanBeArray() const; - - virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - - virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags); - virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, Variable** cl); -}; - -class CharSequenceType : public Type -{ -public: - CharSequenceType(); - - virtual string CreatorName() const; - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); -}; - -class RemoteExceptionType : public Type -{ -public: - RemoteExceptionType(); - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); -}; - -class RuntimeExceptionType : public Type -{ -public: - RuntimeExceptionType(); - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); -}; - -class IBinderType : public Type -{ -public: - IBinderType(); - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - - virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); -}; - -class IInterfaceType : public Type -{ -public: - IInterfaceType(); - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); -}; - -class BinderType : public Type -{ -public: - BinderType(); - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); -}; - -class BinderProxyType : public Type -{ -public: - BinderProxyType(); - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); -}; - -class ParcelType : public Type -{ -public: - ParcelType(); - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); -}; - -class ParcelableInterfaceType : public Type -{ -public: - ParcelableInterfaceType(); - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); -}; - -class MapType : public Type -{ -public: - MapType(); - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - virtual void ReadFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); -}; - -class ListType : public Type -{ -public: - ListType(); - - virtual string InstantiableName() const; - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - virtual void ReadFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - - virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags); - virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, Variable** cl); -}; - -class UserDataType : public Type -{ -public: - UserDataType(const string& package, const string& name, - bool builtIn, bool canWriteToParcel, bool canWriteToRpcData, - const string& declFile = "", int declLine = -1); - - virtual string CreatorName() const; - virtual string RpcCreatorName() const; - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - virtual void ReadFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - - virtual bool CanBeArray() const; - - virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - - virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags); - virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, Variable** cl); -}; - -class InterfaceType : public Type -{ -public: - InterfaceType(const string& package, const string& name, - bool builtIn, bool oneway, - const string& declFile, int declLine); - - bool OneWay() const; - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - -private: - bool m_oneway; -}; - - -class GenericType : public Type -{ -public: - GenericType(const string& package, const string& name, - const vector<Type*>& args); - - const vector<Type*>& GenericArgumentTypes() const; - string GenericArguments() const; - - virtual string ImportType() const; - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - virtual void ReadFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - -private: - string m_genericArguments; - string m_importName; - vector<Type*> m_args; -}; - -class RpcDataType : public UserDataType -{ -public: - RpcDataType(); - - virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags); - virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, Variable** cl); -}; - -class ClassLoaderType : public Type -{ -public: - ClassLoaderType(); -}; - -class GenericListType : public GenericType -{ -public: - GenericListType(const string& package, const string& name, - const vector<Type*>& args); - - virtual string CreatorName() const; - virtual string InstantiableName() const; - - virtual void WriteToParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, int flags); - virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - virtual void ReadFromParcel(StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl); - - virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, int flags); - virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, - Variable* data, Variable** cl); - -private: - string m_creator; -}; - -class Namespace -{ -public: - Namespace(); - ~Namespace(); - void Add(Type* type); - - // args is the number of template types (what is this called?) - void AddGenericType(const string& package, const string& name, int args); - - // lookup a specific class name - Type* Find(const string& name) const; - Type* Find(const char* package, const char* name) const; - - // try to search by either a full name or a partial name - Type* Search(const string& name); - - void Dump() const; - -private: - struct Generic { - string package; - string name; - string qualified; - int args; - }; - - const Generic* search_generic(const string& name) const; - - vector<Type*> m_types; - vector<Generic> m_generics; -}; - -extern Namespace NAMES; - -extern Type* VOID_TYPE; -extern Type* BOOLEAN_TYPE; -extern Type* BYTE_TYPE; -extern Type* CHAR_TYPE; -extern Type* INT_TYPE; -extern Type* LONG_TYPE; -extern Type* FLOAT_TYPE; -extern Type* DOUBLE_TYPE; -extern Type* OBJECT_TYPE; -extern Type* STRING_TYPE; -extern Type* CHAR_SEQUENCE_TYPE; -extern Type* TEXT_UTILS_TYPE; -extern Type* REMOTE_EXCEPTION_TYPE; -extern Type* RUNTIME_EXCEPTION_TYPE; -extern Type* IBINDER_TYPE; -extern Type* IINTERFACE_TYPE; -extern Type* BINDER_NATIVE_TYPE; -extern Type* BINDER_PROXY_TYPE; -extern Type* PARCEL_TYPE; -extern Type* PARCELABLE_INTERFACE_TYPE; - -extern Type* CONTEXT_TYPE; - -extern Type* RPC_DATA_TYPE; -extern Type* RPC_ERROR_TYPE; -extern Type* RPC_CONTEXT_TYPE; -extern Type* EVENT_FAKE_TYPE; - -extern Expression* NULL_VALUE; -extern Expression* THIS_VALUE; -extern Expression* SUPER_VALUE; -extern Expression* TRUE_VALUE; -extern Expression* FALSE_VALUE; - -void register_base_types(); - -#endif // AIDL_TYPE_H diff --git a/tools/aidl/aidl.cpp b/tools/aidl/aidl.cpp deleted file mode 100644 index 14c9f95a247b..000000000000 --- a/tools/aidl/aidl.cpp +++ /dev/null @@ -1,1158 +0,0 @@ - -#include "aidl_language.h" -#include "options.h" -#include "search_path.h" -#include "Type.h" -#include "generate_java.h" -#include <unistd.h> -#include <fcntl.h> -#include <sys/param.h> -#include <sys/stat.h> - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <map> - -#ifdef HAVE_MS_C_RUNTIME -#include <io.h> -#include <direct.h> -#include <sys/stat.h> -#endif - -#ifndef O_BINARY -# define O_BINARY 0 -#endif - -// The following are gotten as the offset from the allowable id's between -// android.os.IBinder.FIRST_CALL_TRANSACTION=1 and -// android.os.IBinder.LAST_CALL_TRANSACTION=16777215 -#define MIN_USER_SET_METHOD_ID 0 -#define MAX_USER_SET_METHOD_ID 16777214 - -using namespace std; - -static void -test_document(document_item_type* d) -{ - while (d) { - if (d->item_type == INTERFACE_TYPE_BINDER) { - interface_type* c = (interface_type*)d; - printf("interface %s %s {\n", c->package, c->name.data); - interface_item_type *q = (interface_item_type*)c->interface_items; - while (q) { - if (q->item_type == METHOD_TYPE) { - method_type *m = (method_type*)q; - printf(" %s %s(", m->type.type.data, m->name.data); - arg_type *p = m->args; - while (p) { - printf("%s %s",p->type.type.data,p->name.data); - if (p->next) printf(", "); - p=p->next; - } - printf(")"); - printf(";\n"); - } - q=q->next; - } - printf("}\n"); - } - else if (d->item_type == USER_DATA_TYPE) { - user_data_type* b = (user_data_type*)d; - if ((b->flattening_methods & PARCELABLE_DATA) != 0) { - printf("parcelable %s %s;\n", b->package, b->name.data); - } - if ((b->flattening_methods & RPC_DATA) != 0) { - printf("flattenable %s %s;\n", b->package, b->name.data); - } - } - else { - printf("UNKNOWN d=0x%08lx d->item_type=%d\n", (long)d, d->item_type); - } - d = d->next; - } -} - -// ========================================================== -int -convert_direction(const char* direction) -{ - if (direction == NULL) { - return IN_PARAMETER; - } - if (0 == strcmp(direction, "in")) { - return IN_PARAMETER; - } - if (0 == strcmp(direction, "out")) { - return OUT_PARAMETER; - } - return INOUT_PARAMETER; -} - -// ========================================================== -struct import_info { - const char* from; - const char* filename; - buffer_type statement; - const char* neededClass; - document_item_type* doc; - struct import_info* next; -}; - -document_item_type* g_document = NULL; -import_info* g_imports = NULL; - -static void -main_document_parsed(document_item_type* d) -{ - g_document = d; -} - -static void -main_import_parsed(buffer_type* statement) -{ - import_info* import = (import_info*)malloc(sizeof(import_info)); - memset(import, 0, sizeof(import_info)); - import->from = strdup(g_currentFilename); - import->statement.lineno = statement->lineno; - import->statement.data = strdup(statement->data); - import->statement.extra = NULL; - import->next = g_imports; - import->neededClass = parse_import_statement(statement->data); - g_imports = import; -} - -static ParserCallbacks g_mainCallbacks = { - &main_document_parsed, - &main_import_parsed -}; - -char* -parse_import_statement(const char* text) -{ - const char* end; - int len; - - while (isspace(*text)) { - text++; - } - while (!isspace(*text)) { - text++; - } - while (isspace(*text)) { - text++; - } - end = text; - while (!isspace(*end) && *end != ';') { - end++; - } - len = end-text; - - char* rv = (char*)malloc(len+1); - memcpy(rv, text, len); - rv[len] = '\0'; - - return rv; -} - -// ========================================================== -static void -import_import_parsed(buffer_type* statement) -{ -} - -static ParserCallbacks g_importCallbacks = { - &main_document_parsed, - &import_import_parsed -}; - -// ========================================================== -static int -check_filename(const char* filename, const char* package, buffer_type* name) -{ - const char* p; - string expected; - string fn; - size_t len; - char cwd[MAXPATHLEN]; - bool valid = false; - -#ifdef HAVE_WINDOWS_PATHS - if (isalpha(filename[0]) && filename[1] == ':' - && filename[2] == OS_PATH_SEPARATOR) { -#else - if (filename[0] == OS_PATH_SEPARATOR) { -#endif - fn = filename; - } else { - fn = getcwd(cwd, sizeof(cwd)); - len = fn.length(); - if (fn[len-1] != OS_PATH_SEPARATOR) { - fn += OS_PATH_SEPARATOR; - } - fn += filename; - } - - if (package) { - expected = package; - expected += '.'; - } - - len = expected.length(); - for (size_t i=0; i<len; i++) { - if (expected[i] == '.') { - expected[i] = OS_PATH_SEPARATOR; - } - } - - p = strchr(name->data, '.'); - len = p ? p-name->data : strlen(name->data); - expected.append(name->data, len); - - expected += ".aidl"; - - len = fn.length(); - valid = (len >= expected.length()); - - if (valid) { - p = fn.c_str() + (len - expected.length()); - -#ifdef HAVE_WINDOWS_PATHS - if (OS_PATH_SEPARATOR != '/') { - // Input filename under cygwin most likely has / separators - // whereas the expected string uses \\ separators. Adjust - // them accordingly. - for (char *c = const_cast<char *>(p); *c; ++c) { - if (*c == '/') *c = OS_PATH_SEPARATOR; - } - } -#endif - - // aidl assumes case-insensitivity on Mac Os and Windows. -#if defined(__linux__) - valid = (expected == p); -#else - valid = !strcasecmp(expected.c_str(), p); -#endif - } - - if (!valid) { - fprintf(stderr, "%s:%d interface %s should be declared in a file" - " called %s.\n", - filename, name->lineno, name->data, expected.c_str()); - return 1; - } - - return 0; -} - -static int -check_filenames(const char* filename, document_item_type* items) -{ - int err = 0; - while (items) { - if (items->item_type == USER_DATA_TYPE) { - user_data_type* p = (user_data_type*)items; - err |= check_filename(filename, p->package, &p->name); - } - else if (items->item_type == INTERFACE_TYPE_BINDER - || items->item_type == INTERFACE_TYPE_RPC) { - interface_type* c = (interface_type*)items; - err |= check_filename(filename, c->package, &c->name); - } - else { - fprintf(stderr, "aidl: internal error unkown document type %d.\n", - items->item_type); - return 1; - } - items = items->next; - } - return err; -} - -// ========================================================== -static const char* -kind_to_string(int kind) -{ - switch (kind) - { - case Type::INTERFACE: - return "an interface"; - case Type::USERDATA: - return "a user data"; - default: - return "ERROR"; - } -} - -static char* -rfind(char* str, char c) -{ - char* p = str + strlen(str) - 1; - while (p >= str) { - if (*p == c) { - return p; - } - p--; - } - return NULL; -} - -static int -gather_types(const char* filename, document_item_type* items) -{ - int err = 0; - while (items) { - Type* type; - if (items->item_type == USER_DATA_TYPE) { - user_data_type* p = (user_data_type*)items; - type = new UserDataType(p->package ? p->package : "", p->name.data, - false, ((p->flattening_methods & PARCELABLE_DATA) != 0), - ((p->flattening_methods & RPC_DATA) != 0), filename, p->name.lineno); - } - else if (items->item_type == INTERFACE_TYPE_BINDER - || items->item_type == INTERFACE_TYPE_RPC) { - interface_type* c = (interface_type*)items; - type = new InterfaceType(c->package ? c->package : "", - c->name.data, false, c->oneway, - filename, c->name.lineno); - } - else { - fprintf(stderr, "aidl: internal error %s:%d\n", __FILE__, __LINE__); - return 1; - } - - Type* old = NAMES.Find(type->QualifiedName()); - if (old == NULL) { - NAMES.Add(type); - - if (items->item_type == INTERFACE_TYPE_BINDER) { - // for interfaces, also add the stub and proxy types, we don't - // bother checking these for duplicates, because the parser - // won't let us do it. - interface_type* c = (interface_type*)items; - - string name = c->name.data; - name += ".Stub"; - Type* stub = new Type(c->package ? c->package : "", - name, Type::GENERATED, false, false, false, - filename, c->name.lineno); - NAMES.Add(stub); - - name = c->name.data; - name += ".Stub.Proxy"; - Type* proxy = new Type(c->package ? c->package : "", - name, Type::GENERATED, false, false, false, - filename, c->name.lineno); - NAMES.Add(proxy); - } - else if (items->item_type == INTERFACE_TYPE_RPC) { - // for interfaces, also add the service base type, we don't - // bother checking these for duplicates, because the parser - // won't let us do it. - interface_type* c = (interface_type*)items; - - string name = c->name.data; - name += ".ServiceBase"; - Type* base = new Type(c->package ? c->package : "", - name, Type::GENERATED, false, false, false, - filename, c->name.lineno); - NAMES.Add(base); - } - } else { - if (old->Kind() == Type::BUILT_IN) { - fprintf(stderr, "%s:%d attempt to redefine built in class %s\n", - filename, type->DeclLine(), - type->QualifiedName().c_str()); - err = 1; - } - else if (type->Kind() != old->Kind()) { - const char* oldKind = kind_to_string(old->Kind()); - const char* newKind = kind_to_string(type->Kind()); - - fprintf(stderr, "%s:%d attempt to redefine %s as %s,\n", - filename, type->DeclLine(), - type->QualifiedName().c_str(), newKind); - fprintf(stderr, "%s:%d previously defined here as %s.\n", - old->DeclFile().c_str(), old->DeclLine(), oldKind); - err = 1; - } - } - - items = items->next; - } - return err; -} - -// ========================================================== -static bool -matches_keyword(const char* str) -{ - static const char* KEYWORDS[] = { "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", - NULL - }; - const char** k = KEYWORDS; - while (*k) { - if (0 == strcmp(str, *k)) { - return true; - } - k++; - } - return false; -} - -static int -check_method(const char* filename, int kind, method_type* m) -{ - int err = 0; - - // return type - Type* returnType = NAMES.Search(m->type.type.data); - if (returnType == NULL) { - fprintf(stderr, "%s:%d unknown return type %s\n", filename, - m->type.type.lineno, m->type.type.data); - err = 1; - return err; - } - - if (returnType == EVENT_FAKE_TYPE) { - if (kind != INTERFACE_TYPE_RPC) { - fprintf(stderr, "%s:%d event methods only supported for rpc interfaces\n", - filename, m->type.type.lineno); - err = 1; - } - } else { - if (!(kind == INTERFACE_TYPE_BINDER ? returnType->CanWriteToParcel() - : returnType->CanWriteToRpcData())) { - fprintf(stderr, "%s:%d return type %s can't be marshalled.\n", filename, - m->type.type.lineno, m->type.type.data); - err = 1; - } - } - - if (m->type.dimension > 0 && !returnType->CanBeArray()) { - fprintf(stderr, "%s:%d return type %s%s can't be an array.\n", filename, - m->type.array_token.lineno, m->type.type.data, - m->type.array_token.data); - err = 1; - } - - if (m->type.dimension > 1) { - fprintf(stderr, "%s:%d return type %s%s only one" - " dimensional arrays are supported\n", filename, - m->type.array_token.lineno, m->type.type.data, - m->type.array_token.data); - err = 1; - } - - int index = 1; - - arg_type* arg = m->args; - while (arg) { - Type* t = NAMES.Search(arg->type.type.data); - - // check the arg type - if (t == NULL) { - fprintf(stderr, "%s:%d parameter %s (%d) unknown type %s\n", - filename, m->type.type.lineno, arg->name.data, index, - arg->type.type.data); - err = 1; - goto next; - } - - if (t == EVENT_FAKE_TYPE) { - fprintf(stderr, "%s:%d parameter %s (%d) event can not be used as a parameter %s\n", - filename, m->type.type.lineno, arg->name.data, index, - arg->type.type.data); - err = 1; - goto next; - } - - if (!(kind == INTERFACE_TYPE_BINDER ? t->CanWriteToParcel() : t->CanWriteToRpcData())) { - fprintf(stderr, "%s:%d parameter %d: '%s %s' can't be marshalled.\n", - filename, m->type.type.lineno, index, - arg->type.type.data, arg->name.data); - err = 1; - } - - if (returnType == EVENT_FAKE_TYPE - && convert_direction(arg->direction.data) != IN_PARAMETER) { - fprintf(stderr, "%s:%d parameter %d: '%s %s' All paremeters on events must be 'in'.\n", - filename, m->type.type.lineno, index, - arg->type.type.data, arg->name.data); - err = 1; - goto next; - } - - if (arg->direction.data == NULL - && (arg->type.dimension != 0 || t->CanBeOutParameter())) { - fprintf(stderr, "%s:%d parameter %d: '%s %s' can be an out" - " parameter, so you must declare it as in," - " out or inout.\n", - filename, m->type.type.lineno, index, - arg->type.type.data, arg->name.data); - err = 1; - } - - if (convert_direction(arg->direction.data) != IN_PARAMETER - && !t->CanBeOutParameter() - && arg->type.dimension == 0) { - fprintf(stderr, "%s:%d parameter %d: '%s %s %s' can only be an in" - " parameter.\n", - filename, m->type.type.lineno, index, - arg->direction.data, arg->type.type.data, - arg->name.data); - err = 1; - } - - if (arg->type.dimension > 0 && !t->CanBeArray()) { - fprintf(stderr, "%s:%d parameter %d: '%s %s%s %s' can't be an" - " array.\n", filename, - m->type.array_token.lineno, index, arg->direction.data, - arg->type.type.data, arg->type.array_token.data, - arg->name.data); - err = 1; - } - - if (arg->type.dimension > 1) { - fprintf(stderr, "%s:%d parameter %d: '%s %s%s %s' only one" - " dimensional arrays are supported\n", filename, - m->type.array_token.lineno, index, arg->direction.data, - arg->type.type.data, arg->type.array_token.data, - arg->name.data); - err = 1; - } - - // check that the name doesn't match a keyword - if (matches_keyword(arg->name.data)) { - fprintf(stderr, "%s:%d parameter %d %s is named the same as a" - " Java or aidl keyword\n", - filename, m->name.lineno, index, arg->name.data); - err = 1; - } - -next: - index++; - arg = arg->next; - } - - return err; -} - -static int -check_types(const char* filename, document_item_type* items) -{ - int err = 0; - while (items) { - // (nothing to check for USER_DATA_TYPE) - if (items->item_type == INTERFACE_TYPE_BINDER - || items->item_type == INTERFACE_TYPE_RPC) { - map<string,method_type*> methodNames; - interface_type* c = (interface_type*)items; - - interface_item_type* member = c->interface_items; - while (member) { - if (member->item_type == METHOD_TYPE) { - method_type* m = (method_type*)member; - - err |= check_method(filename, items->item_type, m); - - // prevent duplicate methods - if (methodNames.find(m->name.data) == methodNames.end()) { - methodNames[m->name.data] = m; - } else { - fprintf(stderr,"%s:%d attempt to redefine method %s,\n", - filename, m->name.lineno, m->name.data); - method_type* old = methodNames[m->name.data]; - fprintf(stderr, "%s:%d previously defined here.\n", - filename, old->name.lineno); - err = 1; - } - } - member = member->next; - } - } - - items = items->next; - } - return err; -} - -// ========================================================== -static int -exactly_one_interface(const char* filename, const document_item_type* items, const Options& options, - bool* onlyParcelable) -{ - if (items == NULL) { - fprintf(stderr, "%s: file does not contain any interfaces\n", - filename); - return 1; - } - - const document_item_type* next = items->next; - // Allow parcelables to skip the "one-only" rule. - if (items->next != NULL && next->item_type != USER_DATA_TYPE) { - int lineno = -1; - if (next->item_type == INTERFACE_TYPE_BINDER) { - lineno = ((interface_type*)next)->interface_token.lineno; - } - else if (next->item_type == INTERFACE_TYPE_RPC) { - lineno = ((interface_type*)next)->interface_token.lineno; - } - fprintf(stderr, "%s:%d aidl can only handle one interface per file\n", - filename, lineno); - return 1; - } - - if (items->item_type == USER_DATA_TYPE) { - *onlyParcelable = true; - if (options.failOnParcelable) { - fprintf(stderr, "%s:%d aidl can only generate code for interfaces, not" - " parcelables or flattenables,\n", filename, - ((user_data_type*)items)->keyword_token.lineno); - fprintf(stderr, "%s:%d .aidl files that only declare parcelables or flattenables" - "may not go in the Makefile.\n", filename, - ((user_data_type*)items)->keyword_token.lineno); - return 1; - } - } else { - *onlyParcelable = false; - } - - return 0; -} - -// ========================================================== -void -generate_dep_file(const Options& options, const document_item_type* items) -{ - /* we open the file in binary mode to ensure that the same output is - * generated on all platforms !! - */ - FILE* to = NULL; - if (options.autoDepFile) { - string fileName = options.outputFileName + ".d"; - to = fopen(fileName.c_str(), "wb"); - } else { - to = fopen(options.depFileName.c_str(), "wb"); - } - - if (to == NULL) { - return; - } - - const char* slash = "\\"; - import_info* import = g_imports; - if (import == NULL) { - slash = ""; - } - - if (items->item_type == INTERFACE_TYPE_BINDER || items->item_type == INTERFACE_TYPE_RPC) { - fprintf(to, "%s: \\\n", options.outputFileName.c_str()); - } else { - // parcelable: there's no output file. - fprintf(to, " : \\\n"); - } - fprintf(to, " %s %s\n", options.inputFileName.c_str(), slash); - - while (import) { - if (import->next == NULL) { - slash = ""; - } - if (import->filename) { - fprintf(to, " %s %s\n", import->filename, slash); - } - import = import->next; - } - - fprintf(to, "\n"); - - // Output "<imported_file>: " so make won't fail if the imported file has - // been deleted, moved or renamed in incremental build. - import = g_imports; - while (import) { - if (import->filename) { - fprintf(to, "%s :\n", import->filename); - } - import = import->next; - } - - fclose(to); -} - -// ========================================================== -static string -generate_outputFileName2(const Options& options, const buffer_type& name, const char* package) -{ - string result; - - // create the path to the destination folder based on the - // interface package name - result = options.outputBaseFolder; - result += OS_PATH_SEPARATOR; - - string packageStr = package; - size_t len = packageStr.length(); - for (size_t i=0; i<len; i++) { - if (packageStr[i] == '.') { - packageStr[i] = OS_PATH_SEPARATOR; - } - } - - result += packageStr; - - // add the filename by replacing the .aidl extension to .java - const char* p = strchr(name.data, '.'); - len = p ? p-name.data : strlen(name.data); - - result += OS_PATH_SEPARATOR; - result.append(name.data, len); - result += ".java"; - - return result; -} - -// ========================================================== -static string -generate_outputFileName(const Options& options, const document_item_type* items) -{ - // items has already been checked to have only one interface. - if (items->item_type == INTERFACE_TYPE_BINDER || items->item_type == INTERFACE_TYPE_RPC) { - interface_type* type = (interface_type*)items; - - return generate_outputFileName2(options, type->name, type->package); - } else if (items->item_type == USER_DATA_TYPE) { - user_data_type* type = (user_data_type*)items; - return generate_outputFileName2(options, type->name, type->package); - } - - // I don't think we can come here, but safer than returning NULL. - string result; - return result; -} - - - -// ========================================================== -static void -check_outputFilePath(const string& path) { - size_t len = path.length(); - for (size_t i=0; i<len ; i++) { - if (path[i] == OS_PATH_SEPARATOR) { - string p = path.substr(0, i); - if (access(path.data(), F_OK) != 0) { -#ifdef HAVE_MS_C_RUNTIME - _mkdir(p.data()); -#else - mkdir(p.data(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP); -#endif - } - } - } -} - - -// ========================================================== -static int -parse_preprocessed_file(const string& filename) -{ - int err; - - FILE* f = fopen(filename.c_str(), "rb"); - if (f == NULL) { - fprintf(stderr, "aidl: can't open preprocessed file: %s\n", - filename.c_str()); - return 1; - } - - int lineno = 1; - char line[1024]; - char type[1024]; - char fullname[1024]; - while (fgets(line, sizeof(line), f)) { - // skip comments and empty lines - if (!line[0] || strncmp(line, "//", 2) == 0) { - continue; - } - - sscanf(line, "%s %[^; \r\n\t];", type, fullname); - - char* packagename; - char* classname = rfind(fullname, '.'); - if (classname != NULL) { - *classname = '\0'; - classname++; - packagename = fullname; - } else { - classname = fullname; - packagename = NULL; - } - - //printf("%s:%d:...%s...%s...%s...\n", filename.c_str(), lineno, - // type, packagename, classname); - document_item_type* doc; - - if (0 == strcmp("parcelable", type)) { - user_data_type* parcl = (user_data_type*)malloc( - sizeof(user_data_type)); - memset(parcl, 0, sizeof(user_data_type)); - parcl->document_item.item_type = USER_DATA_TYPE; - parcl->keyword_token.lineno = lineno; - parcl->keyword_token.data = strdup(type); - parcl->package = packagename ? strdup(packagename) : NULL; - parcl->name.lineno = lineno; - parcl->name.data = strdup(classname); - parcl->semicolon_token.lineno = lineno; - parcl->semicolon_token.data = strdup(";"); - parcl->flattening_methods = PARCELABLE_DATA; - doc = (document_item_type*)parcl; - } - else if (0 == strcmp("flattenable", type)) { - user_data_type* parcl = (user_data_type*)malloc( - sizeof(user_data_type)); - memset(parcl, 0, sizeof(user_data_type)); - parcl->document_item.item_type = USER_DATA_TYPE; - parcl->keyword_token.lineno = lineno; - parcl->keyword_token.data = strdup(type); - parcl->package = packagename ? strdup(packagename) : NULL; - parcl->name.lineno = lineno; - parcl->name.data = strdup(classname); - parcl->semicolon_token.lineno = lineno; - parcl->semicolon_token.data = strdup(";"); - parcl->flattening_methods = RPC_DATA; - doc = (document_item_type*)parcl; - } - else if (0 == strcmp("interface", type)) { - interface_type* iface = (interface_type*)malloc( - sizeof(interface_type)); - memset(iface, 0, sizeof(interface_type)); - iface->document_item.item_type = INTERFACE_TYPE_BINDER; - iface->interface_token.lineno = lineno; - iface->interface_token.data = strdup(type); - iface->package = packagename ? strdup(packagename) : NULL; - iface->name.lineno = lineno; - iface->name.data = strdup(classname); - iface->open_brace_token.lineno = lineno; - iface->open_brace_token.data = strdup("{"); - iface->close_brace_token.lineno = lineno; - iface->close_brace_token.data = strdup("}"); - doc = (document_item_type*)iface; - } - else { - fprintf(stderr, "%s:%d: bad type in line: %s\n", - filename.c_str(), lineno, line); - fclose(f); - return 1; - } - err = gather_types(filename.c_str(), doc); - lineno++; - } - - if (!feof(f)) { - fprintf(stderr, "%s:%d: error reading file, line to long.\n", - filename.c_str(), lineno); - return 1; - } - - fclose(f); - return 0; -} - -static int -check_and_assign_method_ids(const char * filename, interface_item_type* first_item) -{ - // Check whether there are any methods with manually assigned id's and any that are not. - // Either all method id's must be manually assigned or all of them must not. - // Also, check for duplicates of user set id's and that the id's are within the proper bounds. - set<int> usedIds; - interface_item_type* item = first_item; - bool hasUnassignedIds = false; - bool hasAssignedIds = false; - while (item != NULL) { - if (item->item_type == METHOD_TYPE) { - method_type* method_item = (method_type*)item; - if (method_item->hasId) { - hasAssignedIds = true; - method_item->assigned_id = atoi(method_item->id.data); - // Ensure that the user set id is not duplicated. - if (usedIds.find(method_item->assigned_id) != usedIds.end()) { - // We found a duplicate id, so throw an error. - fprintf(stderr, - "%s:%d Found duplicate method id (%d) for method: %s\n", - filename, method_item->id.lineno, - method_item->assigned_id, method_item->name.data); - return 1; - } - // Ensure that the user set id is within the appropriate limits - if (method_item->assigned_id < MIN_USER_SET_METHOD_ID || - method_item->assigned_id > MAX_USER_SET_METHOD_ID) { - fprintf(stderr, "%s:%d Found out of bounds id (%d) for method: %s\n", - filename, method_item->id.lineno, - method_item->assigned_id, method_item->name.data); - fprintf(stderr, " Value for id must be between %d and %d inclusive.\n", - MIN_USER_SET_METHOD_ID, MAX_USER_SET_METHOD_ID); - return 1; - } - usedIds.insert(method_item->assigned_id); - } else { - hasUnassignedIds = true; - } - if (hasAssignedIds && hasUnassignedIds) { - fprintf(stderr, - "%s: You must either assign id's to all methods or to none of them.\n", - filename); - return 1; - } - } - item = item->next; - } - - // In the case that all methods have unassigned id's, set a unique id for them. - if (hasUnassignedIds) { - int newId = 0; - item = first_item; - while (item != NULL) { - if (item->item_type == METHOD_TYPE) { - method_type* method_item = (method_type*)item; - method_item->assigned_id = newId++; - } - item = item->next; - } - } - - // success - return 0; -} - -// ========================================================== -static int -compile_aidl(Options& options) -{ - int err = 0, N; - - set_import_paths(options.importPaths); - - register_base_types(); - - // import the preprocessed file - N = options.preprocessedFiles.size(); - for (int i=0; i<N; i++) { - const string& s = options.preprocessedFiles[i]; - err |= parse_preprocessed_file(s); - } - if (err != 0) { - return err; - } - - // parse the main file - g_callbacks = &g_mainCallbacks; - err = parse_aidl(options.inputFileName.c_str()); - document_item_type* mainDoc = g_document; - g_document = NULL; - - // parse the imports - g_callbacks = &g_mainCallbacks; - import_info* import = g_imports; - while (import) { - if (NAMES.Find(import->neededClass) == NULL) { - import->filename = find_import_file(import->neededClass); - if (!import->filename) { - fprintf(stderr, "%s:%d: couldn't find import for class %s\n", - import->from, import->statement.lineno, - import->neededClass); - err |= 1; - } else { - err |= parse_aidl(import->filename); - import->doc = g_document; - if (import->doc == NULL) { - err |= 1; - } - } - } - import = import->next; - } - // bail out now if parsing wasn't successful - if (err != 0 || mainDoc == NULL) { - //fprintf(stderr, "aidl: parsing failed, stopping.\n"); - return 1; - } - - // complain about ones that aren't in the right files - err |= check_filenames(options.inputFileName.c_str(), mainDoc); - import = g_imports; - while (import) { - err |= check_filenames(import->filename, import->doc); - import = import->next; - } - - // gather the types that have been declared - err |= gather_types(options.inputFileName.c_str(), mainDoc); - import = g_imports; - while (import) { - err |= gather_types(import->filename, import->doc); - import = import->next; - } - -#if 0 - printf("---- main doc ----\n"); - test_document(mainDoc); - - import = g_imports; - while (import) { - printf("---- import doc ----\n"); - test_document(import->doc); - import = import->next; - } - NAMES.Dump(); -#endif - - // check the referenced types in mainDoc to make sure we've imported them - err |= check_types(options.inputFileName.c_str(), mainDoc); - - // finally, there really only needs to be one thing in mainDoc, and it - // needs to be an interface. - bool onlyParcelable = false; - err |= exactly_one_interface(options.inputFileName.c_str(), mainDoc, options, &onlyParcelable); - - // If this includes an interface definition, then assign method ids and validate. - if (!onlyParcelable) { - err |= check_and_assign_method_ids(options.inputFileName.c_str(), - ((interface_type*)mainDoc)->interface_items); - } - - // after this, there shouldn't be any more errors because of the - // input. - if (err != 0 || mainDoc == NULL) { - return 1; - } - - // if needed, generate the outputFileName from the outputBaseFolder - if (options.outputFileName.length() == 0 && - options.outputBaseFolder.length() > 0) { - options.outputFileName = generate_outputFileName(options, mainDoc); - } - - // if we were asked to, generate a make dependency file - // unless it's a parcelable *and* it's supposed to fail on parcelable - if ((options.autoDepFile || options.depFileName != "") && - !(onlyParcelable && options.failOnParcelable)) { - // make sure the folders of the output file all exists - check_outputFilePath(options.outputFileName); - generate_dep_file(options, mainDoc); - } - - // they didn't ask to fail on parcelables, so just exit quietly. - if (onlyParcelable && !options.failOnParcelable) { - return 0; - } - - // make sure the folders of the output file all exists - check_outputFilePath(options.outputFileName); - - err = generate_java(options.outputFileName, options.inputFileName.c_str(), - (interface_type*)mainDoc); - - return err; -} - -static int -preprocess_aidl(const Options& options) -{ - vector<string> lines; - int err; - - // read files - int N = options.filesToPreprocess.size(); - for (int i=0; i<N; i++) { - g_callbacks = &g_mainCallbacks; - err = parse_aidl(options.filesToPreprocess[i].c_str()); - if (err != 0) { - return err; - } - document_item_type* doc = g_document; - string line; - if (doc->item_type == USER_DATA_TYPE) { - user_data_type* parcelable = (user_data_type*)doc; - if ((parcelable->flattening_methods & PARCELABLE_DATA) != 0) { - line = "parcelable "; - } - if ((parcelable->flattening_methods & RPC_DATA) != 0) { - line = "flattenable "; - } - if (parcelable->package) { - line += parcelable->package; - line += '.'; - } - line += parcelable->name.data; - } else { - line = "interface "; - interface_type* iface = (interface_type*)doc; - if (iface->package) { - line += iface->package; - line += '.'; - } - line += iface->name.data; - } - line += ";\n"; - lines.push_back(line); - } - - // write preprocessed file - int fd = open( options.outputFileName.c_str(), - O_RDWR|O_CREAT|O_TRUNC|O_BINARY, -#ifdef HAVE_MS_C_RUNTIME - _S_IREAD|_S_IWRITE); -#else - S_IRUSR|S_IWUSR|S_IRGRP); -#endif - if (fd == -1) { - fprintf(stderr, "aidl: could not open file for write: %s\n", - options.outputFileName.c_str()); - return 1; - } - - N = lines.size(); - for (int i=0; i<N; i++) { - const string& s = lines[i]; - int len = s.length(); - if (len != write(fd, s.c_str(), len)) { - fprintf(stderr, "aidl: error writing to file %s\n", - options.outputFileName.c_str()); - close(fd); - unlink(options.outputFileName.c_str()); - return 1; - } - } - - close(fd); - return 0; -} - -// ========================================================== -int -main(int argc, const char **argv) -{ - Options options; - int result = parse_options(argc, argv, &options); - if (result) { - return result; - } - - switch (options.task) - { - case COMPILE_AIDL: - return compile_aidl(options); - case PREPROCESS_AIDL: - return preprocess_aidl(options); - } - fprintf(stderr, "aidl: internal error\n"); - return 1; -} diff --git a/tools/aidl/aidl_language.cpp b/tools/aidl/aidl_language.cpp deleted file mode 100644 index cd6a3bd5dfc1..000000000000 --- a/tools/aidl/aidl_language.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "aidl_language.h" -#include <stdio.h> -#include <string.h> -#include <stdlib.h> - -#ifdef HAVE_MS_C_RUNTIME -int isatty(int fd) -{ - return (fd == 0); -} -#endif - -#if 0 -ParserCallbacks k_parserCallbacks = { - NULL -}; -#endif - -ParserCallbacks* g_callbacks = NULL; // &k_parserCallbacks; - diff --git a/tools/aidl/aidl_language.h b/tools/aidl/aidl_language.h deleted file mode 100644 index de1370c086f5..000000000000 --- a/tools/aidl/aidl_language.h +++ /dev/null @@ -1,172 +0,0 @@ -#ifndef DEVICE_TOOLS_AIDL_AIDL_LANGUAGE_H -#define DEVICE_TOOLS_AIDL_AIDL_LANGUAGE_H - - -typedef enum { - NO_EXTRA_TEXT = 0, - SHORT_COMMENT, - LONG_COMMENT, - COPY_TEXT, - WHITESPACE -} which_extra_text; - -typedef struct extra_text_type { - unsigned lineno; - which_extra_text which; - char* data; - unsigned len; - struct extra_text_type* next; -} extra_text_type; - -typedef struct buffer_type { - unsigned lineno; - unsigned token; - char *data; - extra_text_type* extra; -} buffer_type; - -typedef struct type_type { - buffer_type type; - buffer_type array_token; - int dimension; -} type_type; - -typedef struct arg_type { - buffer_type comma_token; // empty in the first one in the list - buffer_type direction; - type_type type; - buffer_type name; - struct arg_type *next; -} arg_type; - -enum { - METHOD_TYPE -}; - -typedef struct interface_item_type { - unsigned item_type; - struct interface_item_type* next; -} interface_item_type; - -typedef struct method_type { - interface_item_type interface_item; - type_type type; - bool oneway; - buffer_type oneway_token; - buffer_type name; - buffer_type open_paren_token; - arg_type* args; - buffer_type close_paren_token; - bool hasId; - buffer_type equals_token; - buffer_type id; - // XXX missing comments/copy text here - buffer_type semicolon_token; - buffer_type* comments_token; // points into this structure, DO NOT DELETE - int assigned_id; -} method_type; - -enum { - USER_DATA_TYPE = 12, - INTERFACE_TYPE_BINDER, - INTERFACE_TYPE_RPC -}; - -typedef struct document_item_type { - unsigned item_type; - struct document_item_type* next; -} document_item_type; - - -// for user_data_type.flattening_methods -enum { - PARCELABLE_DATA = 0x1, - RPC_DATA = 0x2 -}; - -typedef struct user_data_type { - document_item_type document_item; - buffer_type keyword_token; // only the first one - char* package; - buffer_type name; - buffer_type semicolon_token; - int flattening_methods; -} user_data_type; - -typedef struct interface_type { - document_item_type document_item; - buffer_type interface_token; - bool oneway; - buffer_type oneway_token; - char* package; - buffer_type name; - buffer_type open_brace_token; - interface_item_type* interface_items; - buffer_type close_brace_token; - buffer_type* comments_token; // points into this structure, DO NOT DELETE -} interface_type; - -typedef union lexer_type { - buffer_type buffer; - type_type type; - arg_type *arg; - method_type* method; - interface_item_type* interface_item; - interface_type* interface_obj; - user_data_type* user_data; - document_item_type* document_item; -} lexer_type; - - -#define YYSTYPE lexer_type - -#if __cplusplus -extern "C" { -#endif - -int parse_aidl(char const *); - -// strips off the leading whitespace, the "import" text -// also returns whether it's a local or system import -// we rely on the input matching the import regex from below -char* parse_import_statement(const char* text); - -// in, out or inout -enum { - IN_PARAMETER = 1, - OUT_PARAMETER = 2, - INOUT_PARAMETER = 3 -}; -int convert_direction(const char* direction); - -// callbacks from within the parser -// these functions all take ownership of the strings -typedef struct ParserCallbacks { - void (*document)(document_item_type* items); - void (*import)(buffer_type* statement); -} ParserCallbacks; - -extern ParserCallbacks* g_callbacks; - -// true if there was an error parsing, false otherwise -extern int g_error; - -// the name of the file we're currently parsing -extern char const* g_currentFilename; - -// the package name for our current file -extern char const* g_currentPackage; - -typedef enum { - STATEMENT_INSIDE_INTERFACE -} error_type; - -void init_buffer_type(buffer_type* buf, int lineno); - - -#if __cplusplus -} -#endif - - -#endif // DEVICE_TOOLS_AIDL_AIDL_LANGUAGE_H diff --git a/tools/aidl/aidl_language_l.l b/tools/aidl/aidl_language_l.l deleted file mode 100644 index 3d33e7a14913..000000000000 --- a/tools/aidl/aidl_language_l.l +++ /dev/null @@ -1,214 +0,0 @@ -%{ -#include "aidl_language.h" -#include "aidl_language_y.h" -#include "search_path.h" -#include <string.h> -#include <stdlib.h> - -extern YYSTYPE yylval; - -// comment and whitespace handling -// these functions save a copy of the buffer -static void begin_extra_text(unsigned lineno, which_extra_text which); -static void append_extra_text(char* text); -static extra_text_type* get_extra_text(void); // you now own the object - // this returns -static void drop_extra_text(void); - -// package handling -static void do_package_statement(const char* importText); - -#define SET_BUFFER(t) \ - do { \ - yylval.buffer.lineno = yylineno; \ - yylval.buffer.token = (t); \ - yylval.buffer.data = strdup(yytext); \ - yylval.buffer.extra = get_extra_text(); \ - } while(0) - -%} - -%option yylineno -%option noyywrap - -%x COPYING LONG_COMMENT - -identifier [_a-zA-Z][_a-zA-Z0-9\.]* -whitespace ([ \t\n\r]+) -brackets \[{whitespace}?\] -idvalue (0|[1-9][0-9]*) - -%% - - -\%\%\{ { begin_extra_text(yylineno, COPY_TEXT); BEGIN(COPYING); } -<COPYING>\}\%\% { BEGIN(INITIAL); } -<COPYING>.*\n { append_extra_text(yytext); } -<COPYING>.* { append_extra_text(yytext); } -<COPYING>\n+ { append_extra_text(yytext); } - - -\/\* { begin_extra_text(yylineno, (which_extra_text)LONG_COMMENT); - BEGIN(LONG_COMMENT); } -<LONG_COMMENT>[^*]* { append_extra_text(yytext); } -<LONG_COMMENT>\*+[^/] { append_extra_text(yytext); } -<LONG_COMMENT>\n { append_extra_text(yytext); } -<LONG_COMMENT>\**\/ { BEGIN(INITIAL); } - -^{whitespace}?import{whitespace}[^ \t\r\n]+{whitespace}?; { - SET_BUFFER(IMPORT); - return IMPORT; - } -^{whitespace}?package{whitespace}[^ \t\r\n]+{whitespace}?; { - do_package_statement(yytext); - SET_BUFFER(PACKAGE); - return PACKAGE; - } -<<EOF>> { yyterminate(); } - -\/\/.*\n { begin_extra_text(yylineno, SHORT_COMMENT); - append_extra_text(yytext); } - -{whitespace} { /* begin_extra_text(yylineno, WHITESPACE); - append_extra_text(yytext); */ } - -; { SET_BUFFER(';'); return ';'; } -\{ { SET_BUFFER('{'); return '{'; } -\} { SET_BUFFER('}'); return '}'; } -\( { SET_BUFFER('('); return '('; } -\) { SET_BUFFER(')'); return ')'; } -, { SET_BUFFER(','); return ','; } -= { SET_BUFFER('='); return '='; } - - /* keywords */ -parcelable { SET_BUFFER(PARCELABLE); return PARCELABLE; } -interface { SET_BUFFER(INTERFACE); return INTERFACE; } -flattenable { SET_BUFFER(FLATTENABLE); return FLATTENABLE; } -rpc { SET_BUFFER(INTERFACE); return RPC; } -in { SET_BUFFER(IN); return IN; } -out { SET_BUFFER(OUT); return OUT; } -inout { SET_BUFFER(INOUT); return INOUT; } -oneway { SET_BUFFER(ONEWAY); return ONEWAY; } - -{brackets}+ { SET_BUFFER(ARRAY); return ARRAY; } -{idvalue} { SET_BUFFER(IDVALUE); return IDVALUE; } -{identifier} { SET_BUFFER(IDENTIFIER); return IDENTIFIER; } -{identifier}\<{whitespace}*{identifier}({whitespace}*,{whitespace}*{identifier})*{whitespace}*\> { - SET_BUFFER(GENERIC); return GENERIC; } - - /* syntax error! */ -. { printf("UNKNOWN(%s)", yytext); - yylval.buffer.lineno = yylineno; - yylval.buffer.token = IDENTIFIER; - yylval.buffer.data = strdup(yytext); - return IDENTIFIER; - } - -%% - -// comment and whitespace handling -// ================================================ -extra_text_type* g_extraText = NULL; -extra_text_type* g_nextExtraText = NULL; - -void begin_extra_text(unsigned lineno, which_extra_text which) -{ - extra_text_type* text = (extra_text_type*)malloc(sizeof(extra_text_type)); - text->lineno = lineno; - text->which = which; - text->data = NULL; - text->len = 0; - text->next = NULL; - if (g_nextExtraText == NULL) { - g_extraText = text; - } else { - g_nextExtraText->next = text; - } - g_nextExtraText = text; -} - -void append_extra_text(char* text) -{ - if (g_nextExtraText->data == NULL) { - g_nextExtraText->data = strdup(text); - g_nextExtraText->len = strlen(text); - } else { - char* orig = g_nextExtraText->data; - unsigned oldLen = g_nextExtraText->len; - unsigned len = strlen(text); - g_nextExtraText->len += len; - g_nextExtraText->data = (char*)malloc(g_nextExtraText->len+1); - memcpy(g_nextExtraText->data, orig, oldLen); - memcpy(g_nextExtraText->data+oldLen, text, len); - g_nextExtraText->data[g_nextExtraText->len] = '\0'; - free(orig); - } -} - -extra_text_type* -get_extra_text(void) -{ - extra_text_type* result = g_extraText; - g_extraText = NULL; - g_nextExtraText = NULL; - return result; -} - -void drop_extra_text(void) -{ - extra_text_type* p = g_extraText; - while (p) { - extra_text_type* next = p->next; - free(p->data); - free(p); - free(next); - } - g_extraText = NULL; - g_nextExtraText = NULL; -} - - -// package handling -// ================================================ -void do_package_statement(const char* importText) -{ - if (g_currentPackage) free((void*)g_currentPackage); - g_currentPackage = parse_import_statement(importText); -} - - -// main parse function -// ================================================ -char const* g_currentFilename = NULL; -char const* g_currentPackage = NULL; - -int yyparse(void); - -int parse_aidl(char const *filename) -{ - yyin = fopen(filename, "r"); - if (yyin) { - char const* oldFilename = g_currentFilename; - char const* oldPackage = g_currentPackage; - g_currentFilename = strdup(filename); - - g_error = 0; - yylineno = 1; - int rv = yyparse(); - if (g_error != 0) { - rv = g_error; - } - - free((void*)g_currentFilename); - g_currentFilename = oldFilename; - - if (g_currentPackage) free((void*)g_currentPackage); - g_currentPackage = oldPackage; - - return rv; - } else { - fprintf(stderr, "aidl: unable to open file for read: %s\n", filename); - return 1; - } -} - diff --git a/tools/aidl/aidl_language_y.y b/tools/aidl/aidl_language_y.y deleted file mode 100644 index 9b40d28ad598..000000000000 --- a/tools/aidl/aidl_language_y.y +++ /dev/null @@ -1,373 +0,0 @@ -%{ -#include "aidl_language.h" -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -int yyerror(char* errstr); -int yylex(void); -extern int yylineno; - -static int count_brackets(const char*); - -%} - -%token IMPORT -%token PACKAGE -%token IDENTIFIER -%token IDVALUE -%token GENERIC -%token ARRAY -%token PARCELABLE -%token INTERFACE -%token FLATTENABLE -%token RPC -%token IN -%token OUT -%token INOUT -%token ONEWAY - -%% -document: - document_items { g_callbacks->document($1.document_item); } - | headers document_items { g_callbacks->document($2.document_item); } - ; - -headers: - package { } - | imports { } - | package imports { } - ; - -package: - PACKAGE { } - ; - -imports: - IMPORT { g_callbacks->import(&($1.buffer)); } - | IMPORT imports { g_callbacks->import(&($1.buffer)); } - ; - -document_items: - { $$.document_item = NULL; } - | document_items declaration { - if ($2.document_item == NULL) { - // error cases only - $$ = $1; - } else { - document_item_type* p = $1.document_item; - while (p && p->next) { - p=p->next; - } - if (p) { - p->next = (document_item_type*)$2.document_item; - $$ = $1; - } else { - $$.document_item = (document_item_type*)$2.document_item; - } - } - } - | document_items error { - fprintf(stderr, "%s:%d: syntax error don't know what to do with \"%s\"\n", g_currentFilename, - $2.buffer.lineno, $2.buffer.data); - $$ = $1; - } - ; - -declaration: - parcelable_decl { $$.document_item = (document_item_type*)$1.user_data; } - | interface_decl { $$.document_item = (document_item_type*)$1.interface_item; } - ; - -parcelable_decl: - PARCELABLE IDENTIFIER ';' { - user_data_type* b = (user_data_type*)malloc(sizeof(user_data_type)); - b->document_item.item_type = USER_DATA_TYPE; - b->document_item.next = NULL; - b->keyword_token = $1.buffer; - b->name = $2.buffer; - b->package = g_currentPackage ? strdup(g_currentPackage) : NULL; - b->semicolon_token = $3.buffer; - b->flattening_methods = PARCELABLE_DATA; - $$.user_data = b; - } - | PARCELABLE ';' { - fprintf(stderr, "%s:%d syntax error in parcelable declaration. Expected type name.\n", - g_currentFilename, $1.buffer.lineno); - $$.user_data = NULL; - } - | PARCELABLE error ';' { - fprintf(stderr, "%s:%d syntax error in parcelable declaration. Expected type name, saw \"%s\".\n", - g_currentFilename, $2.buffer.lineno, $2.buffer.data); - $$.user_data = NULL; - } - | FLATTENABLE IDENTIFIER ';' { - user_data_type* b = (user_data_type*)malloc(sizeof(user_data_type)); - b->document_item.item_type = USER_DATA_TYPE; - b->document_item.next = NULL; - b->keyword_token = $1.buffer; - b->name = $2.buffer; - b->package = g_currentPackage ? strdup(g_currentPackage) : NULL; - b->semicolon_token = $3.buffer; - b->flattening_methods = PARCELABLE_DATA | RPC_DATA; - $$.user_data = b; - } - | FLATTENABLE ';' { - fprintf(stderr, "%s:%d syntax error in flattenable declaration. Expected type name.\n", - g_currentFilename, $1.buffer.lineno); - $$.user_data = NULL; - } - | FLATTENABLE error ';' { - fprintf(stderr, "%s:%d syntax error in flattenable declaration. Expected type name, saw \"%s\".\n", - g_currentFilename, $2.buffer.lineno, $2.buffer.data); - $$.user_data = NULL; - } - - ; - -interface_header: - INTERFACE { - interface_type* c = (interface_type*)malloc(sizeof(interface_type)); - c->document_item.item_type = INTERFACE_TYPE_BINDER; - c->document_item.next = NULL; - c->interface_token = $1.buffer; - c->oneway = false; - memset(&c->oneway_token, 0, sizeof(buffer_type)); - c->comments_token = &c->interface_token; - $$.interface_obj = c; - } - | ONEWAY INTERFACE { - interface_type* c = (interface_type*)malloc(sizeof(interface_type)); - c->document_item.item_type = INTERFACE_TYPE_BINDER; - c->document_item.next = NULL; - c->interface_token = $2.buffer; - c->oneway = true; - c->oneway_token = $1.buffer; - c->comments_token = &c->oneway_token; - $$.interface_obj = c; - } - | RPC { - interface_type* c = (interface_type*)malloc(sizeof(interface_type)); - c->document_item.item_type = INTERFACE_TYPE_RPC; - c->document_item.next = NULL; - c->interface_token = $1.buffer; - c->oneway = false; - memset(&c->oneway_token, 0, sizeof(buffer_type)); - c->comments_token = &c->interface_token; - $$.interface_obj = c; - } - ; - -interface_keywords: - INTERFACE - | RPC - ; - -interface_decl: - interface_header IDENTIFIER '{' interface_items '}' { - interface_type* c = $1.interface_obj; - c->name = $2.buffer; - c->package = g_currentPackage ? strdup(g_currentPackage) : NULL; - c->open_brace_token = $3.buffer; - c->interface_items = $4.interface_item; - c->close_brace_token = $5.buffer; - $$.interface_obj = c; - } - | interface_keywords error '{' interface_items '}' { - fprintf(stderr, "%s:%d: syntax error in interface declaration. Expected type name, saw \"%s\"\n", - g_currentFilename, $2.buffer.lineno, $2.buffer.data); - $$.document_item = NULL; - } - | interface_keywords error '}' { - fprintf(stderr, "%s:%d: syntax error in interface declaration. Expected type name, saw \"%s\"\n", - g_currentFilename, $2.buffer.lineno, $2.buffer.data); - $$.document_item = NULL; - } - - ; - -interface_items: - { $$.interface_item = NULL; } - | interface_items method_decl { - interface_item_type* p=$1.interface_item; - while (p && p->next) { - p=p->next; - } - if (p) { - p->next = (interface_item_type*)$2.method; - $$ = $1; - } else { - $$.interface_item = (interface_item_type*)$2.method; - } - } - | interface_items error ';' { - fprintf(stderr, "%s:%d: syntax error before ';' (expected method declaration)\n", - g_currentFilename, $3.buffer.lineno); - $$ = $1; - } - ; - -method_decl: - type IDENTIFIER '(' arg_list ')' ';' { - method_type *method = (method_type*)malloc(sizeof(method_type)); - method->interface_item.item_type = METHOD_TYPE; - method->interface_item.next = NULL; - method->oneway = false; - method->type = $1.type; - memset(&method->oneway_token, 0, sizeof(buffer_type)); - method->name = $2.buffer; - method->open_paren_token = $3.buffer; - method->args = $4.arg; - method->close_paren_token = $5.buffer; - method->hasId = false; - memset(&method->equals_token, 0, sizeof(buffer_type)); - memset(&method->id, 0, sizeof(buffer_type)); - method->semicolon_token = $6.buffer; - method->comments_token = &method->type.type; - $$.method = method; - } - | ONEWAY type IDENTIFIER '(' arg_list ')' ';' { - method_type *method = (method_type*)malloc(sizeof(method_type)); - method->interface_item.item_type = METHOD_TYPE; - method->interface_item.next = NULL; - method->oneway = true; - method->oneway_token = $1.buffer; - method->type = $2.type; - method->name = $3.buffer; - method->open_paren_token = $4.buffer; - method->args = $5.arg; - method->close_paren_token = $6.buffer; - method->hasId = false; - memset(&method->equals_token, 0, sizeof(buffer_type)); - memset(&method->id, 0, sizeof(buffer_type)); - method->semicolon_token = $7.buffer; - method->comments_token = &method->oneway_token; - $$.method = method; - } - | type IDENTIFIER '(' arg_list ')' '=' IDVALUE ';' { - method_type *method = (method_type*)malloc(sizeof(method_type)); - method->interface_item.item_type = METHOD_TYPE; - method->interface_item.next = NULL; - method->oneway = false; - memset(&method->oneway_token, 0, sizeof(buffer_type)); - method->type = $1.type; - method->name = $2.buffer; - method->open_paren_token = $3.buffer; - method->args = $4.arg; - method->close_paren_token = $5.buffer; - method->hasId = true; - method->equals_token = $6.buffer; - method->id = $7.buffer; - method->semicolon_token = $8.buffer; - method->comments_token = &method->type.type; - $$.method = method; - } - | ONEWAY type IDENTIFIER '(' arg_list ')' '=' IDVALUE ';' { - method_type *method = (method_type*)malloc(sizeof(method_type)); - method->interface_item.item_type = METHOD_TYPE; - method->interface_item.next = NULL; - method->oneway = true; - method->oneway_token = $1.buffer; - method->type = $2.type; - method->name = $3.buffer; - method->open_paren_token = $4.buffer; - method->args = $5.arg; - method->close_paren_token = $6.buffer; - method->hasId = true; - method->equals_token = $7.buffer; - method->id = $8.buffer; - method->semicolon_token = $9.buffer; - method->comments_token = &method->oneway_token; - $$.method = method; - } - ; - -arg_list: - { $$.arg = NULL; } - | arg { $$ = $1; } - | arg_list ',' arg { - if ($$.arg != NULL) { - // only NULL on error - $$ = $1; - arg_type *p = $1.arg; - while (p && p->next) { - p=p->next; - } - $3.arg->comma_token = $2.buffer; - p->next = $3.arg; - } - } - | error { - fprintf(stderr, "%s:%d: syntax error in parameter list\n", g_currentFilename, $1.buffer.lineno); - $$.arg = NULL; - } - ; - -arg: - direction type IDENTIFIER { - arg_type* arg = (arg_type*)malloc(sizeof(arg_type)); - memset(&arg->comma_token, 0, sizeof(buffer_type)); - arg->direction = $1.buffer; - arg->type = $2.type; - arg->name = $3.buffer; - arg->next = NULL; - $$.arg = arg; - } - ; - -type: - IDENTIFIER { - $$.type.type = $1.buffer; - init_buffer_type(&$$.type.array_token, yylineno); - $$.type.dimension = 0; - } - | IDENTIFIER ARRAY { - $$.type.type = $1.buffer; - $$.type.array_token = $2.buffer; - $$.type.dimension = count_brackets($2.buffer.data); - } - | GENERIC { - $$.type.type = $1.buffer; - init_buffer_type(&$$.type.array_token, yylineno); - $$.type.dimension = 0; - } - ; - -direction: - { init_buffer_type(&$$.buffer, yylineno); } - | IN { $$.buffer = $1.buffer; } - | OUT { $$.buffer = $1.buffer; } - | INOUT { $$.buffer = $1.buffer; } - ; - -%% - -#include <ctype.h> -#include <stdio.h> - -int g_error = 0; - -int yyerror(char* errstr) -{ - fprintf(stderr, "%s:%d: %s\n", g_currentFilename, yylineno, errstr); - g_error = 1; - return 1; -} - -void init_buffer_type(buffer_type* buf, int lineno) -{ - buf->lineno = lineno; - buf->token = 0; - buf->data = NULL; - buf->extra = NULL; -} - -static int count_brackets(const char* s) -{ - int n=0; - while (*s) { - if (*s == '[') n++; - s++; - } - return n; -} diff --git a/tools/aidl/generate_java.cpp b/tools/aidl/generate_java.cpp deleted file mode 100644 index 9e57407e772f..000000000000 --- a/tools/aidl/generate_java.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "generate_java.h" -#include "Type.h" -#include <string.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -// ================================================= -VariableFactory::VariableFactory(const string& base) - :m_base(base), - m_index(0) -{ -} - -Variable* -VariableFactory::Get(Type* type) -{ - char name[100]; - sprintf(name, "%s%d", m_base.c_str(), m_index); - m_index++; - Variable* v = new Variable(type, name); - m_vars.push_back(v); - return v; -} - -Variable* -VariableFactory::Get(int index) -{ - return m_vars[index]; -} - -// ================================================= -string -gather_comments(extra_text_type* extra) -{ - string s; - while (extra) { - if (extra->which == SHORT_COMMENT) { - s += extra->data; - } - else if (extra->which == LONG_COMMENT) { - s += "/*"; - s += extra->data; - s += "*/"; - } - extra = extra->next; - } - return s; -} - -string -append(const char* a, const char* b) -{ - string s = a; - s += b; - return s; -} - -// ================================================= -int -generate_java(const string& filename, const string& originalSrc, - interface_type* iface) -{ - Class* cl; - - if (iface->document_item.item_type == INTERFACE_TYPE_BINDER) { - cl = generate_binder_interface_class(iface); - } - else if (iface->document_item.item_type == INTERFACE_TYPE_RPC) { - cl = generate_rpc_interface_class(iface); - } - - Document* document = new Document; - document->comment = ""; - if (iface->package) document->package = iface->package; - document->originalSrc = originalSrc; - document->classes.push_back(cl); - -// printf("outputting... filename=%s\n", filename.c_str()); - FILE* to; - if (filename == "-") { - to = stdout; - } else { - /* open file in binary mode to ensure that the tool produces the - * same output on all platforms !! - */ - to = fopen(filename.c_str(), "wb"); - if (to == NULL) { - fprintf(stderr, "unable to open %s for write\n", filename.c_str()); - return 1; - } - } - - document->Write(to); - - fclose(to); - return 0; -} - diff --git a/tools/aidl/generate_java.h b/tools/aidl/generate_java.h deleted file mode 100644 index 4bfcfeba07c8..000000000000 --- a/tools/aidl/generate_java.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef GENERATE_JAVA_H -#define GENERATE_JAVA_H - -#include "aidl_language.h" -#include "AST.h" - -#include <string> - -using namespace std; - -int generate_java(const string& filename, const string& originalSrc, - interface_type* iface); - -Class* generate_binder_interface_class(const interface_type* iface); -Class* generate_rpc_interface_class(const interface_type* iface); - -string gather_comments(extra_text_type* extra); -string append(const char* a, const char* b); - -class VariableFactory -{ -public: - VariableFactory(const string& base); // base must be short - Variable* Get(Type* type); - Variable* Get(int index); -private: - vector<Variable*> m_vars; - string m_base; - int m_index; -}; - -#endif // GENERATE_JAVA_H - diff --git a/tools/aidl/generate_java_binder.cpp b/tools/aidl/generate_java_binder.cpp deleted file mode 100644 index f291ceb2b09f..000000000000 --- a/tools/aidl/generate_java_binder.cpp +++ /dev/null @@ -1,560 +0,0 @@ -#include "generate_java.h" -#include "Type.h" -#include <string.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -// ================================================= -class StubClass : public Class -{ -public: - StubClass(Type* type, Type* interfaceType); - virtual ~StubClass(); - - Variable* transact_code; - Variable* transact_data; - Variable* transact_reply; - Variable* transact_flags; - SwitchStatement* transact_switch; -private: - void make_as_interface(Type* interfaceType); -}; - -StubClass::StubClass(Type* type, Type* interfaceType) - :Class() -{ - this->comment = "/** Local-side IPC implementation stub class. */"; - this->modifiers = PUBLIC | ABSTRACT | STATIC; - this->what = Class::CLASS; - this->type = type; - this->extends = BINDER_NATIVE_TYPE; - this->interfaces.push_back(interfaceType); - - // descriptor - Field* descriptor = new Field(STATIC | FINAL | PRIVATE, - new Variable(STRING_TYPE, "DESCRIPTOR")); - descriptor->value = "\"" + interfaceType->QualifiedName() + "\""; - this->elements.push_back(descriptor); - - // ctor - Method* ctor = new Method; - ctor->modifiers = PUBLIC; - ctor->comment = "/** Construct the stub at attach it to the " - "interface. */"; - ctor->name = "Stub"; - ctor->statements = new StatementBlock; - MethodCall* attach = new MethodCall(THIS_VALUE, "attachInterface", - 2, THIS_VALUE, new LiteralExpression("DESCRIPTOR")); - ctor->statements->Add(attach); - this->elements.push_back(ctor); - - // asInterface - make_as_interface(interfaceType); - - // asBinder - Method* asBinder = new Method; - asBinder->modifiers = PUBLIC | OVERRIDE; - asBinder->returnType = IBINDER_TYPE; - asBinder->name = "asBinder"; - asBinder->statements = new StatementBlock; - asBinder->statements->Add(new ReturnStatement(THIS_VALUE)); - this->elements.push_back(asBinder); - - // onTransact - this->transact_code = new Variable(INT_TYPE, "code"); - this->transact_data = new Variable(PARCEL_TYPE, "data"); - this->transact_reply = new Variable(PARCEL_TYPE, "reply"); - this->transact_flags = new Variable(INT_TYPE, "flags"); - Method* onTransact = new Method; - onTransact->modifiers = PUBLIC | OVERRIDE; - onTransact->returnType = BOOLEAN_TYPE; - onTransact->name = "onTransact"; - onTransact->parameters.push_back(this->transact_code); - onTransact->parameters.push_back(this->transact_data); - onTransact->parameters.push_back(this->transact_reply); - onTransact->parameters.push_back(this->transact_flags); - onTransact->statements = new StatementBlock; - onTransact->exceptions.push_back(REMOTE_EXCEPTION_TYPE); - this->elements.push_back(onTransact); - this->transact_switch = new SwitchStatement(this->transact_code); - - onTransact->statements->Add(this->transact_switch); - MethodCall* superCall = new MethodCall(SUPER_VALUE, "onTransact", 4, - this->transact_code, this->transact_data, - this->transact_reply, this->transact_flags); - onTransact->statements->Add(new ReturnStatement(superCall)); -} - -StubClass::~StubClass() -{ -} - -void -StubClass::make_as_interface(Type *interfaceType) -{ - Variable* obj = new Variable(IBINDER_TYPE, "obj"); - - Method* m = new Method; - m->comment = "/**\n * Cast an IBinder object into an "; - m->comment += interfaceType->QualifiedName(); - m->comment += " interface,\n"; - m->comment += " * generating a proxy if needed.\n */"; - m->modifiers = PUBLIC | STATIC; - m->returnType = interfaceType; - m->name = "asInterface"; - m->parameters.push_back(obj); - m->statements = new StatementBlock; - - IfStatement* ifstatement = new IfStatement(); - ifstatement->expression = new Comparison(obj, "==", NULL_VALUE); - ifstatement->statements = new StatementBlock; - ifstatement->statements->Add(new ReturnStatement(NULL_VALUE)); - m->statements->Add(ifstatement); - - // IInterface iin = obj.queryLocalInterface(DESCRIPTOR) - MethodCall* queryLocalInterface = new MethodCall(obj, "queryLocalInterface"); - queryLocalInterface->arguments.push_back(new LiteralExpression("DESCRIPTOR")); - IInterfaceType* iinType = new IInterfaceType(); - Variable *iin = new Variable(iinType, "iin"); - VariableDeclaration* iinVd = new VariableDeclaration(iin, queryLocalInterface, NULL); - m->statements->Add(iinVd); - - // Ensure the instance type of the local object is as expected. - // One scenario where this is needed is if another package (with a - // different class loader) runs in the same process as the service. - - // if (iin != null && iin instanceof <interfaceType>) return (<interfaceType>) iin; - Comparison* iinNotNull = new Comparison(iin, "!=", NULL_VALUE); - Comparison* instOfCheck = new Comparison(iin, " instanceof ", - new LiteralExpression(interfaceType->QualifiedName())); - IfStatement* instOfStatement = new IfStatement(); - instOfStatement->expression = new Comparison(iinNotNull, "&&", instOfCheck); - instOfStatement->statements = new StatementBlock; - instOfStatement->statements->Add(new ReturnStatement(new Cast(interfaceType, iin))); - m->statements->Add(instOfStatement); - - string proxyType = interfaceType->QualifiedName(); - proxyType += ".Stub.Proxy"; - NewExpression* ne = new NewExpression(NAMES.Find(proxyType)); - ne->arguments.push_back(obj); - m->statements->Add(new ReturnStatement(ne)); - - this->elements.push_back(m); -} - - - -// ================================================= -class ProxyClass : public Class -{ -public: - ProxyClass(Type* type, InterfaceType* interfaceType); - virtual ~ProxyClass(); - - Variable* mRemote; - bool mOneWay; -}; - -ProxyClass::ProxyClass(Type* type, InterfaceType* interfaceType) - :Class() -{ - this->modifiers = PRIVATE | STATIC; - this->what = Class::CLASS; - this->type = type; - this->interfaces.push_back(interfaceType); - - mOneWay = interfaceType->OneWay(); - - // IBinder mRemote - mRemote = new Variable(IBINDER_TYPE, "mRemote"); - this->elements.push_back(new Field(PRIVATE, mRemote)); - - // Proxy() - Variable* remote = new Variable(IBINDER_TYPE, "remote"); - Method* ctor = new Method; - ctor->name = "Proxy"; - ctor->statements = new StatementBlock; - ctor->parameters.push_back(remote); - ctor->statements->Add(new Assignment(mRemote, remote)); - this->elements.push_back(ctor); - - // IBinder asBinder() - Method* asBinder = new Method; - asBinder->modifiers = PUBLIC | OVERRIDE; - asBinder->returnType = IBINDER_TYPE; - asBinder->name = "asBinder"; - asBinder->statements = new StatementBlock; - asBinder->statements->Add(new ReturnStatement(mRemote)); - this->elements.push_back(asBinder); -} - -ProxyClass::~ProxyClass() -{ -} - -// ================================================= -static void -generate_new_array(Type* t, StatementBlock* addTo, Variable* v, - Variable* parcel) -{ - Variable* len = new Variable(INT_TYPE, v->name + "_length"); - addTo->Add(new VariableDeclaration(len, new MethodCall(parcel, "readInt"))); - IfStatement* lencheck = new IfStatement(); - lencheck->expression = new Comparison(len, "<", new LiteralExpression("0")); - lencheck->statements->Add(new Assignment(v, NULL_VALUE)); - lencheck->elseif = new IfStatement(); - lencheck->elseif->statements->Add(new Assignment(v, - new NewArrayExpression(t, len))); - addTo->Add(lencheck); -} - -static void -generate_write_to_parcel(Type* t, StatementBlock* addTo, Variable* v, - Variable* parcel, int flags) -{ - if (v->dimension == 0) { - t->WriteToParcel(addTo, v, parcel, flags); - } - if (v->dimension == 1) { - t->WriteArrayToParcel(addTo, v, parcel, flags); - } -} - -static void -generate_create_from_parcel(Type* t, StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl) -{ - if (v->dimension == 0) { - t->CreateFromParcel(addTo, v, parcel, cl); - } - if (v->dimension == 1) { - t->CreateArrayFromParcel(addTo, v, parcel, cl); - } -} - -static void -generate_read_from_parcel(Type* t, StatementBlock* addTo, Variable* v, - Variable* parcel, Variable** cl) -{ - if (v->dimension == 0) { - t->ReadFromParcel(addTo, v, parcel, cl); - } - if (v->dimension == 1) { - t->ReadArrayFromParcel(addTo, v, parcel, cl); - } -} - - -static void -generate_method(const method_type* method, Class* interface, - StubClass* stubClass, ProxyClass* proxyClass, int index) -{ - arg_type* arg; - int i; - bool hasOutParams = false; - - const bool oneway = proxyClass->mOneWay || method->oneway; - - // == the TRANSACT_ constant ============================================= - string transactCodeName = "TRANSACTION_"; - transactCodeName += method->name.data; - - char transactCodeValue[60]; - sprintf(transactCodeValue, "(android.os.IBinder.FIRST_CALL_TRANSACTION + %d)", index); - - Field* transactCode = new Field(STATIC | FINAL, - new Variable(INT_TYPE, transactCodeName)); - transactCode->value = transactCodeValue; - stubClass->elements.push_back(transactCode); - - // == the declaration in the interface =================================== - Method* decl = new Method; - decl->comment = gather_comments(method->comments_token->extra); - decl->modifiers = PUBLIC; - decl->returnType = NAMES.Search(method->type.type.data); - decl->returnTypeDimension = method->type.dimension; - decl->name = method->name.data; - - arg = method->args; - while (arg != NULL) { - decl->parameters.push_back(new Variable( - NAMES.Search(arg->type.type.data), arg->name.data, - arg->type.dimension)); - arg = arg->next; - } - - decl->exceptions.push_back(REMOTE_EXCEPTION_TYPE); - - interface->elements.push_back(decl); - - // == the stub method ==================================================== - - Case* c = new Case(transactCodeName); - - MethodCall* realCall = new MethodCall(THIS_VALUE, method->name.data); - - // interface token validation is the very first thing we do - c->statements->Add(new MethodCall(stubClass->transact_data, - "enforceInterface", 1, new LiteralExpression("DESCRIPTOR"))); - - // args - Variable* cl = NULL; - VariableFactory stubArgs("_arg"); - arg = method->args; - while (arg != NULL) { - Type* t = NAMES.Search(arg->type.type.data); - Variable* v = stubArgs.Get(t); - v->dimension = arg->type.dimension; - - c->statements->Add(new VariableDeclaration(v)); - - if (convert_direction(arg->direction.data) & IN_PARAMETER) { - generate_create_from_parcel(t, c->statements, v, - stubClass->transact_data, &cl); - } else { - if (arg->type.dimension == 0) { - c->statements->Add(new Assignment(v, new NewExpression(v->type))); - } - else if (arg->type.dimension == 1) { - generate_new_array(v->type, c->statements, v, - stubClass->transact_data); - } - else { - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, - __LINE__); - } - } - - realCall->arguments.push_back(v); - - arg = arg->next; - } - - // the real call - Variable* _result = NULL; - if (0 == strcmp(method->type.type.data, "void")) { - c->statements->Add(realCall); - - if (!oneway) { - // report that there were no exceptions - MethodCall* ex = new MethodCall(stubClass->transact_reply, - "writeNoException", 0); - c->statements->Add(ex); - } - } else { - _result = new Variable(decl->returnType, "_result", - decl->returnTypeDimension); - c->statements->Add(new VariableDeclaration(_result, realCall)); - - if (!oneway) { - // report that there were no exceptions - MethodCall* ex = new MethodCall(stubClass->transact_reply, - "writeNoException", 0); - c->statements->Add(ex); - } - - // marshall the return value - generate_write_to_parcel(decl->returnType, c->statements, _result, - stubClass->transact_reply, - Type::PARCELABLE_WRITE_RETURN_VALUE); - } - - // out parameters - i = 0; - arg = method->args; - while (arg != NULL) { - Type* t = NAMES.Search(arg->type.type.data); - Variable* v = stubArgs.Get(i++); - - if (convert_direction(arg->direction.data) & OUT_PARAMETER) { - generate_write_to_parcel(t, c->statements, v, - stubClass->transact_reply, - Type::PARCELABLE_WRITE_RETURN_VALUE); - hasOutParams = true; - } - - arg = arg->next; - } - - // return true - c->statements->Add(new ReturnStatement(TRUE_VALUE)); - stubClass->transact_switch->cases.push_back(c); - - // == the proxy method =================================================== - Method* proxy = new Method; - proxy->comment = gather_comments(method->comments_token->extra); - proxy->modifiers = PUBLIC | OVERRIDE; - proxy->returnType = NAMES.Search(method->type.type.data); - proxy->returnTypeDimension = method->type.dimension; - proxy->name = method->name.data; - proxy->statements = new StatementBlock; - arg = method->args; - while (arg != NULL) { - proxy->parameters.push_back(new Variable( - NAMES.Search(arg->type.type.data), arg->name.data, - arg->type.dimension)); - arg = arg->next; - } - proxy->exceptions.push_back(REMOTE_EXCEPTION_TYPE); - proxyClass->elements.push_back(proxy); - - // the parcels - Variable* _data = new Variable(PARCEL_TYPE, "_data"); - proxy->statements->Add(new VariableDeclaration(_data, - new MethodCall(PARCEL_TYPE, "obtain"))); - Variable* _reply = NULL; - if (!oneway) { - _reply = new Variable(PARCEL_TYPE, "_reply"); - proxy->statements->Add(new VariableDeclaration(_reply, - new MethodCall(PARCEL_TYPE, "obtain"))); - } - - // the return value - _result = NULL; - if (0 != strcmp(method->type.type.data, "void")) { - _result = new Variable(proxy->returnType, "_result", - method->type.dimension); - proxy->statements->Add(new VariableDeclaration(_result)); - } - - // try and finally - TryStatement* tryStatement = new TryStatement(); - proxy->statements->Add(tryStatement); - FinallyStatement* finallyStatement = new FinallyStatement(); - proxy->statements->Add(finallyStatement); - - // the interface identifier token: the DESCRIPTOR constant, marshalled as a string - tryStatement->statements->Add(new MethodCall(_data, "writeInterfaceToken", - 1, new LiteralExpression("DESCRIPTOR"))); - - // the parameters - arg = method->args; - while (arg != NULL) { - Type* t = NAMES.Search(arg->type.type.data); - Variable* v = new Variable(t, arg->name.data, arg->type.dimension); - int dir = convert_direction(arg->direction.data); - if (dir == OUT_PARAMETER && arg->type.dimension != 0) { - IfStatement* checklen = new IfStatement(); - checklen->expression = new Comparison(v, "==", NULL_VALUE); - checklen->statements->Add(new MethodCall(_data, "writeInt", 1, - new LiteralExpression("-1"))); - checklen->elseif = new IfStatement(); - checklen->elseif->statements->Add(new MethodCall(_data, "writeInt", - 1, new FieldVariable(v, "length"))); - tryStatement->statements->Add(checklen); - } - else if (dir & IN_PARAMETER) { - generate_write_to_parcel(t, tryStatement->statements, v, _data, 0); - } - arg = arg->next; - } - - // the transact call - MethodCall* call = new MethodCall(proxyClass->mRemote, "transact", 4, - new LiteralExpression("Stub." + transactCodeName), - _data, _reply ? _reply : NULL_VALUE, - new LiteralExpression( - oneway ? "android.os.IBinder.FLAG_ONEWAY" : "0")); - tryStatement->statements->Add(call); - - // throw back exceptions. - if (_reply) { - MethodCall* ex = new MethodCall(_reply, "readException", 0); - tryStatement->statements->Add(ex); - } - - // returning and cleanup - if (_reply != NULL) { - if (_result != NULL) { - generate_create_from_parcel(proxy->returnType, - tryStatement->statements, _result, _reply, &cl); - } - - // the out/inout parameters - arg = method->args; - while (arg != NULL) { - Type* t = NAMES.Search(arg->type.type.data); - Variable* v = new Variable(t, arg->name.data, arg->type.dimension); - if (convert_direction(arg->direction.data) & OUT_PARAMETER) { - generate_read_from_parcel(t, tryStatement->statements, - v, _reply, &cl); - } - arg = arg->next; - } - - finallyStatement->statements->Add(new MethodCall(_reply, "recycle")); - } - finallyStatement->statements->Add(new MethodCall(_data, "recycle")); - - if (_result != NULL) { - proxy->statements->Add(new ReturnStatement(_result)); - } -} - -static void -generate_interface_descriptors(StubClass* stub, ProxyClass* proxy) -{ - // the interface descriptor transaction handler - Case* c = new Case("INTERFACE_TRANSACTION"); - c->statements->Add(new MethodCall(stub->transact_reply, "writeString", - 1, new LiteralExpression("DESCRIPTOR"))); - c->statements->Add(new ReturnStatement(TRUE_VALUE)); - stub->transact_switch->cases.push_back(c); - - // and the proxy-side method returning the descriptor directly - Method* getDesc = new Method; - getDesc->modifiers = PUBLIC; - getDesc->returnType = STRING_TYPE; - getDesc->returnTypeDimension = 0; - getDesc->name = "getInterfaceDescriptor"; - getDesc->statements = new StatementBlock; - getDesc->statements->Add(new ReturnStatement(new LiteralExpression("DESCRIPTOR"))); - proxy->elements.push_back(getDesc); -} - -Class* -generate_binder_interface_class(const interface_type* iface) -{ - InterfaceType* interfaceType = static_cast<InterfaceType*>( - NAMES.Find(iface->package, iface->name.data)); - - // the interface class - Class* interface = new Class; - interface->comment = gather_comments(iface->comments_token->extra); - interface->modifiers = PUBLIC; - interface->what = Class::INTERFACE; - interface->type = interfaceType; - interface->interfaces.push_back(IINTERFACE_TYPE); - - // the stub inner class - StubClass* stub = new StubClass( - NAMES.Find(iface->package, append(iface->name.data, ".Stub").c_str()), - interfaceType); - interface->elements.push_back(stub); - - // the proxy inner class - ProxyClass* proxy = new ProxyClass( - NAMES.Find(iface->package, - append(iface->name.data, ".Stub.Proxy").c_str()), - interfaceType); - stub->elements.push_back(proxy); - - // stub and proxy support for getInterfaceDescriptor() - generate_interface_descriptors(stub, proxy); - - // all the declared methods of the interface - int index = 0; - interface_item_type* item = iface->interface_items; - while (item != NULL) { - if (item->item_type == METHOD_TYPE) { - method_type * method_item = (method_type*) item; - generate_method(method_item, interface, stub, proxy, method_item->assigned_id); - } - item = item->next; - index++; - } - - return interface; -} - diff --git a/tools/aidl/generate_java_rpc.cpp b/tools/aidl/generate_java_rpc.cpp deleted file mode 100644 index 5e4daccf6334..000000000000 --- a/tools/aidl/generate_java_rpc.cpp +++ /dev/null @@ -1,1001 +0,0 @@ -#include "generate_java.h" -#include "Type.h" -#include <string.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -Type* SERVICE_CONTEXT_TYPE = new Type("android.content", - "Context", Type::BUILT_IN, false, false, false); -Type* PRESENTER_BASE_TYPE = new Type("android.support.place.connector", - "EventListener", Type::BUILT_IN, false, false, false); -Type* PRESENTER_LISTENER_BASE_TYPE = new Type("android.support.place.connector", - "EventListener.Listener", Type::BUILT_IN, false, false, false); -Type* RPC_BROKER_TYPE = new Type("android.support.place.connector", "Broker", - Type::BUILT_IN, false, false, false); -Type* RPC_CONTAINER_TYPE = new Type("com.android.athome.connector", "ConnectorContainer", - Type::BUILT_IN, false, false, false); -Type* PLACE_INFO_TYPE = new Type("android.support.place.connector", "PlaceInfo", - Type::BUILT_IN, false, false, false); -// TODO: Just use Endpoint, so this works for all endpoints. -Type* RPC_CONNECTOR_TYPE = new Type("android.support.place.connector", "Connector", - Type::BUILT_IN, false, false, false); -Type* RPC_ENDPOINT_INFO_TYPE = new UserDataType("android.support.place.rpc", - "EndpointInfo", true, __FILE__, __LINE__); -Type* RPC_RESULT_HANDLER_TYPE = new UserDataType("android.support.place.rpc", "RpcResultHandler", - true, __FILE__, __LINE__); -Type* RPC_ERROR_LISTENER_TYPE = new Type("android.support.place.rpc", "RpcErrorHandler", - Type::BUILT_IN, false, false, false); -Type* RPC_CONTEXT_TYPE = new UserDataType("android.support.place.rpc", "RpcContext", true, - __FILE__, __LINE__); - -static void generate_create_from_data(Type* t, StatementBlock* addTo, const string& key, - Variable* v, Variable* data, Variable** cl); -static void generate_new_array(Type* t, StatementBlock* addTo, Variable* v, Variable* from); -static void generate_write_to_data(Type* t, StatementBlock* addTo, Expression* k, Variable* v, - Variable* data); - -static string -format_int(int n) -{ - char str[20]; - sprintf(str, "%d", n); - return string(str); -} - -static string -class_name_leaf(const string& str) -{ - string::size_type pos = str.rfind('.'); - if (pos == string::npos) { - return str; - } else { - return string(str, pos+1); - } -} - -static string -results_class_name(const string& n) -{ - string str = n; - str[0] = toupper(str[0]); - str.insert(0, "On"); - return str; -} - -static string -results_method_name(const string& n) -{ - string str = n; - str[0] = toupper(str[0]); - str.insert(0, "on"); - return str; -} - -static string -push_method_name(const string& n) -{ - string str = n; - str[0] = toupper(str[0]); - str.insert(0, "push"); - return str; -} - -// ================================================= -class DispatcherClass : public Class -{ -public: - DispatcherClass(const interface_type* iface, Expression* target); - virtual ~DispatcherClass(); - - void AddMethod(const method_type* method); - void DoneWithMethods(); - - Method* processMethod; - Variable* actionParam; - Variable* requestParam; - Variable* rpcContextParam; - Variable* errorParam; - Variable* requestData; - Variable* resultData; - IfStatement* dispatchIfStatement; - Expression* targetExpression; - -private: - void generate_process(); -}; - -DispatcherClass::DispatcherClass(const interface_type* iface, Expression* target) - :Class(), - dispatchIfStatement(NULL), - targetExpression(target) -{ - generate_process(); -} - -DispatcherClass::~DispatcherClass() -{ -} - -void -DispatcherClass::generate_process() -{ - // byte[] process(String action, byte[] params, RpcContext context, RpcError status) - this->processMethod = new Method; - this->processMethod->modifiers = PUBLIC; - this->processMethod->returnType = BYTE_TYPE; - this->processMethod->returnTypeDimension = 1; - this->processMethod->name = "process"; - this->processMethod->statements = new StatementBlock; - - this->actionParam = new Variable(STRING_TYPE, "action"); - this->processMethod->parameters.push_back(this->actionParam); - - this->requestParam = new Variable(BYTE_TYPE, "requestParam", 1); - this->processMethod->parameters.push_back(this->requestParam); - - this->rpcContextParam = new Variable(RPC_CONTEXT_TYPE, "context", 0); - this->processMethod->parameters.push_back(this->rpcContextParam); - - this->errorParam = new Variable(RPC_ERROR_TYPE, "errorParam", 0); - this->processMethod->parameters.push_back(this->errorParam); - - this->requestData = new Variable(RPC_DATA_TYPE, "request"); - this->processMethod->statements->Add(new VariableDeclaration(requestData, - new NewExpression(RPC_DATA_TYPE, 1, this->requestParam))); - - this->resultData = new Variable(RPC_DATA_TYPE, "resultData"); - this->processMethod->statements->Add(new VariableDeclaration(this->resultData, - NULL_VALUE)); -} - -void -DispatcherClass::AddMethod(const method_type* method) -{ - arg_type* arg; - - // The if/switch statement - IfStatement* ifs = new IfStatement(); - ifs->expression = new MethodCall(new StringLiteralExpression(method->name.data), "equals", - 1, this->actionParam); - StatementBlock* block = ifs->statements = new StatementBlock; - if (this->dispatchIfStatement == NULL) { - this->dispatchIfStatement = ifs; - this->processMethod->statements->Add(dispatchIfStatement); - } else { - this->dispatchIfStatement->elseif = ifs; - this->dispatchIfStatement = ifs; - } - - // The call to decl (from above) - MethodCall* realCall = new MethodCall(this->targetExpression, method->name.data); - - // args - Variable* classLoader = NULL; - VariableFactory stubArgs("_arg"); - arg = method->args; - while (arg != NULL) { - Type* t = NAMES.Search(arg->type.type.data); - Variable* v = stubArgs.Get(t); - v->dimension = arg->type.dimension; - - // Unmarshall the parameter - block->Add(new VariableDeclaration(v)); - if (convert_direction(arg->direction.data) & IN_PARAMETER) { - generate_create_from_data(t, block, arg->name.data, v, - this->requestData, &classLoader); - } else { - if (arg->type.dimension == 0) { - block->Add(new Assignment(v, new NewExpression(v->type))); - } - else if (arg->type.dimension == 1) { - generate_new_array(v->type, block, v, this->requestData); - } - else { - fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, - __LINE__); - } - } - - // Add that parameter to the method call - realCall->arguments.push_back(v); - - arg = arg->next; - } - - // Add a final parameter: RpcContext. Contains data about - // incoming request (e.g., certificate) - realCall->arguments.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0)); - - Type* returnType = NAMES.Search(method->type.type.data); - if (returnType == EVENT_FAKE_TYPE) { - returnType = VOID_TYPE; - } - - // the real call - bool first = true; - Variable* _result = NULL; - if (returnType == VOID_TYPE) { - block->Add(realCall); - } else { - _result = new Variable(returnType, "_result", - method->type.dimension); - block->Add(new VariableDeclaration(_result, realCall)); - - // need the result RpcData - if (first) { - block->Add(new Assignment(this->resultData, - new NewExpression(RPC_DATA_TYPE))); - first = false; - } - - // marshall the return value - generate_write_to_data(returnType, block, - new StringLiteralExpression("_result"), _result, this->resultData); - } - - // out parameters - int i = 0; - arg = method->args; - while (arg != NULL) { - Type* t = NAMES.Search(arg->type.type.data); - Variable* v = stubArgs.Get(i++); - - if (convert_direction(arg->direction.data) & OUT_PARAMETER) { - // need the result RpcData - if (first) { - block->Add(new Assignment(this->resultData, new NewExpression(RPC_DATA_TYPE))); - first = false; - } - - generate_write_to_data(t, block, new StringLiteralExpression(arg->name.data), - v, this->resultData); - } - - arg = arg->next; - } -} - -void -DispatcherClass::DoneWithMethods() -{ - if (this->dispatchIfStatement == NULL) { - return; - } - - this->elements.push_back(this->processMethod); - - IfStatement* fallthrough = new IfStatement(); - fallthrough->statements = new StatementBlock; - fallthrough->statements->Add(new ReturnStatement( - new MethodCall(SUPER_VALUE, "process", 4, - this->actionParam, this->requestParam, - this->rpcContextParam, - this->errorParam))); - this->dispatchIfStatement->elseif = fallthrough; - IfStatement* s = new IfStatement; - s->statements = new StatementBlock; - this->processMethod->statements->Add(s); - s->expression = new Comparison(this->resultData, "!=", NULL_VALUE); - s->statements->Add(new ReturnStatement(new MethodCall(this->resultData, "serialize"))); - s->elseif = new IfStatement; - s = s->elseif; - s->statements->Add(new ReturnStatement(NULL_VALUE)); -} - -// ================================================= -class RpcProxyClass : public Class -{ -public: - RpcProxyClass(const interface_type* iface, InterfaceType* interfaceType); - virtual ~RpcProxyClass(); - - Variable* endpoint; - Variable* broker; - -private: - void generate_ctor(); - void generate_get_endpoint_info(); -}; - -RpcProxyClass::RpcProxyClass(const interface_type* iface, InterfaceType* interfaceType) - :Class() -{ - this->comment = gather_comments(iface->comments_token->extra); - this->modifiers = PUBLIC; - this->what = Class::CLASS; - this->type = interfaceType; - - // broker - this->broker = new Variable(RPC_BROKER_TYPE, "_broker"); - this->elements.push_back(new Field(PRIVATE, this->broker)); - // endpoint - this->endpoint = new Variable(RPC_ENDPOINT_INFO_TYPE, "_endpoint"); - this->elements.push_back(new Field(PRIVATE, this->endpoint)); - - // methods - generate_ctor(); - generate_get_endpoint_info(); -} - -RpcProxyClass::~RpcProxyClass() -{ -} - -void -RpcProxyClass::generate_ctor() -{ - Variable* broker = new Variable(RPC_BROKER_TYPE, "broker"); - Variable* endpoint = new Variable(RPC_ENDPOINT_INFO_TYPE, "endpoint"); - Method* ctor = new Method; - ctor->modifiers = PUBLIC; - ctor->name = class_name_leaf(this->type->Name()); - ctor->statements = new StatementBlock; - ctor->parameters.push_back(broker); - ctor->parameters.push_back(endpoint); - this->elements.push_back(ctor); - - ctor->statements->Add(new Assignment(this->broker, broker)); - ctor->statements->Add(new Assignment(this->endpoint, endpoint)); -} - -void -RpcProxyClass::generate_get_endpoint_info() -{ - Method* get = new Method; - get->modifiers = PUBLIC; - get->returnType = RPC_ENDPOINT_INFO_TYPE; - get->name = "getEndpointInfo"; - get->statements = new StatementBlock; - this->elements.push_back(get); - - get->statements->Add(new ReturnStatement(this->endpoint)); -} - -// ================================================= -class EventListenerClass : public DispatcherClass -{ -public: - EventListenerClass(const interface_type* iface, Type* listenerType); - virtual ~EventListenerClass(); - - Variable* _listener; - -private: - void generate_ctor(); -}; - -Expression* -generate_get_listener_expression(Type* cast) -{ - return new Cast(cast, new MethodCall(THIS_VALUE, "getView")); -} - -EventListenerClass::EventListenerClass(const interface_type* iface, Type* listenerType) - :DispatcherClass(iface, new FieldVariable(THIS_VALUE, "_listener")) -{ - this->modifiers = PRIVATE; - this->what = Class::CLASS; - this->type = new Type(iface->package ? iface->package : "", - append(iface->name.data, ".Presenter"), - Type::GENERATED, false, false, false); - this->extends = PRESENTER_BASE_TYPE; - - this->_listener = new Variable(listenerType, "_listener"); - this->elements.push_back(new Field(PRIVATE, this->_listener)); - - // methods - generate_ctor(); -} - -EventListenerClass::~EventListenerClass() -{ -} - -void -EventListenerClass::generate_ctor() -{ - Variable* broker = new Variable(RPC_BROKER_TYPE, "broker"); - Variable* listener = new Variable(this->_listener->type, "listener"); - Method* ctor = new Method; - ctor->modifiers = PUBLIC; - ctor->name = class_name_leaf(this->type->Name()); - ctor->statements = new StatementBlock; - ctor->parameters.push_back(broker); - ctor->parameters.push_back(listener); - this->elements.push_back(ctor); - - ctor->statements->Add(new MethodCall("super", 2, broker, listener)); - ctor->statements->Add(new Assignment(this->_listener, listener)); -} - -// ================================================= -class ListenerClass : public Class -{ -public: - ListenerClass(const interface_type* iface); - virtual ~ListenerClass(); - - bool needed; - -private: - void generate_ctor(); -}; - -ListenerClass::ListenerClass(const interface_type* iface) - :Class(), - needed(false) -{ - this->comment = "/** Extend this to listen to the events from this class. */"; - this->modifiers = STATIC | PUBLIC ; - this->what = Class::CLASS; - this->type = new Type(iface->package ? iface->package : "", - append(iface->name.data, ".Listener"), - Type::GENERATED, false, false, false); - this->extends = PRESENTER_LISTENER_BASE_TYPE; -} - -ListenerClass::~ListenerClass() -{ -} - -// ================================================= -class EndpointBaseClass : public DispatcherClass -{ -public: - EndpointBaseClass(const interface_type* iface); - virtual ~EndpointBaseClass(); - - bool needed; - -private: - void generate_ctor(); -}; - -EndpointBaseClass::EndpointBaseClass(const interface_type* iface) - :DispatcherClass(iface, THIS_VALUE), - needed(false) -{ - this->comment = "/** Extend this to implement a link service. */"; - this->modifiers = STATIC | PUBLIC | ABSTRACT; - this->what = Class::CLASS; - this->type = new Type(iface->package ? iface->package : "", - append(iface->name.data, ".EndpointBase"), - Type::GENERATED, false, false, false); - this->extends = RPC_CONNECTOR_TYPE; - - // methods - generate_ctor(); -} - -EndpointBaseClass::~EndpointBaseClass() -{ -} - -void -EndpointBaseClass::generate_ctor() -{ - Variable* container = new Variable(RPC_CONTAINER_TYPE, "container"); - Variable* broker = new Variable(RPC_BROKER_TYPE, "broker"); - Variable* place = new Variable(PLACE_INFO_TYPE, "placeInfo"); - Method* ctor = new Method; - ctor->modifiers = PUBLIC; - ctor->name = class_name_leaf(this->type->Name()); - ctor->statements = new StatementBlock; - ctor->parameters.push_back(container); - ctor->parameters.push_back(broker); - ctor->parameters.push_back(place); - this->elements.push_back(ctor); - - ctor->statements->Add(new MethodCall("super", 3, container, broker, place)); -} - -// ================================================= -class ResultDispatcherClass : public Class -{ -public: - ResultDispatcherClass(); - virtual ~ResultDispatcherClass(); - - void AddMethod(int index, const string& name, Method** method, Variable** param); - - bool needed; - Variable* methodId; - Variable* callback; - Method* onResultMethod; - Variable* resultParam; - SwitchStatement* methodSwitch; - -private: - void generate_ctor(); - void generate_onResult(); -}; - -ResultDispatcherClass::ResultDispatcherClass() - :Class(), - needed(false) -{ - this->modifiers = PRIVATE | FINAL; - this->what = Class::CLASS; - this->type = new Type("_ResultDispatcher", Type::GENERATED, false, false, false); - this->interfaces.push_back(RPC_RESULT_HANDLER_TYPE); - - // methodId - this->methodId = new Variable(INT_TYPE, "methodId"); - this->elements.push_back(new Field(PRIVATE, this->methodId)); - this->callback = new Variable(OBJECT_TYPE, "callback"); - this->elements.push_back(new Field(PRIVATE, this->callback)); - - // methods - generate_ctor(); - generate_onResult(); -} - -ResultDispatcherClass::~ResultDispatcherClass() -{ -} - -void -ResultDispatcherClass::generate_ctor() -{ - Variable* methodIdParam = new Variable(INT_TYPE, "methId"); - Variable* callbackParam = new Variable(OBJECT_TYPE, "cbObj"); - Method* ctor = new Method; - ctor->modifiers = PUBLIC; - ctor->name = class_name_leaf(this->type->Name()); - ctor->statements = new StatementBlock; - ctor->parameters.push_back(methodIdParam); - ctor->parameters.push_back(callbackParam); - this->elements.push_back(ctor); - - ctor->statements->Add(new Assignment(this->methodId, methodIdParam)); - ctor->statements->Add(new Assignment(this->callback, callbackParam)); -} - -void -ResultDispatcherClass::generate_onResult() -{ - this->onResultMethod = new Method; - this->onResultMethod->modifiers = PUBLIC; - this->onResultMethod->returnType = VOID_TYPE; - this->onResultMethod->returnTypeDimension = 0; - this->onResultMethod->name = "onResult"; - this->onResultMethod->statements = new StatementBlock; - this->elements.push_back(this->onResultMethod); - - this->resultParam = new Variable(BYTE_TYPE, "result", 1); - this->onResultMethod->parameters.push_back(this->resultParam); - - this->methodSwitch = new SwitchStatement(this->methodId); - this->onResultMethod->statements->Add(this->methodSwitch); -} - -void -ResultDispatcherClass::AddMethod(int index, const string& name, Method** method, Variable** param) -{ - Method* m = new Method; - m->modifiers = PUBLIC; - m->returnType = VOID_TYPE; - m->returnTypeDimension = 0; - m->name = name; - m->statements = new StatementBlock; - *param = new Variable(BYTE_TYPE, "result", 1); - m->parameters.push_back(*param); - this->elements.push_back(m); - *method = m; - - Case* c = new Case(format_int(index)); - c->statements->Add(new MethodCall(new LiteralExpression("this"), name, 1, this->resultParam)); - c->statements->Add(new Break()); - - this->methodSwitch->cases.push_back(c); -} - -// ================================================= -static void -generate_new_array(Type* t, StatementBlock* addTo, Variable* v, Variable* from) -{ - fprintf(stderr, "aidl: implement generate_new_array %s:%d\n", __FILE__, __LINE__); - exit(1); -} - -static void -generate_create_from_data(Type* t, StatementBlock* addTo, const string& key, Variable* v, - Variable* data, Variable** cl) -{ - Expression* k = new StringLiteralExpression(key); - if (v->dimension == 0) { - t->CreateFromRpcData(addTo, k, v, data, cl); - } - if (v->dimension == 1) { - //t->ReadArrayFromRpcData(addTo, v, data, cl); - fprintf(stderr, "aidl: implement generate_create_from_data for arrays%s:%d\n", - __FILE__, __LINE__); - } -} - -static void -generate_write_to_data(Type* t, StatementBlock* addTo, Expression* k, Variable* v, Variable* data) -{ - if (v->dimension == 0) { - t->WriteToRpcData(addTo, k, v, data, 0); - } - if (v->dimension == 1) { - //t->WriteArrayToParcel(addTo, v, data); - fprintf(stderr, "aidl: implement generate_write_to_data for arrays%s:%d\n", - __FILE__, __LINE__); - } -} - -// ================================================= -static Type* -generate_results_method(const method_type* method, RpcProxyClass* proxyClass) -{ - arg_type* arg; - - string resultsMethodName = results_method_name(method->name.data); - Type* resultsInterfaceType = new Type(results_class_name(method->name.data), - Type::GENERATED, false, false, false); - - if (!method->oneway) { - Class* resultsClass = new Class; - resultsClass->modifiers = STATIC | PUBLIC; - resultsClass->what = Class::INTERFACE; - resultsClass->type = resultsInterfaceType; - - Method* resultMethod = new Method; - resultMethod->comment = gather_comments(method->comments_token->extra); - resultMethod->modifiers = PUBLIC; - resultMethod->returnType = VOID_TYPE; - resultMethod->returnTypeDimension = 0; - resultMethod->name = resultsMethodName; - if (0 != strcmp("void", method->type.type.data)) { - resultMethod->parameters.push_back(new Variable(NAMES.Search(method->type.type.data), - "_result", method->type.dimension)); - } - arg = method->args; - while (arg != NULL) { - if (convert_direction(arg->direction.data) & OUT_PARAMETER) { - resultMethod->parameters.push_back(new Variable( - NAMES.Search(arg->type.type.data), arg->name.data, - arg->type.dimension)); - } - arg = arg->next; - } - resultsClass->elements.push_back(resultMethod); - - if (resultMethod->parameters.size() > 0) { - proxyClass->elements.push_back(resultsClass); - return resultsInterfaceType; - } - } - //delete resultsInterfaceType; - return NULL; -} - -static void -generate_proxy_method(const method_type* method, RpcProxyClass* proxyClass, - ResultDispatcherClass* resultsDispatcherClass, Type* resultsInterfaceType, int index) -{ - arg_type* arg; - Method* proxyMethod = new Method; - proxyMethod->comment = gather_comments(method->comments_token->extra); - proxyMethod->modifiers = PUBLIC; - proxyMethod->returnType = VOID_TYPE; - proxyMethod->returnTypeDimension = 0; - proxyMethod->name = method->name.data; - proxyMethod->statements = new StatementBlock; - proxyClass->elements.push_back(proxyMethod); - - // The local variables - Variable* _data = new Variable(RPC_DATA_TYPE, "_data"); - proxyMethod->statements->Add(new VariableDeclaration(_data, new NewExpression(RPC_DATA_TYPE))); - - // Add the arguments - arg = method->args; - while (arg != NULL) { - if (convert_direction(arg->direction.data) & IN_PARAMETER) { - // Function signature - Type* t = NAMES.Search(arg->type.type.data); - Variable* v = new Variable(t, arg->name.data, arg->type.dimension); - proxyMethod->parameters.push_back(v); - - // Input parameter marshalling - generate_write_to_data(t, proxyMethod->statements, - new StringLiteralExpression(arg->name.data), v, _data); - } - arg = arg->next; - } - - // If there is a results interface for this class - Expression* resultParameter; - if (resultsInterfaceType != NULL) { - // Result interface parameter - Variable* resultListener = new Variable(resultsInterfaceType, "_result"); - proxyMethod->parameters.push_back(resultListener); - - // Add the results dispatcher callback - resultsDispatcherClass->needed = true; - resultParameter = new NewExpression(resultsDispatcherClass->type, 2, - new LiteralExpression(format_int(index)), resultListener); - } else { - resultParameter = NULL_VALUE; - } - - // All proxy methods take an error parameter - Variable* errorListener = new Variable(RPC_ERROR_LISTENER_TYPE, "_errors"); - proxyMethod->parameters.push_back(errorListener); - - // Call the broker - proxyMethod->statements->Add(new MethodCall(new FieldVariable(THIS_VALUE, "_broker"), - "sendRpc", 5, - proxyClass->endpoint, - new StringLiteralExpression(method->name.data), - new MethodCall(_data, "serialize"), - resultParameter, - errorListener)); -} - -static void -generate_result_dispatcher_method(const method_type* method, - ResultDispatcherClass* resultsDispatcherClass, Type* resultsInterfaceType, int index) -{ - arg_type* arg; - Method* dispatchMethod; - Variable* dispatchParam; - resultsDispatcherClass->AddMethod(index, method->name.data, &dispatchMethod, &dispatchParam); - - Variable* classLoader = NULL; - Variable* resultData = new Variable(RPC_DATA_TYPE, "resultData"); - dispatchMethod->statements->Add(new VariableDeclaration(resultData, - new NewExpression(RPC_DATA_TYPE, 1, dispatchParam))); - - // The callback method itself - MethodCall* realCall = new MethodCall( - new Cast(resultsInterfaceType, new FieldVariable(THIS_VALUE, "callback")), - results_method_name(method->name.data)); - - // The return value - { - Type* t = NAMES.Search(method->type.type.data); - if (t != VOID_TYPE) { - Variable* rv = new Variable(t, "rv"); - dispatchMethod->statements->Add(new VariableDeclaration(rv)); - generate_create_from_data(t, dispatchMethod->statements, "_result", rv, - resultData, &classLoader); - realCall->arguments.push_back(rv); - } - } - - VariableFactory stubArgs("arg"); - arg = method->args; - while (arg != NULL) { - if (convert_direction(arg->direction.data) & OUT_PARAMETER) { - // Unmarshall the results - Type* t = NAMES.Search(arg->type.type.data); - Variable* v = stubArgs.Get(t); - dispatchMethod->statements->Add(new VariableDeclaration(v)); - - generate_create_from_data(t, dispatchMethod->statements, arg->name.data, v, - resultData, &classLoader); - - // Add the argument to the callback - realCall->arguments.push_back(v); - } - arg = arg->next; - } - - // Call the callback method - IfStatement* ifst = new IfStatement; - ifst->expression = new Comparison(new FieldVariable(THIS_VALUE, "callback"), "!=", NULL_VALUE); - dispatchMethod->statements->Add(ifst); - ifst->statements->Add(realCall); -} - -static void -generate_regular_method(const method_type* method, RpcProxyClass* proxyClass, - EndpointBaseClass* serviceBaseClass, ResultDispatcherClass* resultsDispatcherClass, - int index) -{ - arg_type* arg; - - // == the callback interface for results ================================ - // the service base class - Type* resultsInterfaceType = generate_results_method(method, proxyClass); - - // == the method in the proxy class ===================================== - generate_proxy_method(method, proxyClass, resultsDispatcherClass, resultsInterfaceType, index); - - // == the method in the result dispatcher class ========================= - if (resultsInterfaceType != NULL) { - generate_result_dispatcher_method(method, resultsDispatcherClass, resultsInterfaceType, - index); - } - - // == The abstract method that the service developers implement ========== - Method* decl = new Method; - decl->comment = gather_comments(method->comments_token->extra); - decl->modifiers = PUBLIC | ABSTRACT; - decl->returnType = NAMES.Search(method->type.type.data); - decl->returnTypeDimension = method->type.dimension; - decl->name = method->name.data; - arg = method->args; - while (arg != NULL) { - decl->parameters.push_back(new Variable( - NAMES.Search(arg->type.type.data), arg->name.data, - arg->type.dimension)); - arg = arg->next; - } - - // Add the default RpcContext param to all methods - decl->parameters.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0)); - - serviceBaseClass->elements.push_back(decl); - - - // == the dispatch method in the service base class ====================== - serviceBaseClass->AddMethod(method); -} - -static void -generate_event_method(const method_type* method, RpcProxyClass* proxyClass, - EndpointBaseClass* serviceBaseClass, ListenerClass* listenerClass, - EventListenerClass* presenterClass, int index) -{ - arg_type* arg; - listenerClass->needed = true; - - // == the push method in the service base class ========================= - Method* push = new Method; - push->modifiers = PUBLIC; - push->name = push_method_name(method->name.data); - push->statements = new StatementBlock; - push->returnType = VOID_TYPE; - serviceBaseClass->elements.push_back(push); - - // The local variables - Variable* _data = new Variable(RPC_DATA_TYPE, "_data"); - push->statements->Add(new VariableDeclaration(_data, new NewExpression(RPC_DATA_TYPE))); - - // Add the arguments - arg = method->args; - while (arg != NULL) { - // Function signature - Type* t = NAMES.Search(arg->type.type.data); - Variable* v = new Variable(t, arg->name.data, arg->type.dimension); - push->parameters.push_back(v); - - // Input parameter marshalling - generate_write_to_data(t, push->statements, - new StringLiteralExpression(arg->name.data), v, _data); - - arg = arg->next; - } - - // Send the notifications - push->statements->Add(new MethodCall("pushEvent", 2, - new StringLiteralExpression(method->name.data), - new MethodCall(_data, "serialize"))); - - // == the event callback dispatcher method ==================================== - presenterClass->AddMethod(method); - - // == the event method in the listener base class ===================== - Method* event = new Method; - event->modifiers = PUBLIC; - event->name = method->name.data; - event->statements = new StatementBlock; - event->returnType = VOID_TYPE; - listenerClass->elements.push_back(event); - arg = method->args; - while (arg != NULL) { - event->parameters.push_back(new Variable( - NAMES.Search(arg->type.type.data), arg->name.data, - arg->type.dimension)); - arg = arg->next; - } - - // Add a final parameter: RpcContext. Contains data about - // incoming request (e.g., certificate) - event->parameters.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0)); -} - -static void -generate_listener_methods(RpcProxyClass* proxyClass, Type* presenterType, Type* listenerType) -{ - // AndroidAtHomePresenter _presenter; - // void startListening(Listener listener) { - // stopListening(); - // _presenter = new Presenter(_broker, listener); - // _presenter.startListening(_endpoint); - // } - // void stopListening() { - // if (_presenter != null) { - // _presenter.stopListening(); - // } - // } - - Variable* _presenter = new Variable(presenterType, "_presenter"); - proxyClass->elements.push_back(new Field(PRIVATE, _presenter)); - - Variable* listener = new Variable(listenerType, "listener"); - - Method* startListeningMethod = new Method; - startListeningMethod->modifiers = PUBLIC; - startListeningMethod->returnType = VOID_TYPE; - startListeningMethod->name = "startListening"; - startListeningMethod->statements = new StatementBlock; - startListeningMethod->parameters.push_back(listener); - proxyClass->elements.push_back(startListeningMethod); - - startListeningMethod->statements->Add(new MethodCall(THIS_VALUE, "stopListening")); - startListeningMethod->statements->Add(new Assignment(_presenter, - new NewExpression(presenterType, 2, proxyClass->broker, listener))); - startListeningMethod->statements->Add(new MethodCall(_presenter, - "startListening", 1, proxyClass->endpoint)); - - Method* stopListeningMethod = new Method; - stopListeningMethod->modifiers = PUBLIC; - stopListeningMethod->returnType = VOID_TYPE; - stopListeningMethod->name = "stopListening"; - stopListeningMethod->statements = new StatementBlock; - proxyClass->elements.push_back(stopListeningMethod); - - IfStatement* ifst = new IfStatement; - ifst->expression = new Comparison(_presenter, "!=", NULL_VALUE); - stopListeningMethod->statements->Add(ifst); - - ifst->statements->Add(new MethodCall(_presenter, "stopListening")); - ifst->statements->Add(new Assignment(_presenter, NULL_VALUE)); -} - -Class* -generate_rpc_interface_class(const interface_type* iface) -{ - // the proxy class - InterfaceType* interfaceType = static_cast<InterfaceType*>( - NAMES.Find(iface->package, iface->name.data)); - RpcProxyClass* proxy = new RpcProxyClass(iface, interfaceType); - - // the listener class - ListenerClass* listener = new ListenerClass(iface); - - // the presenter class - EventListenerClass* presenter = new EventListenerClass(iface, listener->type); - - // the service base class - EndpointBaseClass* base = new EndpointBaseClass(iface); - proxy->elements.push_back(base); - - // the result dispatcher - ResultDispatcherClass* results = new ResultDispatcherClass(); - - // all the declared methods of the proxy - int index = 0; - interface_item_type* item = iface->interface_items; - while (item != NULL) { - if (item->item_type == METHOD_TYPE) { - if (NAMES.Search(((method_type*)item)->type.type.data) == EVENT_FAKE_TYPE) { - generate_event_method((method_type*)item, proxy, base, listener, presenter, index); - } else { - generate_regular_method((method_type*)item, proxy, base, results, index); - } - } - item = item->next; - index++; - } - presenter->DoneWithMethods(); - base->DoneWithMethods(); - - // only add this if there are methods with results / out parameters - if (results->needed) { - proxy->elements.push_back(results); - } - if (listener->needed) { - proxy->elements.push_back(listener); - proxy->elements.push_back(presenter); - generate_listener_methods(proxy, presenter->type, listener->type); - } - - return proxy; -} diff --git a/tools/aidl/options.cpp b/tools/aidl/options.cpp deleted file mode 100644 index 7b2daebec09e..000000000000 --- a/tools/aidl/options.cpp +++ /dev/null @@ -1,154 +0,0 @@ - -#include "options.h" - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -static int -usage() -{ - fprintf(stderr, - "usage: aidl OPTIONS INPUT [OUTPUT]\n" - " aidl --preprocess OUTPUT INPUT...\n" - "\n" - "OPTIONS:\n" - " -I<DIR> search path for import statements.\n" - " -d<FILE> generate dependency file.\n" - " -a generate dependency file next to the output file with the name based on the input file.\n" - " -p<FILE> file created by --preprocess to import.\n" - " -o<FOLDER> base output folder for generated files.\n" - " -b fail when trying to compile a parcelable.\n" - "\n" - "INPUT:\n" - " An aidl interface file.\n" - "\n" - "OUTPUT:\n" - " The generated interface files.\n" - " If omitted and the -o option is not used, the input filename is used, with the .aidl extension changed to a .java extension.\n" - " If the -o option is used, the generated files will be placed in the base output folder, under their package folder\n" - ); - return 1; -} - -int -parse_options(int argc, const char* const* argv, Options *options) -{ - int i = 1; - - if (argc >= 2 && 0 == strcmp(argv[1], "--preprocess")) { - if (argc < 4) { - return usage(); - } - options->outputFileName = argv[2]; - for (int i=3; i<argc; i++) { - options->filesToPreprocess.push_back(argv[i]); - } - options->task = PREPROCESS_AIDL; - return 0; - } - - options->task = COMPILE_AIDL; - options->failOnParcelable = false; - options->autoDepFile = false; - - // OPTIONS - while (i < argc) { - const char* s = argv[i]; - int len = strlen(s); - if (s[0] == '-') { - if (len > 1) { - // -I<system-import-path> - if (s[1] == 'I') { - if (len > 2) { - options->importPaths.push_back(s+2); - } else { - fprintf(stderr, "-I option (%d) requires a path.\n", i); - return usage(); - } - } - else if (s[1] == 'd') { - if (len > 2) { - options->depFileName = s+2; - } else { - fprintf(stderr, "-d option (%d) requires a file.\n", i); - return usage(); - } - } - else if (s[1] == 'a') { - options->autoDepFile = true; - } - else if (s[1] == 'p') { - if (len > 2) { - options->preprocessedFiles.push_back(s+2); - } else { - fprintf(stderr, "-p option (%d) requires a file.\n", i); - return usage(); - } - } - else if (s[1] == 'o') { - if (len > 2) { - options->outputBaseFolder = s+2; - } else { - fprintf(stderr, "-o option (%d) requires a path.\n", i); - return usage(); - } - } - else if (len == 2 && s[1] == 'b') { - options->failOnParcelable = true; - } - else { - // s[1] is not known - fprintf(stderr, "unknown option (%d): %s\n", i, s); - return usage(); - } - } else { - // len <= 1 - fprintf(stderr, "unknown option (%d): %s\n", i, s); - return usage(); - } - } else { - // s[0] != '-' - break; - } - i++; - } - - // INPUT - if (i < argc) { - options->inputFileName = argv[i]; - i++; - } else { - fprintf(stderr, "INPUT required\n"); - return usage(); - } - - // OUTPUT - if (i < argc) { - options->outputFileName = argv[i]; - i++; - } else if (options->outputBaseFolder.length() == 0) { - // copy input into output and change the extension from .aidl to .java - options->outputFileName = options->inputFileName; - string::size_type pos = options->outputFileName.size()-5; - if (options->outputFileName.compare(pos, 5, ".aidl") == 0) { // 5 = strlen(".aidl") - options->outputFileName.replace(pos, 5, ".java"); // 5 = strlen(".aidl") - } else { - fprintf(stderr, "INPUT is not an .aidl file.\n"); - return usage(); - } - } - - // anything remaining? - if (i != argc) { - fprintf(stderr, "unknown option%s:", (i==argc-1?(const char*)"":(const char*)"s")); - for (; i<argc-1; i++) { - fprintf(stderr, " %s", argv[i]); - } - fprintf(stderr, "\n"); - return usage(); - } - - return 0; -} - diff --git a/tools/aidl/options.h b/tools/aidl/options.h deleted file mode 100644 index 387e37d08732..000000000000 --- a/tools/aidl/options.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef DEVICE_TOOLS_AIDL_H -#define DEVICE_TOOLS_AIDL_H - -#include <string.h> -#include <string> -#include <vector> - -using namespace std; - -enum { - COMPILE_AIDL, - PREPROCESS_AIDL -}; - -// This struct is the parsed version of the command line options -struct Options -{ - int task; - bool failOnParcelable; - vector<string> importPaths; - vector<string> preprocessedFiles; - string inputFileName; - string outputFileName; - string outputBaseFolder; - string depFileName; - bool autoDepFile; - - vector<string> filesToPreprocess; -}; - -// takes the inputs from the command line and fills in the Options struct -// Returns 0 on success, and nonzero on failure. -// It also prints the usage statement on failure. -int parse_options(int argc, const char* const* argv, Options *options); - -#endif // DEVICE_TOOLS_AIDL_H diff --git a/tools/aidl/options_test.cpp b/tools/aidl/options_test.cpp deleted file mode 100644 index bd106ce54f2d..000000000000 --- a/tools/aidl/options_test.cpp +++ /dev/null @@ -1,291 +0,0 @@ -#include <iostream> -#include "options.h" - -const bool VERBOSE = false; - -using namespace std; - -struct Answer { - const char* argv[8]; - int result; - const char* systemSearchPath[8]; - const char* localSearchPath[8]; - const char* inputFileName; - language_t nativeLanguage; - const char* outputH; - const char* outputCPP; - const char* outputJava; -}; - -bool -match_arrays(const char* const*expected, const vector<string> &got) -{ - int count = 0; - while (expected[count] != NULL) { - count++; - } - if (got.size() != count) { - return false; - } - for (int i=0; i<count; i++) { - if (got[i] != expected[i]) { - return false; - } - } - return true; -} - -void -print_array(const char* prefix, const char* const*expected) -{ - while (*expected) { - cout << prefix << *expected << endl; - expected++; - } -} - -void -print_array(const char* prefix, const vector<string> &got) -{ - size_t count = got.size(); - for (size_t i=0; i<count; i++) { - cout << prefix << got[i] << endl; - } -} - -static int -test(const Answer& answer) -{ - int argc = 0; - while (answer.argv[argc]) { - argc++; - } - - int err = 0; - - Options options; - int result = parse_options(argc, answer.argv, &options); - - // result - if (((bool)result) != ((bool)answer.result)) { - cout << "mismatch: result: got " << result << " expected " << - answer.result << endl; - err = 1; - } - - if (result != 0) { - // if it failed, everything is invalid - return err; - } - - // systemSearchPath - if (!match_arrays(answer.systemSearchPath, options.systemSearchPath)) { - cout << "mismatch: systemSearchPath: got" << endl; - print_array(" ", options.systemSearchPath); - cout << " expected" << endl; - print_array(" ", answer.systemSearchPath); - err = 1; - } - - // localSearchPath - if (!match_arrays(answer.localSearchPath, options.localSearchPath)) { - cout << "mismatch: localSearchPath: got" << endl; - print_array(" ", options.localSearchPath); - cout << " expected" << endl; - print_array(" ", answer.localSearchPath); - err = 1; - } - - // inputFileName - if (answer.inputFileName != options.inputFileName) { - cout << "mismatch: inputFileName: got " << options.inputFileName - << " expected " << answer.inputFileName << endl; - err = 1; - } - - // nativeLanguage - if (answer.nativeLanguage != options.nativeLanguage) { - cout << "mismatch: nativeLanguage: got " << options.nativeLanguage - << " expected " << answer.nativeLanguage << endl; - err = 1; - } - - // outputH - if (answer.outputH != options.outputH) { - cout << "mismatch: outputH: got " << options.outputH - << " expected " << answer.outputH << endl; - err = 1; - } - - // outputCPP - if (answer.outputCPP != options.outputCPP) { - cout << "mismatch: outputCPP: got " << options.outputCPP - << " expected " << answer.outputCPP << endl; - err = 1; - } - - // outputJava - if (answer.outputJava != options.outputJava) { - cout << "mismatch: outputJava: got " << options.outputJava - << " expected " << answer.outputJava << endl; - err = 1; - } - - return err; -} - -const Answer g_tests[] = { - - { - /* argv */ { "test", "-i/moof", "-I/blah", "-Ibleh", "-imoo", "inputFileName.aidl_cpp", NULL, NULL }, - /* result */ 0, - /* systemSearchPath */ { "/blah", "bleh", NULL, NULL, NULL, NULL, NULL, NULL }, - /* localSearchPath */ { "/moof", "moo", NULL, NULL, NULL, NULL, NULL, NULL }, - /* inputFileName */ "inputFileName.aidl_cpp", - /* nativeLanguage */ CPP, - /* outputH */ "", - /* outputCPP */ "", - /* outputJava */ "" - }, - - { - /* argv */ { "test", "inputFileName.aidl_cpp", "-oh", "outputH", NULL, NULL, NULL, NULL }, - /* result */ 0, - /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, - /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, - /* inputFileName */ "inputFileName.aidl_cpp", - /* nativeLanguage */ CPP, - /* outputH */ "outputH", - /* outputCPP */ "", - /* outputJava */ "" - }, - - { - /* argv */ { "test", "inputFileName.aidl_cpp", "-ocpp", "outputCPP", NULL, NULL, NULL, NULL }, - /* result */ 0, - /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, - /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, - /* inputFileName */ "inputFileName.aidl_cpp", - /* nativeLanguage */ CPP, - /* outputH */ "", - /* outputCPP */ "outputCPP", - /* outputJava */ "" - }, - - { - /* argv */ { "test", "inputFileName.aidl_cpp", "-ojava", "outputJava", NULL, NULL, NULL, NULL }, - /* result */ 0, - /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, - /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, - /* inputFileName */ "inputFileName.aidl_cpp", - /* nativeLanguage */ CPP, - /* outputH */ "", - /* outputCPP */ "", - /* outputJava */ "outputJava" - }, - - { - /* argv */ { "test", "inputFileName.aidl_cpp", "-oh", "outputH", "-ocpp", "outputCPP", "-ojava", "outputJava" }, - /* result */ 0, - /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, - /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, - /* inputFileName */ "inputFileName.aidl_cpp", - /* nativeLanguage */ CPP, - /* outputH */ "outputH", - /* outputCPP */ "outputCPP", - /* outputJava */ "outputJava" - }, - - { - /* argv */ { "test", "inputFileName.aidl_cpp", "-oh", "outputH", "-oh", "outputH1", NULL, NULL }, - /* result */ 1, - /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, - /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, - /* inputFileName */ "", - /* nativeLanguage */ CPP, - /* outputH */ "", - /* outputCPP */ "", - /* outputJava */ "" - }, - - { - /* argv */ { "test", "inputFileName.aidl_cpp", "-ocpp", "outputCPP", "-ocpp", "outputCPP1", NULL, NULL }, - /* result */ 1, - /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, - /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, - /* inputFileName */ "", - /* nativeLanguage */ CPP, - /* outputH */ "", - /* outputCPP */ "", - /* outputJava */ "" - }, - - { - /* argv */ { "test", "inputFileName.aidl_cpp", "-ojava", "outputJava", "-ojava", "outputJava1", NULL, NULL }, - /* result */ 1, - /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, - /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, - /* inputFileName */ "", - /* nativeLanguage */ CPP, - /* outputH */ "", - /* outputCPP */ "", - /* outputJava */ "" - }, - -}; - -int -main(int argc, const char** argv) -{ - const int count = sizeof(g_tests)/sizeof(g_tests[0]); - int matches[count]; - - int result = 0; - for (int i=0; i<count; i++) { - if (VERBOSE) { - cout << endl; - cout << "---------------------------------------------" << endl; - const char* const* p = g_tests[i].argv; - while (*p) { - cout << " " << *p; - p++; - } - cout << endl; - cout << "---------------------------------------------" << endl; - } - matches[i] = test(g_tests[i]); - if (VERBOSE) { - if (0 == matches[i]) { - cout << "passed" << endl; - } else { - cout << "failed" << endl; - } - result |= matches[i]; - } - } - - cout << endl; - cout << "=============================================" << endl; - cout << "options_test summary" << endl; - cout << "=============================================" << endl; - - if (!result) { - cout << "passed" << endl; - } else { - cout << "failed the following tests:" << endl; - for (int i=0; i<count; i++) { - if (matches[i]) { - cout << " "; - const char* const* p = g_tests[i].argv; - while (*p) { - cout << " " << *p; - p++; - } - cout << endl; - } - } - } - - return result; -} - diff --git a/tools/aidl/search_path.cpp b/tools/aidl/search_path.cpp deleted file mode 100644 index ffb6cb2932e4..000000000000 --- a/tools/aidl/search_path.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include <unistd.h> -#include "search_path.h" -#include "options.h" -#include <string.h> - -#ifdef HAVE_MS_C_RUNTIME -#include <io.h> -#endif - -static vector<string> g_importPaths; - -void -set_import_paths(const vector<string>& importPaths) -{ - g_importPaths = importPaths; -} - -char* -find_import_file(const char* given) -{ - string expected = given; - - int N = expected.length(); - for (int i=0; i<N; i++) { - char c = expected[i]; - if (c == '.') { - expected[i] = OS_PATH_SEPARATOR; - } - } - expected += ".aidl"; - - vector<string>& paths = g_importPaths; - for (vector<string>::iterator it=paths.begin(); it!=paths.end(); it++) { - string f = *it; - if (f.size() == 0) { - f = "."; - f += OS_PATH_SEPARATOR; - } - else if (f[f.size()-1] != OS_PATH_SEPARATOR) { - f += OS_PATH_SEPARATOR; - } - f.append(expected); - -#ifdef HAVE_MS_C_RUNTIME - /* check that the file exists and is not write-only */ - if (0 == _access(f.c_str(), 0) && /* mode 0=exist */ - 0 == _access(f.c_str(), 4) ) { /* mode 4=readable */ -#else - if (0 == access(f.c_str(), R_OK)) { -#endif - return strdup(f.c_str()); - } - } - - return NULL; -} - diff --git a/tools/aidl/search_path.h b/tools/aidl/search_path.h deleted file mode 100644 index 2bf94b12bbf1..000000000000 --- a/tools/aidl/search_path.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef DEVICE_TOOLS_AIDL_SEARCH_PATH_H -#define DEVICE_TOOLS_AIDL_SEARCH_PATH_H - -#include <stdio.h> - -#if __cplusplus -#include <vector> -#include <string> -using namespace std; -extern "C" { -#endif - -// returns a FILE* and the char* for the file that it found -// given is the class name we're looking for -char* find_import_file(const char* given); - -#if __cplusplus -}; // extern "C" -void set_import_paths(const vector<string>& importPaths); -#endif - -#endif // DEVICE_TOOLS_AIDL_SEARCH_PATH_H - diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py index df76bc9ba4cc..ca2d2e75ee89 100644 --- a/tools/apilint/apilint.py +++ b/tools/apilint/apilint.py @@ -264,6 +264,9 @@ def verify_constants(clazz): if "static" in f.split and "final" in f.split: if re.match("[A-Z0-9_]+", f.name) is None: error(clazz, f, "C2", "Constant field names must be FOO_NAME") + elif f.typ != "java.lang.String": + if f.name.startswith("MIN_") or f.name.startswith("MAX_"): + warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods") def verify_enums(clazz): @@ -417,6 +420,9 @@ def verify_parcelable(clazz): if len(creator) == 0 or len(write) == 0 or len(describe) == 0: error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one") + if " final class " not in clazz.raw: + error(clazz, None, "FW8", "Parcelable classes must be final") + def verify_protected(clazz): """Verify that no protected methods or fields are allowed.""" @@ -730,6 +736,13 @@ def verify_exception(clazz): if "throws java.lang.Exception" in m.raw or "throws java.lang.Throwable" in m.raw or "throws java.lang.Error" in m.raw: error(clazz, m, "S1", "Methods must not throw generic exceptions") + if "throws android.os.RemoteException" in m.raw: + if clazz.name == "android.content.ContentProviderClient": continue + if clazz.name == "android.os.Binder": continue + if clazz.name == "android.os.IBinder": continue + + error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException") + def verify_google(clazz): """Verifies that APIs never reference Google.""" @@ -946,6 +959,37 @@ def verify_resource_names(clazz): error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style") +def verify_files(clazz): + """Verifies that methods accepting File also accept streams.""" + + has_file = set() + has_stream = set() + + test = [] + test.extend(clazz.ctors) + test.extend(clazz.methods) + + for m in test: + if "java.io.File" in m.args: + has_file.add(m) + if "java.io.FileDescriptor" in m.args or "android.os.ParcelFileDescriptor" in m.args or "java.io.InputStream" in m.args or "java.io.OutputStream" in m.args: + has_stream.add(m.name) + + for m in has_file: + if m.name not in has_stream: + warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams") + + +def verify_manager_list(clazz): + """Verifies that managers return List<? extends Parcelable> instead of arrays.""" + + if not clazz.name.endswith("Manager"): return + + for m in clazz.methods: + if m.typ.startswith("android.") and m.typ.endswith("[]"): + warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood") + + def examine_clazz(clazz): """Find all style issues in the given class.""" if clazz.pkg.name.startswith("java"): return @@ -954,6 +998,7 @@ def examine_clazz(clazz): if clazz.pkg.name.startswith("org.xml"): return if clazz.pkg.name.startswith("org.json"): return if clazz.pkg.name.startswith("org.w3c"): return + if clazz.pkg.name.startswith("android.icu."): return verify_constants(clazz) verify_enums(clazz) @@ -989,6 +1034,8 @@ def examine_clazz(clazz): verify_context_first(clazz) verify_listener_last(clazz) verify_resource_names(clazz) + verify_files(clazz) + verify_manager_list(clazz) def examine_stream(stream): diff --git a/tools/fonts/fontchain_lint.py b/tools/fonts/fontchain_lint.py new file mode 100755 index 000000000000..ea36e2cb0d6a --- /dev/null +++ b/tools/fonts/fontchain_lint.py @@ -0,0 +1,559 @@ +#!/usr/bin/env python + +import collections +import copy +import glob +import itertools +from os import path +import sys +from xml.etree import ElementTree + +from fontTools import ttLib + +EMOJI_VS = 0xFE0F + +LANG_TO_SCRIPT = { + 'as': 'Beng', + 'bn': 'Beng', + 'cy': 'Latn', + 'da': 'Latn', + 'de': 'Latn', + 'en': 'Latn', + 'es': 'Latn', + 'et': 'Latn', + 'eu': 'Latn', + 'fr': 'Latn', + 'ga': 'Latn', + 'gu': 'Gujr', + 'hi': 'Deva', + 'hr': 'Latn', + 'hu': 'Latn', + 'hy': 'Armn', + 'ja': 'Jpan', + 'kn': 'Knda', + 'ko': 'Kore', + 'ml': 'Mlym', + 'mn': 'Cyrl', + 'mr': 'Deva', + 'nb': 'Latn', + 'nn': 'Latn', + 'or': 'Orya', + 'pa': 'Guru', + 'pt': 'Latn', + 'sl': 'Latn', + 'ta': 'Taml', + 'te': 'Telu', + 'tk': 'Latn', +} + +def lang_to_script(lang_code): + lang = lang_code.lower() + while lang not in LANG_TO_SCRIPT: + hyphen_idx = lang.rfind('-') + assert hyphen_idx != -1, ( + 'We do not know what script the "%s" language is written in.' + % lang_code) + assumed_script = lang[hyphen_idx+1:] + if len(assumed_script) == 4 and assumed_script.isalpha(): + # This is actually the script + return assumed_script.title() + lang = lang[:hyphen_idx] + return LANG_TO_SCRIPT[lang] + + +def printable(inp): + if type(inp) is set: # set of character sequences + return '{' + ', '.join([printable(seq) for seq in inp]) + '}' + if type(inp) is tuple: # character sequence + return '<' + (', '.join([printable(ch) for ch in inp])) + '>' + else: # single character + return 'U+%04X' % inp + + +def open_font(font): + font_file, index = font + font_path = path.join(_fonts_dir, font_file) + if index is not None: + return ttLib.TTFont(font_path, fontNumber=index) + else: + return ttLib.TTFont(font_path) + + +def get_best_cmap(font): + ttfont = open_font(font) + all_unicode_cmap = None + bmp_cmap = None + for cmap in ttfont['cmap'].tables: + specifier = (cmap.format, cmap.platformID, cmap.platEncID) + if specifier == (4, 3, 1): + assert bmp_cmap is None, 'More than one BMP cmap in %s' % (font, ) + bmp_cmap = cmap + elif specifier == (12, 3, 10): + assert all_unicode_cmap is None, ( + 'More than one UCS-4 cmap in %s' % (font, )) + all_unicode_cmap = cmap + + return all_unicode_cmap.cmap if all_unicode_cmap else bmp_cmap.cmap + + +def get_variation_sequences_cmap(font): + ttfont = open_font(font) + vs_cmap = None + for cmap in ttfont['cmap'].tables: + specifier = (cmap.format, cmap.platformID, cmap.platEncID) + if specifier == (14, 0, 5): + assert vs_cmap is None, 'More than one VS cmap in %s' % (font, ) + vs_cmap = cmap + return vs_cmap + + +def get_emoji_map(font): + # Add normal characters + emoji_map = copy.copy(get_best_cmap(font)) + reverse_cmap = {glyph: code for code, glyph in emoji_map.items()} + + # Add variation sequences + vs_dict = get_variation_sequences_cmap(font).uvsDict + for vs in vs_dict: + for base, glyph in vs_dict[vs]: + if glyph is None: + emoji_map[(base, vs)] = emoji_map[base] + else: + emoji_map[(base, vs)] = glyph + + # Add GSUB rules + ttfont = open_font(font) + for lookup in ttfont['GSUB'].table.LookupList.Lookup: + assert lookup.LookupType == 4, 'We only understand type 4 lookups' + for subtable in lookup.SubTable: + ligatures = subtable.ligatures + for first_glyph in ligatures: + for ligature in ligatures[first_glyph]: + sequence = [first_glyph] + ligature.Component + sequence = [reverse_cmap[glyph] for glyph in sequence] + sequence = tuple(sequence) + # Make sure no starting subsequence of 'sequence' has been + # seen before. + for sub_len in range(2, len(sequence)+1): + subsequence = sequence[:sub_len] + assert subsequence not in emoji_map + emoji_map[sequence] = ligature.LigGlyph + + return emoji_map + + +def assert_font_supports_any_of_chars(font, chars): + best_cmap = get_best_cmap(font) + for char in chars: + if char in best_cmap: + return + sys.exit('None of characters in %s were found in %s' % (chars, font)) + + +def assert_font_supports_all_of_chars(font, chars): + best_cmap = get_best_cmap(font) + for char in chars: + assert char in best_cmap, ( + 'U+%04X was not found in %s' % (char, font)) + + +def assert_font_supports_none_of_chars(font, chars): + best_cmap = get_best_cmap(font) + for char in chars: + assert char not in best_cmap, ( + 'U+%04X was found in %s' % (char, font)) + + +def assert_font_supports_all_sequences(font, sequences): + vs_dict = get_variation_sequences_cmap(font).uvsDict + for base, vs in sorted(sequences): + assert vs in vs_dict and (base, None) in vs_dict[vs], ( + '<U+%04X, U+%04X> was not found in %s' % (base, vs, font)) + + +def check_hyphens(hyphens_dir): + # Find all the scripts that need automatic hyphenation + scripts = set() + for hyb_file in glob.iglob(path.join(hyphens_dir, '*.hyb')): + hyb_file = path.basename(hyb_file) + assert hyb_file.startswith('hyph-'), ( + 'Unknown hyphenation file %s' % hyb_file) + lang_code = hyb_file[hyb_file.index('-')+1:hyb_file.index('.')] + scripts.add(lang_to_script(lang_code)) + + HYPHENS = {0x002D, 0x2010} + for script in scripts: + fonts = _script_to_font_map[script] + assert fonts, 'No fonts found for the "%s" script' % script + for font in fonts: + assert_font_supports_any_of_chars(font, HYPHENS) + + +class FontRecord(object): + def __init__(self, name, scripts, variant, weight, style, font): + self.name = name + self.scripts = scripts + self.variant = variant + self.weight = weight + self.style = style + self.font = font + + +def parse_fonts_xml(fonts_xml_path): + global _script_to_font_map, _fallback_chain + _script_to_font_map = collections.defaultdict(set) + _fallback_chain = [] + tree = ElementTree.parse(fonts_xml_path) + for family in tree.findall('family'): + name = family.get('name') + variant = family.get('variant') + langs = family.get('lang') + if name: + assert variant is None, ( + 'No variant expected for LGC font %s.' % name) + assert langs is None, ( + 'No language expected for LGC fonts %s.' % name) + else: + assert variant in {None, 'elegant', 'compact'}, ( + 'Unexpected value for variant: %s' % variant) + + if langs: + langs = langs.split() + scripts = {lang_to_script(lang) for lang in langs} + else: + scripts = set() + + for child in family: + assert child.tag == 'font', ( + 'Unknown tag <%s>' % child.tag) + font_file = child.text + weight = int(child.get('weight')) + assert weight % 100 == 0, ( + 'Font weight "%d" is not a multiple of 100.' % weight) + + style = child.get('style') + assert style in {'normal', 'italic'}, ( + 'Unknown style "%s"' % style) + + index = child.get('index') + if index: + index = int(index) + + _fallback_chain.append(FontRecord( + name, + frozenset(scripts), + variant, + weight, + style, + (font_file, index))) + + if name: # non-empty names are used for default LGC fonts + map_scripts = {'Latn', 'Grek', 'Cyrl'} + else: + map_scripts = scripts + for script in map_scripts: + _script_to_font_map[script].add((font_file, index)) + + +def check_emoji_coverage(all_emoji, equivalent_emoji): + emoji_fonts = [ + record.font for record in _fallback_chain + if 'Zsye' in record.scripts] + assert len(emoji_fonts) == 1, 'There are %d emoji fonts.' % len(emoji_fonts) + emoji_font = emoji_fonts[0] + coverage = get_emoji_map(emoji_font) + + for sequence in all_emoji: + assert sequence in coverage, ( + '%s is not supported in the emoji font.' % printable(sequence)) + + for sequence in coverage: + if sequence in {0x0000, 0x000D, 0x0020}: + # The font needs to support a few extra characters, which is OK + continue + assert sequence in all_emoji, ( + 'Emoji font should not support %s.' % printable(sequence)) + + for first, second in sorted(equivalent_emoji.items()): + assert coverage[first] == coverage[second], ( + '%s and %s should map to the same glyph.' % ( + printable(first), + printable(second))) + + for glyph in set(coverage.values()): + maps_to_glyph = [seq for seq in coverage if coverage[seq] == glyph] + if len(maps_to_glyph) > 1: + # There are more than one sequences mapping to the same glyph. We + # need to make sure they were expected to be equivalent. + equivalent_seqs = set() + for seq in maps_to_glyph: + equivalent_seq = seq + while equivalent_seq in equivalent_emoji: + equivalent_seq = equivalent_emoji[equivalent_seq] + equivalent_seqs.add(equivalent_seq) + assert len(equivalent_seqs) == 1, ( + 'The sequences %s should not result in the same glyph %s' % ( + printable(equivalent_seqs), + glyph)) + + +def check_emoji_defaults(default_emoji): + missing_text_chars = _emoji_properties['Emoji'] - default_emoji + emoji_font_seen = False + for record in _fallback_chain: + if 'Zsye' in record.scripts: + emoji_font_seen = True + # No need to check the emoji font + 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: + continue + + # Check default emoji-style characters + assert_font_supports_none_of_chars(record.font, sorted(default_emoji)) + + # Mark default text-style characters appearing in fonts above the emoji + # font as seen + if not emoji_font_seen: + missing_text_chars -= set(get_best_cmap(record.font)) + + # Noto does not have monochrome glyphs for Unicode 7.0 wingdings and + # webdings yet. + missing_text_chars -= _chars_by_age['7.0'] + # TODO: Remove these after b/26113320 is fixed + missing_text_chars -= { + 0x263A, # WHITE SMILING FACE + 0x270C, # VICTORY HAND + 0x2744, # SNOWFLAKE + 0x2764, # HEAVY BLACK HEART + } + assert missing_text_chars == set(), ( + 'Text style version of some emoji characters are missing: ' + repr(missing_text_chars)) + + +# Setting reverse to true returns a dictionary that maps the values to sets of +# characters, useful for some binary properties. Otherwise, we get a +# dictionary that maps characters to the property values, assuming there's only +# one property in the file. +def parse_unicode_datafile(file_path, reverse=False): + if reverse: + output_dict = collections.defaultdict(set) + else: + output_dict = {} + with open(file_path) as datafile: + for line in datafile: + if '#' in line: + line = line[:line.index('#')] + line = line.strip() + if not line: + continue + + chars, prop = line.split(';') + chars = chars.strip() + prop = prop.strip() + + if ' ' in chars: # character sequence + sequence = [int(ch, 16) for ch in chars.split(' ')] + additions = [tuple(sequence)] + elif '..' in chars: # character range + char_start, char_end = chars.split('..') + char_start = int(char_start, 16) + char_end = int(char_end, 16) + additions = xrange(char_start, char_end+1) + else: # singe character + additions = [int(chars, 16)] + if reverse: + output_dict[prop].update(additions) + else: + for addition in additions: + assert addition not in output_dict + output_dict[addition] = prop + return output_dict + + +def parse_standardized_variants(file_path): + emoji_set = set() + text_set = set() + with open(file_path) as datafile: + for line in datafile: + if '#' in line: + line = line[:line.index('#')] + line = line.strip() + if not line: + continue + sequence, description, _ = line.split(';') + sequence = sequence.strip().split(' ') + base = int(sequence[0], 16) + vs = int(sequence[1], 16) + description = description.strip() + if description == 'text style': + text_set.add((base, vs)) + elif description == 'emoji style': + emoji_set.add((base, vs)) + return text_set, emoji_set + + +def parse_ucd(ucd_path): + global _emoji_properties, _chars_by_age + global _text_variation_sequences, _emoji_variation_sequences + global _emoji_sequences, _emoji_zwj_sequences + _emoji_properties = parse_unicode_datafile( + path.join(ucd_path, 'emoji-data.txt'), reverse=True) + _chars_by_age = parse_unicode_datafile( + path.join(ucd_path, 'DerivedAge.txt'), reverse=True) + sequences = parse_standardized_variants( + path.join(ucd_path, 'StandardizedVariants.txt')) + _text_variation_sequences, _emoji_variation_sequences = sequences + _emoji_sequences = parse_unicode_datafile( + path.join(ucd_path, 'emoji-sequences.txt')) + _emoji_zwj_sequences = parse_unicode_datafile( + path.join(ucd_path, 'emoji-zwj-sequences.txt')) + + # filter modern pentathlon, as it seems likely to be removed from final spec + # also filter rifle + def is_excluded(n): + return n in [0x1f93b, 0x1f946] + + def contains_excluded(t): + if type(t) == int: + return is_excluded(t) + return any(is_excluded(cp) for cp in t) + + # filter modern pentathlon, as it seems likely to be removed from final spec + _emoji_properties['Emoji'] = set( + t for t in _emoji_properties['Emoji'] if not contains_excluded(t)) + _emoji_sequences = dict( + (t, v) for (t, v) in _emoji_sequences.items() if not contains_excluded(t)) + +def flag_sequence(territory_code): + return tuple(0x1F1E6 + ord(ch) - ord('A') for ch in territory_code) + + +UNSUPPORTED_FLAGS = frozenset({ + flag_sequence('BL'), flag_sequence('BQ'), flag_sequence('DG'), + flag_sequence('EA'), flag_sequence('EH'), flag_sequence('FK'), + flag_sequence('GF'), flag_sequence('GP'), flag_sequence('GS'), + flag_sequence('MF'), flag_sequence('MQ'), flag_sequence('NC'), + flag_sequence('PM'), flag_sequence('RE'), flag_sequence('TF'), + flag_sequence('WF'), flag_sequence('XK'), flag_sequence('YT'), +}) + +EQUIVALENT_FLAGS = { + flag_sequence('BV'): flag_sequence('NO'), + flag_sequence('CP'): flag_sequence('FR'), + flag_sequence('HM'): flag_sequence('AU'), + flag_sequence('SJ'): flag_sequence('NO'), + flag_sequence('UM'): flag_sequence('US'), +} + +COMBINING_KEYCAP = 0x20E3 + +LEGACY_ANDROID_EMOJI = { + 0xFE4E5: flag_sequence('JP'), + 0xFE4E6: flag_sequence('US'), + 0xFE4E7: flag_sequence('FR'), + 0xFE4E8: flag_sequence('DE'), + 0xFE4E9: flag_sequence('IT'), + 0xFE4EA: flag_sequence('GB'), + 0xFE4EB: flag_sequence('ES'), + 0xFE4EC: flag_sequence('RU'), + 0xFE4ED: flag_sequence('CN'), + 0xFE4EE: flag_sequence('KR'), + 0xFE82C: (ord('#'), COMBINING_KEYCAP), + 0xFE82E: (ord('1'), COMBINING_KEYCAP), + 0xFE82F: (ord('2'), COMBINING_KEYCAP), + 0xFE830: (ord('3'), COMBINING_KEYCAP), + 0xFE831: (ord('4'), COMBINING_KEYCAP), + 0xFE832: (ord('5'), COMBINING_KEYCAP), + 0xFE833: (ord('6'), COMBINING_KEYCAP), + 0xFE834: (ord('7'), COMBINING_KEYCAP), + 0xFE835: (ord('8'), COMBINING_KEYCAP), + 0xFE836: (ord('9'), COMBINING_KEYCAP), + 0xFE837: (ord('0'), COMBINING_KEYCAP), +} + +ZWJ_IDENTICALS = { + # KISS + (0x1F469, 0x200D, 0x2764, 0x200D, 0x1F48B, 0x200D, 0x1F468): 0x1F48F, + # COUPLE WITH HEART + (0x1F469, 0x200D, 0x2764, 0x200D, 0x1F468): 0x1F491, + # FAMILY + (0x1F468, 0x200D, 0x1F469, 0x200D, 0x1F466): 0x1F46A, +} + +def compute_expected_emoji(): + equivalent_emoji = {} + sequence_pieces = set() + all_sequences = set() + all_sequences.update(_emoji_variation_sequences) + + for sequence in _emoji_sequences.keys(): + sequence = tuple(ch for ch in sequence if ch != EMOJI_VS) + all_sequences.add(sequence) + sequence_pieces.update(sequence) + + for sequence in _emoji_zwj_sequences.keys(): + sequence = tuple(ch for ch in sequence if ch != EMOJI_VS) + all_sequences.add(sequence) + sequence_pieces.update(sequence) + # Add reverse of all emoji ZWJ sequences, which are added to the fonts + # as a workaround to get the sequences work in RTL text. + reversed_seq = tuple(reversed(sequence)) + all_sequences.add(reversed_seq) + equivalent_emoji[reversed_seq] = sequence + + # Add all two-letter flag sequences, as even the unsupported ones should + # resolve to a flag tofu. + all_letters = [chr(code) for code in range(ord('A'), ord('Z')+1)] + all_two_letter_codes = itertools.product(all_letters, repeat=2) + all_flags = {flag_sequence(code) for code in all_two_letter_codes} + all_sequences.update(all_flags) + tofu_flags = UNSUPPORTED_FLAGS | (all_flags - set(_emoji_sequences.keys())) + + all_emoji = ( + _emoji_properties['Emoji'] | + all_sequences | + sequence_pieces | + set(LEGACY_ANDROID_EMOJI.keys())) + default_emoji = ( + _emoji_properties['Emoji_Presentation'] | + all_sequences | + set(LEGACY_ANDROID_EMOJI.keys())) + + first_tofu_flag = sorted(tofu_flags)[0] + for flag in tofu_flags: + if flag != first_tofu_flag: + equivalent_emoji[flag] = first_tofu_flag + equivalent_emoji.update(EQUIVALENT_FLAGS) + equivalent_emoji.update(LEGACY_ANDROID_EMOJI) + equivalent_emoji.update(ZWJ_IDENTICALS) + for seq in _emoji_variation_sequences: + equivalent_emoji[seq] = seq[0] + + return all_emoji, default_emoji, equivalent_emoji + + +def main(): + target_out = sys.argv[1] + global _fonts_dir + _fonts_dir = path.join(target_out, 'fonts') + + fonts_xml_path = path.join(target_out, 'etc', 'fonts.xml') + parse_fonts_xml(fonts_xml_path) + + hyphens_dir = path.join(target_out, 'usr', 'hyphen-data') + check_hyphens(hyphens_dir) + + check_emoji = sys.argv[2] + if check_emoji == 'true': + ucd_path = sys.argv[3] + parse_ucd(ucd_path) + all_emoji, default_emoji, equivalent_emoji = compute_expected_emoji() + check_emoji_coverage(all_emoji, equivalent_emoji) + check_emoji_defaults(default_emoji) + + +if __name__ == '__main__': + main() diff --git a/tools/layoutlib/.idea/codeStyleSettings.xml b/tools/layoutlib/.idea/codeStyleSettings.xml index 89f7b341fb8b..ac90d1e540be 100644 --- a/tools/layoutlib/.idea/codeStyleSettings.xml +++ b/tools/layoutlib/.idea/codeStyleSettings.xml @@ -40,6 +40,7 @@ <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" /> </XML> <codeStyleSettings language="JAVA"> + <option name="KEEP_LINE_BREAKS" value="false" /> <option name="ALIGN_MULTILINE_PARAMETERS" value="false" /> <option name="CALL_PARAMETERS_WRAP" value="1" /> <option name="METHOD_PARAMETERS_WRAP" value="1" /> @@ -55,6 +56,7 @@ <option name="DOWHILE_BRACE_FORCE" value="3" /> <option name="WHILE_BRACE_FORCE" value="3" /> <option name="FOR_BRACE_FORCE" value="3" /> + <option name="WRAP_LONG_LINES" value="true" /> <arrangement> <groups> <group> diff --git a/tools/layoutlib/.idea/compiler.xml b/tools/layoutlib/.idea/compiler.xml index 5aaaf18f9d94..35961a2896ac 100644 --- a/tools/layoutlib/.idea/compiler.xml +++ b/tools/layoutlib/.idea/compiler.xml @@ -21,7 +21,5 @@ <processorPath useClasspath="true" /> </profile> </annotationProcessing> - <bytecodeTargetLevel target="1.6" /> </component> -</project> - +</project>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml index 5bb3e3e47922..3681f2aaf3f1 100644 --- a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml +++ b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> <component name="InspectionProjectProfileManager"> <profile version="1.0" is_locked="false"> <option name="myName" value="Project Default" /> @@ -8,6 +7,15 @@ <option name="CHECK_TRY_CATCH_SECTION" value="true" /> <option name="CHECK_METHOD_BODY" value="true" /> </inspection_tool> + <inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false"> + <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" /> + <option name="SUGGEST_PRIVATE_FOR_INNERS" value="true" /> + </inspection_tool> </profile> </component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/junit.xml b/tools/layoutlib/.idea/libraries/junit.xml new file mode 100644 index 000000000000..c889f5ff6c97 --- /dev/null +++ b/tools/layoutlib/.idea/libraries/junit.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="junit"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../out/host/common/obj/JAVA_LIBRARIES/junit_intermediates/javalib.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="file://$PROJECT_DIR$/../../../../external/junit/src" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/misc.xml b/tools/layoutlib/.idea/misc.xml index b474bdc00013..44b47f2c91d4 100644 --- a/tools/layoutlib/.idea/misc.xml +++ b/tools/layoutlib/.idea/misc.xml @@ -37,7 +37,7 @@ </value> </option> </component> - <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" default="false" assert-keyword="true" jdk-15="true" project-jdk-name="1.6" project-jdk-type="JavaSDK"> + <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <output url="file://$PROJECT_DIR$/out" /> </component> </project>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml b/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml index 4f0eb8dc23a4..b402849f22a3 100644 --- a/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml +++ b/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml @@ -26,4 +26,4 @@ <ConfigurationWrapper RunnerId="Run" /> <method /> </configuration> -</component>
\ No newline at end of file +</component> diff --git a/tools/layoutlib/.idea/runConfigurations/Create.xml b/tools/layoutlib/.idea/runConfigurations/Create.xml index 58f057ac7289..536a23fdfeba 100644 --- a/tools/layoutlib/.idea/runConfigurations/Create.xml +++ b/tools/layoutlib/.idea/runConfigurations/Create.xml @@ -2,8 +2,8 @@ <configuration default="false" name="Create" type="Application" factoryName="Application" singleton="true"> <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> <option name="MAIN_CLASS_NAME" value="com.android.tools.layoutlib.create.Main" /> - <option name="VM_PARAMETERS" value="" /> - <option name="PROGRAM_PARAMETERS" value="out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/icu4j-icudata-jarjar_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/icu4j-icutzdata-jarjar_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/javalib.jar" /> + <option name="VM_PARAMETERS" value="-ea" /> + <option name="PROGRAM_PARAMETERS" value="out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar out/host/common/obj/JAVA_LIBRARIES/icu4j-icudata-host-jarjar_intermediates/classes-jarjar.jar out/host/common/obj/JAVA_LIBRARIES/icu4j-icutzdata-host-jarjar_intermediates/classes-jarjar.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/javalib.jar" /> <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/../../../../" /> <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> <option name="ALTERNATIVE_JRE_PATH" value="" /> diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk index 61ddb04bc08c..f87f6c53c8dc 100644 --- a/tools/layoutlib/Android.mk +++ b/tools/layoutlib/Android.mk @@ -16,8 +16,6 @@ LOCAL_PATH := $(my-dir) include $(CLEAR_VARS) -LOCAL_JAVACFLAGS := -source 6 -target 6 - # # Define rules to build temp_layoutlib.jar, which contains a subset of # the classes in framework.jar. The layoutlib_create tool is used to @@ -30,6 +28,9 @@ LOCAL_JAVACFLAGS := -source 6 -target 6 built_framework_dep := $(call java-lib-deps,framework) built_framework_classes := $(call java-lib-files,framework) +built_oj_dep := $(call java-lib-deps,core-oj) +built_oj_classes := $(call java-lib-files,core-oj) + built_core_dep := $(call java-lib-deps,core-libart) built_core_classes := $(call java-lib-files,core-libart) @@ -37,10 +38,8 @@ built_ext_dep := $(call java-lib-deps,ext) built_ext_classes := $(call java-lib-files,ext) built_ext_data := $(call intermediates-dir-for, \ JAVA_LIBRARIES,ext,,COMMON)/javalib.jar -built_icudata_dep := $(call java-lib-deps,icu4j-icudata-jarjar) -built_icudata_data := $(call java-lib-files,icu4j-icudata-jarjar) -built_icutzdata_dep := $(call java-lib-deps,icu4j-icutzdata-jarjar) -built_icutzdata_data := $(call java-lib-files,icu4j-icutzdata-jarjar) +built_icudata_dep := $(call java-lib-deps,icu4j-icudata-host-jarjar,HOST) +built_icutzdata_dep := $(call java-lib-deps,icu4j-icutzdata-host-jarjar,HOST) built_layoutlib_create_jar := $(call intermediates-dir-for, \ JAVA_LIBRARIES,layoutlib_create,HOST)/javalib.jar @@ -56,7 +55,8 @@ LOCAL_BUILT_MODULE_STEM := javalib.jar include $(BUILD_SYSTEM)/base_rules.mk ####################################### -$(LOCAL_BUILT_MODULE): $(built_core_dep) \ +$(LOCAL_BUILT_MODULE): $(built_oj_dep) \ + $(built_core_dep) \ $(built_framework_dep) \ $(built_ext_dep) \ $(built_ext_data) \ @@ -69,11 +69,12 @@ $(LOCAL_BUILT_MODULE): $(built_core_dep) \ $(hide) ls -l $(built_framework_classes) $(hide) java -ea -jar $(built_layoutlib_create_jar) \ $@ \ + $(built_oj_classes) \ $(built_core_classes) \ $(built_framework_classes) \ $(built_ext_classes) \ - $(built_icudata_data) \ - $(built_icutzdata_data) \ + $(built_icudata_dep) \ + $(built_icutzdata_dep) \ $(built_ext_data) $(hide) ls -l $(built_framework_classes) diff --git a/tools/layoutlib/bridge/Android.mk b/tools/layoutlib/bridge/Android.mk index 0dbdd5627e63..3dd8002bcff5 100644 --- a/tools/layoutlib/bridge/Android.mk +++ b/tools/layoutlib/bridge/Android.mk @@ -18,8 +18,6 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_JAVA_RESOURCE_DIRS := resources -LOCAL_JAVACFLAGS := -source 6 -target 6 - LOCAL_JAVA_LIBRARIES := \ layoutlib_api-prebuilt \ diff --git a/tools/layoutlib/bridge/bridge.iml b/tools/layoutlib/bridge/bridge.iml index ccc10b325b77..57d08cb22c7e 100644 --- a/tools/layoutlib/bridge/bridge.iml +++ b/tools/layoutlib/bridge/bridge.iml @@ -84,6 +84,6 @@ </SOURCES> </library> </orderEntry> - <orderEntry type="library" scope="TEST" name="JUnit4" level="application" /> + <orderEntry type="library" scope="TEST" name="junit" level="project" /> </component> </module>
\ No newline at end of file diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java index ef681d2c9d26..d0e431acadff 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java @@ -25,15 +25,9 @@ import com.android.ide.common.rendering.api.StyleResourceValue; import com.android.internal.util.XmlUtils; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; -import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; -import com.android.layoutlib.bridge.android.RenderParamsFlags; -import com.android.layoutlib.bridge.impl.ParserFactory; import com.android.layoutlib.bridge.impl.ResourceHelper; import com.android.resources.ResourceType; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import android.annotation.Nullable; import android.content.res.Resources.NotFoundException; import android.content.res.Resources.Theme; @@ -43,7 +37,6 @@ import android.util.TypedValue; import android.view.LayoutInflater_Delegate; import android.view.ViewGroup.LayoutParams; -import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Map; @@ -306,76 +299,22 @@ public final class BridgeTypedArray extends TypedArray { return defValue; } - /** - * Retrieve the ColorStateList for the attribute at <var>index</var>. - * The value may be either a single solid color or a reference to - * a color or complex {@link android.content.res.ColorStateList} description. - * - * @param index Index of attribute to retrieve. - * - * @return ColorStateList for the attribute, or null if not defined. - */ @Override public ColorStateList getColorStateList(int index) { if (!hasValue(index)) { return null; } - ResourceValue resValue = mResourceData[index]; - String value = resValue.getValue(); - - if (value == null) { - return null; - } - - - try { - XmlPullParser parser = null; - Boolean psiParserSupport = mContext.getLayoutlibCallback().getFlag( - RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT); - if (psiParserSupport != null && psiParserSupport) { - // Get the state list file content from callback to parse PSI file - parser = mContext.getLayoutlibCallback().getXmlFileParser(value); - } - if (parser == null) { - // If used with a version of Android Studio that does not implement getXmlFileParser - // fall back to reading the file from disk - File f = new File(value); - if (f.isFile()) { - parser = ParserFactory.create(f); - } - } - if (parser != null) { - BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( - parser, mContext, resValue.isFramework()); - try { - return ColorStateList.createFromXml(mContext.getResources(), blockParser, - mContext.getTheme()); - } finally { - blockParser.ensurePopped(); - } - } - } catch (XmlPullParserException e) { - Bridge.getLog().error(LayoutLog.TAG_BROKEN, - "Failed to configure parser for " + value, e, null); - return null; - } catch (Exception e) { - // this is an error and not warning since the file existence is checked before - // attempting to parse it. - Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, - "Failed to parse file " + value, e, null); + return ResourceHelper.getColorStateList(mResourceData[index], mContext); + } + @Override + public ComplexColor getComplexColor(int index) { + if (!hasValue(index)) { return null; } - try { - int color = ResourceHelper.getColor(value); - return ColorStateList.valueOf(color); - } catch (NumberFormatException e) { - Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, null); - } - - return null; + return ResourceHelper.getComplexColor(mResourceData[index], mContext); } /** diff --git a/tools/layoutlib/bridge/src/android/content/res/ComplexColor_Accessor.java b/tools/layoutlib/bridge/src/android/content/res/ComplexColor_Accessor.java new file mode 100644 index 000000000000..09c0260196cf --- /dev/null +++ b/tools/layoutlib/bridge/src/android/content/res/ComplexColor_Accessor.java @@ -0,0 +1,46 @@ +/* + * 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.content.res; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.Resources.Theme; +import android.util.AttributeSet; + +import java.io.IOException; + +/** + * Class that provides access to the {@link GradientColor#createFromXmlInner(Resources, + * XmlPullParser, AttributeSet, Theme)} and {@link ColorStateList#createFromXmlInner(Resources, + * XmlPullParser, AttributeSet, Theme)} methods + */ +public class ComplexColor_Accessor { + public static GradientColor createGradientColorFromXmlInner(@NonNull Resources r, + @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) + throws IOException, XmlPullParserException { + return GradientColor.createFromXmlInner(r, parser, attrs, theme); + } + + public static ColorStateList createColorStateListFromXmlInner(@NonNull Resources r, + @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) + throws IOException, XmlPullParserException { + return ColorStateList.createFromXmlInner(r, parser, attrs, theme); + } +} 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 6e8e42ff1073..ea320c701c24 100644 --- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java +++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java @@ -21,6 +21,7 @@ import com.android.ide.common.rendering.api.ArrayResourceValue; import com.android.ide.common.rendering.api.DensityBasedResourceValue; 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.ResourceValue; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.BridgeConstants; @@ -84,7 +85,7 @@ public class Resources_Delegate { return new BridgeTypedArray(resources, resources.mContext, numEntries, platformFile); } - private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id, + private static Pair<ResourceType, String> getResourceInfo(Resources resources, int id, boolean[] platformResFlag_out) { // first get the String related to this id in the framework Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); @@ -97,11 +98,7 @@ public class Resources_Delegate { if (resourceInfo != null) { platformResFlag_out[0] = true; - String attributeName = resourceInfo.getSecond(); - - return Pair.of(attributeName, - resources.mContext.getRenderResources().getFrameworkResource( - resourceInfo.getFirst(), attributeName)); + return resourceInfo; } // didn't find a match in the framework? look in the project. @@ -110,13 +107,24 @@ public class Resources_Delegate { if (resourceInfo != null) { platformResFlag_out[0] = false; - String attributeName = resourceInfo.getSecond(); - - return Pair.of(attributeName, - resources.mContext.getRenderResources().getProjectResource( - resourceInfo.getFirst(), attributeName)); + return resourceInfo; } } + return null; + } + + private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id, + boolean[] platformResFlag_out) { + Pair<ResourceType, String> resourceInfo = + getResourceInfo(resources, id, platformResFlag_out); + + if (resourceInfo != null) { + String attributeName = resourceInfo.getSecond(); + RenderResources renderResources = resources.mContext.getRenderResources(); + return Pair.of(attributeName, platformResFlag_out[0] ? + renderResources.getFrameworkResource(resourceInfo.getFirst(), attributeName) : + renderResources.getProjectResource(resourceInfo.getFirst(), attributeName)); + } return null; } @@ -609,7 +617,6 @@ public class Resources_Delegate { if (value != null) { ResourceValue resValue = value.getSecond(); - assert resValue != null; if (resValue != null) { String v = resValue.getValue(); if (v != null) { @@ -627,17 +634,57 @@ public class Resources_Delegate { @LayoutlibDelegate static String getResourceEntryName(Resources resources, int resid) throws NotFoundException { - throw new UnsupportedOperationException(); + Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]); + if (resourceInfo != null) { + return resourceInfo.getSecond(); + } + throwException(resid, null); + return null; + } @LayoutlibDelegate static String getResourceName(Resources resources, int resid) throws NotFoundException { - throw new UnsupportedOperationException(); + boolean[] platformOut = new boolean[1]; + Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut); + String packageName; + if (resourceInfo != null) { + if (platformOut[0]) { + packageName = SdkConstants.ANDROID_NS_NAME; + } else { + packageName = resources.mContext.getPackageName(); + packageName = packageName == null ? SdkConstants.APP_PREFIX : packageName; + } + return packageName + ':' + resourceInfo.getFirst().getName() + '/' + + resourceInfo.getSecond(); + } + throwException(resid, null); + return null; + } + + @LayoutlibDelegate + static String getResourcePackageName(Resources resources, int resid) throws NotFoundException { + boolean[] platformOut = new boolean[1]; + Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut); + if (resourceInfo != null) { + if (platformOut[0]) { + return SdkConstants.ANDROID_NS_NAME; + } + String packageName = resources.mContext.getPackageName(); + return packageName == null ? SdkConstants.APP_PREFIX : packageName; + } + throwException(resid, null); + return null; } @LayoutlibDelegate static String getResourceTypeName(Resources resources, int resid) throws NotFoundException { - throw new UnsupportedOperationException(); + Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]); + if (resourceInfo != null) { + return resourceInfo.getFirst().getName(); + } + throwException(resid, null); + return null; } @LayoutlibDelegate @@ -850,22 +897,17 @@ public class Resources_Delegate { * @throws NotFoundException */ private static void throwException(Resources resources, int id) throws NotFoundException { - // first get the String related to this id in the framework - Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); - - // if the name is unknown in the framework, get it from the custom view loader. - if (resourceInfo == null && resources.mLayoutlibCallback != null) { - resourceInfo = resources.mLayoutlibCallback.resolveResourceId(id); - } + throwException(id, getResourceInfo(resources, id, new boolean[1])); + } + private static void throwException(int id, @Nullable Pair<ResourceType, String> resourceInfo) { String message; if (resourceInfo != null) { message = String.format( "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.", resourceInfo.getFirst(), id, resourceInfo.getSecond()); } else { - message = String.format( - "Could not resolve resource value: 0x%1$X.", id); + message = String.format("Could not resolve resource value: 0x%1$X.", id); } throw new NotFoundException(message); diff --git a/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java deleted file mode 100644 index 34ae825baac8..000000000000 --- a/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java +++ /dev/null @@ -1,70 +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.AvoidXfermode - * - * Through the layoutlib_create tool, the original native methods of AvoidXfermode 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 AvoidXfermode 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 AvoidXfermode_Delegate extends Xfermode_Delegate { - - // ---- delegate data ---- - - // ---- Public Helper methods ---- - - @Override - public Composite getComposite(int alpha) { - // FIXME - return null; - } - - @Override - public boolean isSupported() { - return false; - } - - @Override - public String getSupportMessage() { - return "Avoid Xfermodes are not supported in Layout Preview mode."; - } - - // ---- native methods ---- - - @LayoutlibDelegate - /*package*/ static long nativeCreate(int opColor, int tolerance, int nativeMode) { - AvoidXfermode_Delegate newDelegate = new AvoidXfermode_Delegate(); - return sManager.addNewDelegate(newDelegate); - } - - // ---- Private delegate/helper methods ---- -} diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java index 07376828a1fe..f1da3a266448 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java @@ -17,9 +17,14 @@ package android.graphics; import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.layoutlib.bridge.impl.RenderAction; import com.android.resources.Density; +import com.android.resources.ResourceType; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.annotation.Nullable; @@ -38,6 +43,7 @@ import java.util.EnumSet; import java.util.Set; import javax.imageio.ImageIO; +import libcore.util.NativeAllocationRegistry_Delegate; /** * Delegate implementing the native methods of android.graphics.Bitmap @@ -61,13 +67,14 @@ public final class Bitmap_Delegate { // ---- delegate manager ---- private static final DelegateManager<Bitmap_Delegate> sManager = - new DelegateManager<Bitmap_Delegate>(Bitmap_Delegate.class); + new DelegateManager<>(Bitmap_Delegate.class); + private static long sFinalizer = -1; // ---- delegate helper data ---- // ---- delegate data ---- private final Config mConfig; - private BufferedImage mImage; + private final BufferedImage mImage; private boolean mHasAlpha = true; private boolean mHasMipMap = false; // TODO: check the default. private boolean mIsPremultiplied = true; @@ -114,10 +121,25 @@ public final class Bitmap_Delegate { * @see Bitmap#isMutable() * @see Bitmap#getDensity() */ - public static Bitmap createBitmap(File input, Set<BitmapCreateFlags> createFlags, + private static Bitmap createBitmap(File input, Set<BitmapCreateFlags> createFlags, Density density) throws IOException { // create a delegate with the content of the file. - Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888); + BufferedImage image = ImageIO.read(input); + if (image == null && input.exists()) { + // There was a problem decoding the image, or the decoder isn't registered. Webp maybe. + // Replace with a broken image icon. + BridgeContext currentContext = RenderAction.getCurrentContext(); + if (currentContext != null) { + RenderResources resources = currentContext.getRenderResources(); + ResourceValue broken = resources.getFrameworkResource(ResourceType.DRAWABLE, + "ic_menu_report_image"); + File brokenFile = new File(broken.getValue()); + if (brokenFile.exists()) { + image = ImageIO.read(brokenFile); + } + } + } + Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888); return createBitmap(delegate, createFlags, density.getDpiValue()); } @@ -281,13 +303,25 @@ public final class Bitmap_Delegate { } @LayoutlibDelegate - /*package*/ static void nativeDestructor(long nativeBitmap) { - sManager.removeJavaReferenceFor(nativeBitmap); + /*package*/ static Bitmap nativeCopyAshmemConfig(long nativeSrcBitmap, int nativeConfig) { + // Unused method; no implementation provided. + assert false; + return null; + } + + @LayoutlibDelegate + /*package*/ static long nativeGetNativeFinalizer() { + synchronized (Bitmap_Delegate.class) { + if (sFinalizer == -1) { + sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor); + } + return sFinalizer; + } } @LayoutlibDelegate /*package*/ static boolean nativeRecycle(long nativeBitmap) { - sManager.removeJavaReferenceFor(nativeBitmap); + // In our case reycle() is a no-op. We will let the finalizer to dispose the bitmap. return true; } diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index ba0d399ce52f..fa880f0710c4 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -21,6 +21,7 @@ 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.annotation.Nullable; @@ -38,6 +39,8 @@ import java.awt.geom.Arc2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; +import libcore.util.NativeAllocationRegistry_Delegate; + /** * Delegate implementing the native methods of android.graphics.Canvas @@ -57,6 +60,7 @@ public final class Canvas_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 ---- @@ -138,7 +142,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setBitmap(long canvas, Bitmap bitmap) { + public static void native_setBitmap(long canvas, Bitmap bitmap) { Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas); Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); if (canvasDelegate == null || bitmapDelegate==null) { @@ -149,7 +153,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_isOpaque(long nativeCanvas) { + public static boolean native_isOpaque(long nativeCanvas) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { @@ -160,7 +164,10 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static int native_getWidth(long nativeCanvas) { + public static void native_setHighContrastText(long nativeCanvas, boolean highContrastText){} + + @LayoutlibDelegate + public static int native_getWidth(long nativeCanvas) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { @@ -171,7 +178,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static int native_getHeight(long nativeCanvas) { + public static int native_getHeight(long nativeCanvas) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { @@ -182,7 +189,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static int native_save(long nativeCanvas, int saveFlags) { + public static int native_save(long nativeCanvas, int saveFlags) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { @@ -193,7 +200,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static int native_saveLayer(long nativeCanvas, float l, + public static int native_saveLayer(long nativeCanvas, float l, float t, float r, float b, long paint, int layerFlags) { // get the delegate from the native int. @@ -212,7 +219,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static int native_saveLayerAlpha(long nativeCanvas, float l, + public static int native_saveLayerAlpha(long nativeCanvas, float l, float t, float r, float b, int alpha, int layerFlags) { // get the delegate from the native int. @@ -225,7 +232,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_restore(long nativeCanvas, boolean throwOnUnderflow) { + public static void native_restore(long nativeCanvas, boolean throwOnUnderflow) { // FIXME: implement throwOnUnderflow. // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); @@ -237,7 +244,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_restoreToCount(long nativeCanvas, int saveCount, + public static void native_restoreToCount(long nativeCanvas, int saveCount, boolean throwOnUnderflow) { // FIXME: implement throwOnUnderflow. // get the delegate from the native int. @@ -250,7 +257,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static int native_getSaveCount(long nativeCanvas) { + public static int native_getSaveCount(long nativeCanvas) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { @@ -261,7 +268,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_translate(long nativeCanvas, float dx, float dy) { + public static void native_translate(long nativeCanvas, float dx, float dy) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { @@ -272,7 +279,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_scale(long nativeCanvas, float sx, float sy) { + public static void native_scale(long nativeCanvas, float sx, float sy) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { @@ -283,7 +290,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_rotate(long nativeCanvas, float degrees) { + public static void native_rotate(long nativeCanvas, float degrees) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { @@ -294,7 +301,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_skew(long nativeCanvas, float kx, float ky) { + public static void native_skew(long nativeCanvas, float kx, float ky) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { @@ -318,7 +325,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_concat(long nCanvas, long nMatrix) { + public static void native_concat(long nCanvas, long nMatrix) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); if (canvasDelegate == null) { @@ -346,7 +353,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setMatrix(long nCanvas, long nMatrix) { + public static void native_setMatrix(long nCanvas, long nMatrix) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); if (canvasDelegate == null) { @@ -376,7 +383,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_clipRect(long nCanvas, + public static boolean native_clipRect(long nCanvas, float left, float top, float right, float bottom, int regionOp) { @@ -390,7 +397,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_clipPath(long nativeCanvas, + public static boolean native_clipPath(long nativeCanvas, long nativePath, int regionOp) { Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); @@ -407,7 +414,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_clipRegion(long nativeCanvas, + public static boolean native_clipRegion(long nativeCanvas, long nativeRegion, int regionOp) { Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); @@ -424,7 +431,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void nativeSetDrawFilter(long nativeCanvas, long nativeFilter) { + public static void nativeSetDrawFilter(long nativeCanvas, long nativeFilter) { Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; @@ -439,7 +446,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_getClipBounds(long nativeCanvas, + public static boolean native_getClipBounds(long nativeCanvas, Rect bounds) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); @@ -460,7 +467,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_getCTM(long canvas, long matrix) { + public static void native_getCTM(long canvas, long matrix) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas); if (canvasDelegate == null) { @@ -477,13 +484,13 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_quickReject(long nativeCanvas, long path) { + public static boolean native_quickReject(long nativeCanvas, long path) { // FIXME properly implement quickReject return false; } @LayoutlibDelegate - /*package*/ static boolean native_quickReject(long nativeCanvas, + public static boolean native_quickReject(long nativeCanvas, float left, float top, float right, float bottom) { // FIXME properly implement quickReject @@ -491,7 +498,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawColor(long nativeCanvas, final int color, final int mode) { + 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) { @@ -522,14 +529,14 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawPaint(long nativeCanvas, long paint) { + 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 - /*package*/ static void native_drawPoint(long nativeCanvas, float x, float y, + public static void native_drawPoint(long nativeCanvas, float x, float y, long nativePaint) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -537,7 +544,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawPoints(long nativeCanvas, float[] pts, int offset, int count, + public static void native_drawPoints(long nativeCanvas, float[] pts, int offset, int count, long nativePaint) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -545,7 +552,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawLine(long nativeCanvas, + 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*/, @@ -558,7 +565,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawLines(long nativeCanvas, + public static void native_drawLines(long nativeCanvas, final float[] pts, final int offset, final int count, long nativePaint) { draw(nativeCanvas, nativePaint, false /*compositeOnly*/, @@ -574,7 +581,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawRect(long nativeCanvas, + 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*/, @@ -600,7 +607,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawOval(long nativeCanvas, final float left, + 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*/, @@ -627,7 +634,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawCircle(long nativeCanvas, + 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, @@ -635,7 +642,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawArc(long nativeCanvas, + 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) { @@ -667,7 +674,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawRoundRect(long nativeCanvas, + 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*/, @@ -697,7 +704,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawPath(long nativeCanvas, long path, long paint) { + public static void native_drawPath(long nativeCanvas, long path, long paint) { final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path); if (pathDelegate == null) { return; @@ -749,7 +756,62 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap, + 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, @@ -771,7 +833,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap, + 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) { @@ -787,7 +849,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawBitmap(long nativeCanvas, int[] colors, + 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, @@ -812,7 +874,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void nativeDrawBitmapMatrix(long nCanvas, Bitmap bitmap, + 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); @@ -853,7 +915,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void nativeDrawBitmapMesh(long nCanvas, Bitmap bitmap, + public static void nativeDrawBitmapMesh(long nCanvas, Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, long nPaint) { // FIXME @@ -862,7 +924,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void nativeDrawVertices(long nCanvas, int mode, int n, + public static void nativeDrawVertices(long nCanvas, int mode, int n, float[] verts, int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset, @@ -874,14 +936,14 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawText(long nativeCanvas, char[] text, int index, int count, + 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 - /*package*/ static void native_drawText(long nativeCanvas, String text, + 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; @@ -892,7 +954,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawTextRun(long nativeCanvas, String text, + 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; @@ -903,14 +965,14 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawTextRun(long nativeCanvas, char[] text, + 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 - /*package*/ static void native_drawTextOnPath(long nativeCanvas, + public static void native_drawTextOnPath(long nativeCanvas, char[] text, int index, int count, long path, float hOffset, @@ -922,7 +984,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawTextOnPath(long nativeCanvas, + public static void native_drawTextOnPath(long nativeCanvas, String text, long path, float hOffset, float vOffset, @@ -934,20 +996,21 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void finalizer(long nativeCanvas) { - // get the delegate from the native int so that it can be disposed. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); - if (canvasDelegate == null) { - return; + /*package*/ static long getNativeFinalizer() { + synchronized (Canvas_Delegate.class) { + if (sFinalizer == -1) { + sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(nativePtr -> { + Canvas_Delegate delegate = sManager.getDelegate(nativePtr); + if (delegate != null) { + delegate.dispose(); + } + sManager.removeJavaReferenceFor(nativePtr); + }); + } } - - canvasDelegate.dispose(); - - // remove it from the manager. - sManager.removeJavaReferenceFor(nativeCanvas); + return sFinalizer; } - // ---- Private delegate/helper methods ---- /** diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java index c7b24bcb352d..50efc7f7db86 100644 --- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java @@ -33,13 +33,13 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Scanner; import java.util.Set; @@ -73,7 +73,7 @@ public class FontFamily_Delegate { private static final Map<String, FontInfo> sCache = new LinkedHashMap<String, FontInfo>(CACHE_SIZE) { @Override - protected boolean removeEldestEntry(Entry<String, FontInfo> eldest) { + protected boolean removeEldestEntry(Map.Entry<String, FontInfo> eldest) { return size() > CACHE_SIZE; } @@ -213,7 +213,7 @@ public class FontFamily_Delegate { return mValid; } - /*package*/ static Font loadFont(String path) { + private static Font loadFont(String path) { if (path.startsWith(SYSTEM_FONTS) ) { String relativePath = path.substring(SYSTEM_FONTS.length()); File f = new File(sFontLocation, relativePath); @@ -245,6 +245,13 @@ public class FontFamily_Delegate { return sFontLocation; } + // ---- delegate methods ---- + @LayoutlibDelegate + /*package*/ static boolean addFont(FontFamily thisFontFamily, String path, int ttcIndex) { + final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mNativePtr); + return delegate != null && delegate.addFont(path, ttcIndex); + } + // ---- native methods ---- @LayoutlibDelegate @@ -270,35 +277,25 @@ public class FontFamily_Delegate { } @LayoutlibDelegate - /*package*/ static boolean nAddFont(long nativeFamily, final String path) { - final FontFamily_Delegate delegate = getDelegate(nativeFamily); - if (delegate != null) { - if (sFontLocation == null) { - delegate.mPostInitRunnables.add(new Runnable() { - @Override - public void run() { - delegate.addFont(path); - } - }); - return true; - } - return delegate.addFont(path); - } + /*package*/ static boolean nAddFont(long nativeFamily, ByteBuffer font, int ttcIndex) { + assert false : "The only client of this method has been overriden."; return false; } @LayoutlibDelegate - /*package*/ static boolean nAddFontWeightStyle(long nativeFamily, final String path, - final int weight, final boolean isItalic) { + /*package*/ static boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font, + int ttcIndex, List<FontListParser.Axis> listOfAxis, + int weight, boolean isItalic) { + assert false : "The only client of this method has been overriden."; + return false; + } + + static boolean addFont(long nativeFamily, final String path, final int weight, + final boolean isItalic) { final FontFamily_Delegate delegate = getDelegate(nativeFamily); if (delegate != null) { if (sFontLocation == null) { - delegate.mPostInitRunnables.add(new Runnable() { - @Override - public void run() { - delegate.addFont(path, weight, isItalic); - } - }); + delegate.mPostInitRunnables.add(() -> delegate.addFont(path, weight, isItalic)); return true; } return delegate.addFont(path, weight, isItalic); @@ -309,6 +306,9 @@ public class FontFamily_Delegate { @LayoutlibDelegate /*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) { FontFamily_Delegate ffd = sManager.getDelegate(nativeFamily); + if (ffd == null) { + return false; + } ffd.mValid = true; if (mgr == null) { return false; @@ -389,6 +389,15 @@ public class FontFamily_Delegate { mPostInitRunnables = null; } + private boolean addFont(final String path, int ttcIndex) { + // FIXME: support ttc fonts. Hack JRE?? + if (sFontLocation == null) { + mPostInitRunnables.add(() -> addFont(path)); + return true; + } + return addFont(path); + } + private boolean addFont(@NonNull String path) { return addFont(path, DEFAULT_FONT_WEIGHT, path.endsWith(FONT_SUFFIX_ITALIC)); } @@ -452,6 +461,7 @@ public class FontFamily_Delegate { private FontInfo deriveFont(@NonNull FontInfo srcFont, @NonNull FontInfo outFont) { int desiredWeight = outFont.mWeight; int srcWeight = srcFont.mWeight; + assert srcFont.mFont != null; Font derivedFont = srcFont.mFont; // Embolden the font if required. if (desiredWeight >= BOLD_FONT_WEIGHT && desiredWeight - srcWeight > BOLD_FONT_WEIGHT_DELTA / 2) { diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java index e8d34d0562aa..1f0eb3bab55b 100644 --- a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java @@ -169,77 +169,18 @@ public final class NinePatch_Delegate { sManager.removeJavaReferenceFor(chunk); } - @LayoutlibDelegate - /*package*/ static void nativeDraw(long canvas_instance, RectF loc, Bitmap bitmap_instance, - long chunk, long paint_instance_or_null, int destDensity, int srcDensity) { - draw(canvas_instance, - (int) loc.left, (int) loc.top, (int) loc.right, (int) loc.bottom, - bitmap_instance, chunk, paint_instance_or_null, - destDensity, srcDensity); - } - - @LayoutlibDelegate - /*package*/ static void nativeDraw(long canvas_instance, Rect loc, Bitmap bitmap_instance, - long chunk, long paint_instance_or_null, int destDensity, int srcDensity) { - draw(canvas_instance, - loc.left, loc.top, loc.right, loc.bottom, - bitmap_instance, chunk, paint_instance_or_null, - destDensity, srcDensity); - } @LayoutlibDelegate /*package*/ static long nativeGetTransparentRegion(Bitmap bitmap, long chunk, Rect location) { return 0; } - // ---- Private Helper methods ---- - - private static void draw(long canvas_instance, - final int left, final int top, final int right, final int bottom, - Bitmap bitmap_instance, long chunk, long paint_instance_or_null, - final int destDensity, final int srcDensity) { - // get the delegate from the native int. - final Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance); - if (bitmap_delegate == null) { - return; - } - - byte[] c = null; - NinePatch_Delegate delegate = sManager.getDelegate(chunk); + static byte[] getChunk(long nativeNinePatch) { + NinePatch_Delegate delegate = sManager.getDelegate(nativeNinePatch); if (delegate != null) { - c = delegate.chunk; - } - if (c == null) { - // not a 9-patch? - BufferedImage image = bitmap_delegate.getImage(); - Canvas_Delegate.native_drawBitmap(null, canvas_instance, bitmap_instance, - 0f, 0f, (float)image.getWidth(), (float)image.getHeight(), - (float)left, (float)top, (float)right, (float)bottom, - paint_instance_or_null, destDensity, srcDensity); - return; + return delegate.chunk; } + return null; + } - final NinePatchChunk chunkObject = getChunk(c); - assert chunkObject != null; - if (chunkObject == null) { - return; - } - - Canvas_Delegate canvas_delegate = Canvas_Delegate.getDelegate(canvas_instance); - if (canvas_delegate == null) { - return; - } - - // this one can be null - Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null); - - canvas_delegate.getSnapshot().draw(new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paint) { - chunkObject.draw(bitmap_delegate.getImage(), graphics, - left, top, right - left, bottom - top, destDensity, srcDensity); - } - }, paint_delegate, true /*compositeOnly*/, false /*forceSrcMode*/); - - } } diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java index dbd45c4f68be..33296e1abdc9 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -39,6 +39,8 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import libcore.util.NativeAllocationRegistry_Delegate; + /** * Delegate implementing the native methods of android.graphics.Paint * @@ -65,6 +67,7 @@ public class Paint_Delegate { // ---- delegate manager ---- private static final DelegateManager<Paint_Delegate> sManager = new DelegateManager<Paint_Delegate>(Paint_Delegate.class); + private static long sFinalizer = -1; // ---- delegate helper data ---- @@ -220,6 +223,14 @@ public class Paint_Delegate { return mColorFilter; } + public void setColorFilter(long colorFilterPtr) { + mColorFilter = ColorFilter_Delegate.getDelegate(colorFilterPtr); + } + + public void setShader(long shaderPtr) { + mShader = Shader_Delegate.getDelegate(shaderPtr); + } + /** * Returns the {@link Shader} delegate or null if none have been set * @@ -250,9 +261,9 @@ public class Paint_Delegate { // ---- native methods ---- @LayoutlibDelegate - /*package*/ static int getFlags(Paint thisPaint) { + /*package*/ static int nGetFlags(Paint thisPaint, long nativePaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 0; } @@ -263,9 +274,9 @@ public class Paint_Delegate { @LayoutlibDelegate - /*package*/ static void setFlags(Paint thisPaint, int flags) { + /*package*/ static void nSetFlags(Paint thisPaint, long nativePaint, int flags) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return; } @@ -274,14 +285,14 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void setFilterBitmap(Paint thisPaint, boolean filter) { - setFlag(thisPaint, Paint.FILTER_BITMAP_FLAG, filter); + /*package*/ static void nSetFilterBitmap(Paint thisPaint, long nativePaint, boolean filter) { + setFlag(nativePaint, Paint.FILTER_BITMAP_FLAG, filter); } @LayoutlibDelegate - /*package*/ static int getHinting(Paint thisPaint) { + /*package*/ static int nGetHinting(Paint thisPaint, long nativePaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return Paint.HINTING_ON; } @@ -290,9 +301,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void setHinting(Paint thisPaint, int mode) { + /*package*/ static void nSetHinting(Paint thisPaint, long nativePaint, int mode) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return; } @@ -301,44 +312,48 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void setAntiAlias(Paint thisPaint, boolean aa) { - setFlag(thisPaint, Paint.ANTI_ALIAS_FLAG, aa); + /*package*/ static void nSetAntiAlias(Paint thisPaint, long nativePaint, boolean aa) { + setFlag(nativePaint, Paint.ANTI_ALIAS_FLAG, aa); } @LayoutlibDelegate - /*package*/ static void setSubpixelText(Paint thisPaint, boolean subpixelText) { - setFlag(thisPaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText); + /*package*/ static void nSetSubpixelText(Paint thisPaint, long nativePaint, + boolean subpixelText) { + setFlag(nativePaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText); } @LayoutlibDelegate - /*package*/ static void setUnderlineText(Paint thisPaint, boolean underlineText) { - setFlag(thisPaint, Paint.UNDERLINE_TEXT_FLAG, underlineText); + /*package*/ static void nSetUnderlineText(Paint thisPaint, long nativePaint, + boolean underlineText) { + setFlag(nativePaint, Paint.UNDERLINE_TEXT_FLAG, underlineText); } @LayoutlibDelegate - /*package*/ static void setStrikeThruText(Paint thisPaint, boolean strikeThruText) { - setFlag(thisPaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText); + /*package*/ static void nSetStrikeThruText(Paint thisPaint, long nativePaint, + boolean strikeThruText) { + setFlag(nativePaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText); } @LayoutlibDelegate - /*package*/ static void setFakeBoldText(Paint thisPaint, boolean fakeBoldText) { - setFlag(thisPaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText); + /*package*/ static void nSetFakeBoldText(Paint thisPaint, long nativePaint, + boolean fakeBoldText) { + setFlag(nativePaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText); } @LayoutlibDelegate - /*package*/ static void setDither(Paint thisPaint, boolean dither) { - setFlag(thisPaint, Paint.DITHER_FLAG, dither); + /*package*/ static void nSetDither(Paint thisPaint, long nativePaint, boolean dither) { + setFlag(nativePaint, Paint.DITHER_FLAG, dither); } @LayoutlibDelegate - /*package*/ static void setLinearText(Paint thisPaint, boolean linearText) { - setFlag(thisPaint, Paint.LINEAR_TEXT_FLAG, linearText); + /*package*/ static void nSetLinearText(Paint thisPaint, long nativePaint, boolean linearText) { + setFlag(nativePaint, Paint.LINEAR_TEXT_FLAG, linearText); } @LayoutlibDelegate - /*package*/ static int getColor(Paint thisPaint) { + /*package*/ static int nGetColor(Paint thisPaint, long nativePaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 0; } @@ -347,9 +362,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void setColor(Paint thisPaint, int color) { + /*package*/ static void nSetColor(Paint thisPaint, long nativePaint, int color) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return; } @@ -358,9 +373,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int getAlpha(Paint thisPaint) { + /*package*/ static int nGetAlpha(Paint thisPaint, long nativePaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 0; } @@ -369,9 +384,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void setAlpha(Paint thisPaint, int a) { + /*package*/ static void nSetAlpha(Paint thisPaint, long nativePaint, int a) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return; } @@ -380,9 +395,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float getStrokeWidth(Paint thisPaint) { + /*package*/ static float nGetStrokeWidth(Paint thisPaint, long nativePaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 1.f; } @@ -391,9 +406,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void setStrokeWidth(Paint thisPaint, float width) { + /*package*/ static void nSetStrokeWidth(Paint thisPaint, long nativePaint, float width) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return; } @@ -402,9 +417,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float getStrokeMiter(Paint thisPaint) { + /*package*/ static float nGetStrokeMiter(Paint thisPaint, long nativePaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 1.f; } @@ -413,9 +428,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void setStrokeMiter(Paint thisPaint, float miter) { + /*package*/ static void nSetStrokeMiter(Paint thisPaint, long nativePaint, float miter) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return; } @@ -424,7 +439,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setShadowLayer(long paint, float radius, float dx, float dy, + /*package*/ static void nSetShadowLayer(long paint, float radius, float dx, float dy, int color) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -432,7 +447,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_hasShadowLayer(long paint) { + /*package*/ static boolean nHasShadowLayer(long paint) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "Paint.hasShadowLayer is not supported.", null, null /*data*/); @@ -440,16 +455,17 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static boolean isElegantTextHeight(Paint thisPaint) { + /*package*/ static boolean nIsElegantTextHeight(Paint thisPaint, long nativePaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); return delegate != null && delegate.mFontVariant == FontVariant.ELEGANT; } @LayoutlibDelegate - /*package*/ static void setElegantTextHeight(Paint thisPaint, boolean elegant) { + /*package*/ static void nSetElegantTextHeight(Paint thisPaint, long nativePaint, + boolean elegant) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return; } @@ -458,9 +474,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float getTextSize(Paint thisPaint) { + /*package*/ static float nGetTextSize(Paint thisPaint, long nativePaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 1.f; } @@ -469,9 +485,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void setTextSize(Paint thisPaint, float textSize) { + /*package*/ static void nSetTextSize(Paint thisPaint, long nativePaint, float textSize) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return; } @@ -483,9 +499,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float getTextScaleX(Paint thisPaint) { + /*package*/ static float nGetTextScaleX(Paint thisPaint, long nativePaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 1.f; } @@ -494,9 +510,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void setTextScaleX(Paint thisPaint, float scaleX) { + /*package*/ static void nSetTextScaleX(Paint thisPaint, long nativePaint, float scaleX) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return; } @@ -508,9 +524,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float getTextSkewX(Paint thisPaint) { + /*package*/ static float nGetTextSkewX(Paint thisPaint, long nativePaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 1.f; } @@ -519,9 +535,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void setTextSkewX(Paint thisPaint, float skewX) { + /*package*/ static void nSetTextSkewX(Paint thisPaint, long nativePaint, float skewX) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return; } @@ -533,9 +549,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float ascent(Paint thisPaint) { + /*package*/ static float nAscent(Paint thisPaint, long nativePaint, long nativeTypeface) { // get the delegate - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 0; } @@ -550,9 +566,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float descent(Paint thisPaint) { + /*package*/ static float nDescent(Paint thisPaint, long nativePaint, long nativeTypeface) { // get the delegate - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 0; } @@ -567,9 +583,10 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float getFontMetrics(Paint thisPaint, FontMetrics metrics) { + /*package*/ static float nGetFontMetrics(Paint thisPaint, long nativePaint, long nativeTypeface, + FontMetrics metrics) { // get the delegate - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 0; } @@ -578,9 +595,10 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int getFontMetricsInt(Paint thisPaint, FontMetricsInt fmi) { + /*package*/ static int nGetFontMetricsInt(Paint thisPaint, long nativePaint, + long nativeTypeface, FontMetricsInt fmi) { // get the delegate - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 0; } @@ -603,31 +621,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float native_measureText(Paint thisPaint, char[] text, int index, - int count, int bidiFlags) { - // get the delegate - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); - if (delegate == null) { - return 0; - } - - RectF bounds = delegate.measureText(text, index, count, null, 0, bidiFlags); - return bounds.right - bounds.left; - } - - @LayoutlibDelegate - /*package*/ static float native_measureText(Paint thisPaint, String text, int start, int end, - int bidiFlags) { - return native_measureText(thisPaint, text.toCharArray(), start, end - start, bidiFlags); - } - - @LayoutlibDelegate - /*package*/ static float native_measureText(Paint thisPaint, String text, int bidiFlags) { - return native_measureText(thisPaint, text.toCharArray(), 0, text.length(), bidiFlags); - } - - @LayoutlibDelegate - /*package*/ static int native_breakText(long nativePaint, long nativeTypeface, char[] text, + /*package*/ static int nBreakText(long nativePaint, long nativeTypeface, char[] text, int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth) { // get the delegate @@ -669,21 +663,21 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int native_breakText(long nativePaint, long nativeTypeface, String text, + /*package*/ static int nBreakText(long nativePaint, long nativeTypeface, String text, boolean measureForwards, float maxWidth, int bidiFlags, float[] measuredWidth) { - return native_breakText(nativePaint, nativeTypeface, text.toCharArray(), 0, text.length(), + return nBreakText(nativePaint, nativeTypeface, text.toCharArray(), 0, text.length(), maxWidth, bidiFlags, measuredWidth); } @LayoutlibDelegate - /*package*/ static long native_init() { + /*package*/ static long nInit() { Paint_Delegate newDelegate = new Paint_Delegate(); return sManager.addNewDelegate(newDelegate); } @LayoutlibDelegate - /*package*/ static long native_initWithPaint(long paint) { + /*package*/ static long nInitWithPaint(long paint) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(paint); if (delegate == null) { @@ -695,7 +689,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_reset(long native_object) { + /*package*/ static void nReset(long native_object) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -706,7 +700,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_set(long native_dst, long native_src) { + /*package*/ static void nSet(long native_dst, long native_src) { // get the delegate from the native int. Paint_Delegate delegate_dst = sManager.getDelegate(native_dst); if (delegate_dst == null) { @@ -723,7 +717,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int native_getStyle(long native_object) { + /*package*/ static int nGetStyle(long native_object) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -734,7 +728,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setStyle(long native_object, int style) { + /*package*/ static void nSetStyle(long native_object, int style) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -745,7 +739,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int native_getStrokeCap(long native_object) { + /*package*/ static int nGetStrokeCap(long native_object) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -756,7 +750,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setStrokeCap(long native_object, int cap) { + /*package*/ static void nSetStrokeCap(long native_object, int cap) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -767,7 +761,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int native_getStrokeJoin(long native_object) { + /*package*/ static int nGetStrokeJoin(long native_object) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -778,7 +772,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setStrokeJoin(long native_object, int join) { + /*package*/ static void nSetStrokeJoin(long native_object, int join) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -789,7 +783,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_getFillPath(long native_object, long src, long dst) { + /*package*/ static boolean nGetFillPath(long native_object, long src, long dst) { Paint_Delegate paint = sManager.getDelegate(native_object); if (paint == null) { return false; @@ -815,7 +809,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_setShader(long native_object, long shader) { + /*package*/ static long nSetShader(long native_object, long shader) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -828,7 +822,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_setColorFilter(long native_object, long filter) { + /*package*/ static long nSetColorFilter(long native_object, long filter) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -847,7 +841,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_setXfermode(long native_object, long xfermode) { + /*package*/ static long nSetXfermode(long native_object, long xfermode) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -860,7 +854,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_setPathEffect(long native_object, long effect) { + /*package*/ static long nSetPathEffect(long native_object, long effect) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -873,7 +867,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_setMaskFilter(long native_object, long maskfilter) { + /*package*/ static long nSetMaskFilter(long native_object, long maskfilter) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -892,7 +886,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_setTypeface(long native_object, long typeface) { + /*package*/ static long nSetTypeface(long native_object, long typeface) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -909,7 +903,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_setRasterizer(long native_object, long rasterizer) { + /*package*/ static long nSetRasterizer(long native_object, long rasterizer) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -928,7 +922,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int native_getTextAlign(long native_object) { + /*package*/ static int nGetTextAlign(long native_object) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -939,7 +933,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setTextAlign(long native_object, int align) { + /*package*/ static void nSetTextAlign(long native_object, int align) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -950,58 +944,27 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setTextLocale(long native_object, String locale) { - // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(native_object); - if (delegate == null) { - return; - } - - delegate.setTextLocale(locale); - } - - @LayoutlibDelegate - /*package*/ static int native_getTextWidths(long native_object, long native_typeface, - char[] text, int index, int count, int bidiFlags, float[] widths) { - - if (widths != null) { - for (int i = 0; i< count; i++) { - widths[i]=0; - } - } + /*package*/ static int nSetTextLocales(long native_object, String locale) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { return 0; } - // native_typeface is passed here since Framework's old implementation did not have the - // typeface object associated with the Paint. Since, we follow the new framework way, - // we store the typeface with the paint and use it directly. - assert (native_typeface == delegate.mNativeTypeface); - - RectF bounds = delegate.measureText(text, index, count, widths, 0, bidiFlags); - return ((int) (bounds.right - bounds.left)); - } - - @LayoutlibDelegate - /*package*/ static int native_getTextWidths(long native_object, long native_typeface, - String text, int start, int end, int bidiFlags, float[] widths) { - return native_getTextWidths(native_object, native_typeface, text.toCharArray(), start, - end - start, bidiFlags, widths); + delegate.setTextLocale(locale); + return 0; } @LayoutlibDelegate - /* package */static int native_getTextGlyphs(long native_object, String text, int start, - int end, int contextStart, int contextEnd, int flags, char[] glyphs) { + /*package*/ static void nSetTextLocalesByMinikinLangListId(long paintPtr, + int mMinikinLangListId) { // FIXME - return 0; } @LayoutlibDelegate - /*package*/ static float native_getTextRunAdvances(long native_object, long native_typeface, + /*package*/ static float nGetTextAdvances(long native_object, long native_typeface, char[] text, int index, int count, int contextIndex, int contextCount, - boolean isRtl, float[] advances, int advancesIndex) { + int bidiFlags, float[] advances, int advancesIndex) { if (advances != null) for (int i = advancesIndex; i< advancesIndex+count; i++) @@ -1017,25 +980,25 @@ public class Paint_Delegate { // we store the typeface with the paint and use it directly. assert (native_typeface == delegate.mNativeTypeface); - RectF bounds = delegate.measureText(text, index, count, advances, advancesIndex, isRtl); + RectF bounds = delegate.measureText(text, index, count, advances, advancesIndex, bidiFlags); return bounds.right - bounds.left; } @LayoutlibDelegate - /*package*/ static float native_getTextRunAdvances(long native_object, long native_typeface, + /*package*/ static float nGetTextAdvances(long native_object, long native_typeface, String text, int start, int end, int contextStart, int contextEnd, - boolean isRtl, float[] advances, int advancesIndex) { + int bidiFlags, float[] advances, int advancesIndex) { // FIXME: support contextStart and contextEnd int count = end - start; char[] buffer = TemporaryBuffer.obtain(count); TextUtils.getChars(text, start, end, buffer, 0); - return native_getTextRunAdvances(native_object, native_typeface, buffer, 0, count, - contextStart, contextEnd - contextStart, isRtl, advances, advancesIndex); + return nGetTextAdvances(native_object, native_typeface, buffer, 0, count, + contextStart, contextEnd - contextStart, bidiFlags, advances, advancesIndex); } @LayoutlibDelegate - /*package*/ static int native_getTextRunCursor(Paint thisPaint, long native_object, char[] text, + /*package*/ static int nGetTextRunCursor(Paint thisPaint, long native_object, char[] text, int contextStart, int contextLength, int flags, int offset, int cursorOpt) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -1044,7 +1007,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int native_getTextRunCursor(Paint thisPaint, long native_object, String text, + /*package*/ static int nGetTextRunCursor(Paint thisPaint, long native_object, String text, int contextStart, int contextEnd, int flags, int offset, int cursorOpt) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -1053,7 +1016,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_getTextPath(long native_object, long native_typeface, + /*package*/ static void nGetTextPath(long native_object, long native_typeface, int bidiFlags, char[] text, int index, int count, float x, float y, long path) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -1061,7 +1024,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_getTextPath(long native_object, long native_typeface, + /*package*/ static void nGetTextPath(long native_object, long native_typeface, int bidiFlags, String text, int start, int end, float x, float y, long path) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -1069,14 +1032,14 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void nativeGetStringBounds(long nativePaint, long native_typeface, + /*package*/ static void nGetStringBounds(long nativePaint, long native_typeface, String text, int start, int end, int bidiFlags, Rect bounds) { - nativeGetCharArrayBounds(nativePaint, native_typeface, text.toCharArray(), start, + nGetCharArrayBounds(nativePaint, native_typeface, text.toCharArray(), start, end - start, bidiFlags, bounds); } @LayoutlibDelegate - /*package*/ static void nativeGetCharArrayBounds(long nativePaint, long native_typeface, + /*package*/ static void nGetCharArrayBounds(long nativePaint, long native_typeface, char[] text, int index, int count, int bidiFlags, Rect bounds) { // get the delegate from the native int. @@ -1092,12 +1055,18 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void finalizer(long nativePaint) { - sManager.removeJavaReferenceFor(nativePaint); + /*package*/ static long nGetNativeFinalizer() { + synchronized (Paint_Delegate.class) { + if (sFinalizer == -1) { + sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer( + sManager::removeJavaReferenceFor); + } + } + return sFinalizer; } @LayoutlibDelegate - /*package*/ static float native_getLetterSpacing(long nativePaint) { + /*package*/ static float nGetLetterSpacing(long nativePaint) { Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 0; @@ -1106,7 +1075,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setLetterSpacing(long nativePaint, float letterSpacing) { + /*package*/ static void nSetLetterSpacing(long nativePaint, float letterSpacing) { Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING, "Paint.setLetterSpacing() not supported.", null, null); Paint_Delegate delegate = sManager.getDelegate(nativePaint); @@ -1117,13 +1086,13 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setFontFeatureSettings(long nativePaint, String settings) { + /*package*/ static void nSetFontFeatureSettings(long nativePaint, String settings) { Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING, "Paint.setFontFeatureSettings() not supported.", null, null); } @LayoutlibDelegate - /*package*/ static int native_getHyphenEdit(long nativePaint) { + /*package*/ static int nGetHyphenEdit(long nativePaint) { Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 0; @@ -1132,7 +1101,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setHyphenEdit(long nativePaint, int hyphen) { + /*package*/ static void nSetHyphenEdit(long nativePaint, int hyphen) { Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return; @@ -1141,7 +1110,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_hasGlyph(long nativePaint, long nativeTypeface, int bidiFlags, + /*package*/ static boolean nHasGlyph(long nativePaint, long nativeTypeface, int bidiFlags, String string) { Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { @@ -1169,13 +1138,14 @@ public class Paint_Delegate { @LayoutlibDelegate - /*package*/ static float native_getRunAdvance(long nativePaint, long nativeTypeface, + /*package*/ static float nGetRunAdvance(long nativePaint, long nativeTypeface, @NonNull char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset) { int count = end - start; float[] advances = new float[count]; - native_getTextRunAdvances(nativePaint, nativeTypeface, text, start, count, - contextStart, contextEnd - contextStart, isRtl, advances, 0); + int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR; + nGetTextAdvances(nativePaint, nativeTypeface, text, start, count, + contextStart, contextEnd - contextStart, bidiFlags, advances, 0); int startOffset = offset - start; // offset from start. float sum = 0; for (int i = 0; i < startOffset; i++) { @@ -1185,13 +1155,14 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int native_getOffsetForAdvance(long nativePaint, long nativeTypeface, + /*package*/ static int nGetOffsetForAdvance(long nativePaint, long nativeTypeface, char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance) { int count = end - start; float[] advances = new float[count]; - native_getTextRunAdvances(nativePaint, nativeTypeface, text, start, count, - contextStart, contextEnd - contextStart, isRtl, advances, 0); + int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR; + nGetTextAdvances(nativePaint, nativeTypeface, text, start, count, + contextStart, contextEnd - contextStart, bidiFlags, advances, 0); float sum = 0; int i; for (i = 0; i < count && sum < advance; i++) { @@ -1359,9 +1330,9 @@ public class Paint_Delegate { mLocale = new Locale(locale); } - private static void setFlag(Paint thisPaint, int flagMask, boolean flagValue) { + private static void setFlag(long nativePaint, int flagMask, boolean flagValue) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return; } diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java index a10ac00fc356..265ebd1755e3 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java @@ -75,7 +75,7 @@ public final class Path_Delegate { return sManager.getDelegate(nPath); } - public Shape getJavaShape() { + public Path2D getJavaShape() { return mPath; } @@ -167,13 +167,13 @@ public final class Path_Delegate { } @LayoutlibDelegate - /*package*/ static void native_setFillType(long nPath, int ft) { + public static void native_setFillType(long nPath, int ft) { Path_Delegate pathDelegate = sManager.getDelegate(nPath); if (pathDelegate == null) { return; } - pathDelegate.mFillType = Path.sFillTypeArray[ft]; + pathDelegate.setFillType(Path.sFillTypeArray[ft]); } @LayoutlibDelegate @@ -423,21 +423,13 @@ public final class Path_Delegate { } @LayoutlibDelegate - /*package*/ static void native_offset(long nPath, float dx, float dy, long dst_path) { + /*package*/ static void native_offset(long nPath, float dx, float dy) { Path_Delegate pathDelegate = sManager.getDelegate(nPath); if (pathDelegate == null) { return; } - // could be null if the int is 0; - Path_Delegate dstDelegate = sManager.getDelegate(dst_path); - - pathDelegate.offset(dx, dy, dstDelegate); - } - - @LayoutlibDelegate - /*package*/ static void native_offset(long nPath, float dx, float dy) { - native_offset(nPath, dx, dy, 0); + pathDelegate.offset(dx, dy); } @LayoutlibDelegate @@ -572,7 +564,7 @@ public final class Path_Delegate { return null; } - private static void addPath(long destPath, long srcPath, AffineTransform transform) { + public static void addPath(long destPath, long srcPath, AffineTransform transform) { Path_Delegate destPathDelegate = sManager.getDelegate(destPath); if (destPathDelegate == null) { return; @@ -630,7 +622,7 @@ public final class Path_Delegate { * Fills the given {@link RectF} with the path bounds. * @param bounds the RectF to be filled. */ - private void fillBounds(RectF bounds) { + public void fillBounds(RectF bounds) { Rectangle2D rect = mPath.getBounds2D(); bounds.left = (float)rect.getMinX(); bounds.right = (float)rect.getMaxX(); @@ -644,7 +636,7 @@ public final class Path_Delegate { * @param x The x-coordinate of the start of a new contour * @param y The y-coordinate of the start of a new contour */ - private void moveTo(float x, float y) { + public void moveTo(float x, float y) { mPath.moveTo(mLastX = x, mLastY = y); } @@ -658,7 +650,7 @@ public final class Path_Delegate { * @param dy The amount to add to the y-coordinate of the end of the * previous contour, to specify the start of a new contour */ - private void rMoveTo(float dx, float dy) { + public void rMoveTo(float dx, float dy) { dx += mLastX; dy += mLastY; mPath.moveTo(mLastX = dx, mLastY = dy); @@ -672,7 +664,7 @@ public final class Path_Delegate { * @param x The x-coordinate of the end of a line * @param y The y-coordinate of the end of a line */ - private void lineTo(float x, float y) { + public void lineTo(float x, float y) { if (!hasPoints()) { mPath.moveTo(mLastX = 0, mLastY = 0); } @@ -689,7 +681,7 @@ public final class Path_Delegate { * @param dy The amount to add to the y-coordinate of the previous point on * this contour, to specify a line */ - private void rLineTo(float dx, float dy) { + public void rLineTo(float dx, float dy) { if (!hasPoints()) { mPath.moveTo(mLastX = 0, mLastY = 0); } @@ -714,7 +706,7 @@ public final class Path_Delegate { * @param x2 The x-coordinate of the end point on a quadratic curve * @param y2 The y-coordinate of the end point on a quadratic curve */ - private void quadTo(float x1, float y1, float x2, float y2) { + public void quadTo(float x1, float y1, float x2, float y2) { mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2); } @@ -732,7 +724,7 @@ public final class Path_Delegate { * @param dy2 The amount to add to the y-coordinate of the last point on * this contour, for the end point of a quadratic curve */ - private void rQuadTo(float dx1, float dy1, float dx2, float dy2) { + public void rQuadTo(float dx1, float dy1, float dx2, float dy2) { if (!hasPoints()) { mPath.moveTo(mLastX = 0, mLastY = 0); } @@ -755,7 +747,7 @@ public final class Path_Delegate { * @param x3 The x-coordinate of the end point on a cubic curve * @param y3 The y-coordinate of the end point on a cubic curve */ - private void cubicTo(float x1, float y1, float x2, float y2, + public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) { if (!hasPoints()) { mPath.moveTo(0, 0); @@ -768,7 +760,7 @@ public final class Path_Delegate { * current point on this contour. If there is no previous point, then a * moveTo(0,0) is inserted automatically. */ - private void rCubicTo(float dx1, float dy1, float dx2, float dy2, + public void rCubicTo(float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) { if (!hasPoints()) { mPath.moveTo(mLastX = 0, mLastY = 0); @@ -798,7 +790,7 @@ public final class Path_Delegate { * mod 360. * @param forceMoveTo If true, always begin a new contour with the arc */ - private void arcTo(float left, float top, float right, float bottom, float startAngle, + public void arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo) { Arc2D arc = new Arc2D.Float(left, top, right - left, bottom - top, -startAngle, @@ -812,7 +804,7 @@ public final class Path_Delegate { * Close the current contour. If the current point is not equal to the * first point of the contour, a line segment is automatically added. */ - private void close() { + public void close() { mPath.closePath(); } @@ -831,7 +823,7 @@ public final class Path_Delegate { * @param bottom The bottom of a rectangle to add to the path * @param dir The direction to wind the rectangle's contour */ - private void addRect(float left, float top, float right, float bottom, + public void addRect(float left, float top, float right, float bottom, int dir) { moveTo(left, top); @@ -860,21 +852,14 @@ public final class Path_Delegate { * * @param dx The amount in the X direction to offset the entire path * @param dy The amount in the Y direction to offset the entire path - * @param dst The translated path is written here. If this is null, then - * the original path is modified. */ - public void offset(float dx, float dy, Path_Delegate dst) { + public void offset(float dx, float dy) { GeneralPath newPath = new GeneralPath(); PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy)); newPath.append(iterator, false /*connect*/); - - if (dst != null) { - dst.mPath = newPath; - } else { - mPath = newPath; - } + mPath = newPath; } /** diff --git a/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java deleted file mode 100644 index f27144f45448..000000000000 --- a/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java +++ /dev/null @@ -1,70 +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.PixelXorXfermode - * - * Through the layoutlib_create tool, the original native methods of PixelXorXfermode 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 PixelXorXfermode 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}. - * - * @see Xfermode_Delegate - */ -public class PixelXorXfermode_Delegate extends Xfermode_Delegate { - // ---- delegate data ---- - - // ---- Public Helper methods ---- - - @Override - public Composite getComposite(int alpha) { - // FIXME - return null; - } - - @Override - public boolean isSupported() { - return false; - } - - @Override - public String getSupportMessage() { - return "Pixel XOR Xfermodes are not supported in Layout Preview mode."; - } - - // ---- native methods ---- - - @LayoutlibDelegate - /*package*/ static long nativeCreate(int opColor) { - PixelXorXfermode_Delegate newDelegate = new PixelXorXfermode_Delegate(); - return sManager.addNewDelegate(newDelegate); - } - - // ---- Private delegate/helper methods ---- -} diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java index 85e65e690dc7..5cd34f6e000f 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java @@ -24,8 +24,10 @@ import android.graphics.FontFamily_Delegate.FontVariant; import java.awt.Font; import java.io.File; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import java.util.Map; import static android.graphics.FontFamily_Delegate.getFontLocation; @@ -205,6 +207,17 @@ public final class Typeface_Delegate { return new File(getFontLocation()); } + @LayoutlibDelegate + /*package*/ static FontFamily makeFamilyFromParsed(FontListParser.Family family, + Map<String, ByteBuffer> bufferForPath) { + FontFamily fontFamily = new FontFamily(family.lang, family.variant); + for (FontListParser.Font font : family.fonts) { + FontFamily_Delegate.addFont(fontFamily.mNativePtr, font.fontName, font.weight, + font.isItalic); + } + return fontFamily; + } + // ---- Private delegate/helper methods ---- private Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style) { diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java new file mode 100644 index 000000000000..200fe3b1d192 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java @@ -0,0 +1,284 @@ +/* + * 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.drawable; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.internal.view.animation.NativeInterpolatorFactoryHelper_Delegate; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT; +import android.graphics.drawable.VectorDrawable_Delegate.VFullPath_Delegate; +import android.graphics.drawable.VectorDrawable_Delegate.VGroup_Delegate; +import android.graphics.drawable.VectorDrawable_Delegate.VNativeObject; +import android.graphics.drawable.VectorDrawable_Delegate.VPathRenderer_Delegate; + +import java.util.ArrayList; +import java.util.function.Consumer; + +/** + * Delegate used to provide new implementation of a select few methods of {@link + * AnimatedVectorDrawable} + * <p> + * Through the layoutlib_create tool, the original methods of AnimatedVectorDrawable have been + * replaced by calls to methods of the same name in this delegate class. + */ +@SuppressWarnings("unused") +public class AnimatedVectorDrawable_Delegate { + private static DelegateManager<AnimatorSetHolder> sAnimatorSets = new + DelegateManager<>(AnimatorSetHolder.class); + private static DelegateManager<PropertySetter> sHolders = new + DelegateManager<>(PropertySetter.class); + + + @LayoutlibDelegate + /*package*/ static long nCreateAnimatorSet() { + return sAnimatorSets.addNewDelegate(new AnimatorSetHolder()); + } + + @LayoutlibDelegate + /*package*/ static void nAddAnimator(long setPtr, long propertyValuesHolder, + long nativeInterpolator, long startDelay, long duration, int repeatCount) { + PropertySetter holder = sHolders.getDelegate(propertyValuesHolder); + if (holder == null || holder.getValues() == null) { + return; + } + + ObjectAnimator animator = new ObjectAnimator(); + animator.setValues(holder.getValues()); + animator.setInterpolator( + NativeInterpolatorFactoryHelper_Delegate.getDelegate(nativeInterpolator)); + animator.setStartDelay(startDelay); + animator.setDuration(duration); + animator.setRepeatCount(repeatCount); + animator.setTarget(holder); + animator.setPropertyName(holder.getValues().getPropertyName()); + + AnimatorSetHolder set = sAnimatorSets.getDelegate(setPtr); + assert set != null; + set.addAnimator(animator); + } + + @LayoutlibDelegate + /*package*/ static long nCreateGroupPropertyHolder(long nativePtr, int propertyId, + float startValue, float endValue) { + VGroup_Delegate group = VNativeObject.getDelegate(nativePtr); + Consumer<Float> setter = group.getPropertySetter(propertyId); + + return sHolders.addNewDelegate(FloatPropertySetter.of(setter, startValue, + endValue)); + } + + @LayoutlibDelegate + /*package*/ static long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr, + long endValuePtr) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "AnimatedVectorDrawable path " + + "animations are not supported.", null, null); + return 0; + } + + @LayoutlibDelegate + /*package*/ static long nCreatePathColorPropertyHolder(long nativePtr, int propertyId, + int startValue, int endValue) { + VFullPath_Delegate path = VNativeObject.getDelegate(nativePtr); + Consumer<Integer> setter = path.getIntPropertySetter(propertyId); + + return sHolders.addNewDelegate(IntPropertySetter.of(setter, startValue, + endValue)); + } + + @LayoutlibDelegate + /*package*/ static long nCreatePathPropertyHolder(long nativePtr, int propertyId, + float startValue, float endValue) { + VFullPath_Delegate path = VNativeObject.getDelegate(nativePtr); + Consumer<Float> setter = path.getFloatPropertySetter(propertyId); + + return sHolders.addNewDelegate(FloatPropertySetter.of(setter, startValue, + endValue)); + } + + @LayoutlibDelegate + /*package*/ static long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue, + float endValue) { + VPathRenderer_Delegate renderer = VNativeObject.getDelegate(nativePtr); + + return sHolders.addNewDelegate(FloatPropertySetter.of(renderer::setRootAlpha, + startValue, + endValue)); + } + + @LayoutlibDelegate + /*package*/ static void nSetPropertyHolderData(long nativePtr, float[] 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; + + animatorSet.start(); + } + + @LayoutlibDelegate + /*package*/ static void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) { + AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr); + assert animatorSet != null; + + animatorSet.reverse(); + } + + @LayoutlibDelegate + /*package*/ static void nEnd(long animatorSetPtr) { + AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr); + assert animatorSet != null; + + animatorSet.end(); + } + + @LayoutlibDelegate + /*package*/ static void nReset(long animatorSetPtr) { + AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr); + assert animatorSet != null; + + animatorSet.end(); + animatorSet.start(); + } + + private static class AnimatorSetHolder { + private ArrayList<Animator> mAnimators = new ArrayList<>(); + private AnimatorSet mAnimatorSet = null; + + private void addAnimator(@NonNull Animator animator) { + mAnimators.add(animator); + } + + private void ensureAnimatorSet() { + if (mAnimatorSet == null) { + mAnimatorSet = new AnimatorSet(); + mAnimatorSet.playTogether(mAnimators); + } + } + + private void start() { + ensureAnimatorSet(); + + mAnimatorSet.start(); + } + + private void end() { + mAnimatorSet.end(); + } + + private void reset() { + end(); + start(); + } + + private void reverse() { + mAnimatorSet.reverse(); + } + } + + /** + * Class that allows setting a value and holds the range of values for the given property. + * + * @param <T> the type of the property + */ + private static class PropertySetter<T> { + final Consumer<T> mValueSetter; + private PropertyValuesHolder mValues; + + private PropertySetter(@NonNull Consumer<T> valueSetter) { + mValueSetter = valueSetter; + } + + /** + * Method to set an {@link Integer} value for this property. The default implementation of + * this method doesn't do anything. This method is accessed via reflection by the + * PropertyValuesHolder. + */ + public void setIntValue(Integer value) { + } + + /** + * Method to set an {@link Integer} value for this property. The default implementation of + * this method doesn't do anything. This method is accessed via reflection by the + * PropertyValuesHolder. + */ + public void setFloatValue(Float value) { + } + + void setValues(float... values) { + mValues = PropertyValuesHolder.ofFloat("floatValue", values); + } + + @Nullable + PropertyValuesHolder getValues() { + return mValues; + } + + void setValues(int... values) { + mValues = PropertyValuesHolder.ofInt("intValue", values); + } + } + + private static class IntPropertySetter extends PropertySetter<Integer> { + private IntPropertySetter(Consumer<Integer> valueSetter) { + super(valueSetter); + } + + private static PropertySetter of(Consumer<Integer> valueSetter, int... values) { + PropertySetter setter = new IntPropertySetter(valueSetter); + setter.setValues(values); + + return setter; + } + + public void setIntValue(Integer value) { + mValueSetter.accept(value); + } + } + + private static class FloatPropertySetter extends PropertySetter<Float> { + private FloatPropertySetter(Consumer<Float> valueSetter) { + super(valueSetter); + } + + private static PropertySetter of(Consumer<Float> valueSetter, float... values) { + PropertySetter setter = new FloatPropertySetter(valueSetter); + setter.setValues(values); + + return setter; + } + + public void setFloatValue(Float value) { + mValueSetter.accept(value); + } + + } +} diff --git a/tools/layoutlib/bridge/src/libcore/util/ZoneInfo_WallTime_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java index f29c5c05fcac..3d78931a152c 100644 --- a/tools/layoutlib/bridge/src/libcore/util/ZoneInfo_WallTime_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * 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. @@ -14,19 +14,15 @@ * limitations under the License. */ -package libcore.util; +package android.graphics.drawable; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; -import java.util.GregorianCalendar; - -/** - * Delegate used to provide alternate implementation of select methods in {@link ZoneInfo.WallTime} - */ -public class ZoneInfo_WallTime_Delegate { +import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT; +public class AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate { @LayoutlibDelegate - static GregorianCalendar createGregorianCalendar() { - return new GregorianCalendar(); + /*package*/ static boolean useLastSeenTarget(VectorDrawableAnimatorRT thisDrawableAnimator) { + return true; } } diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java new file mode 100644 index 000000000000..49f8691986be --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java @@ -0,0 +1,1213 @@ +/* + * 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.drawable; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.annotation.NonNull; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.content.res.TypedArray; +import android.graphics.Canvas_Delegate; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Paint.Cap; +import android.graphics.Paint.Join; +import android.graphics.Paint_Delegate; +import android.graphics.Path; +import android.graphics.PathMeasure; +import android.graphics.Path_Delegate; +import android.graphics.Rect; +import android.graphics.Region.Op; +import android.util.ArrayMap; +import android.util.AttributeSet; +import android.util.Log; +import android.util.MathUtils; +import android.util.PathParser_Delegate; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.function.Consumer; + +import static android.graphics.Canvas.CLIP_SAVE_FLAG; +import static android.graphics.Canvas.MATRIX_SAVE_FLAG; +import static android.graphics.Paint.Cap.BUTT; +import static android.graphics.Paint.Cap.ROUND; +import static android.graphics.Paint.Cap.SQUARE; +import static android.graphics.Paint.Join.BEVEL; +import static android.graphics.Paint.Join.MITER; +import static android.graphics.Paint.Style; + +/** + * Delegate used to provide new implementation of a select few methods of {@link VectorDrawable} + * <p> + * Through the layoutlib_create tool, the original methods of VectorDrawable have been replaced by + * calls to methods of the same name in this delegate class. + */ +@SuppressWarnings("unused") +public class VectorDrawable_Delegate { + private static final String LOGTAG = VectorDrawable_Delegate.class.getSimpleName(); + private static final boolean DBG_VECTOR_DRAWABLE = false; + + private static final DelegateManager<VNativeObject> sPathManager = + new DelegateManager<>(VNativeObject.class); + + /** + * Obtains styled attributes from the theme, if available, or unstyled resources if the theme is + * null. + */ + private static TypedArray obtainAttributes( + Resources res, Theme theme, AttributeSet set, int[] attrs) { + if (theme == null) { + return res.obtainAttributes(set, attrs); + } + return theme.obtainStyledAttributes(set, attrs, 0, 0); + } + + private static int applyAlpha(int color, float alpha) { + int alphaBytes = Color.alpha(color); + color &= 0x00FFFFFF; + color |= ((int) (alphaBytes * alpha)) << 24; + return color; + } + + @LayoutlibDelegate + static long nCreateTree(long rootGroupPtr) { + VGroup_Delegate rootGroup = VNativeObject.getDelegate(rootGroupPtr); + return sPathManager.addNewDelegate(new VPathRenderer_Delegate(rootGroup)); + } + + @LayoutlibDelegate + static void nSetRendererViewportSize(long rendererPtr, float viewportWidth, + float viewportHeight) { + VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr); + nativePathRenderer.mViewportWidth = viewportWidth; + nativePathRenderer.mViewportHeight = viewportHeight; + } + + @LayoutlibDelegate + static boolean nSetRootAlpha(long rendererPtr, float alpha) { + VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr); + nativePathRenderer.setRootAlpha(alpha); + + return true; + } + + @LayoutlibDelegate + static float nGetRootAlpha(long rendererPtr) { + VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr); + + return nativePathRenderer.getRootAlpha(); + } + + @LayoutlibDelegate + static void nSetAllowCaching(long rendererPtr, boolean allowCaching) { + // ignored + } + + @LayoutlibDelegate + static int nDraw(long rendererPtr, long canvasWrapperPtr, + 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); + + if (needsMirroring) { + Canvas_Delegate.native_translate(canvasWrapperPtr, bounds.width(), 0); + Canvas_Delegate.native_scale(canvasWrapperPtr, -1.0f, 1.0f); + } + + // At this point, canvas has been translated to the right position. + // And we use this bound for the destination rect for the drawBitmap, so + // we offset to (0, 0); + bounds.offsetTo(0, 0); + nativePathRenderer.draw(canvasWrapperPtr, colorFilterPtr, bounds.width(), bounds.height()); + + Canvas_Delegate.native_restore(canvasWrapperPtr, true); + + return bounds.width() * bounds.height(); + } + + @LayoutlibDelegate + static long nCreateFullPath() { + return sPathManager.addNewDelegate(new VFullPath_Delegate()); + } + + @LayoutlibDelegate + static long nCreateFullPath(long nativeFullPathPtr) { + VFullPath_Delegate original = VNativeObject.getDelegate(nativeFullPathPtr); + + return sPathManager.addNewDelegate(new VFullPath_Delegate(original)); + } + + @LayoutlibDelegate + static boolean nGetFullPathProperties(long pathPtr, byte[] propertiesData, + int length) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + + ByteBuffer properties = ByteBuffer.wrap(propertiesData); + properties.order(ByteOrder.nativeOrder()); + + properties.putFloat(VFullPath_Delegate.STROKE_WIDTH_INDEX * 4, path.getStrokeWidth()); + properties.putInt(VFullPath_Delegate.STROKE_COLOR_INDEX * 4, path.getStrokeColor()); + properties.putFloat(VFullPath_Delegate.STROKE_ALPHA_INDEX * 4, path.getStrokeAlpha()); + properties.putInt(VFullPath_Delegate.FILL_COLOR_INDEX * 4, path.getFillColor()); + properties.putFloat(VFullPath_Delegate.FILL_ALPHA_INDEX * 4, path.getStrokeAlpha()); + properties.putFloat(VFullPath_Delegate.TRIM_PATH_START_INDEX * 4, path.getTrimPathStart()); + properties.putFloat(VFullPath_Delegate.TRIM_PATH_END_INDEX * 4, path.getTrimPathEnd()); + properties.putFloat(VFullPath_Delegate.TRIM_PATH_OFFSET_INDEX * 4, + path.getTrimPathOffset()); + properties.putInt(VFullPath_Delegate.STROKE_LINE_CAP_INDEX * 4, path.getStrokeLineCap()); + properties.putInt(VFullPath_Delegate.STROKE_LINE_JOIN_INDEX * 4, path.getStrokeLineJoin()); + properties.putFloat(VFullPath_Delegate.STROKE_MITER_LIMIT_INDEX * 4, + path.getStrokeMiterlimit()); + properties.putInt(VFullPath_Delegate.FILL_TYPE_INDEX * 4, path.getFillType()); + + return true; + } + + @LayoutlibDelegate + static void nUpdateFullPathProperties(long pathPtr, float strokeWidth, + int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart, + float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap, + int strokeLineJoin, int fillType) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + + path.setStrokeWidth(strokeWidth); + path.setStrokeColor(strokeColor); + path.setStrokeAlpha(strokeAlpha); + path.setFillColor(fillColor); + path.setFillAlpha(fillAlpha); + path.setTrimPathStart(trimPathStart); + path.setTrimPathEnd(trimPathEnd); + path.setTrimPathOffset(trimPathOffset); + path.setStrokeMiterlimit(strokeMiterLimit); + path.setStrokeLineCap(strokeLineCap); + path.setStrokeLineJoin(strokeLineJoin); + path.setFillType(fillType); + } + + @LayoutlibDelegate + static void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + + path.setFillGradient(fillGradientPtr); + } + + @LayoutlibDelegate + static void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + + path.setStrokeGradient(strokeGradientPtr); + } + + @LayoutlibDelegate + static long nCreateClipPath() { + return sPathManager.addNewDelegate(new VClipPath_Delegate()); + } + + @LayoutlibDelegate + static long nCreateClipPath(long clipPathPtr) { + VClipPath_Delegate original = VNativeObject.getDelegate(clipPathPtr); + return sPathManager.addNewDelegate(new VClipPath_Delegate(original)); + } + + @LayoutlibDelegate + static long nCreateGroup() { + return sPathManager.addNewDelegate(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>())); + } + + @LayoutlibDelegate + static void nSetName(long nodePtr, String name) { + VNativeObject group = VNativeObject.getDelegate(nodePtr); + group.setName(name); + } + + @LayoutlibDelegate + static boolean nGetGroupProperties(long groupPtr, float[] propertiesData, + int length) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + + FloatBuffer properties = FloatBuffer.wrap(propertiesData); + + properties.put(VGroup_Delegate.ROTATE_INDEX, group.getRotation()); + properties.put(VGroup_Delegate.PIVOT_X_INDEX, group.getPivotX()); + properties.put(VGroup_Delegate.PIVOT_Y_INDEX, group.getPivotY()); + properties.put(VGroup_Delegate.SCALE_X_INDEX, group.getScaleX()); + properties.put(VGroup_Delegate.SCALE_Y_INDEX, group.getScaleY()); + properties.put(VGroup_Delegate.TRANSLATE_X_INDEX, group.getTranslateX()); + properties.put(VGroup_Delegate.TRANSLATE_Y_INDEX, group.getTranslateY()); + + return true; + } + @LayoutlibDelegate + static void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX, + float pivotY, float scaleX, float scaleY, float translateX, float translateY) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + + group.setRotation(rotate); + group.setPivotX(pivotX); + group.setPivotY(pivotY); + group.setScaleX(scaleX); + group.setScaleY(scaleY); + group.setTranslateX(translateX); + group.setTranslateY(translateY); + } + + @LayoutlibDelegate + static void nAddChild(long groupPtr, long nodePtr) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + group.mChildren.add(VNativeObject.getDelegate(nodePtr)); + } + + @LayoutlibDelegate + static void nSetPathString(long pathPtr, String pathString, int length) { + VPath_Delegate path = VNativeObject.getDelegate(pathPtr); + path.setPathData(PathParser_Delegate.createNodesFromPathData(pathString)); + } + + /** + * The setters and getters below for paths and groups are here temporarily, and will be removed + * once the animation in AVD is replaced with RenderNodeAnimator, in which case the animation + * will modify these properties in native. By then no JNI hopping would be necessary for VD + * during animation, and these setters and getters will be obsolete. + */ + // Setters and getters during animation. + @LayoutlibDelegate + static float nGetRotation(long groupPtr) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + return group.getRotation(); + } + + @LayoutlibDelegate + static void nSetRotation(long groupPtr, float rotation) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + group.setRotation(rotation); + } + + @LayoutlibDelegate + static float nGetPivotX(long groupPtr) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + return group.getPivotX(); + } + + @LayoutlibDelegate + static void nSetPivotX(long groupPtr, float pivotX) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + group.setPivotX(pivotX); + } + + @LayoutlibDelegate + static float nGetPivotY(long groupPtr) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + return group.getPivotY(); + } + + @LayoutlibDelegate + static void nSetPivotY(long groupPtr, float pivotY) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + group.setPivotY(pivotY); + } + + @LayoutlibDelegate + static float nGetScaleX(long groupPtr) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + return group.getScaleX(); + } + + @LayoutlibDelegate + static void nSetScaleX(long groupPtr, float scaleX) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + group.setScaleX(scaleX); + } + + @LayoutlibDelegate + static float nGetScaleY(long groupPtr) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + return group.getScaleY(); + } + + @LayoutlibDelegate + static void nSetScaleY(long groupPtr, float scaleY) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + group.setScaleY(scaleY); + } + + @LayoutlibDelegate + static float nGetTranslateX(long groupPtr) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + return group.getTranslateX(); + } + + @LayoutlibDelegate + static void nSetTranslateX(long groupPtr, float translateX) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + group.setTranslateX(translateX); + } + + @LayoutlibDelegate + static float nGetTranslateY(long groupPtr) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + return group.getTranslateY(); + } + + @LayoutlibDelegate + static void nSetTranslateY(long groupPtr, float translateY) { + VGroup_Delegate group = VNativeObject.getDelegate(groupPtr); + group.setTranslateY(translateY); + } + + @LayoutlibDelegate + static void nSetPathData(long pathPtr, long pathDataPtr) { + VPath_Delegate path = VNativeObject.getDelegate(pathPtr); + path.setPathData(PathParser_Delegate.getDelegate(pathDataPtr).getPathDataNodes()); + } + + @LayoutlibDelegate + static float nGetStrokeWidth(long pathPtr) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + return path.getStrokeWidth(); + } + + @LayoutlibDelegate + static void nSetStrokeWidth(long pathPtr, float width) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + path.setStrokeWidth(width); + } + + @LayoutlibDelegate + static int nGetStrokeColor(long pathPtr) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + return path.getStrokeColor(); + } + + @LayoutlibDelegate + static void nSetStrokeColor(long pathPtr, int strokeColor) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + path.setStrokeColor(strokeColor); + } + + @LayoutlibDelegate + static float nGetStrokeAlpha(long pathPtr) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + return path.getStrokeAlpha(); + } + + @LayoutlibDelegate + static void nSetStrokeAlpha(long pathPtr, float alpha) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + path.setStrokeAlpha(alpha); + } + + @LayoutlibDelegate + static int nGetFillColor(long pathPtr) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + return path.getFillColor(); + } + + @LayoutlibDelegate + static void nSetFillColor(long pathPtr, int fillColor) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + path.setFillColor(fillColor); + } + + @LayoutlibDelegate + static float nGetFillAlpha(long pathPtr) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + return path.getFillAlpha(); + } + + @LayoutlibDelegate + static void nSetFillAlpha(long pathPtr, float fillAlpha) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + path.setFillAlpha(fillAlpha); + } + + @LayoutlibDelegate + static float nGetTrimPathStart(long pathPtr) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + return path.getTrimPathStart(); + } + + @LayoutlibDelegate + static void nSetTrimPathStart(long pathPtr, float trimPathStart) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + path.setTrimPathStart(trimPathStart); + } + + @LayoutlibDelegate + static float nGetTrimPathEnd(long pathPtr) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + return path.getTrimPathEnd(); + } + + @LayoutlibDelegate + static void nSetTrimPathEnd(long pathPtr, float trimPathEnd) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + path.setTrimPathEnd(trimPathEnd); + } + + @LayoutlibDelegate + static float nGetTrimPathOffset(long pathPtr) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + return path.getTrimPathOffset(); + } + + @LayoutlibDelegate + static void nSetTrimPathOffset(long pathPtr, float trimPathOffset) { + VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr); + path.setTrimPathOffset(trimPathOffset); + } + + /** + * Base class for all the internal Delegates that does two functions: + * <ol> + * <li>Serves as base class to store all the delegates in one {@link DelegateManager} + * <li>Provides setName for all the classes. {@link VPathRenderer_Delegate} does actually + * not need it + * </ol> + */ + interface VNativeObject { + @NonNull + static <T> T getDelegate(long nativePtr) { + //noinspection unchecked + T vNativeObject = (T) sPathManager.getDelegate(nativePtr); + + assert vNativeObject != null; + return vNativeObject; + } + + void setName(String name); + } + + private static class VClipPath_Delegate extends VPath_Delegate { + private VClipPath_Delegate() { + // Empty constructor. + } + + private VClipPath_Delegate(VClipPath_Delegate copy) { + super(copy); + } + + @Override + public boolean isClipPath() { + return true; + } + } + + static class VFullPath_Delegate extends VPath_Delegate { + // These constants need to be kept in sync with their values in VectorDrawable.VFullPath + private static final int STROKE_WIDTH_INDEX = 0; + private static final int STROKE_COLOR_INDEX = 1; + private static final int STROKE_ALPHA_INDEX = 2; + private static final int FILL_COLOR_INDEX = 3; + private static final int FILL_ALPHA_INDEX = 4; + private static final int TRIM_PATH_START_INDEX = 5; + private static final int TRIM_PATH_END_INDEX = 6; + private static final int TRIM_PATH_OFFSET_INDEX = 7; + private static final int STROKE_LINE_CAP_INDEX = 8; + private static final int STROKE_LINE_JOIN_INDEX = 9; + private static final int STROKE_MITER_LIMIT_INDEX = 10; + private static final int FILL_TYPE_INDEX = 11; + + private static final int LINECAP_BUTT = 0; + private static final int LINECAP_ROUND = 1; + private static final int LINECAP_SQUARE = 2; + + private static final int LINEJOIN_MITER = 0; + private static final int LINEJOIN_ROUND = 1; + private static final int LINEJOIN_BEVEL = 2; + + @NonNull + public Consumer<Float> getFloatPropertySetter(int propertyIdx) { + switch (propertyIdx) { + case STROKE_ALPHA_INDEX: + return this::setStrokeAlpha; + case FILL_ALPHA_INDEX: + return this::setFillAlpha; + case TRIM_PATH_START_INDEX: + return this::setTrimPathStart; + case TRIM_PATH_END_INDEX: + return this::setTrimPathEnd; + case TRIM_PATH_OFFSET_INDEX: + return this::setTrimPathOffset; + } + + throw new IllegalArgumentException("Invalid VFullPath_Delegate property index " + + propertyIdx); + } + + @NonNull + public Consumer<Integer> getIntPropertySetter(int propertyIdx) { + switch (propertyIdx) { + case STROKE_COLOR_INDEX: + return this::setStrokeColor; + case FILL_COLOR_INDEX: + return this::setFillColor; + } + + throw new IllegalArgumentException("Invalid VFullPath_Delegate property index " + + propertyIdx); + } + + ///////////////////////////////////////////////////// + // Variables below need to be copied (deep copy if applicable) for mutation. + + int mStrokeColor = Color.TRANSPARENT; + float mStrokeWidth = 0; + + int mFillColor = Color.TRANSPARENT; + long mStrokeGradient = 0; + long mFillGradient = 0; + float mStrokeAlpha = 1.0f; + float mFillAlpha = 1.0f; + float mTrimPathStart = 0; + float mTrimPathEnd = 1; + float mTrimPathOffset = 0; + + Cap mStrokeLineCap = BUTT; + Join mStrokeLineJoin = MITER; + float mStrokeMiterlimit = 4; + + int mFillType = 0; // WINDING(0) is the default value. See Path.FillType + + private VFullPath_Delegate() { + // Empty constructor. + } + + private VFullPath_Delegate(VFullPath_Delegate copy) { + super(copy); + + mStrokeColor = copy.mStrokeColor; + mStrokeWidth = copy.mStrokeWidth; + mStrokeAlpha = copy.mStrokeAlpha; + mFillColor = copy.mFillColor; + mFillAlpha = copy.mFillAlpha; + mTrimPathStart = copy.mTrimPathStart; + mTrimPathEnd = copy.mTrimPathEnd; + mTrimPathOffset = copy.mTrimPathOffset; + + mStrokeLineCap = copy.mStrokeLineCap; + mStrokeLineJoin = copy.mStrokeLineJoin; + mStrokeMiterlimit = copy.mStrokeMiterlimit; + + mStrokeGradient = copy.mStrokeGradient; + mFillGradient = copy.mFillGradient; + mFillType = copy.mFillType; + } + + private int getStrokeLineCap() { + switch (mStrokeLineCap) { + case BUTT: + return LINECAP_BUTT; + case ROUND: + return LINECAP_ROUND; + case SQUARE: + return LINECAP_SQUARE; + default: + assert false; + } + + return -1; + } + + private void setStrokeLineCap(int cap) { + switch (cap) { + case LINECAP_BUTT: + mStrokeLineCap = BUTT; + break; + case LINECAP_ROUND: + mStrokeLineCap = ROUND; + break; + case LINECAP_SQUARE: + mStrokeLineCap = SQUARE; + break; + default: + assert false; + } + } + + private int getStrokeLineJoin() { + switch (mStrokeLineJoin) { + case MITER: + return LINEJOIN_MITER; + case ROUND: + return LINEJOIN_ROUND; + case BEVEL: + return LINEJOIN_BEVEL; + default: + assert false; + } + + return -1; + } + + private void setStrokeLineJoin(int join) { + switch (join) { + case LINEJOIN_BEVEL: + mStrokeLineJoin = BEVEL; + break; + case LINEJOIN_MITER: + mStrokeLineJoin = MITER; + break; + case LINEJOIN_ROUND: + mStrokeLineJoin = Join.ROUND; + break; + default: + assert false; + } + } + + private int getStrokeColor() { + return mStrokeColor; + } + + private void setStrokeColor(int strokeColor) { + mStrokeColor = strokeColor; + } + + private float getStrokeWidth() { + return mStrokeWidth; + } + + private void setStrokeWidth(float strokeWidth) { + mStrokeWidth = strokeWidth; + } + + private float getStrokeAlpha() { + return mStrokeAlpha; + } + + private void setStrokeAlpha(float strokeAlpha) { + mStrokeAlpha = strokeAlpha; + } + + private int getFillColor() { + return mFillColor; + } + + private void setFillColor(int fillColor) { + mFillColor = fillColor; + } + + private float getFillAlpha() { + return mFillAlpha; + } + + private void setFillAlpha(float fillAlpha) { + mFillAlpha = fillAlpha; + } + + private float getTrimPathStart() { + return mTrimPathStart; + } + + private void setTrimPathStart(float trimPathStart) { + mTrimPathStart = trimPathStart; + } + + private float getTrimPathEnd() { + return mTrimPathEnd; + } + + private void setTrimPathEnd(float trimPathEnd) { + mTrimPathEnd = trimPathEnd; + } + + private float getTrimPathOffset() { + return mTrimPathOffset; + } + + private void setTrimPathOffset(float trimPathOffset) { + mTrimPathOffset = trimPathOffset; + } + + private void setStrokeMiterlimit(float limit) { + mStrokeMiterlimit = limit; + } + + private float getStrokeMiterlimit() { + return mStrokeMiterlimit; + } + + private void setStrokeGradient(long gradientPtr) { + mStrokeGradient = gradientPtr; + } + + private void setFillGradient(long gradientPtr) { + mFillGradient = gradientPtr; + } + + private void setFillType(int fillType) { + mFillType = fillType; + } + + private int getFillType() { + return mFillType; + } + } + + static class VGroup_Delegate implements 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; + private static final int PIVOT_Y_INDEX = 2; + private static final int SCALE_X_INDEX = 3; + private static final int SCALE_Y_INDEX = 4; + private static final int TRANSLATE_X_INDEX = 5; + private static final int TRANSLATE_Y_INDEX = 6; + + public Consumer<Float> getPropertySetter(int propertyIdx) { + switch (propertyIdx) { + case ROTATE_INDEX: + return this::setRotation; + case PIVOT_X_INDEX: + return this::setPivotX; + case PIVOT_Y_INDEX: + return this::setPivotY; + case SCALE_X_INDEX: + return this::setScaleX; + case SCALE_Y_INDEX: + return this::setScaleY; + case TRANSLATE_X_INDEX: + return this::setTranslateX; + case TRANSLATE_Y_INDEX: + return this::setTranslateY; + } + + throw new IllegalArgumentException("Invalid VGroup_Delegate property index " + + propertyIdx); + } + + ///////////////////////////////////////////////////// + // Variables below need to be copied (deep copy if applicable) for mutation. + final ArrayList<Object> mChildren = new ArrayList<>(); + // mStackedMatrix is only used temporarily when drawing, it combines all + // the parents' local matrices with the current one. + private final Matrix mStackedMatrix = new Matrix(); + // mLocalMatrix is updated based on the update of transformation information, + // either parsed from the XML or by animation. + private final Matrix mLocalMatrix = new Matrix(); + private float mRotate = 0; + private float mPivotX = 0; + private float mPivotY = 0; + private float mScaleX = 1; + private float mScaleY = 1; + private float mTranslateX = 0; + private float mTranslateY = 0; + private int mChangingConfigurations; + private String mGroupName = null; + + private VGroup_Delegate(VGroup_Delegate copy, ArrayMap<String, Object> targetsMap) { + mRotate = copy.mRotate; + mPivotX = copy.mPivotX; + mPivotY = copy.mPivotY; + mScaleX = copy.mScaleX; + mScaleY = copy.mScaleY; + mTranslateX = copy.mTranslateX; + mTranslateY = copy.mTranslateY; + mGroupName = copy.mGroupName; + mChangingConfigurations = copy.mChangingConfigurations; + if (mGroupName != null) { + targetsMap.put(mGroupName, this); + } + + mLocalMatrix.set(copy.mLocalMatrix); + + final ArrayList<Object> children = copy.mChildren; + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < children.size(); i++) { + Object copyChild = children.get(i); + if (copyChild instanceof VGroup_Delegate) { + VGroup_Delegate copyGroup = (VGroup_Delegate) copyChild; + mChildren.add(new VGroup_Delegate(copyGroup, targetsMap)); + } else { + VPath_Delegate newPath; + if (copyChild instanceof VFullPath_Delegate) { + newPath = new VFullPath_Delegate((VFullPath_Delegate) copyChild); + } else if (copyChild instanceof VClipPath_Delegate) { + newPath = new VClipPath_Delegate((VClipPath_Delegate) copyChild); + } else { + throw new IllegalStateException("Unknown object in the tree!"); + } + mChildren.add(newPath); + if (newPath.mPathName != null) { + targetsMap.put(newPath.mPathName, newPath); + } + } + } + } + + private VGroup_Delegate() { + } + + private void updateLocalMatrix() { + // The order we apply is the same as the + // RenderNode.cpp::applyViewPropertyTransforms(). + mLocalMatrix.reset(); + mLocalMatrix.postTranslate(-mPivotX, -mPivotY); + mLocalMatrix.postScale(mScaleX, mScaleY); + mLocalMatrix.postRotate(mRotate, 0, 0); + mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY); + } + + /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ + private float getRotation() { + return mRotate; + } + + private void setRotation(float rotation) { + if (rotation != mRotate) { + mRotate = rotation; + updateLocalMatrix(); + } + } + + private float getPivotX() { + return mPivotX; + } + + private void setPivotX(float pivotX) { + if (pivotX != mPivotX) { + mPivotX = pivotX; + updateLocalMatrix(); + } + } + + private float getPivotY() { + return mPivotY; + } + + private void setPivotY(float pivotY) { + if (pivotY != mPivotY) { + mPivotY = pivotY; + updateLocalMatrix(); + } + } + + private float getScaleX() { + return mScaleX; + } + + private void setScaleX(float scaleX) { + if (scaleX != mScaleX) { + mScaleX = scaleX; + updateLocalMatrix(); + } + } + + private float getScaleY() { + return mScaleY; + } + + private void setScaleY(float scaleY) { + if (scaleY != mScaleY) { + mScaleY = scaleY; + updateLocalMatrix(); + } + } + + private float getTranslateX() { + return mTranslateX; + } + + private void setTranslateX(float translateX) { + if (translateX != mTranslateX) { + mTranslateX = translateX; + updateLocalMatrix(); + } + } + + private float getTranslateY() { + return mTranslateY; + } + + private void setTranslateY(float translateY) { + if (translateY != mTranslateY) { + mTranslateY = translateY; + updateLocalMatrix(); + } + } + + @Override + public void setName(String name) { + mGroupName = name; + } + } + + public static class VPath_Delegate implements VNativeObject { + protected PathParser_Delegate.PathDataNode[] mNodes = null; + String mPathName; + int mChangingConfigurations; + + public VPath_Delegate() { + // Empty constructor. + } + + public VPath_Delegate(VPath_Delegate copy) { + mPathName = copy.mPathName; + mChangingConfigurations = copy.mChangingConfigurations; + mNodes = PathParser_Delegate.deepCopyNodes(copy.mNodes); + } + + public void toPath(Path path) { + path.reset(); + if (mNodes != null) { + PathParser_Delegate.PathDataNode.nodesToPath(mNodes, + Path_Delegate.getDelegate(path.mNativePath)); + } + } + + @Override + public void setName(String name) { + mPathName = name; + } + + public boolean isClipPath() { + return false; + } + + private void setPathData(PathParser_Delegate.PathDataNode[] nodes) { + if (!PathParser_Delegate.canMorph(mNodes, nodes)) { + // This should not happen in the middle of animation. + mNodes = PathParser_Delegate.deepCopyNodes(nodes); + } else { + PathParser_Delegate.updateNodes(mNodes, nodes); + } + } + } + + static class VPathRenderer_Delegate implements 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 + * no children. + * One example can be: + * Root Group + * / | \ + * Group Path Group + * / \ | + * Path Path Path + * + */ + // Variables that only used temporarily inside the draw() call, so there + // is no need for deep copying. + private final Path mPath; + private final Path mRenderPath; + private final Matrix mFinalPathMatrix = new Matrix(); + private final VGroup_Delegate mRootGroup; + private float mViewportWidth = 0; + private float mViewportHeight = 0; + private float mRootAlpha = 1.0f; + private Paint mStrokePaint; + private Paint mFillPaint; + private PathMeasure mPathMeasure; + + private VPathRenderer_Delegate(VGroup_Delegate rootGroup) { + mRootGroup = rootGroup; + mPath = new Path(); + mRenderPath = new Path(); + } + + private float getRootAlpha() { + return mRootAlpha; + } + + void setRootAlpha(float alpha) { + mRootAlpha = alpha; + } + + private void drawGroupTree(VGroup_Delegate currentGroup, Matrix currentMatrix, + long canvasPtr, int w, int h, long filterPtr) { + // Calculate current group's matrix by preConcat the parent's and + // and the current one on the top of the stack. + // Basically the Mfinal = Mviewport * M0 * M1 * M2; + // Mi the local matrix at level i of the group tree. + currentGroup.mStackedMatrix.set(currentMatrix); + 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); + // 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); + if (child instanceof VGroup_Delegate) { + VGroup_Delegate childGroup = (VGroup_Delegate) child; + drawGroupTree(childGroup, currentGroup.mStackedMatrix, + canvasPtr, w, h, filterPtr); + } else if (child instanceof VPath_Delegate) { + VPath_Delegate childPath = (VPath_Delegate) child; + drawPath(currentGroup, childPath, canvasPtr, w, h, filterPtr); + } + } + Canvas_Delegate.native_restore(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); + } + + private void drawPath(VGroup_Delegate VGroup, VPath_Delegate VPath, long canvasPtr, + int w, + int h, + long filterPtr) { + final float scaleX = w / mViewportWidth; + final float scaleY = h / mViewportHeight; + final float minScale = Math.min(scaleX, scaleY); + final Matrix groupStackedMatrix = VGroup.mStackedMatrix; + + mFinalPathMatrix.set(groupStackedMatrix); + mFinalPathMatrix.postScale(scaleX, scaleY); + + final float matrixScale = getMatrixScale(groupStackedMatrix); + if (matrixScale == 0) { + // When either x or y is scaled to 0, we don't need to draw anything. + return; + } + VPath.toPath(mPath); + final Path path = mPath; + + mRenderPath.reset(); + + if (VPath.isClipPath()) { + mRenderPath.addPath(path, mFinalPathMatrix); + Canvas_Delegate.native_clipPath(canvasPtr, mRenderPath.mNativePath, Op + .INTERSECT.nativeInt); + } else { + VFullPath_Delegate fullPath = (VFullPath_Delegate) VPath; + if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) { + float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f; + float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f; + + if (mPathMeasure == null) { + mPathMeasure = new PathMeasure(); + } + mPathMeasure.setPath(mPath, false); + + float len = mPathMeasure.getLength(); + start = start * len; + end = end * len; + path.reset(); + if (start > end) { + mPathMeasure.getSegment(start, len, path, true); + mPathMeasure.getSegment(0f, end, path, true); + } else { + mPathMeasure.getSegment(start, end, path, true); + } + path.rLineTo(0, 0); // fix bug in measure + } + mRenderPath.addPath(path, mFinalPathMatrix); + + if (fullPath.mFillColor != Color.TRANSPARENT) { + if (mFillPaint == null) { + mFillPaint = new Paint(); + mFillPaint.setStyle(Style.FILL); + mFillPaint.setAntiAlias(true); + } + + final Paint fillPaint = mFillPaint; + fillPaint.setColor(applyAlpha(fullPath.mFillColor, fullPath.mFillAlpha)); + Paint_Delegate fillPaintDelegate = Paint_Delegate.getDelegate(fillPaint + .getNativeInstance()); + // mFillPaint can not be null at this point so we will have a delegate + assert fillPaintDelegate != null; + fillPaintDelegate.setColorFilter(filterPtr); + fillPaintDelegate.setShader(fullPath.mFillGradient); + Path_Delegate.native_setFillType(mRenderPath.mNativePath, fullPath.mFillType); + Canvas_Delegate.native_drawPath(canvasPtr, mRenderPath.mNativePath, fillPaint + .getNativeInstance()); + } + + if (fullPath.mStrokeColor != Color.TRANSPARENT) { + if (mStrokePaint == null) { + mStrokePaint = new Paint(); + mStrokePaint.setStyle(Style.STROKE); + mStrokePaint.setAntiAlias(true); + } + + final Paint strokePaint = mStrokePaint; + if (fullPath.mStrokeLineJoin != null) { + strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin); + } + + if (fullPath.mStrokeLineCap != null) { + strokePaint.setStrokeCap(fullPath.mStrokeLineCap); + } + + strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit); + strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha)); + Paint_Delegate strokePaintDelegate = Paint_Delegate.getDelegate(strokePaint + .getNativeInstance()); + // mStrokePaint can not be null at this point so we will have a delegate + assert strokePaintDelegate != null; + strokePaintDelegate.setColorFilter(filterPtr); + final float finalStrokeScale = minScale * matrixScale; + strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale); + strokePaintDelegate.setShader(fullPath.mStrokeGradient); + Canvas_Delegate.native_drawPath(canvasPtr, mRenderPath.mNativePath, strokePaint + .getNativeInstance()); + } + } + } + + private float getMatrixScale(Matrix groupStackedMatrix) { + // Given unit vectors A = (0, 1) and B = (1, 0). + // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'. + // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)), + // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|); + // If max (|A'|, |B'|) = 0, that means either x or y has a scale of 0. + // + // For non-skew case, which is most of the cases, matrix scale is computing exactly the + // scale on x and y axis, and take the minimal of these two. + // For skew case, an unit square will mapped to a parallelogram. And this function will + // return the minimal height of the 2 bases. + float[] unitVectors = new float[]{0, 1, 1, 0}; + groupStackedMatrix.mapVectors(unitVectors); + float scaleX = MathUtils.mag(unitVectors[0], unitVectors[1]); + float scaleY = MathUtils.mag(unitVectors[2], unitVectors[3]); + float crossProduct = MathUtils.cross(unitVectors[0], unitVectors[1], + unitVectors[2], unitVectors[3]); + float maxScale = MathUtils.max(scaleX, scaleY); + + float matrixScale = 0; + if (maxScale > 0) { + matrixScale = MathUtils.abs(crossProduct) / maxScale; + } + if (DBG_VECTOR_DRAWABLE) { + Log.d(LOGTAG, "Scale x " + scaleX + " y " + scaleY + " final " + matrixScale); + } + return matrixScale; + } + + @Override + public void setName(String name) { + } + } +} diff --git a/tools/layoutlib/bridge/src/android/os/ServiceManager.java b/tools/layoutlib/bridge/src/android/os/ServiceManager.java index 6a68ee29c9cb..549074d15757 100644 --- a/tools/layoutlib/bridge/src/android/os/ServiceManager.java +++ b/tools/layoutlib/bridge/src/android/os/ServiceManager.java @@ -51,8 +51,10 @@ public final class ServiceManager { /** * Return a list of all currently running services. + * @return an array of all currently running services, or <code>null</code> in + * case of an exception */ - public static String[] listServices() throws RemoteException { + public static String[] listServices() { // actual implementation returns null sometimes, so it's ok // to return null instead of an empty list. return null; diff --git a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java index 44ce7311a95c..fcd63ea993c8 100644 --- a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java +++ b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java @@ -25,7 +25,7 @@ import java.nio.ByteBuffer; /** * Delegate that overrides implementation for certain methods in {@link android.text.Hyphenator} * <p/> - * Through the layoutlib_create tool, selected methods of StaticLayout have been replaced + * Through the layoutlib_create tool, selected methods of Hyphenator have been replaced * by calls to methods of the same name in this delegate class. */ public class Hyphenator_Delegate { @@ -39,7 +39,8 @@ public class Hyphenator_Delegate { return null; } - /*package*/ static long loadHyphenator(ByteBuffer buf, int offset) { + /*package*/ @SuppressWarnings("UnusedParameters") // TODO implement this. + static long loadHyphenator(ByteBuffer buffer, int offset) { return sDelegateManager.addNewDelegate(new Hyphenator_Delegate()); } } diff --git a/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java b/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java new file mode 100644 index 000000000000..6d3bb4ca9115 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java @@ -0,0 +1,843 @@ +/* + * 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 android.util; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.annotation.NonNull; +import android.graphics.Path_Delegate; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Delegate that provides implementation for native methods in {@link android.util.PathParser} + * <p/> + * Through the layoutlib_create tool, selected methods of PathParser have been replaced by calls to + * methods of the same name in this delegate class. + * + * Most of the code has been taken from the implementation in + * {@code tools/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/PathParser.java} + * revision be6fe89a3b686db5a75e7e692a148699973957f3 + */ +public class PathParser_Delegate { + + private static final Logger LOGGER = Logger.getLogger("PathParser"); + + // ---- Builder delegate manager ---- + private static final DelegateManager<PathParser_Delegate> sManager = + new DelegateManager<PathParser_Delegate>(PathParser_Delegate.class); + + // ---- delegate data ---- + @NonNull + private PathDataNode[] mPathDataNodes; + + public static PathParser_Delegate getDelegate(long nativePtr) { + return sManager.getDelegate(nativePtr); + } + + private PathParser_Delegate(@NonNull PathDataNode[] nodes) { + mPathDataNodes = nodes; + } + + public PathDataNode[] getPathDataNodes() { + return mPathDataNodes; + } + + @LayoutlibDelegate + /*package*/ static void nParseStringForPath(long pathPtr, @NonNull String pathString, int + stringLength) { + Path_Delegate path_delegate = Path_Delegate.getDelegate(pathPtr); + if (path_delegate == null) { + return; + } + assert pathString.length() == stringLength; + PathDataNode.nodesToPath(createNodesFromPathData(pathString), path_delegate); + } + + @LayoutlibDelegate + /*package*/ static void nCreatePathFromPathData(long outPathPtr, long pathData) { + Path_Delegate path_delegate = Path_Delegate.getDelegate(outPathPtr); + PathParser_Delegate source = sManager.getDelegate(outPathPtr); + if (source == null || path_delegate == null) { + return; + } + PathDataNode.nodesToPath(source.mPathDataNodes, path_delegate); + } + + @LayoutlibDelegate + /*package*/ static long nCreateEmptyPathData() { + PathParser_Delegate newDelegate = new PathParser_Delegate(new PathDataNode[0]); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static long nCreatePathData(long nativePtr) { + PathParser_Delegate source = sManager.getDelegate(nativePtr); + if (source == null) { + return 0; + } + PathParser_Delegate dest = new PathParser_Delegate(deepCopyNodes(source.mPathDataNodes)); + return sManager.addNewDelegate(dest); + } + + @LayoutlibDelegate + /*package*/ static long nCreatePathDataFromString(@NonNull String pathString, + int stringLength) { + assert pathString.length() == stringLength : "Inconsistent path string length."; + PathDataNode[] nodes = createNodesFromPathData(pathString); + PathParser_Delegate delegate = new PathParser_Delegate(nodes); + return sManager.addNewDelegate(delegate); + + } + + @LayoutlibDelegate + /*package*/ static boolean nInterpolatePathData(long outDataPtr, long fromDataPtr, + long toDataPtr, float fraction) { + PathParser_Delegate out = sManager.getDelegate(outDataPtr); + PathParser_Delegate from = sManager.getDelegate(fromDataPtr); + PathParser_Delegate to = sManager.getDelegate(toDataPtr); + if (out == null || from == null || to == null) { + return false; + } + int length = from.mPathDataNodes.length; + if (length != to.mPathDataNodes.length) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Cannot interpolate path data with different lengths (from " + length + " to " + + to.mPathDataNodes.length + ").", null); + return false; + } + if (out.mPathDataNodes.length != length) { + out.mPathDataNodes = new PathDataNode[length]; + } + for (int i = 0; i < length; i++) { + if (out.mPathDataNodes[i] == null) { + out.mPathDataNodes[i] = new PathDataNode(from.mPathDataNodes[i]); + } + out.mPathDataNodes[i].interpolatePathDataNode(from.mPathDataNodes[i], + to.mPathDataNodes[i], fraction); + } + return true; + } + + @LayoutlibDelegate + /*package*/ static void nFinalize(long nativePtr) { + sManager.removeJavaReferenceFor(nativePtr); + } + + @LayoutlibDelegate + /*package*/ static boolean nCanMorph(long fromDataPtr, long toDataPtr) { + PathParser_Delegate fromPath = PathParser_Delegate.getDelegate(fromDataPtr); + PathParser_Delegate toPath = PathParser_Delegate.getDelegate(toDataPtr); + if (fromPath == null || toPath == null || fromPath.getPathDataNodes() == null || toPath + .getPathDataNodes() == null) { + return true; + } + return PathParser_Delegate.canMorph(fromPath.getPathDataNodes(), toPath.getPathDataNodes()); + } + + @LayoutlibDelegate + /*package*/ static void nSetPathData(long outDataPtr, long fromDataPtr) { + PathParser_Delegate out = sManager.getDelegate(outDataPtr); + PathParser_Delegate from = sManager.getDelegate(fromDataPtr); + if (from == null || out == null) { + return; + } + out.mPathDataNodes = deepCopyNodes(from.mPathDataNodes); + } + + /** + * @param pathData The string representing a path, the same as "d" string in svg file. + * + * @return an array of the PathDataNode. + */ + @NonNull + public static PathDataNode[] createNodesFromPathData(@NonNull String pathData) { + int start = 0; + int end = 1; + + ArrayList<PathDataNode> list = new ArrayList<PathDataNode>(); + while (end < pathData.length()) { + end = nextStart(pathData, end); + String s = pathData.substring(start, end).trim(); + if (s.length() > 0) { + float[] val = getFloats(s); + addNode(list, s.charAt(0), val); + } + + start = end; + end++; + } + if ((end - start) == 1 && start < pathData.length()) { + addNode(list, pathData.charAt(start), new float[0]); + } + return list.toArray(new PathDataNode[list.size()]); + } + + /** + * @param source The array of PathDataNode to be duplicated. + * + * @return a deep copy of the <code>source</code>. + */ + @NonNull + public static PathDataNode[] deepCopyNodes(@NonNull PathDataNode[] source) { + PathDataNode[] copy = new PathDataNode[source.length]; + for (int i = 0; i < source.length; i++) { + copy[i] = new PathDataNode(source[i]); + } + return copy; + } + + /** + * @param nodesFrom The source path represented in an array of PathDataNode + * @param nodesTo The target path represented in an array of PathDataNode + * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code> + */ + public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) { + if (nodesFrom == null || nodesTo == null) { + return false; + } + + if (nodesFrom.length != nodesTo.length) { + return false; + } + + for (int i = 0; i < nodesFrom.length; i ++) { + if (nodesFrom[i].mType != nodesTo[i].mType + || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) { + return false; + } + } + return true; + } + + /** + * Update the target's data to match the source. + * Before calling this, make sure canMorph(target, source) is true. + * + * @param target The target path represented in an array of PathDataNode + * @param source The source path represented in an array of PathDataNode + */ + public static void updateNodes(PathDataNode[] target, PathDataNode[] source) { + for (int i = 0; i < source.length; i ++) { + target[i].mType = source[i].mType; + for (int j = 0; j < source[i].mParams.length; j ++) { + target[i].mParams[j] = source[i].mParams[j]; + } + } + } + + private static int nextStart(@NonNull String s, int end) { + char c; + + while (end < s.length()) { + c = s.charAt(end); + // Note that 'e' or 'E' are not valid path commands, but could be + // used for floating point numbers' scientific notation. + // Therefore, when searching for next command, we should ignore 'e' + // and 'E'. + if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0)) + && c != 'e' && c != 'E') { + return end; + } + end++; + } + return end; + } + + /** + * Calculate the position of the next comma or space or negative sign + * + * @param s the string to search + * @param start the position to start searching + * @param result the result of the extraction, including the position of the the starting + * position of next number, whether it is ending with a '-'. + */ + private static void extract(@NonNull String s, int start, @NonNull ExtractFloatResult result) { + // Now looking for ' ', ',', '.' or '-' from the start. + int currentIndex = start; + boolean foundSeparator = false; + result.mEndWithNegOrDot = false; + boolean secondDot = false; + boolean isExponential = false; + for (; currentIndex < s.length(); currentIndex++) { + boolean isPrevExponential = isExponential; + isExponential = false; + char currentChar = s.charAt(currentIndex); + switch (currentChar) { + case ' ': + case ',': + foundSeparator = true; + break; + case '-': + // The negative sign following a 'e' or 'E' is not a separator. + if (currentIndex != start && !isPrevExponential) { + foundSeparator = true; + result.mEndWithNegOrDot = true; + } + break; + case '.': + if (!secondDot) { + secondDot = true; + } else { + // This is the second dot, and it is considered as a separator. + foundSeparator = true; + result.mEndWithNegOrDot = true; + } + break; + case 'e': + case 'E': + isExponential = true; + break; + } + if (foundSeparator) { + break; + } + } + // When there is nothing found, then we put the end position to the end + // of the string. + result.mEndPosition = currentIndex; + } + + /** + * Parse the floats in the string. This is an optimized version of + * parseFloat(s.split(",|\\s")); + * + * @param s the string containing a command and list of floats + * + * @return array of floats + */ + @NonNull + private static float[] getFloats(@NonNull String s) { + if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') { + return new float[0]; + } + try { + float[] results = new float[s.length()]; + int count = 0; + int startPosition = 1; + int endPosition; + + ExtractFloatResult result = new ExtractFloatResult(); + int totalLength = s.length(); + + // The startPosition should always be the first character of the + // current number, and endPosition is the character after the current + // number. + while (startPosition < totalLength) { + extract(s, startPosition, result); + endPosition = result.mEndPosition; + + if (startPosition < endPosition) { + results[count++] = Float.parseFloat( + s.substring(startPosition, endPosition)); + } + + if (result.mEndWithNegOrDot) { + // Keep the '-' or '.' sign with next number. + startPosition = endPosition; + } else { + startPosition = endPosition + 1; + } + } + return Arrays.copyOf(results, count); + } catch (NumberFormatException e) { + throw new RuntimeException("error in parsing \"" + s + "\"", e); + } + } + + + private static void addNode(@NonNull ArrayList<PathDataNode> list, char cmd, + @NonNull float[] val) { + list.add(new PathDataNode(cmd, val)); + } + + private static class ExtractFloatResult { + // We need to return the position of the next separator and whether the + // next float starts with a '-' or a '.'. + private int mEndPosition; + private boolean mEndWithNegOrDot; + } + + /** + * Each PathDataNode represents one command in the "d" attribute of the svg file. An array of + * PathDataNode can represent the whole "d" attribute. + */ + public static class PathDataNode { + private char mType; + @NonNull + private float[] mParams; + + private PathDataNode(char type, @NonNull float[] params) { + mType = type; + mParams = params; + } + + public char getType() { + return mType; + } + + @NonNull + public float[] getParams() { + return mParams; + } + + private PathDataNode(@NonNull PathDataNode n) { + mType = n.mType; + mParams = Arrays.copyOf(n.mParams, n.mParams.length); + } + + /** + * Convert an array of PathDataNode to Path. Reset the passed path as needed before + * calling this method. + * + * @param node The source array of PathDataNode. + * @param path The target Path object. + */ + public static void nodesToPath(@NonNull PathDataNode[] node, @NonNull Path_Delegate path) { + float[] current = new float[6]; + char previousCommand = 'm'; + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < node.length; i++) { + addCommand(path, current, previousCommand, node[i].mType, node[i].mParams); + previousCommand = node[i].mType; + } + } + + /** + * The current PathDataNode will be interpolated between the <code>nodeFrom</code> and + * <code>nodeTo</code> according to the <code>fraction</code>. + * + * @param nodeFrom The start value as a PathDataNode. + * @param nodeTo The end value as a PathDataNode + * @param fraction The fraction to interpolate. + */ + private void interpolatePathDataNode(@NonNull PathDataNode nodeFrom, + @NonNull PathDataNode nodeTo, float fraction) { + for (int i = 0; i < nodeFrom.mParams.length; i++) { + mParams[i] = nodeFrom.mParams[i] * (1 - fraction) + + nodeTo.mParams[i] * fraction; + } + } + + @SuppressWarnings("PointlessArithmeticExpression") + private static void addCommand(@NonNull Path_Delegate path, float[] current, + char previousCmd, char cmd, @NonNull float[] val) { + + int incr = 2; + float currentX = current[0]; + float currentY = current[1]; + float ctrlPointX = current[2]; + float ctrlPointY = current[3]; + float currentSegmentStartX = current[4]; + float currentSegmentStartY = current[5]; + float reflectiveCtrlPointX; + float reflectiveCtrlPointY; + + switch (cmd) { + case 'z': + case 'Z': + path.close(); + // Path is closed here, but we need to move the pen to the + // closed position. So we cache the segment's starting position, + // and restore it here. + currentX = currentSegmentStartX; + currentY = currentSegmentStartY; + ctrlPointX = currentSegmentStartX; + ctrlPointY = currentSegmentStartY; + path.moveTo(currentX, currentY); + break; + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + incr = 2; + break; + case 'h': + case 'H': + case 'v': + case 'V': + incr = 1; + break; + case 'c': + case 'C': + incr = 6; + break; + case 's': + case 'S': + case 'q': + case 'Q': + incr = 4; + break; + case 'a': + case 'A': + incr = 7; + break; + } + + for (int k = 0; k < val.length; k += incr) { + switch (cmd) { + case 'm': // moveto - Start a new sub-path (relative) + currentX += val[k + 0]; + currentY += val[k + 1]; + + if (k > 0) { + // According to the spec, if a moveto is followed by multiple + // pairs of coordinates, the subsequent pairs are treated as + // implicit lineto commands. + path.rLineTo(val[k + 0], val[k + 1]); + } else { + path.rMoveTo(val[k + 0], val[k + 1]); + currentSegmentStartX = currentX; + currentSegmentStartY = currentY; + } + break; + case 'M': // moveto - Start a new sub-path + currentX = val[k + 0]; + currentY = val[k + 1]; + + if (k > 0) { + // According to the spec, if a moveto is followed by multiple + // pairs of coordinates, the subsequent pairs are treated as + // implicit lineto commands. + path.lineTo(val[k + 0], val[k + 1]); + } else { + path.moveTo(val[k + 0], val[k + 1]); + currentSegmentStartX = currentX; + currentSegmentStartY = currentY; + } + break; + case 'l': // lineto - Draw a line from the current point (relative) + path.rLineTo(val[k + 0], val[k + 1]); + currentX += val[k + 0]; + currentY += val[k + 1]; + break; + case 'L': // lineto - Draw a line from the current point + path.lineTo(val[k + 0], val[k + 1]); + currentX = val[k + 0]; + currentY = val[k + 1]; + break; + case 'h': // horizontal lineto - Draws a horizontal line (relative) + path.rLineTo(val[k + 0], 0); + currentX += val[k + 0]; + break; + case 'H': // horizontal lineto - Draws a horizontal line + path.lineTo(val[k + 0], currentY); + currentX = val[k + 0]; + break; + case 'v': // vertical lineto - Draws a vertical line from the current point (r) + path.rLineTo(0, val[k + 0]); + currentY += val[k + 0]; + break; + case 'V': // vertical lineto - Draws a vertical line from the current point + path.lineTo(currentX, val[k + 0]); + currentY = val[k + 0]; + break; + case 'c': // curveto - Draws a cubic Bézier curve (relative) + path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], + val[k + 4], val[k + 5]); + + ctrlPointX = currentX + val[k + 2]; + ctrlPointY = currentY + val[k + 3]; + currentX += val[k + 4]; + currentY += val[k + 5]; + + break; + case 'C': // curveto - Draws a cubic Bézier curve + path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], + val[k + 4], val[k + 5]); + currentX = val[k + 4]; + currentY = val[k + 5]; + ctrlPointX = val[k + 2]; + ctrlPointY = val[k + 3]; + break; + case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp) + reflectiveCtrlPointX = 0; + reflectiveCtrlPointY = 0; + if (previousCmd == 'c' || previousCmd == 's' + || previousCmd == 'C' || previousCmd == 'S') { + reflectiveCtrlPointX = currentX - ctrlPointX; + reflectiveCtrlPointY = currentY - ctrlPointY; + } + path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1], + val[k + 2], val[k + 3]); + + ctrlPointX = currentX + val[k + 0]; + ctrlPointY = currentY + val[k + 1]; + currentX += val[k + 2]; + currentY += val[k + 3]; + break; + case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp) + reflectiveCtrlPointX = currentX; + reflectiveCtrlPointY = currentY; + if (previousCmd == 'c' || previousCmd == 's' + || previousCmd == 'C' || previousCmd == 'S') { + reflectiveCtrlPointX = 2 * currentX - ctrlPointX; + reflectiveCtrlPointY = 2 * currentY - ctrlPointY; + } + path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1], val[k + 2], val[k + 3]); + ctrlPointX = val[k + 0]; + ctrlPointY = val[k + 1]; + currentX = val[k + 2]; + currentY = val[k + 3]; + break; + case 'q': // Draws a quadratic Bézier (relative) + path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); + ctrlPointX = currentX + val[k + 0]; + ctrlPointY = currentY + val[k + 1]; + currentX += val[k + 2]; + currentY += val[k + 3]; + break; + case 'Q': // Draws a quadratic Bézier + path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); + ctrlPointX = val[k + 0]; + ctrlPointY = val[k + 1]; + currentX = val[k + 2]; + currentY = val[k + 3]; + break; + case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) + reflectiveCtrlPointX = 0; + reflectiveCtrlPointY = 0; + if (previousCmd == 'q' || previousCmd == 't' + || previousCmd == 'Q' || previousCmd == 'T') { + reflectiveCtrlPointX = currentX - ctrlPointX; + reflectiveCtrlPointY = currentY - ctrlPointY; + } + path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1]); + ctrlPointX = currentX + reflectiveCtrlPointX; + ctrlPointY = currentY + reflectiveCtrlPointY; + currentX += val[k + 0]; + currentY += val[k + 1]; + break; + case 'T': // Draws a quadratic Bézier curve (reflective control point) + reflectiveCtrlPointX = currentX; + reflectiveCtrlPointY = currentY; + if (previousCmd == 'q' || previousCmd == 't' + || previousCmd == 'Q' || previousCmd == 'T') { + reflectiveCtrlPointX = 2 * currentX - ctrlPointX; + reflectiveCtrlPointY = 2 * currentY - ctrlPointY; + } + path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1]); + ctrlPointX = reflectiveCtrlPointX; + ctrlPointY = reflectiveCtrlPointY; + currentX = val[k + 0]; + currentY = val[k + 1]; + break; + case 'a': // Draws an elliptical arc + // (rx ry x-axis-rotation large-arc-flag sweep-flag x y) + drawArc(path, + currentX, + currentY, + val[k + 5] + currentX, + val[k + 6] + currentY, + val[k + 0], + val[k + 1], + val[k + 2], + val[k + 3] != 0, + val[k + 4] != 0); + currentX += val[k + 5]; + currentY += val[k + 6]; + ctrlPointX = currentX; + ctrlPointY = currentY; + break; + case 'A': // Draws an elliptical arc + drawArc(path, + currentX, + currentY, + val[k + 5], + val[k + 6], + val[k + 0], + val[k + 1], + val[k + 2], + val[k + 3] != 0, + val[k + 4] != 0); + currentX = val[k + 5]; + currentY = val[k + 6]; + ctrlPointX = currentX; + ctrlPointY = currentY; + break; + } + previousCmd = cmd; + } + current[0] = currentX; + current[1] = currentY; + current[2] = ctrlPointX; + current[3] = ctrlPointY; + current[4] = currentSegmentStartX; + current[5] = currentSegmentStartY; + } + + private static void drawArc(@NonNull Path_Delegate p, float x0, float y0, float x1, + float y1, float a, float b, float theta, boolean isMoreThanHalf, + boolean isPositiveArc) { + + LOGGER.log(Level.FINE, "(" + x0 + "," + y0 + ")-(" + x1 + "," + y1 + + ") {" + a + " " + b + "}"); + /* Convert rotation angle from degrees to radians */ + double thetaD = theta * Math.PI / 180.0f; + /* Pre-compute rotation matrix entries */ + double cosTheta = Math.cos(thetaD); + double sinTheta = Math.sin(thetaD); + /* Transform (x0, y0) and (x1, y1) into unit space */ + /* using (inverse) rotation, followed by (inverse) scale */ + double x0p = (x0 * cosTheta + y0 * sinTheta) / a; + double y0p = (-x0 * sinTheta + y0 * cosTheta) / b; + double x1p = (x1 * cosTheta + y1 * sinTheta) / a; + double y1p = (-x1 * sinTheta + y1 * cosTheta) / b; + LOGGER.log(Level.FINE, "unit space (" + x0p + "," + y0p + ")-(" + x1p + + "," + y1p + ")"); + /* Compute differences and averages */ + double dx = x0p - x1p; + double dy = y0p - y1p; + double xm = (x0p + x1p) / 2; + double ym = (y0p + y1p) / 2; + /* Solve for intersecting unit circles */ + double dsq = dx * dx + dy * dy; + if (dsq == 0.0) { + LOGGER.log(Level.FINE, " Points are coincident"); + return; /* Points are coincident */ + } + double disc = 1.0 / dsq - 1.0 / 4.0; + if (disc < 0.0) { + LOGGER.log(Level.FINE, "Points are too far apart " + dsq); + float adjust = (float) (Math.sqrt(dsq) / 1.99999); + drawArc(p, x0, y0, x1, y1, a * adjust, b * adjust, theta, + isMoreThanHalf, isPositiveArc); + return; /* Points are too far apart */ + } + double s = Math.sqrt(disc); + double sdx = s * dx; + double sdy = s * dy; + double cx; + double cy; + if (isMoreThanHalf == isPositiveArc) { + cx = xm - sdy; + cy = ym + sdx; + } else { + cx = xm + sdy; + cy = ym - sdx; + } + + double eta0 = Math.atan2((y0p - cy), (x0p - cx)); + LOGGER.log(Level.FINE, "eta0 = Math.atan2( " + (y0p - cy) + " , " + + (x0p - cx) + ") = " + Math.toDegrees(eta0)); + + double eta1 = Math.atan2((y1p - cy), (x1p - cx)); + LOGGER.log(Level.FINE, "eta1 = Math.atan2( " + (y1p - cy) + " , " + + (x1p - cx) + ") = " + Math.toDegrees(eta1)); + double sweep = (eta1 - eta0); + if (isPositiveArc != (sweep >= 0)) { + if (sweep > 0) { + sweep -= 2 * Math.PI; + } else { + sweep += 2 * Math.PI; + } + } + + cx *= a; + cy *= b; + double tcx = cx; + cx = cx * cosTheta - cy * sinTheta; + cy = tcx * sinTheta + cy * cosTheta; + LOGGER.log( + Level.FINE, + "cx, cy, a, b, x0, y0, thetaD, eta0, sweep = " + cx + " , " + + cy + " , " + a + " , " + b + " , " + x0 + " , " + y0 + + " , " + Math.toDegrees(thetaD) + " , " + + Math.toDegrees(eta0) + " , " + Math.toDegrees(sweep)); + + arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep); + } + + /** + * Converts an arc to cubic Bezier segments and records them in p. + * + * @param p The target for the cubic Bezier segments + * @param cx The x coordinate center of the ellipse + * @param cy The y coordinate center of the ellipse + * @param a The radius of the ellipse in the horizontal direction + * @param b The radius of the ellipse in the vertical direction + * @param e1x E(eta1) x coordinate of the starting point of the arc + * @param e1y E(eta2) y coordinate of the starting point of the arc + * @param theta The angle that the ellipse bounding rectangle makes with the horizontal + * plane + * @param start The start angle of the arc on the ellipse + * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse + */ + private static void arcToBezier(@NonNull Path_Delegate p, double cx, double cy, double a, + double b, double e1x, double e1y, double theta, double start, + double sweep) { + // Taken from equations at: + // http://spaceroots.org/documents/ellipse/node8.html + // and http://www.spaceroots.org/documents/ellipse/node22.html + // Maximum of 45 degrees per cubic Bezier segment + int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI)); + + + double eta1 = start; + double cosTheta = Math.cos(theta); + double sinTheta = Math.sin(theta); + double cosEta1 = Math.cos(eta1); + double sinEta1 = Math.sin(eta1); + double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1); + double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1); + + double anglePerSegment = sweep / numSegments; + for (int i = 0; i < numSegments; i++) { + double eta2 = eta1 + anglePerSegment; + double sinEta2 = Math.sin(eta2); + double cosEta2 = Math.cos(eta2); + double e2x = cx + (a * cosTheta * cosEta2) + - (b * sinTheta * sinEta2); + double e2y = cy + (a * sinTheta * cosEta2) + + (b * cosTheta * sinEta2); + double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2; + double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2; + double tanDiff2 = Math.tan((eta2 - eta1) / 2); + double alpha = Math.sin(eta2 - eta1) + * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3; + double q1x = e1x + alpha * ep1x; + double q1y = e1y + alpha * ep1y; + double q2x = e2x - alpha * ep2x; + double q2y = e2y - alpha * ep2y; + + p.cubicTo((float) q1x, + (float) q1y, + (float) q2x, + (float) q2y, + (float) e2x, + (float) e2y); + eta1 = eta2; + e1x = e2x; + e1y = e2y; + ep1x = ep2x; + ep1y = ep2y; + } + } + } +} diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java index 723e8278bcc0..bdddfd8d8ba3 100644 --- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java @@ -265,12 +265,15 @@ public final class BridgeInflater extends LayoutInflater { if (viewKey != null) { bc.addViewKey(view, viewKey); } - String scrollPos = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY"); - if (scrollPos != null) { - if (scrollPos.endsWith("px")) { - int value = Integer.parseInt(scrollPos.substring(0, scrollPos.length() - 2)); - bc.setScrollYPos(view, value); - } + String scrollPosX = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollX"); + if (scrollPosX != null && scrollPosX.endsWith("px")) { + int value = Integer.parseInt(scrollPosX.substring(0, scrollPosX.length() - 2)); + bc.setScrollXPos(view, value); + } + String scrollPosY = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY"); + if (scrollPosY != null && scrollPosY.endsWith("px")) { + int value = Integer.parseInt(scrollPosY.substring(0, scrollPosY.length() - 2)); + bc.setScrollYPos(view, value); } if (ReflectionUtils.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) { Integer resourceId = null; diff --git a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java index 01af669e39d3..381eb1f50973 100644 --- a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java +++ b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java @@ -15,8 +15,11 @@ */ package android.view; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import java.lang.reflect.Field; import java.util.concurrent.atomic.AtomicReference; /** @@ -64,4 +67,18 @@ public class Choreographer_Delegate { thisChoreographer.doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); } + + public static void dispose() { + try { + Field threadInstanceField = Choreographer.class.getDeclaredField("sThreadInstance"); + threadInstanceField.setAccessible(true); + @SuppressWarnings("unchecked") ThreadLocal<Choreographer> threadInstance = + (ThreadLocal<Choreographer>) threadInstanceField.get(null); + threadInstance.remove(); + } catch (ReflectiveOperationException e) { + assert false; + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Unable to clear Choreographer memory.", e, null); + } + } } diff --git a/tools/layoutlib/bridge/src/android/view/ViewRootImpl_RunQueue_Delegate.java b/tools/layoutlib/bridge/src/android/view/HandlerActionQueue_Delegate.java index 51b42a626a2e..e580ed0e14f7 100644 --- a/tools/layoutlib/bridge/src/android/view/ViewRootImpl_RunQueue_Delegate.java +++ b/tools/layoutlib/bridge/src/android/view/HandlerActionQueue_Delegate.java @@ -20,18 +20,18 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; /** * Delegate used to provide new implementation of a select few methods of - * {@link ViewRootImpl.RunQueue} + * {@link HandlerActionQueue} * * Through the layoutlib_create tool, the original methods of ViewRootImpl.RunQueue have been * replaced by calls to methods of the same name in this delegate class. * */ -public class ViewRootImpl_RunQueue_Delegate { +public class HandlerActionQueue_Delegate { @LayoutlibDelegate - /*package*/ static void postDelayed(ViewRootImpl.RunQueue thisQueue, Runnable action, long + /*package*/ static void postDelayed(HandlerActionQueue thisQueue, Runnable action, long delayMillis) { - // The actual RunQueue is never run and therefore never cleared. This method avoids - // runnables to be added to the RunQueue so they do not leak resources. + // The actual HandlerActionQueue is never run and therefore never cleared. This method + // avoids runnables to be added to the RunQueue so they do not leak resources. } } diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index 82012c1e2767..04a59bcfb5ae 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -17,7 +17,10 @@ package android.view; 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.IShortcutService; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; @@ -29,6 +32,7 @@ import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; import android.util.DisplayMetrics; +import android.view.AppTransitionAnimationSpec; import java.lang.Override; @@ -73,10 +77,10 @@ public class IWindowManagerImpl implements 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) + boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9, boolean arg10, + Rect arg11, Configuration arg12, int arg13, boolean arg14, boolean arg15, int arg16) throws RemoteException { // TODO Auto-generated method stub - } @Override @@ -240,6 +244,19 @@ public class IWindowManagerImpl implements IWindowManager { } @Override + public void overridePendingAppTransitionMultiThumbFuture( + IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback startedCallback, + boolean scaleUp) throws RemoteException { + + } + + @Override + public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs, + IRemoteCallback callback0, IRemoteCallback callback1, boolean scaleUp) { + // TODO Auto-generated method stub + } + + @Override public void pauseKeyDispatching(IBinder arg0) throws RemoteException { // TODO Auto-generated method stub @@ -284,7 +301,7 @@ public class IWindowManagerImpl implements IWindowManager { @Override public Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, - int maxHeight) throws RemoteException { + int maxHeight, float frameScale) throws RemoteException { // TODO Auto-generated method stub return null; } @@ -307,9 +324,10 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public void setAppTask(IBinder arg0, int arg1) throws RemoteException { + public void setAppTask(IBinder arg0, int arg1, int arg2, Rect arg3, Configuration arg4, + int arg5, boolean arg6) + throws RemoteException { // TODO Auto-generated method stub - } @Override @@ -318,10 +336,11 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public void setAppStartingWindow(IBinder arg0, String arg1, int arg2, CompatibilityInfo arg3, + public boolean setAppStartingWindow(IBinder arg0, String arg1, int arg2, CompatibilityInfo arg3, CharSequence arg4, int arg5, int arg6, int arg7, int arg8, IBinder arg9, boolean arg10) throws RemoteException { // TODO Auto-generated method stub + return false; } @Override @@ -331,7 +350,7 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public void setAppWillBeHidden(IBinder arg0) throws RemoteException { + public void notifyAppStopped(IBinder token, boolean stopped) throws RemoteException { // TODO Auto-generated method stub } @@ -385,8 +404,15 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public void setNewConfiguration(Configuration arg0) throws RemoteException { + public int[] setNewConfiguration(Configuration arg0) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + @Override + public Rect getBoundsForNewConfiguration(int stackId) throws RemoteException { // TODO Auto-generated method stub + return null; } @Override @@ -474,8 +500,7 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public void keyguardGoingAway(boolean disableWindowAnimations, - boolean keyguardGoingToNotificationShade) throws RemoteException { + public void keyguardGoingAway(int flags) throws RemoteException { } @Override @@ -511,4 +536,57 @@ public class IWindowManagerImpl implements IWindowManager { // TODO Auto-generated method stub return null; } + + @Override + public int getDockedStackSide() throws RemoteException { + return 0; + } + + @Override + public void setDockedStackResizing(boolean resizing) throws RemoteException { + } + + @Override + public void cancelTaskWindowTransition(int taskId) { + } + + @Override + public void cancelTaskThumbnailTransition(int taskId) { + } + + @Override + public void endProlongedAnimations() { + } + + @Override + public void registerDockedStackListener(IDockedStackListener listener) throws RemoteException { + } + + @Override + public void setResizeDimLayer(boolean visible, int targetStackId, float alpha) + throws RemoteException { + } + + @Override + public void setDockedStackDividerTouchRegion(Rect touchableRegion) throws RemoteException { + } + + @Override + public void requestAppKeyboardShortcuts( + IResultReceiver receiver, int deviceId) throws RemoteException { + } + + @Override + public void getStableInsets(Rect outInsets) throws RemoteException { + } + + @Override + public void registerShortcutKey(long shortcutCode, IShortcutService service) + throws RemoteException {} + + @Override + public void createWallpaperInputConsumer(InputChannel inputChannel) throws RemoteException {} + + @Override + public void removeWallpaperInputConsumer() throws RemoteException {} } diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java index 30512aad4509..ea9a255e8561 100644 --- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java +++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java @@ -44,6 +44,11 @@ public class RectShadowPainter { private static final float PERPENDICULAR_ANGLE = 90f; public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas) { + Rect outline = new Rect(); + if (!viewOutline.getRect(outline)) { + throw new IllegalArgumentException("Outline is not a rect shadow"); + } + float shadowSize = elevationToShadow(elevation); int saved = modifyCanvas(canvas, shadowSize); if (saved == -1) { @@ -54,8 +59,7 @@ public class RectShadowPainter { cornerPaint.setStyle(Style.FILL); Paint edgePaint = new Paint(cornerPaint); edgePaint.setAntiAlias(false); - Rect outline = viewOutline.mRect; - float radius = viewOutline.mRadius; + float radius = viewOutline.getRadius(); float outerArcRadius = radius + shadowSize; int[] colors = {START_COLOR, START_COLOR, END_COLOR}; cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors, diff --git a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java index 1465f5089599..24f788766cc9 100644 --- a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java +++ b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java @@ -55,7 +55,7 @@ public class RenderNode_Delegate { private String mName; @LayoutlibDelegate - /*package*/ static long nCreate(String name) { + /*package*/ static long nCreate(RenderNode thisRenderNode, String name) { RenderNode_Delegate renderNodeDelegate = new RenderNode_Delegate(); renderNodeDelegate.mName = name; return sManager.addNewDelegate(renderNodeDelegate); diff --git a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java index 51d32e351eb8..23caaf85eb8e 100644 --- a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java +++ b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java @@ -64,7 +64,7 @@ public class ViewGroup_Delegate { private static void drawShadow(ViewGroup parent, Canvas canvas, View child, Outline outline) { float elevation = getElevation(child, parent); - if(outline.mRect != null) { + if(outline.mMode == Outline.MODE_ROUND_RECT && outline.mRect != null) { RectShadowPainter.paintShadow(outline, elevation, canvas); return; } diff --git a/tools/layoutlib/bridge/src/android/view/WindowCallback.java b/tools/layoutlib/bridge/src/android/view/WindowCallback.java index d691c8ea71a5..1ea8a9f2294e 100644 --- a/tools/layoutlib/bridge/src/android/view/WindowCallback.java +++ b/tools/layoutlib/bridge/src/android/view/WindowCallback.java @@ -16,10 +16,13 @@ package android.view; +import android.annotation.Nullable; import android.view.ActionMode.Callback; import android.view.WindowManager.LayoutParams; import android.view.accessibility.AccessibilityEvent; +import java.util.List; + /** * An empty implementation of {@link Window.Callback} that always returns null/false. */ diff --git a/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java b/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java deleted file mode 100644 index 1bd98301dc42..000000000000 --- a/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2014 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.widget; - -import com.android.tools.layoutlib.annotations.LayoutlibDelegate; - -import android.view.KeyEvent; - -/** - * Delegate used to provide new implementation of few methods in {@link TimePickerClockDelegate}. - */ -public class TimePickerClockDelegate_Delegate { - - // Copied from TimePickerClockDelegate. - private static final int AM = 0; - private static final int PM = 1; - - @LayoutlibDelegate - static int getAmOrPmKeyCode(TimePickerClockDelegate tpcd, int amOrPm) { - // We don't care about locales here. - if (amOrPm == AM) { - return KeyEvent.KEYCODE_A; - } else if (amOrPm == PM) { - return KeyEvent.KEYCODE_P; - } else { - assert false : "amOrPm value in TimePickerSpinnerDelegate can only be 0 or 1"; - return -1; - } - } -} diff --git a/tools/layoutlib/bridge/src/com/android/internal/util/VirtualRefBasePtr_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/util/VirtualRefBasePtr_Delegate.java new file mode 100644 index 000000000000..01fe45d2644c --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/internal/util/VirtualRefBasePtr_Delegate.java @@ -0,0 +1,53 @@ +/* + * 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.internal.util; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.util.LongSparseLongArray; + +/** + * Delegate used to provide new implementation the native methods of {@link VirtualRefBasePtr} + * + * Through the layoutlib_create tool, the original native methods of VirtualRefBasePtr have been + * replaced by calls to methods of the same name in this delegate class. + * + */ +@SuppressWarnings("unused") +public class VirtualRefBasePtr_Delegate { + private static final DelegateManager<Object> sManager = new DelegateManager<>(Object.class); + private static final LongSparseLongArray sRefCount = new LongSparseLongArray(); + + @LayoutlibDelegate + /*package*/ static synchronized void nIncStrong(long ptr) { + long counter = sRefCount.get(ptr); + sRefCount.put(ptr, ++counter); + } + + @LayoutlibDelegate + /*package*/ static synchronized void nDecStrong(long ptr) { + long counter = sRefCount.get(ptr); + + if (counter > 1) { + sRefCount.put(ptr, --counter); + } else { + sRefCount.delete(ptr); + sManager.removeJavaReferenceFor(ptr); + } + } +} 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 new file mode 100644 index 000000000000..0f39e8042883 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/internal/view/animation/NativeInterpolatorFactoryHelper_Delegate.java @@ -0,0 +1,128 @@ +/* + * 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.internal.view.animation; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.util.MathUtils; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.AnticipateInterpolator; +import android.view.animation.AnticipateOvershootInterpolator; +import android.view.animation.BaseInterpolator; +import android.view.animation.BounceInterpolator; +import android.view.animation.CycleInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; +import android.view.animation.OvershootInterpolator; + +/** + * Delegate used to provide new implementation of a select few methods of {@link + * NativeInterpolatorFactoryHelper} + * <p> + * Through the layoutlib_create tool, the original methods of NativeInterpolatorFactoryHelper have + * been replaced by calls to methods of the same name in this delegate class. + */ +@SuppressWarnings("unused") +public class NativeInterpolatorFactoryHelper_Delegate { + private static final DelegateManager<Interpolator> sManager = new DelegateManager<> + (Interpolator.class); + + public static Interpolator getDelegate(long nativePtr) { + return sManager.getDelegate(nativePtr); + } + + @LayoutlibDelegate + /*package*/ static long createAccelerateDecelerateInterpolator() { + return sManager.addNewDelegate(new AccelerateDecelerateInterpolator()); + } + + @LayoutlibDelegate + /*package*/ static long createAccelerateInterpolator(float factor) { + return sManager.addNewDelegate(new AccelerateInterpolator(factor)); + } + + @LayoutlibDelegate + /*package*/ static long createAnticipateInterpolator(float tension) { + return sManager.addNewDelegate(new AnticipateInterpolator(tension)); + } + + @LayoutlibDelegate + /*package*/ static long createAnticipateOvershootInterpolator(float tension) { + return sManager.addNewDelegate(new AnticipateOvershootInterpolator(tension)); + } + + @LayoutlibDelegate + /*package*/ static long createBounceInterpolator() { + return sManager.addNewDelegate(new BounceInterpolator()); + } + + @LayoutlibDelegate + /*package*/ static long createCycleInterpolator(float cycles) { + return sManager.addNewDelegate(new CycleInterpolator(cycles)); + } + + @LayoutlibDelegate + /*package*/ static long createDecelerateInterpolator(float factor) { + return sManager.addNewDelegate(new DecelerateInterpolator(factor)); + } + + @LayoutlibDelegate + /*package*/ static long createLinearInterpolator() { + return sManager.addNewDelegate(new LinearInterpolator()); + } + + @LayoutlibDelegate + /*package*/ static long createOvershootInterpolator(float tension) { + return sManager.addNewDelegate(new OvershootInterpolator(tension)); + } + + private static class LutInterpolator extends BaseInterpolator { + private final float[] mValues; + private final int mSize; + + private LutInterpolator(float[] values) { + mValues = values; + mSize = mValues.length; + } + + @Override + public float getInterpolation(float input) { + float lutpos = input * mSize; + if (lutpos >= (mSize - 1)) { + return mValues[mSize - 1]; + } + + int ipart = (int) lutpos; + float weight = lutpos - ipart; + + int i1 = ipart; + int i2 = Math.min(i1 + 1, mSize - 1); + + assert i1 >= 0 && i2 >= 0 : "Negatives in the interpolation"; + + return MathUtils.lerp(mValues[i1], mValues[i2], weight); + } + } + + @LayoutlibDelegate + /*package*/ static long createLutInterpolator(float[] values) { + return sManager.addNewDelegate(new LutInterpolator(values)); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index c8e3d03169e8..3b882909698d 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -24,6 +24,7 @@ import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.Result.Status; import com.android.ide.common.rendering.api.SessionParams; +import com.android.layoutlib.bridge.android.RenderParamsFlags; import com.android.layoutlib.bridge.impl.RenderDrawable; import com.android.layoutlib.bridge.impl.RenderSessionImpl; import com.android.layoutlib.bridge.util.DynamicIdMap; @@ -408,7 +409,9 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { /** * Starts a layout session by inflating and rendering it. The method returns a * {@link RenderSession} on which further actions can be taken. - * + * <p/> + * If {@link SessionParams} includes the {@link RenderParamsFlags#FLAG_DO_NOT_RENDER_ON_CREATE}, + * this method will only inflate the layout but will NOT render it. * @param params the {@link SessionParams} object with all the information necessary to create * the scene. * @return a new {@link RenderSession} object that contains the result of the layout. @@ -424,7 +427,10 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { lastResult = scene.init(params.getTimeout()); if (lastResult.isSuccess()) { lastResult = scene.inflate(); - if (lastResult.isSuccess()) { + + boolean doNotRenderOnCreate = Boolean.TRUE.equals( + params.getFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE)); + if (lastResult.isSuccess() && !doNotRenderOnCreate) { lastResult = scene.render(true /*freshRender*/); } } @@ -531,7 +537,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { * Note that while this can be called several time, the first call to {@link #cleanupThread()} * will do the clean-up, and make the thread unable to do further scene actions. */ - public static void prepareThread() { + public synchronized static void prepareThread() { // we need to make sure the Looper has been initialized for this thread. // this is required for View that creates Handler objects. if (Looper.myLooper() == null) { @@ -545,7 +551,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single * call to this will prevent the thread from doing further scene actions */ - public static void cleanupThread() { + public synchronized static void cleanupThread() { // clean up the looper Looper_Accessor.cleanupThread(); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java index fea633e7036d..3ac1889dbe72 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java @@ -24,6 +24,7 @@ import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.ViewInfo; import com.android.layoutlib.bridge.impl.RenderSessionImpl; import com.android.tools.layoutlib.java.System_Delegate; +import com.android.util.PropertiesMap; import android.view.View; import android.view.ViewGroup; @@ -70,20 +71,8 @@ public class BridgeRenderSession extends RenderSession { } @Override - public Map<String, String> getDefaultProperties(Object viewObject) { - return mSession.getDefaultProperties(viewObject); - } - - @Override - public Result getProperty(Object objectView, String propertyName) { - // pass - return super.getProperty(objectView, propertyName); - } - - @Override - public Result setProperty(Object objectView, String propertyName, String propertyValue) { - // pass - return super.setProperty(objectView, propertyName, propertyValue); + public Map<Object, PropertiesMap> getDefaultProperties() { + return mSession.getDefaultProperties(); } @Override @@ -203,7 +192,9 @@ public class BridgeRenderSession extends RenderSession { @Override public void setElapsedFrameTimeNanos(long nanos) { - mSession.setElapsedFrameTimeNanos(nanos); + if (mSession != null) { + mSession.setElapsedFrameTimeNanos(nanos); + } } @Override 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 2fd1f18a369f..616cb5761402 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 @@ -32,6 +32,8 @@ import com.android.layoutlib.bridge.impl.ParserFactory; import com.android.layoutlib.bridge.impl.Stack; import com.android.resources.ResourceType; import com.android.util.Pair; +import com.android.util.PropertiesMap; +import com.android.util.PropertiesMap.Property; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -73,6 +75,7 @@ import android.os.Looper; import android.os.Parcel; import android.os.PowerManager; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.UserHandle; import android.util.AttributeSet; import android.util.DisplayMetrics; @@ -109,13 +112,13 @@ import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_AP public final class BridgeContext extends Context { /** The map adds cookies to each view so that IDE can link xml tags to views. */ - private final HashMap<View, Object> mViewKeyMap = new HashMap<View, Object>(); + private final HashMap<View, Object> mViewKeyMap = new HashMap<>(); /** * In some cases, when inflating an xml, some objects are created. Then later, the objects are * converted to views. This map stores the mapping from objects to cookies which can then be * used to populate the mViewKeyMap. */ - private final HashMap<Object, Object> mViewKeyHelpMap = new HashMap<Object, Object>(); + private final HashMap<Object, Object> mViewKeyHelpMap = new HashMap<>(); private final BridgeAssetManager mAssets; private Resources mSystemResources; private final Object mProjectKey; @@ -126,12 +129,12 @@ public final class BridgeContext extends Context { private final LayoutlibCallback mLayoutlibCallback; private final WindowManager mWindowManager; private final DisplayManager mDisplayManager; - private final HashMap<View, Integer> mScrollYPos = new HashMap<View, Integer>(); + private final HashMap<View, Integer> mScrollYPos = new HashMap<>(); + private final HashMap<View, Integer> mScrollXPos = new HashMap<>(); private Resources.Theme mTheme; - private final Map<Object, Map<String, String>> mDefaultPropMaps = - new IdentityHashMap<Object, Map<String,String>>(); + private final Map<Object, PropertiesMap> mDefaultPropMaps = new IdentityHashMap<>(); // maps for dynamically generated id representing style objects (StyleResourceValue) @Nullable @@ -140,19 +143,17 @@ public final class BridgeContext extends Context { private int mDynamicIdGenerator = 0x02030000; // Base id for R.style in custom namespace // cache for TypedArray generated from StyleResourceValue object - private Map<int[], Map<List<StyleResourceValue>, Map<Integer, BridgeTypedArray>>> - mTypedArrayCache; + private TypedArrayCache mTypedArrayCache; private BridgeInflater mBridgeInflater; private BridgeContentResolver mContentResolver; - private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<BridgeXmlBlockParser>(); + private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<>(); private SharedPreferences mSharedPreferences; private ClassLoader mClassLoader; private IBinder mBinder; private PackageManager mPackageManager; - /** * Some applications that target both pre API 17 and post API 17, set the newer attrs to * reference the older ones. For example, android:paddingStart will resolve to @@ -160,7 +161,7 @@ public final class BridgeContext extends Context { * This a map from value to attribute name. Warning for missing references shouldn't be logged * if value and attr name pair is the same as an entry in this map. */ - private static Map<String, String> RTL_ATTRS = new HashMap<String, String>(10); + private static Map<String, String> RTL_ATTRS = new HashMap<>(10); static { RTL_ATTRS.put("?android:attr/paddingLeft", "paddingStart"); @@ -275,8 +276,8 @@ public final class BridgeContext extends Context { return mRenderResources; } - public Map<String, String> getDefaultPropMap(Object key) { - return mDefaultPropMaps.get(key); + public Map<Object, PropertiesMap> getDefaultProperties() { + return mDefaultPropMaps; } public Configuration getConfiguration() { @@ -323,11 +324,11 @@ public final class BridgeContext extends Context { return mParserStack.get(mParserStack.size() - 2); } - public boolean resolveThemeAttribute(int resid, TypedValue outValue, boolean resolveRefs) { - Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(resid); + public boolean resolveThemeAttribute(int resId, TypedValue outValue, boolean resolveRefs) { + Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(resId); boolean isFrameworkRes = true; if (resourceInfo == null) { - resourceInfo = mLayoutlibCallback.resolveResourceId(resid); + resourceInfo = mLayoutlibCallback.resolveResourceId(resId); isFrameworkRes = false; } @@ -600,23 +601,20 @@ public final class BridgeContext extends Context { @Override public final BridgeTypedArray obtainStyledAttributes(int[] attrs) { - // No style is specified here, so create the typed array based on the default theme - // and the styles already applied to it. A null value of style indicates that the default - // theme should be used. - return createStyleBasedTypedArray(null, attrs); + return obtainStyledAttributes(0, attrs); } @Override - public final BridgeTypedArray obtainStyledAttributes(int resid, int[] attrs) + public final BridgeTypedArray obtainStyledAttributes(int resId, int[] attrs) throws Resources.NotFoundException { StyleResourceValue style = null; // get the StyleResourceValue based on the resId; - if (resid != 0) { - style = getStyleByDynamicId(resid); + if (resId != 0) { + style = getStyleByDynamicId(resId); if (style == null) { // In some cases, style may not be a dynamic id, so we do a full search. - ResourceReference ref = resolveId(resid); + ResourceReference ref = resolveId(resId); if (ref != null) { style = mRenderResources.getStyle(ref.getName(), ref.isFramework()); } @@ -627,41 +625,34 @@ public final class BridgeContext extends Context { } } - // The map is from - // attrs (int[]) -> context's current themes (List<StyleRV>) -> resid (int) -> typed array. if (mTypedArrayCache == null) { - mTypedArrayCache = new IdentityHashMap<int[], - Map<List<StyleResourceValue>, Map<Integer, BridgeTypedArray>>>(); - } - - // get the 2nd map - Map<List<StyleResourceValue>, Map<Integer, BridgeTypedArray>> map2 = - mTypedArrayCache.get(attrs); - if (map2 == null) { - map2 = new HashMap<List<StyleResourceValue>, Map<Integer, BridgeTypedArray>>(); - mTypedArrayCache.put(attrs, map2); + mTypedArrayCache = new TypedArrayCache(); } - // get the 3rd map List<StyleResourceValue> currentThemes = mRenderResources.getAllThemes(); - Map<Integer, BridgeTypedArray> map3 = map2.get(currentThemes); - if (map3 == null) { - map3 = new HashMap<Integer, BridgeTypedArray>(); - // Create a copy of the list before adding it to the map. This allows reusing the - // existing list. - currentThemes = new ArrayList<StyleResourceValue>(currentThemes); - map2.put(currentThemes, map3); - } - // get the array from the 3rd map - BridgeTypedArray ta = map3.get(resid); + Pair<BridgeTypedArray, PropertiesMap> typeArrayAndPropertiesPair = + mTypedArrayCache.get(attrs, currentThemes, resId); - if (ta == null) { - ta = createStyleBasedTypedArray(style, attrs); - map3.put(resid, ta); + if (typeArrayAndPropertiesPair == null) { + typeArrayAndPropertiesPair = createStyleBasedTypedArray(style, attrs); + mTypedArrayCache.put(attrs, currentThemes, resId, typeArrayAndPropertiesPair); } - - return ta; + // Add value to defaultPropsMap if needed + if (typeArrayAndPropertiesPair.getSecond() != null) { + BridgeXmlBlockParser parser = getCurrentParser(); + Object key = parser != null ? parser.getViewCookie() : null; + if (key != null) { + PropertiesMap defaultPropMap = mDefaultPropMaps.get(key); + if (defaultPropMap == null) { + defaultPropMap = typeArrayAndPropertiesPair.getSecond(); + mDefaultPropMaps.put(key, defaultPropMap); + } else { + defaultPropMap.putAll(typeArrayAndPropertiesPair.getSecond()); + } + } + } + return typeArrayAndPropertiesPair.getFirst(); } @Override @@ -673,7 +664,7 @@ public final class BridgeContext extends Context { public BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) { - Map<String, String> defaultPropMap = null; + PropertiesMap defaultPropMap = null; boolean isPlatformFile = true; // Hint: for XmlPullParser, attach source //DEVICE_SRC/dalvik/libcore/xml/src/java @@ -687,7 +678,7 @@ public final class BridgeContext extends Context { if (key != null) { defaultPropMap = mDefaultPropMaps.get(key); if (defaultPropMap == null) { - defaultPropMap = new HashMap<String, String>(); + defaultPropMap = new PropertiesMap(); mDefaultPropMaps.put(key, defaultPropMap); } } @@ -741,16 +732,10 @@ public final class BridgeContext extends Context { Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE, "Failed to find the style corresponding to the id " + defStyleAttr, null); } else { - if (defaultPropMap != null) { - String defStyleName = defStyleAttribute.getFirst(); - if (defStyleAttribute.getSecond()) { - defStyleName = "android:" + defStyleName; - } - defaultPropMap.put("style", defStyleName); - } + String defStyleName = defStyleAttribute.getFirst(); // look for the style in the current theme, and its parent: - ResourceValue item = mRenderResources.findItemInTheme(defStyleAttribute.getFirst(), + ResourceValue item = mRenderResources.findItemInTheme(defStyleName, defStyleAttribute.getSecond()); if (item != null) { @@ -760,6 +745,12 @@ public final class BridgeContext extends Context { if (item instanceof StyleResourceValue) { defStyleValues = (StyleResourceValue) item; } + if (defaultPropMap != null) { + if (defStyleAttribute.getSecond()) { + defStyleName = "android:" + defStyleName; + } + defaultPropMap.put("style", new Property(defStyleName, item.getValue())); + } } else { Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR, String.format( @@ -786,7 +777,8 @@ public final class BridgeContext extends Context { item = mRenderResources.getStyle(value.getSecond(), isFrameworkRes); if (item != null) { if (defaultPropMap != null) { - defaultPropMap.put("style", item.getName()); + String name = item.getName(); + defaultPropMap.put("style", new Property(name, name)); } defStyleValues = item; @@ -865,13 +857,16 @@ public final class BridgeContext extends Context { // if we found a value, we make sure this doesn't reference another value. // So we resolve it. if (resValue != null) { - // put the first default value, before the resolution. + String preResolve = resValue.getValue(); + resValue = mRenderResources.resolveResValue(resValue); + if (defaultPropMap != null) { - defaultPropMap.put(attrName, resValue.getValue()); + defaultPropMap.put( + frameworkAttr ? SdkConstants.PREFIX_ANDROID + attrName : + attrName, + new Property(preResolve, resValue.getValue())); } - resValue = mRenderResources.resolveResValue(resValue); - // 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(); @@ -935,40 +930,45 @@ public final class BridgeContext extends Context { * * @see #obtainStyledAttributes(int, int[]) */ - private BridgeTypedArray createStyleBasedTypedArray(@Nullable StyleResourceValue style, - int[] attrs) throws Resources.NotFoundException { - + private Pair<BridgeTypedArray, PropertiesMap> createStyleBasedTypedArray( + @Nullable StyleResourceValue style, int[] attrs) throws Resources.NotFoundException { List<Pair<String, Boolean>> attributes = searchAttrs(attrs); - BridgeTypedArray ta = Resources_Delegate.newTypeArray(mSystemResources, attrs.length, - false); + BridgeTypedArray ta = + Resources_Delegate.newTypeArray(mSystemResources, attrs.length, false); + PropertiesMap defaultPropMap = new PropertiesMap(); // for each attribute, get its name so that we can search it in the style - for (int i = 0 ; i < attrs.length ; i++) { + for (int i = 0; i < attrs.length; i++) { Pair<String, Boolean> attribute = attributes.get(i); if (attribute != null) { // look for the value in the given style ResourceValue resValue; + String attrName = attribute.getFirst(); + boolean frameworkAttr = attribute.getSecond(); if (style != null) { - resValue = mRenderResources.findItemInStyle(style, attribute.getFirst(), - attribute.getSecond()); + resValue = mRenderResources.findItemInStyle(style, attrName, frameworkAttr); } else { - resValue = mRenderResources.findItemInTheme(attribute.getFirst(), - attribute.getSecond()); + resValue = mRenderResources.findItemInTheme(attrName, frameworkAttr); } if (resValue != null) { + // Add it to defaultPropMap before resolving + String preResolve = resValue.getValue(); // resolve it to make sure there are no references left. - ta.bridgeSetValue(i, attribute.getFirst(), attribute.getSecond(), - mRenderResources.resolveResValue(resValue)); + resValue = mRenderResources.resolveResValue(resValue); + ta.bridgeSetValue(i, attrName, frameworkAttr, resValue); + defaultPropMap.put( + frameworkAttr ? SdkConstants.ANDROID_PREFIX + attrName : attrName, + new Property(preResolve, resValue.getValue())); } } } ta.sealArray(); - return ta; + return Pair.of(ta, defaultPropMap); } /** @@ -980,7 +980,7 @@ public final class BridgeContext extends Context { * @return List of attribute information. */ private List<Pair<String, Boolean>> searchAttrs(int[] attrs) { - List<Pair<String, Boolean>> results = new ArrayList<Pair<String, Boolean>>(attrs.length); + List<Pair<String, Boolean>> results = new ArrayList<>(attrs.length); // for each attribute, get its name so that we can search it in the style for (int attr : attrs) { @@ -1009,7 +1009,7 @@ public final class BridgeContext extends Context { * @return A (name, isFramework) pair describing the attribute if found. Returns null * if nothing is found. */ - public Pair<String, Boolean> searchAttr(int attr) { + private Pair<String, Boolean> searchAttr(int attr) { Pair<ResourceType, String> info = Bridge.resolveResourceId(attr); if (info != null) { return Pair.of(info.getSecond(), Boolean.TRUE); @@ -1026,8 +1026,8 @@ public final class BridgeContext extends Context { public int getDynamicIdByStyle(StyleResourceValue resValue) { if (mDynamicIdToStyleMap == null) { // create the maps. - mDynamicIdToStyleMap = new HashMap<Integer, StyleResourceValue>(); - mStyleToDynamicIdMap = new HashMap<StyleResourceValue, Integer>(); + mDynamicIdToStyleMap = new HashMap<>(); + mStyleToDynamicIdMap = new HashMap<>(); } // look for an existing id @@ -1135,6 +1135,11 @@ public final class BridgeContext extends Context { public boolean unlinkToDeath(DeathRecipient recipient, int flags) { return false; } + + @Override + public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ResultReceiver resultReceiver) { + } }; } return mBinder; @@ -1252,6 +1257,12 @@ public final class BridgeContext extends Context { } @Override + public boolean moveDatabaseFrom(Context sourceContext, String name) { + // pass + return false; + } + + @Override public boolean deleteDatabase(String arg0) { // pass return false; @@ -1364,6 +1375,18 @@ public final class BridgeContext extends Context { } @Override + public File getSharedPreferencesPath(String name) { + // pass + return null; + } + + @Override + public File getDataDir() { + // pass + return null; + } + + @Override public File getFilesDir() { // pass return null; @@ -1411,13 +1434,15 @@ public final class BridgeContext extends Context { } @Override - public File getSharedPrefsFile(String name) { - // pass - return null; + public SharedPreferences getSharedPreferences(String arg0, int arg1) { + if (mSharedPreferences == null) { + mSharedPreferences = new BridgeSharedPreferences(); + } + return mSharedPreferences; } @Override - public SharedPreferences getSharedPreferences(String arg0, int arg1) { + public SharedPreferences getSharedPreferences(File arg0, int arg1) { if (mSharedPreferences == null) { mSharedPreferences = new BridgeSharedPreferences(); } @@ -1425,6 +1450,18 @@ public final class BridgeContext extends Context { } @Override + public boolean moveSharedPreferencesFrom(Context sourceContext, String name) { + // pass + return false; + } + + @Override + public boolean deleteSharedPreferences(String name) { + // pass + return false; + } + + @Override public Drawable getWallpaper() { // pass return null; @@ -1625,6 +1662,11 @@ public final class BridgeContext extends Context { } @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) { + // pass + } + + @Override public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, @@ -1757,6 +1799,12 @@ public final class BridgeContext extends Context { } @Override + public Display getDisplay() { + // pass + return null; + } + + @Override public int getUserId() { return 0; // not used } @@ -1793,4 +1841,92 @@ public final class BridgeContext extends Context { Integer pos = mScrollYPos.get(view); return pos != null ? pos : 0; } + + public void setScrollXPos(@NonNull View view, int scrollPos) { + mScrollXPos.put(view, scrollPos); + } + + public int getScrollXPos(@NonNull View view) { + Integer pos = mScrollXPos.get(view); + return pos != null ? pos : 0; + } + + @Override + public Context createDeviceProtectedStorageContext() { + // pass + return null; + } + + @Override + public Context createCredentialProtectedStorageContext() { + // pass + return null; + } + + @Override + public boolean isDeviceProtectedStorage() { + return false; + } + + @Override + public boolean isCredentialProtectedStorage() { + return false; + } + + /** + * The cached value depends on + * <ol> + * <li>{@code int[]}: the attributes for which TypedArray is created </li> + * <li>{@code List<StyleResourceValue>}: the themes set on the context at the time of + * creation of the TypedArray</li> + * <li>{@code Integer}: the default style used at the time of creation</li> + * </ol> + * + * The class is created by using nested maps resolving one dependency at a time. + * <p/> + * The final value of the nested maps is a pair of the typed array and a map of properties + * that should be added to {@link #mDefaultPropMaps}, if needed. + */ + private static class TypedArrayCache { + + private Map<int[], + Map<List<StyleResourceValue>, + Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>>> mCache; + + public TypedArrayCache() { + mCache = new IdentityHashMap<>(); + } + + public Pair<BridgeTypedArray, PropertiesMap> get(int[] attrs, + List<StyleResourceValue> themes, int resId) { + Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>> + cacheFromThemes = mCache.get(attrs); + if (cacheFromThemes != null) { + Map<Integer, Pair<BridgeTypedArray, PropertiesMap>> cacheFromResId = + cacheFromThemes.get(themes); + if (cacheFromResId != null) { + return cacheFromResId.get(resId); + } + } + return null; + } + + public void put(int[] attrs, List<StyleResourceValue> themes, int resId, + Pair<BridgeTypedArray, PropertiesMap> value) { + Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>> + cacheFromThemes = mCache.get(attrs); + if (cacheFromThemes == null) { + cacheFromThemes = new HashMap<>(); + mCache.put(attrs, cacheFromThemes); + } + Map<Integer, Pair<BridgeTypedArray, PropertiesMap>> cacheFromResId = + cacheFromThemes.get(themes); + if (cacheFromResId == null) { + cacheFromResId = new HashMap<>(); + cacheFromThemes.put(themes, cacheFromResId); + } + cacheFromResId.put(resId, value); + } + + } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java index 8899e53648d2..3f276c9375ba 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java @@ -184,13 +184,6 @@ public class BridgeIInputMethodManager implements IInputMethodManager { } @Override - public InputBindResult startInput(IInputMethodClient client, IInputContext inputContext, - EditorInfo attribute, int controlFlags) throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - @Override public boolean switchToLastInputMethod(IBinder arg0) throws RemoteException { // TODO Auto-generated method stub return false; @@ -226,9 +219,17 @@ public class BridgeIInputMethodManager implements IInputMethodManager { } @Override - public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken, - int controlFlags, int softInputMode, int windowFlags, EditorInfo attribute, - IInputContext inputContext) throws RemoteException { + public void clearLastInputMethodWindowForTransition(IBinder arg0) throws RemoteException { + // TODO Auto-generated method stub + } + + @Override + public InputBindResult startInputOrWindowGainedFocus( + /* @InputMethodClient.StartInputReason */ int startInputReason, + IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode, + int windowFlags, EditorInfo attribute, IInputContext inputContext, + /* @InputConnectionInspector.MissingMethodFlags */ int missingMethodFlags) + throws RemoteException { // TODO Auto-generated method stub return null; } 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 f04654eded0f..b3ed9e1a0164 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 @@ -16,6 +16,7 @@ package com.android.layoutlib.bridge.android; +import android.annotation.NonNull; import android.app.PackageInstallObserver; import android.content.ComponentName; import android.content.Intent; @@ -23,7 +24,7 @@ import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; -import android.content.pm.ContainerEncryptionParams; +import android.content.pm.EphemeralApplicationInfo; import android.content.pm.FeatureInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; @@ -32,7 +33,6 @@ import android.content.pm.IPackageStatsObserver; import android.content.pm.InstrumentationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.KeySet; -import android.content.pm.ManifestDigest; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageItemInfo; @@ -42,7 +42,6 @@ import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.content.pm.VerificationParams; import android.content.pm.VerifierDeviceIdentity; import android.content.res.Resources; import android.content.res.XmlResourceParser; @@ -50,6 +49,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Handler; +import android.os.RemoteException; import android.os.UserHandle; import android.os.storage.VolumeInfo; import java.util.List; @@ -65,6 +65,12 @@ public class BridgePackageManager extends PackageManager { } @Override + public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId) + throws NameNotFoundException { + return null; + } + + @Override public String[] currentToCanonicalPackageNames(String[] names) { return new String[0]; } @@ -90,7 +96,22 @@ public class BridgePackageManager extends PackageManager { } @Override - public int getPackageUid(String packageName, int userHandle) throws NameNotFoundException { + public int[] getPackageGids(String packageName, int flags) throws NameNotFoundException { + return new int[0]; + } + + @Override + public int getPackageUid(String packageName, int flags) throws NameNotFoundException { + return 0; + } + + @Override + public int getPackageUidAsUser(String packageName, int userHandle) throws NameNotFoundException { + return 0; + } + + @Override + public int getPackageUidAsUser(String packageName, int flags, int userHandle) throws NameNotFoundException { return 0; } @@ -123,6 +144,12 @@ public class BridgePackageManager extends PackageManager { } @Override + public ApplicationInfo getApplicationInfoAsUser(String packageName, int flags, int userId) + throws NameNotFoundException { + return null; + } + + @Override public ActivityInfo getActivityInfo(ComponentName component, int flags) throws NameNotFoundException { return null; @@ -157,7 +184,7 @@ public class BridgePackageManager extends PackageManager { } @Override - public List<PackageInfo> getInstalledPackages(int flags, int userId) { + public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) { return null; } @@ -245,11 +272,51 @@ public class BridgePackageManager extends PackageManager { } @Override + public List<EphemeralApplicationInfo> getEphemeralApplications() { + return null; + } + + @Override + public Drawable getEphemeralApplicationIcon(String packageName) { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] getEphemeralCookie() { + return new byte[0]; + } + + @Override + public boolean isEphemeralApplication() { + return false; + } + + @Override + public int getEphemeralCookieMaxSizeBytes() { + return 0; + } + + @Override + public boolean setEphemeralCookie(@NonNull byte[] cookie) { + return false; + } + + @Override public String[] getSystemSharedLibraryNames() { return new String[0]; } @Override + public String getServicesSystemSharedLibraryPackageName() { + return null; + } + + @Override + public @NonNull String getSharedSystemSharedLibraryPackageName() { + return null; + } + + @Override public FeatureInfo[] getSystemAvailableFeatures() { return new FeatureInfo[0]; } @@ -260,6 +327,11 @@ public class BridgePackageManager extends PackageManager { } @Override + public boolean hasSystemFeature(String name, int version) { + return false; + } + + @Override public ResolveInfo resolveActivity(Intent intent, int flags) { return null; } @@ -291,7 +363,7 @@ public class BridgePackageManager extends PackageManager { } @Override - public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags, int userId) { + public List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent, int flags, int userId) { return null; } @@ -418,6 +490,12 @@ 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; } @@ -434,6 +512,11 @@ public class BridgePackageManager extends PackageManager { } @Override + public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) { + return null; + } + + @Override public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) { return null; } @@ -482,36 +565,18 @@ public class BridgePackageManager extends PackageManager { } @Override - public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer, - int flags, String installerPackageName, Uri verificationURI, - ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) { - } - - @Override - public void installPackageWithVerificationAndEncryption(Uri packageURI, - IPackageInstallObserver observer, int flags, String installerPackageName, - VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) { - } - - @Override public void installPackage(Uri packageURI, PackageInstallObserver observer, int flags, String installerPackageName) { } @Override - public void installPackageWithVerification(Uri packageURI, PackageInstallObserver observer, - int flags, String installerPackageName, Uri verificationURI, - ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) { - } - - @Override - public void installPackageWithVerificationAndEncryption(Uri packageURI, - PackageInstallObserver observer, int flags, String installerPackageName, - VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) { + public int installExistingPackage(String packageName) throws NameNotFoundException { + return 0; } @Override - public int installExistingPackage(String packageName) throws NameNotFoundException { + public int installExistingPackageAsUser(String packageName, int userId) + throws NameNotFoundException { return 0; } @@ -530,12 +595,12 @@ public class BridgePackageManager extends PackageManager { } @Override - public int getIntentVerificationStatus(String packageName, int userId) { + public int getIntentVerificationStatusAsUser(String packageName, int userId) { return 0; } @Override - public boolean updateIntentVerificationStatus(String packageName, int status, int userId) { + public boolean updateIntentVerificationStatusAsUser(String packageName, int status, int userId) { return false; } @@ -550,12 +615,12 @@ public class BridgePackageManager extends PackageManager { } @Override - public String getDefaultBrowserPackageName(int userId) { + public String getDefaultBrowserPackageNameAsUser(int userId) { return null; } @Override - public boolean setDefaultBrowserPackageName(String packageName, int userId) { + public boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId) { return false; } @@ -568,6 +633,11 @@ public class BridgePackageManager extends PackageManager { } @Override + public void deletePackageAsUser(String packageName, IPackageDeleteObserver observer, int flags, + int userId) { + } + + @Override public String getInstallerPackageName(String packageName) { return null; } @@ -581,6 +651,11 @@ public class BridgePackageManager extends PackageManager { } @Override + public void deleteApplicationCacheFilesAsUser(String packageName, int userId, + IPackageDataObserver observer) { + } + + @Override public void freeStorageAndNotify(String volumeUuid, long freeStorageSize, IPackageDataObserver observer) { } @@ -590,7 +665,7 @@ public class BridgePackageManager extends PackageManager { } @Override - public void getPackageSizeInfo(String packageName, int userHandle, + public void getPackageSizeInfoAsUser(String packageName, int userHandle, IPackageStatsObserver observer) { } @@ -651,6 +726,10 @@ public class BridgePackageManager extends PackageManager { } @Override + public void flushPackageRestrictionsAsUser(int userId) { + } + + @Override public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden, UserHandle userHandle) { return false; @@ -695,6 +774,17 @@ public class BridgePackageManager extends PackageManager { } @Override + public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended, + int userId) { + return new String[]{}; + } + + @Override + public boolean isPackageSuspendedForUser(String packageName, int userId) { + return false; + } + + @Override public int getMoveStatus(int moveId) { return 0; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java index 895f9c9eaeac..9f73d79ff142 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java @@ -86,7 +86,12 @@ public class BridgePowerManager implements IPowerManager { } @Override - public void shutdown(boolean confirm, boolean wait) { + public void rebootSafeMode(boolean confirm, boolean wait) { + // pass for now. + } + + @Override + public void shutdown(boolean confirm, String reason, boolean wait) { // pass for now. } @@ -152,6 +157,11 @@ public class BridgePowerManager implements IPowerManager { } @Override + public boolean isLightDeviceIdleMode() throws RemoteException { + return false; + } + + @Override public boolean isScreenBrightnessBoosted() throws RemoteException { return false; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java index 771c3c85d2f5..a83f10087649 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java @@ -16,6 +16,8 @@ package com.android.layoutlib.bridge.android; +import com.android.internal.os.IResultReceiver; + import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; @@ -48,7 +50,8 @@ public final class BridgeWindow implements IWindow { @Override public void resized(Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5, Rect rect6, - boolean b, Configuration configuration) throws RemoteException { + boolean b, Configuration configuration, Rect rect7, boolean b2, boolean b3) + throws RemoteException { // pass for now. } @@ -85,21 +88,23 @@ public final class BridgeWindow implements IWindow { } @Override - public void dispatchSystemUiVisibilityChanged(int seq, int globalUi, - int localValue, int localChanges) { - // pass for now. + public void updatePointerIcon(float x, float y) { + // pass for now } @Override - public void onAnimationStarted(int remainingFrameCount) { + public void dispatchSystemUiVisibilityChanged(int seq, int globalUi, + int localValue, int localChanges) { + // pass for now. } @Override - public void onAnimationStopped() { + public void dispatchWindowShown() { } @Override - public void dispatchWindowShown() { + public void requestAppKeyboardShortcuts( + IResultReceiver receiver, int deviceId) throws RemoteException { } @Override @@ -107,4 +112,5 @@ public final class BridgeWindow implements IWindow { // pass for now. return null; } + } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java index bea1f86907e0..7582fda6a0fb 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java @@ -89,12 +89,20 @@ public final class BridgeWindowSession implements IWindowSession { @Override public int relayout(IWindow iWindow, int i, LayoutParams layoutParams, int i2, int i3, int i4, int i5, Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5, - Rect rect6, Configuration configuration, Surface surface) throws RemoteException { + Rect rect6, Rect rect7, Configuration configuration, Surface surface) + throws RemoteException { // pass for now. return 0; } @Override + public void repositionChild(IWindow window, int left, int top, int right, int bottom, + long deferTransactionUntilFrame, Rect outFrame) { + // pass for now. + return; + } + + @Override public void performDeferredDestroy(IWindow window) { // pass for now. } @@ -140,7 +148,7 @@ public final class BridgeWindowSession implements IWindowSession { @Override public boolean performDrag(IWindow window, IBinder dragToken, - float touchX, float touchY, float thumbCenterX, float thumbCenterY, + int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) throws RemoteException { // pass for now @@ -148,11 +156,23 @@ public final class BridgeWindowSession implements IWindowSession { } @Override + public boolean startMovingTask(IWindow window, float startX, float startY) + throws RemoteException { + // pass for now + return false; + } + + @Override public void reportDropResult(IWindow window, boolean consumed) throws RemoteException { // pass for now } @Override + public void cancelDragAndDrop(IBinder dragToken) throws RemoteException { + // pass for now + } + + @Override public void dragRecipientEntered(IWindow window) throws RemoteException { // pass for now } @@ -211,4 +231,14 @@ public final class BridgeWindowSession implements IWindowSession { public void pokeDrawLock(IBinder window) { // pass for now. } + + @Override + public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) { + // pass for now. + } + + @Override + public void updatePointerIcon(IWindow window) { + // pass for now. + } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java index bd17a2fe6ca2..051de9055042 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java @@ -53,6 +53,12 @@ public final class RenderParamsFlags { */ public static final Key<Boolean> FLAG_KEY_XML_FILE_PARSER_SUPPORT = new Key<Boolean>("xmlFileParser", Boolean.class); + /** + * To tell LayoutLib to not render when creating a new session. This allows controlling when the first + * layout rendering will happen. + */ + public static final Key<Boolean> FLAG_DO_NOT_RENDER_ON_CREATE = + new Key<Boolean>("doNotRenderOnCreate", Boolean.class); // Disallow instances. private RenderParamsFlags() {} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/graphics/NopCanvas.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/graphics/NopCanvas.java new file mode 100644 index 000000000000..2b4661b08210 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/graphics/NopCanvas.java @@ -0,0 +1,303 @@ +/* + * 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.graphics; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.NinePatch; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Picture; +import android.graphics.PorterDuff.Mode; +import android.graphics.Rect; +import android.graphics.RectF; + +/** + * Canvas implementation that does not do any rendering + */ +public class NopCanvas extends Canvas { + public NopCanvas() { + super(); + } + + @Override + public boolean isHardwareAccelerated() { + // Return true so there is no allocations for the software renderer in the constructor + return true; + } + + @Override + public int save() { + return 0; + } + + @Override + public int save(int saveFlags) { + return 0; + } + + @Override + public int saveLayer(RectF bounds, Paint paint, int saveFlags) { + return 0; + } + + @Override + public int saveLayer(RectF bounds, Paint paint) { + return 0; + } + + @Override + public int saveLayer(float left, float top, float right, float bottom, Paint paint, + int saveFlags) { + return 0; + } + + @Override + public int saveLayer(float left, float top, float right, float bottom, Paint paint) { + return 0; + } + + @Override + public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags) { + return 0; + } + + @Override + public int saveLayerAlpha(RectF bounds, int alpha) { + return 0; + } + + @Override + public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha, + int saveFlags) { + return 0; + } + + @Override + public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha) { + return 0; + } + + @Override + public void restore() { + } + + @Override + public int getSaveCount() { + return 0; + } + + @Override + public void restoreToCount(int saveCount) { + } + + @Override + public void drawRGB(int r, int g, int b) { + } + + @Override + public void drawARGB(int a, int r, int g, int b) { + } + + @Override + public void drawColor(int color) { + } + + @Override + public void drawColor(int color, Mode mode) { + } + + @Override + public void drawPaint(Paint paint) { + } + + @Override + public void drawPoints(float[] pts, int offset, int count, Paint paint) { + } + + @Override + public void drawPoints(float[] pts, Paint paint) { + } + + @Override + public void drawPoint(float x, float y, Paint paint) { + } + + @Override + public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) { + } + + @Override + public void drawLines(float[] pts, int offset, int count, Paint paint) { + } + + @Override + public void drawLines(float[] pts, Paint paint) { + } + + @Override + public void drawRect(RectF rect, Paint paint) { + } + + @Override + public void drawRect(Rect r, Paint paint) { + } + + @Override + public void drawRect(float left, float top, float right, float bottom, Paint paint) { + } + + @Override + public void drawOval(RectF oval, Paint paint) { + } + + @Override + public void drawOval(float left, float top, float right, float bottom, Paint paint) { + } + + @Override + public void drawCircle(float cx, float cy, float radius, Paint paint) { + } + + @Override + public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, + Paint paint) { + } + + @Override + public void drawArc(float left, float top, float right, float bottom, float startAngle, + float sweepAngle, boolean useCenter, Paint paint) { + } + + @Override + public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) { + } + + @Override + public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, + Paint paint) { + } + + @Override + public void drawPath(Path path, Paint paint) { + } + + @Override + protected void throwIfCannotDraw(Bitmap bitmap) { + } + + @Override + public void drawPatch(NinePatch patch, Rect dst, Paint paint) { + } + + @Override + public void drawPatch(NinePatch patch, RectF dst, Paint paint) { + } + + @Override + public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { + } + + @Override + public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { + } + + @Override + public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { + } + + @Override + public void drawBitmap(int[] colors, int offset, int stride, float x, float y, int width, + int height, boolean hasAlpha, Paint paint) { + } + + @Override + public void drawBitmap(int[] colors, int offset, int stride, int x, int y, int width, + int height, boolean hasAlpha, Paint paint) { + } + + @Override + public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { + } + + @Override + public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, + int vertOffset, int[] colors, int colorOffset, Paint paint) { + } + + @Override + public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset, + float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices, + int indexOffset, int indexCount, Paint paint) { + } + + @Override + public void drawText(char[] text, int index, int count, float x, float y, Paint paint) { + } + + @Override + public void drawText(String text, float x, float y, Paint paint) { + } + + @Override + public void drawText(String text, int start, int end, float x, float y, Paint paint) { + } + + @Override + public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { + } + + @Override + public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount, + float x, float y, boolean isRtl, Paint paint) { + } + + @Override + public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, + float x, float y, boolean isRtl, Paint paint) { + } + + @Override + public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) { + } + + @Override + public void drawPosText(String text, float[] pos, Paint paint) { + } + + @Override + public void drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, + float vOffset, Paint paint) { + } + + @Override + public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) { + } + + @Override + public void drawPicture(Picture picture) { + } + + @Override + public void drawPicture(Picture picture, RectF dst) { + } + + @Override + public void drawPicture(Picture picture, Rect dst) { + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java index 7e5ae8d8c756..30317012fb52 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java @@ -63,4 +63,9 @@ public class WindowManagerImpl implements WindowManager { public void removeViewImmediate(View arg0) { // pass } + + @Override + public void requestAppKeyboardShortcuts( + KeyboardShortcutsReceiver receiver, int deviceId) { + } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java index af6ba2445309..2cdc647b7996 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java @@ -43,6 +43,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowCallback; import android.widget.ActionMenuPresenter; +import android.widget.ActionMenuView; import android.widget.Toolbar; import android.widget.Toolbar_Accessor; @@ -196,11 +197,16 @@ public abstract class FrameworkActionBarWrapper { @Override protected void inflateMenus() { super.inflateMenus(); - // Inflating the menus doesn't initialize the ActionMenuPresenter. Setting a fake menu - // and then setting it back does the trick. + // Inflating the menus isn't enough. ActionMenuPresenter needs to be initialized too. MenuBuilder menu = getMenuBuilder(); DecorToolbar decorToolbar = getDecorToolbar(); + // Setting a menu different from the above initializes the presenter. decorToolbar.setMenu(new MenuBuilder(getActionMenuContext()), null); + // ActionMenuView needs to be recreated to be able to set the menu back. + ActionMenuPresenter presenter = getActionMenuPresenter(); + if (presenter != null) { + presenter.setMenuView(new ActionMenuView(getPopupContext())); + } decorToolbar.setMenu(menu, null); } @@ -255,7 +261,7 @@ public abstract class FrameworkActionBarWrapper { @NonNull ActionBarView actionBarView) { super(context, callback, new WindowDecorActionBar(decorContentRoot)); mActionBarView = actionBarView; - mActionBar = ((WindowDecorActionBar) super.mActionBar); + mActionBar = (WindowDecorActionBar) super.mActionBar; mDecorContentRoot = decorContentRoot; } 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 baf2e2e11564..c59b1a66bb02 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 @@ -22,9 +22,11 @@ import com.android.layoutlib.bridge.util.SparseWeakArray; import android.annotation.Nullable; import android.util.SparseArray; +import java.io.PrintStream; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; /** * Manages native delegates. @@ -73,14 +75,14 @@ import java.util.List; public final class DelegateManager<T> { @SuppressWarnings("FieldCanBeLocal") private final Class<T> mClass; - private final SparseWeakArray<T> mDelegates = new SparseWeakArray<T>(); + private static final SparseWeakArray<Object> sDelegates = new SparseWeakArray<>(); /** list 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 final List<T> mJavaReferences = new ArrayList<T>(); - private int mDelegateCounter = 0; + private static final List<Object> sJavaReferences = new ArrayList<>(); + private static final AtomicLong sDelegateCounter = new AtomicLong(1); public DelegateManager(Class<T> theClass) { mClass = theClass; @@ -97,9 +99,12 @@ public final class DelegateManager<T> { * @return the delegate or null if not found. */ @Nullable - public synchronized T getDelegate(long native_object) { + public T getDelegate(long native_object) { if (native_object > 0) { - T delegate = mDelegates.get(native_object); + Object delegate; + synchronized (DelegateManager.class) { + delegate = sDelegates.get(native_object); + } if (Debug.DEBUG) { if (delegate == null) { @@ -109,7 +114,8 @@ public final class DelegateManager<T> { } assert delegate != null; - return delegate; + //noinspection unchecked + return (T)delegate; } return null; } @@ -119,12 +125,13 @@ public final class DelegateManager<T> { * @param newDelegate the delegate to add * @return a unique native int to identify the delegate */ - public synchronized long addNewDelegate(T newDelegate) { - long native_object = ++mDelegateCounter; - - mDelegates.put(native_object, newDelegate); - assert !mJavaReferences.contains(newDelegate); - mJavaReferences.add(newDelegate); + public long addNewDelegate(T newDelegate) { + long native_object = sDelegateCounter.getAndIncrement(); + synchronized (DelegateManager.class) { + sDelegates.put(native_object, newDelegate); + assert !sJavaReferences.contains(newDelegate); + sJavaReferences.add(newDelegate); + } if (Debug.DEBUG) { System.out.println( @@ -140,14 +147,23 @@ public final class DelegateManager<T> { * Removes the main reference on the given delegate. * @param native_object the native integer representing the delegate. */ - public synchronized void removeJavaReferenceFor(long native_object) { - T delegate = getDelegate(native_object); + public void removeJavaReferenceFor(long native_object) { + synchronized (DelegateManager.class) { + T delegate = getDelegate(native_object); - if (Debug.DEBUG) { - System.out.println("Removing main Java ref on " + mClass.getSimpleName() + - " with int " + native_object); + if (Debug.DEBUG) { + System.out.println("Removing main Java ref on " + mClass.getSimpleName() + + " with int " + native_object); + } + + sJavaReferences.remove(delegate); } + } - mJavaReferences.remove(delegate); + public synchronized static void dump(PrintStream out) { + for (Object reference : sJavaReferences) { + int idx = sDelegates.indexOfValue(reference); + out.printf("[%d] %s\n", sDelegates.keyAt(idx), reference.getClass().getSimpleName()); + } } } 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 cbd041593d95..1afd90d39f31 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 @@ -23,6 +23,7 @@ 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; import com.android.layoutlib.bridge.bars.AppCompatActionBar; import com.android.layoutlib.bridge.bars.BridgeActionBar; import com.android.layoutlib.bridge.bars.Config; @@ -232,8 +233,10 @@ class Layout extends RelativeLayout { private BridgeActionBar createActionBar(@NonNull BridgeContext context, @NonNull SessionParams params) { + boolean isMenu = "menu".equals(params.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG)); + BridgeActionBar actionBar; - if (mBuilder.isThemeAppCompat()) { + if (mBuilder.isThemeAppCompat() && !isMenu) { actionBar = new AppCompatActionBar(context, params); } else { actionBar = new FrameworkActionBar(context, params); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java index 4e4fcd0aff2d..0c537533479e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java @@ -122,7 +122,7 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso // build the context mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources, - mParams.getAssets(), mParams.getLayoutlibCallback(), getConfiguration(), + mParams.getAssets(), mParams.getLayoutlibCallback(), getConfiguration(mParams), mParams.getTargetSdkVersion(), mParams.isRtlSupported()); setUp(); @@ -130,7 +130,6 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso return SUCCESS.createResult(); } - /** * Prepares the scene for action. * <p> @@ -320,10 +319,11 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso } } - private Configuration getConfiguration() { + // VisibleForTesting + public static Configuration getConfiguration(RenderParams params) { Configuration config = new Configuration(); - HardwareConfig hardwareConfig = mParams.getHardwareConfig(); + HardwareConfig hardwareConfig = params.getHardwareConfig(); ScreenSize screenSize = hardwareConfig.getScreenSize(); if (screenSize != null) { @@ -392,7 +392,7 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso } else { config.screenLayout |= Configuration.SCREENLAYOUT_ROUND_UNDEFINED; } - String locale = getParams().getLocale(); + String locale = params.getLocale(); if (locale != null && !locale.isEmpty()) config.locale = new Locale(locale); // TODO: fill in more config info. 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 99af226e4c4c..a8077ccae01a 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 @@ -42,12 +42,14 @@ import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes; 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.impl.binding.FakeAdapter; import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter; import com.android.resources.ResourceType; import com.android.tools.layoutlib.java.System_Delegate; import com.android.util.Pair; +import com.android.util.PropertiesMap; import android.animation.AnimationThread; import android.animation.Animator; @@ -60,6 +62,7 @@ import android.app.Fragment_Delegate; import android.graphics.Bitmap; import android.graphics.Bitmap_Delegate; import android.graphics.Canvas; +import android.os.Looper; import android.preference.Preference_Delegate; import android.view.AttachInfo_Accessor; import android.view.BridgeInflater; @@ -112,6 +115,8 @@ import static com.android.layoutlib.bridge.util.ReflectionUtils.isInstanceOf; */ public class RenderSessionImpl extends RenderAction<SessionParams> { + private static final Canvas NOP_CANVAS = new NopCanvas(); + // scene state private RenderSession mScene; private BridgeXmlBlockParser mBlockParser; @@ -132,6 +137,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { private List<ViewInfo> mViewInfoList; private List<ViewInfo> mSystemViewInfoList; private Layout.Builder mLayoutBuilder; + private boolean mNewRenderSize; private static final class PostInflateException extends Exception { private static final long serialVersionUID = 1L; @@ -198,6 +204,88 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } /** + * Measures the the current layout if needed (see {@link #invalidateRenderingSize}). + */ + private void measure(@NonNull SessionParams params) { + // only do the screen measure when needed. + if (mMeasuredScreenWidth != -1) { + return; + } + + RenderingMode renderingMode = params.getRenderingMode(); + HardwareConfig hardwareConfig = params.getHardwareConfig(); + + mNewRenderSize = true; + mMeasuredScreenWidth = hardwareConfig.getScreenWidth(); + mMeasuredScreenHeight = hardwareConfig.getScreenHeight(); + + if (renderingMode != RenderingMode.NORMAL) { + int widthMeasureSpecMode = renderingMode.isHorizExpand() ? + MeasureSpec.UNSPECIFIED // this lets us know the actual needed size + : MeasureSpec.EXACTLY; + int heightMeasureSpecMode = renderingMode.isVertExpand() ? + MeasureSpec.UNSPECIFIED // this lets us know the actual needed size + : MeasureSpec.EXACTLY; + + // We used to compare the measured size of the content to the screen size but + // this does not work anymore due to the 2 following issues: + // - If the content is in a decor (system bar, title/action bar), the root view + // will not resize even with the UNSPECIFIED because of the embedded layout. + // - If there is no decor, but a dialog frame, then the dialog padding prevents + // comparing the size of the content to the screen frame (as it would not + // take into account the dialog padding). + + // The solution is to first get the content size in a normal rendering, inside + // the decor or the dialog padding. + // Then measure only the content with UNSPECIFIED to see the size difference + // and apply this to the screen size. + + // first measure the full layout, with EXACTLY to get the size of the + // content as it is inside the decor/dialog + @SuppressWarnings("deprecation") + Pair<Integer, Integer> exactMeasure = measureView( + mViewRoot, mContentRoot.getChildAt(0), + mMeasuredScreenWidth, MeasureSpec.EXACTLY, + mMeasuredScreenHeight, MeasureSpec.EXACTLY); + + // now measure the content only using UNSPECIFIED (where applicable, based on + // the rendering mode). This will give us the size the content needs. + @SuppressWarnings("deprecation") + Pair<Integer, Integer> result = measureView( + mContentRoot, mContentRoot.getChildAt(0), + mMeasuredScreenWidth, widthMeasureSpecMode, + mMeasuredScreenHeight, heightMeasureSpecMode); + + // now look at the difference and add what is needed. + if (renderingMode.isHorizExpand()) { + int measuredWidth = exactMeasure.getFirst(); + int neededWidth = result.getFirst(); + if (neededWidth > measuredWidth) { + mMeasuredScreenWidth += neededWidth - measuredWidth; + } + if (mMeasuredScreenWidth < measuredWidth) { + // If the screen width is less than the exact measured width, + // expand to match. + mMeasuredScreenWidth = measuredWidth; + } + } + + if (renderingMode.isVertExpand()) { + int measuredHeight = exactMeasure.getSecond(); + int neededHeight = result.getSecond(); + if (neededHeight > measuredHeight) { + mMeasuredScreenHeight += neededHeight - measuredHeight; + } + if (mMeasuredScreenHeight < measuredHeight) { + // If the screen height is less than the exact measured height, + // expand to match. + mMeasuredScreenHeight = measuredHeight; + } + } + } + } + + /** * Inflates the layout. * <p> * {@link #acquire(long)} must have been called before this. @@ -243,6 +331,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { setActiveToolbar(view, context, params); + measure(params); + measureView(mViewRoot, null /*measuredView*/, + mMeasuredScreenWidth, MeasureSpec.EXACTLY, + mMeasuredScreenHeight, MeasureSpec.EXACTLY); + mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight); + mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(), + false); + return SUCCESS.createResult(); } catch (PostInflateException e) { return ERROR_INFLATION.createResult(e.getMessage(), e); @@ -266,6 +362,34 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } /** + * Renders the given view hierarchy to the passed canvas and returns the result of the render + * operation. + * @param canvas an optional canvas to render the views to. If null, only the measure and + * layout steps will be executed. + */ + private static Result render(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot, + @Nullable Canvas canvas, int width, int height) { + // measure again with the size we need + // This must always be done before the call to layout + measureView(viewRoot, null /*measuredView*/, + width, MeasureSpec.EXACTLY, + height, MeasureSpec.EXACTLY); + + // now do the layout. + viewRoot.layout(0, 0, width, height); + handleScrolling(context, viewRoot); + + if (canvas == null) { + return SUCCESS.createResult(); + } + + AttachInfo_Accessor.dispatchOnPreDraw(viewRoot); + viewRoot.draw(canvas); + + return SUCCESS.createResult(); + } + + /** * Renders the scene. * <p> * {@link #acquire(long)} must have been called before this. @@ -290,100 +414,15 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { return ERROR_NOT_INFLATED.createResult(); } - RenderingMode renderingMode = params.getRenderingMode(); - HardwareConfig hardwareConfig = params.getHardwareConfig(); - - // only do the screen measure when needed. - boolean newRenderSize = false; - if (mMeasuredScreenWidth == -1) { - newRenderSize = true; - mMeasuredScreenWidth = hardwareConfig.getScreenWidth(); - mMeasuredScreenHeight = hardwareConfig.getScreenHeight(); - - if (renderingMode != RenderingMode.NORMAL) { - int widthMeasureSpecMode = renderingMode.isHorizExpand() ? - MeasureSpec.UNSPECIFIED // this lets us know the actual needed size - : MeasureSpec.EXACTLY; - int heightMeasureSpecMode = renderingMode.isVertExpand() ? - MeasureSpec.UNSPECIFIED // this lets us know the actual needed size - : MeasureSpec.EXACTLY; - - // We used to compare the measured size of the content to the screen size but - // this does not work anymore due to the 2 following issues: - // - If the content is in a decor (system bar, title/action bar), the root view - // will not resize even with the UNSPECIFIED because of the embedded layout. - // - If there is no decor, but a dialog frame, then the dialog padding prevents - // comparing the size of the content to the screen frame (as it would not - // take into account the dialog padding). - - // The solution is to first get the content size in a normal rendering, inside - // the decor or the dialog padding. - // Then measure only the content with UNSPECIFIED to see the size difference - // and apply this to the screen size. - - // first measure the full layout, with EXACTLY to get the size of the - // content as it is inside the decor/dialog - @SuppressWarnings("deprecation") - Pair<Integer, Integer> exactMeasure = measureView( - mViewRoot, mContentRoot.getChildAt(0), - mMeasuredScreenWidth, MeasureSpec.EXACTLY, - mMeasuredScreenHeight, MeasureSpec.EXACTLY); - - // now measure the content only using UNSPECIFIED (where applicable, based on - // the rendering mode). This will give us the size the content needs. - @SuppressWarnings("deprecation") - Pair<Integer, Integer> result = measureView( - mContentRoot, mContentRoot.getChildAt(0), - mMeasuredScreenWidth, widthMeasureSpecMode, - mMeasuredScreenHeight, heightMeasureSpecMode); - - // now look at the difference and add what is needed. - if (renderingMode.isHorizExpand()) { - int measuredWidth = exactMeasure.getFirst(); - int neededWidth = result.getFirst(); - if (neededWidth > measuredWidth) { - mMeasuredScreenWidth += neededWidth - measuredWidth; - } - if (mMeasuredScreenWidth < measuredWidth) { - // If the screen width is less than the exact measured width, - // expand to match. - mMeasuredScreenWidth = measuredWidth; - } - } - - if (renderingMode.isVertExpand()) { - int measuredHeight = exactMeasure.getSecond(); - int neededHeight = result.getSecond(); - if (neededHeight > measuredHeight) { - mMeasuredScreenHeight += neededHeight - measuredHeight; - } - if (mMeasuredScreenHeight < measuredHeight) { - // If the screen height is less than the exact measured height, - // expand to match. - mMeasuredScreenHeight = measuredHeight; - } - } - } - } - - // measure again with the size we need - // This must always be done before the call to layout - measureView(mViewRoot, null /*measuredView*/, - mMeasuredScreenWidth, MeasureSpec.EXACTLY, - mMeasuredScreenHeight, MeasureSpec.EXACTLY); - - // now do the layout. - mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight); - - handleScrolling(mViewRoot); + measure(params); + HardwareConfig hardwareConfig = params.getHardwareConfig(); + Result renderResult = SUCCESS.createResult(); if (params.isLayoutOnly()) { // delete the canvas and image to reset them on the next full rendering mImage = null; mCanvas = null; } else { - AttachInfo_Accessor.dispatchOnPreDraw(mViewRoot); - // draw the views // create the BufferedImage into which the layout will be rendered. boolean newImage = false; @@ -394,7 +433,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // it doesn't get cached. boolean disableBitmapCaching = Boolean.TRUE.equals(params.getFlag( RenderParamsFlags.FLAG_KEY_DISABLE_BITMAP_CACHING)); - if (newRenderSize || mCanvas == null || disableBitmapCaching) { + if (mNewRenderSize || mCanvas == null || disableBitmapCaching) { + mNewRenderSize = false; if (params.getImageFactory() != null) { mImage = params.getImageFactory().getImage( mMeasuredScreenWidth, @@ -445,6 +485,9 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { if (mElapsedFrameTimeNanos >= 0) { long initialTime = System_Delegate.nanoTime(); if (!mFirstFrameExecuted) { + // We need to run an initial draw call to initialize the animations + render(getContext(), mViewRoot, NOP_CANVAS, mMeasuredScreenWidth, mMeasuredScreenHeight); + // The first frame will initialize the animations Choreographer_Delegate.doFrame(initialTime); mFirstFrameExecuted = true; @@ -452,14 +495,15 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // Second frame will move the animations Choreographer_Delegate.doFrame(initialTime + mElapsedFrameTimeNanos); } - mViewRoot.draw(mCanvas); + renderResult = render(getContext(), mViewRoot, mCanvas, mMeasuredScreenWidth, + mMeasuredScreenHeight); } mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(), false); // success! - return SUCCESS.createResult(); + return renderResult; } catch (Throwable e) { // get the real cause of the exception. Throwable t = e; @@ -487,7 +531,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * @return the measured width/height if measuredView is non-null, null otherwise. */ @SuppressWarnings("deprecation") // For the use of Pair - private Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView, + private static Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView, int width, int widthMode, int height, int heightMode) { int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode); int h_spec = MeasureSpec.makeMeasureSpec(height, heightMode); @@ -1056,25 +1100,29 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } /** - * Set the vertical scroll position on all the components with the "scrollY" attribute. If the - * component supports nested scrolling attempt that first, then use the unconsumed scroll part - * to scroll the content in the component. + * Set the scroll position on all the components with the "scrollX" and "scrollY" attribute. If + * the component supports nested scrolling attempt that first, then use the unconsumed scroll + * part to scroll the content in the component. */ - private void handleScrolling(View view) { - BridgeContext context = getContext(); - int scrollPos = context.getScrollYPos(view); - if (scrollPos != 0) { + private static void handleScrolling(BridgeContext context, View view) { + int scrollPosX = context.getScrollXPos(view); + int scrollPosY = context.getScrollYPos(view); + if (scrollPosX != 0 || scrollPosY != 0) { if (view.isNestedScrollingEnabled()) { int[] consumed = new int[2]; - if (view.startNestedScroll(DesignLibUtil.SCROLL_AXIS_VERTICAL)) { - view.dispatchNestedPreScroll(0, scrollPos, consumed, null); - view.dispatchNestedScroll(consumed[0], consumed[1], 0, scrollPos, null); + int axis = scrollPosX != 0 ? View.SCROLL_AXIS_HORIZONTAL : 0; + axis |= scrollPosY != 0 ? View.SCROLL_AXIS_VERTICAL : 0; + if (view.startNestedScroll(axis)) { + view.dispatchNestedPreScroll(scrollPosX, scrollPosY, consumed, null); + view.dispatchNestedScroll(consumed[0], consumed[1], scrollPosX, scrollPosY, + null); view.stopNestedScroll(); - scrollPos -= consumed[1]; + scrollPosX -= consumed[0]; + scrollPosY -= consumed[1]; } } - if (scrollPos != 0) { - view.scrollBy(0, scrollPos); + if (scrollPosX != 0 || scrollPosY != 0) { + view.scrollTo(scrollPosX, scrollPosY); } } @@ -1084,7 +1132,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { ViewGroup group = (ViewGroup) view; for (int i = 0; i < group.getChildCount(); i++) { View child = group.getChildAt(i); - handleScrolling(child); + handleScrolling(context, child); } } @@ -1275,14 +1323,20 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { return null; } + ViewParent parent = view.getParent(); ViewInfo result; if (isContentFrame) { + // Account for parent scroll values when calculating the bounding box + int scrollX = parent != null ? ((View)parent).getScrollX() : 0; + int scrollY = parent != null ? ((View)parent).getScrollY() : 0; + // 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), - view.getLeft(), view.getTop() + offset, view.getRight(), - view.getBottom() + offset, view, view.getLayoutParams()); + -scrollX + view.getLeft(), -scrollY + view.getTop() + offset, + -scrollX + view.getRight(), -scrollY + view.getBottom() + offset, + view, view.getLayoutParams()); } else { // We are part of the system decor. SystemViewInfo r = new SystemViewInfo(view.getClass().getName(), @@ -1310,7 +1364,6 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // its parent is of type ActionMenuView. We can also check if the view is // instanceof ActionMenuItemView but that will fail for menus using // actionProviderClass. - ViewParent parent = view.getParent(); while (parent != mViewRoot && parent instanceof ViewGroup) { if (parent instanceof ActionMenuView) { r.setViewType(ViewType.ACTION_BAR_MENU); @@ -1385,8 +1438,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { return mSystemViewInfoList; } - public Map<String, String> getDefaultProperties(Object viewObject) { - return getContext().getDefaultPropMap(viewObject); + public Map<Object, PropertiesMap> getDefaultProperties() { + return getContext().getDefaultProperties(); } public void setScene(RenderSession session) { @@ -1398,6 +1451,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } public void dispose() { + boolean createdLooper = false; + if (Looper.myLooper() == null) { + // Detaching the root view from the window will try to stop any running animations. + // The stop method checks that it can run in the looper so, if there is no current + // looper, we create a temporary one to complete the shutdown. + Bridge.prepareThread(); + createdLooper = true; + } AttachInfo_Accessor.detachFromWindow(mViewRoot); if (mCanvas != null) { mCanvas.release(); @@ -1412,5 +1473,10 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { mImage = null; mViewRoot = null; mContentRoot = null; + + if (createdLooper) { + Bridge.cleanupThread(); + Choreographer_Delegate.dispose(); + } } } 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 c72eeb11c62d..a21de56066cb 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 @@ -25,6 +25,7 @@ import com.android.internal.util.XmlUtils; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; +import com.android.layoutlib.bridge.android.RenderParamsFlags; import com.android.ninepatch.NinePatch; import com.android.ninepatch.NinePatchChunk; import com.android.resources.Density; @@ -33,7 +34,11 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.res.ColorStateList; +import android.content.res.ComplexColor; +import android.content.res.ComplexColor_Accessor; +import android.content.res.GradientColor; import android.content.res.Resources.Theme; import android.graphics.Bitmap; import android.graphics.Bitmap_Delegate; @@ -47,6 +52,7 @@ import android.util.TypedValue; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; @@ -119,47 +125,99 @@ public final class ResourceHelper { throw new NumberFormatException(); } - public static ColorStateList getColorStateList(ResourceValue resValue, BridgeContext context) { + /** + * Returns a {@link ComplexColor} from the given {@link ResourceValue} + * + * @param resValue the value containing a color value or a file path to a complex color + * definition + * @param context the current context + * @param theme the theme to use when resolving the complex color + * @param allowGradients when false, only {@link ColorStateList} will be returned. If a {@link + * GradientColor} is found, null will be returned. + */ + @Nullable + private static ComplexColor getInternalComplexColor(@NonNull ResourceValue resValue, + @NonNull BridgeContext context, @Nullable Theme theme, boolean allowGradients) { String value = resValue.getValue(); - if (value != null && !RenderResources.REFERENCE_NULL.equals(value)) { - // first check if the value is a file (xml most likely) + if (value == null || RenderResources.REFERENCE_NULL.equals(value)) { + return null; + } + + XmlPullParser parser = null; + // first check if the value is a file (xml most likely) + Boolean psiParserSupport = context.getLayoutlibCallback().getFlag( + RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT); + if (psiParserSupport != null && psiParserSupport) { + parser = context.getLayoutlibCallback().getXmlFileParser(value); + } + if (parser == null) { File f = new File(value); if (f.isFile()) { + // let the framework inflate the color from the XML file, by + // providing an XmlPullParser try { - // let the framework inflate the ColorStateList from the XML file, by - // providing an XmlPullParser - XmlPullParser parser = ParserFactory.create(f); - - BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( - parser, context, resValue.isFramework()); - try { - return ColorStateList.createFromXml(context.getResources(), blockParser); - } finally { - blockParser.ensurePopped(); - } - } catch (XmlPullParserException e) { - Bridge.getLog().error(LayoutLog.TAG_BROKEN, - "Failed to configure parser for " + value, e, null /*data*/); - // we'll return null below. - } catch (Exception e) { - // this is an error and not warning since the file existence is - // checked before attempting to parse it. + parser = ParserFactory.create(f); + } catch (XmlPullParserException | FileNotFoundException e) { Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, "Failed to parse file " + value, e, null /*data*/); - - return null; } - } else { - // try to load the color state list from an int + } + } + + if (parser != null) { + try { + BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( + parser, context, resValue.isFramework()); try { - int color = ResourceHelper.getColor(value); - return ColorStateList.valueOf(color); - } catch (NumberFormatException e) { - Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, - "Failed to convert " + value + " into a ColorStateList", e, - null /*data*/); - return null; + // Advance the parser to the first element so we can detect if it's a + // color list or a gradient color + int type; + //noinspection StatementWithEmptyBody + while ((type = blockParser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + // Seek parser to start tag. + } + + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } + + final String name = blockParser.getName(); + if (allowGradients && "gradient".equals(name)) { + return ComplexColor_Accessor.createGradientColorFromXmlInner( + context.getResources(), + blockParser, blockParser, + theme); + } else if ("selector".equals(name)) { + return ComplexColor_Accessor.createColorStateListFromXmlInner( + context.getResources(), + blockParser, blockParser, + theme); + } + } finally { + blockParser.ensurePopped(); } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to configure parser for " + value, e, null /*data*/); + // we'll return null below. + } catch (Exception e) { + // this is an error and not warning since the file existence is + // checked before attempting to parse it. + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, + "Failed to parse file " + value, e, null /*data*/); + + return null; + } + } else { + // try to load the color state list from an int + try { + int color = getColor(value); + return ColorStateList.valueOf(color); + } catch (NumberFormatException e) { + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, + "Failed to convert " + value + " into a ColorStateList", e, + null /*data*/); } } @@ -167,6 +225,33 @@ public final class ResourceHelper { } /** + * Returns a {@link ColorStateList} from the given {@link ResourceValue} + * + * @param resValue the value containing a color value or a file path to a complex color + * definition + * @param context the current context + */ + @Nullable + public static ColorStateList getColorStateList(@NonNull ResourceValue resValue, + @NonNull BridgeContext context) { + return (ColorStateList) getInternalComplexColor(resValue, context, context.getTheme(), + false); + } + + /** + * Returns a {@link ComplexColor} from the given {@link ResourceValue} + * + * @param resValue the value containing a color value or a file path to a complex color + * definition + * @param context the current context + */ + @Nullable + public static ComplexColor getComplexColor(@NonNull ResourceValue resValue, + @NonNull BridgeContext context) { + return getInternalComplexColor(resValue, context, context.getTheme(), true); + } + + /** * Returns a drawable from the given value. * @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable, * or an hexadecimal color diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java index 9aab340909fd..c0ca1d5916e9 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java @@ -36,9 +36,9 @@ import android.widget.TextView; /** * A Helper class to do fake data binding in {@link AdapterView} objects. */ -@SuppressWarnings("deprecation") public class AdapterHelper { + @SuppressWarnings("deprecation") static Pair<View, Boolean> getView(AdapterItem item, AdapterItem parentItem, ViewGroup parent, LayoutlibCallback callback, ResourceReference adapterRef, boolean skipCallbackParser) { // we don't care about recycling here because we never scroll. @@ -114,7 +114,7 @@ public class AdapterHelper { if (value != null) { if (value.getClass() != ViewAttribute.IS_CHECKED.getAttributeClass()) { Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format( - "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s", + "Wrong Adapter Item value class for IS_CHECKED. Expected Boolean, got %s", value.getClass().getName()), null); } else { cb.setChecked((Boolean) value); @@ -134,7 +134,7 @@ public class AdapterHelper { if (value != null) { if (value.getClass() != ViewAttribute.SRC.getAttributeClass()) { Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format( - "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s", + "Wrong Adapter Item value class for SRC. Expected Boolean, got %s", value.getClass().getName()), null); } else { // FIXME diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java index 08a8faf0bbfb..161bf4155a1a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java @@ -27,8 +27,8 @@ import java.util.Map; public class DynamicIdMap { - private final Map<Pair<ResourceType, String>, Integer> mDynamicIds = new HashMap<Pair<ResourceType, String>, Integer>(); - private final SparseArray<Pair<ResourceType, String>> mRevDynamicIds = new SparseArray<Pair<ResourceType, String>>(); + private final Map<Pair<ResourceType, String>, Integer> mDynamicIds = new HashMap<>(); + private final SparseArray<Pair<ResourceType, String>> mRevDynamicIds = new SparseArray<>(); private int mDynamicSeed; public DynamicIdMap(int seed) { diff --git a/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java b/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java new file mode 100644 index 000000000000..6246ec1b8661 --- /dev/null +++ b/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java @@ -0,0 +1,64 @@ +/* + * 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 libcore.util; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of {@link NativeAllocationRegistry} + * + * Through the layoutlib_create tool, the original native methods of NativeAllocationRegistry 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 NativeAllocationRegistry class. + * + * @see DelegateManager + */ +public class NativeAllocationRegistry_Delegate { + + // ---- delegate manager ---- + private static final DelegateManager<NativeAllocationRegistry_Delegate> sManager = + new DelegateManager<>(NativeAllocationRegistry_Delegate.class); + + private final FreeFunction mFinalizer; + + private NativeAllocationRegistry_Delegate(FreeFunction finalizer) { + mFinalizer = finalizer; + } + + /** + * The result of this method should be cached by the class and reused. + */ + public static long createFinalizer(FreeFunction finalizer) { + return sManager.addNewDelegate(new NativeAllocationRegistry_Delegate(finalizer)); + } + + @LayoutlibDelegate + /*package*/ static void applyFreeFunction(long freeFunction, long nativePtr) { + NativeAllocationRegistry_Delegate delegate = sManager.getDelegate(freeFunction); + if (delegate != null) { + delegate.mFinalizer.free(nativePtr); + } + } + + public interface FreeFunction { + void free(long nativePtr); + } +} diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk index 5eef24adbdef..8a81d0b684db 100644 --- a/tools/layoutlib/bridge/tests/Android.mk +++ b/tools/layoutlib/bridge/tests/Android.mk @@ -19,7 +19,6 @@ include $(CLEAR_VARS) # Only compile source java files in this lib. LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_JAVA_RESOURCE_DIRS := res -LOCAL_JAVACFLAGS := -source 6 -target 6 LOCAL_MODULE := layoutlib-tests LOCAL_MODULE_TAGS := optional 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 d8ead233b4ec..0e788e0c8264 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 65d1dc5b1edb..bad296bf4a66 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/scrolled.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/scrolled.png Binary files differnew file mode 100644 index 000000000000..87bd5020bcd4 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/scrolled.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 72b87abfb917..55d6a20949a2 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/src/main/res/color/gradient.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml new file mode 100644 index 000000000000..fc0afa661751 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + + +<gradient xmlns:android="http://schemas.android.com/apk/res/android" + android:startX="10" + android:startY="10" + android:endX="50" + android:endY="50" + android:startColor="#ffff0000" + android:endColor="#ff00ff00" /> 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 ffc70dc1e519..32e6e732bb5a 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 @@ -53,6 +53,35 @@ android:trimPathStart="0.2" android:trimPathEnd="0.8" /> + + <!-- + Draw a line with gradient stroke color + --> + <path + android:strokeWidth="1" + android:strokeColor="#FF00FF" + android:fillColor="@color/gradient" + android:pathData="M-20,-20 l0, 10 l10, 0 l0, -10 l-10,0 " + /> + + <!-- + Draw squares with different fill types + --> + <path + android:fillType="evenOdd" + android:strokeWidth="1" + android:strokeColor="#AABBCC" + android:fillColor="#AAEFCC" + android:pathData="M-20,-40 l0, 10 l10, 0 l0, -10 l-10,0 m5,0 l0, 10 l10, 0 l0, -10 l-10,0" + /> + + <path + android:fillType="nonZero" + android:strokeWidth="1" + android:strokeColor="#AABBCC" + android:fillColor="#AAEFCC" + 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> </vector>
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml index 2da2cb983a6c..adb58a322abb 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml @@ -197,6 +197,14 @@ android:inputType="numberPassword" android:text="numeric password" /> + <ToggleButton + android:id="@+id/toggleButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignTop="@id/editText4" + android:layout_toEndOf="@id/editText4" + android:text="New ToggleButton" /> + <EditText android:id="@id/editText5" android:layout_width="wrap_content" diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml new file mode 100644 index 000000000000..a07498cd07b1 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml @@ -0,0 +1,57 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:scrollX="30px" + android:scrollY="90px"> + <LinearLayout + android:layout_width="60dp" + android:layout_height="60dp" + android:background="#FF0000" /> + <LinearLayout + android:layout_width="60dp" + android:layout_height="30dp" + android:background="#00FF00" /> + <LinearLayout + android:layout_width="60dp" + android:layout_height="60dp" + android:background="#0000FF" /> + <LinearLayout + android:layout_width="60dp" + android:layout_height="30dp" + android:background="#FF00FF" /> + <LinearLayout + android:layout_width="60dp" + android:layout_height="60dp" + android:background="#00FFFF" /> + + <LinearLayout + android:layout_width="200dp" + android:layout_height="400dp" + android:orientation="vertical" + android:scrollX="-90px" + android:scrollY="450px"> + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="60dp" + android:background="#FF0000" /> + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="30dp" + android:background="#00FF00" /> + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="60dp" + android:background="#0000FF" /> + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="30dp" + android:background="#FF00FF" /> + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="60dp" + android:background="#00FFFF" /> + </LinearLayout> + + +</LinearLayout> 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 16911bdd7c2a..8f9fa8a2bcf6 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 @@ -75,8 +75,12 @@ public class ImageUtils { } } else { - BufferedImage goldenImage = ImageIO.read(is); - assertImageSimilar(relativePath, goldenImage, thumbnail, MAX_PERCENT_DIFFERENCE); + try { + BufferedImage goldenImage = ImageIO.read(is); + assertImageSimilar(relativePath, goldenImage, thumbnail, MAX_PERCENT_DIFFERENCE); + } finally { + is.close(); + } } } 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 6b23da71861a..8f570aee96b7 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 @@ -21,6 +21,7 @@ import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.SessionParams; import com.android.ide.common.rendering.api.SessionParams.RenderingMode; +import com.android.ide.common.rendering.api.ViewInfo; import com.android.ide.common.resources.FrameworkResources; import com.android.ide.common.resources.ResourceItem; import com.android.ide.common.resources.ResourceRepository; @@ -28,29 +29,46 @@ import com.android.ide.common.resources.ResourceResolver; import com.android.ide.common.resources.configuration.FolderConfiguration; import com.android.io.FolderWrapper; 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.impl.DelegateManager; +import com.android.layoutlib.bridge.impl.RenderAction; import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator; import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback; 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.utils.ILogger; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.util.DisplayMetrics; import java.io.File; -import java.io.FileFilter; import java.io.IOException; +import java.lang.ref.WeakReference; import java.net.URL; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; -import java.util.Map; import java.util.concurrent.TimeUnit; +import com.google.android.collect.Lists; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** @@ -94,6 +112,21 @@ public class Main { private static ILogger sLogger; private static Bridge sBridge; + /** List of log messages generated by a render call. It can be used to find specific errors */ + private static ArrayList<String> sRenderMessages = Lists.newArrayList(); + + @Rule + public static TestWatcher sRenderMessageWatcher = new TestWatcher() { + @Override + protected void succeeded(Description description) { + // We only check error messages if the rest of the test case was successful. + if (!sRenderMessages.isEmpty()) { + fail(description.getMethodName() + " render error message: " + sRenderMessages.get + (0)); + } + } + }; + static { // Test that System Properties are properly set. PLATFORM_DIR = getPlatformDir(); @@ -157,13 +190,8 @@ public class Main { if (!host.isDirectory()) { return null; } - File[] hosts = host.listFiles(new FileFilter() { - @Override - public boolean accept(File path) { - return path.isDirectory() && (path.getName().startsWith("linux-") || path.getName() - .startsWith("darwin-")); - } - }); + File[] hosts = host.listFiles(path -> path.isDirectory() && + (path.getName().startsWith("linux-") || path.getName().startsWith("darwin-"))); for (File hostOut : hosts) { String platformDir = getPlatformDirFromHostOut(hostOut); if (platformDir != null) { @@ -181,12 +209,9 @@ public class Main { if (!sdkDir.isDirectory()) { return null; } - File[] sdkDirs = sdkDir.listFiles(new FileFilter() { - @Override - public boolean accept(File path) { - // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7) - return path.isDirectory() && path.getName().startsWith("sdk"); - } + File[] sdkDirs = sdkDir.listFiles(path -> { + // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7) + return path.isDirectory() && path.getName().startsWith("sdk"); }); for (File dir : sdkDirs) { String platformDir = getPlatformDirFromHostOutSdkSdk(dir); @@ -198,46 +223,34 @@ public class Main { } private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) { - File[] possibleSdks = sdkDir.listFiles(new FileFilter() { - @Override - public boolean accept(File path) { - return path.isDirectory() && path.getName().contains("android-sdk"); - } - }); + File[] possibleSdks = sdkDir.listFiles( + path -> path.isDirectory() && path.getName().contains("android-sdk")); for (File possibleSdk : possibleSdks) { File platformsDir = new File(possibleSdk, "platforms"); - File[] platforms = platformsDir.listFiles(new FileFilter() { - @Override - public boolean accept(File path) { - return path.isDirectory() && path.getName().startsWith("android-"); - } - }); + File[] platforms = platformsDir.listFiles( + path -> path.isDirectory() && path.getName().startsWith("android-")); if (platforms == null || platforms.length == 0) { continue; } - Arrays.sort(platforms, new Comparator<File>() { - // Codenames before ints. Higher APIs precede lower. - @Override - public int compare(File o1, File o2) { - final int MAX_VALUE = 1000; - String suffix1 = o1.getName().substring("android-".length()); - String suffix2 = o2.getName().substring("android-".length()); - int suff1, suff2; - try { - suff1 = Integer.parseInt(suffix1); - } catch (NumberFormatException e) { - suff1 = MAX_VALUE; - } - try { - suff2 = Integer.parseInt(suffix2); - } catch (NumberFormatException e) { - suff2 = MAX_VALUE; - } - if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) { - return suff2 - suff1; - } - return suffix2.compareTo(suffix1); + Arrays.sort(platforms, (o1, o2) -> { + final int MAX_VALUE = 1000; + String suffix1 = o1.getName().substring("android-".length()); + String suffix2 = o2.getName().substring("android-".length()); + int suff1, suff2; + try { + suff1 = Integer.parseInt(suffix1); + } catch (NumberFormatException e) { + suff1 = MAX_VALUE; + } + try { + suff2 = Integer.parseInt(suffix2); + } catch (NumberFormatException e) { + suff2 = MAX_VALUE; + } + if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) { + return suff2 - suff1; } + return suffix2.compareTo(suffix1); }); return platforms[0].getAbsolutePath(); } @@ -258,6 +271,7 @@ public class Main { return null; } } + /** * Initialize the bridge and the resource maps. */ @@ -287,6 +301,11 @@ public class Main { ConfigGenerator.getEnumMap(attrs), getLayoutLog()); } + @Before + public void beforeTestCase() { + sRenderMessages.clear(); + } + /** Test activity.xml */ @Test public void testActivity() throws ClassNotFoundException { @@ -297,6 +316,9 @@ public class Main { @Test public void testAllWidgets() throws ClassNotFoundException { renderAndVerify("allwidgets.xml", "allwidgets.png"); + + // We expect fidelity warnings for Path.isConvex. Fail for anything else. + sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported.")); } @Test @@ -307,6 +329,19 @@ public class Main { @Test public void testAllWidgetsTablet() throws ClassNotFoundException { renderAndVerify("allwidgets.xml", "allwidgets_tab.png", ConfigGenerator.NEXUS_7_2012); + + // We expect fidelity warnings for Path.isConvex. Fail for anything else. + 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(); + } } @AfterClass @@ -316,14 +351,18 @@ public class Main { sProjectResources = null; sLogger = null; sBridge = null; + + gc(); + + System.out.println("Objects still linked from the DelegateManager:"); + DelegateManager.dump(System.out); } /** Test expand_layout.xml */ @Test public void testExpand() throws ClassNotFoundException { // Create the layout pull parser. - LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" + - "expand_vert_layout.xml"); + LayoutPullParser parser = createLayoutPullParser("expand_vert_layout.xml"); // Create LayoutLibCallback. LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); layoutLibCallback.initResources(); @@ -335,7 +374,7 @@ public class Main { .setNavigation(Navigation.NONAV); SessionParams params = getSessionParams(parser, customConfigGenerator, - layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, + layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false, RenderingMode.V_SCROLL, 22); renderAndVerify(params, "expand_vert_layout.png"); @@ -345,10 +384,9 @@ public class Main { .setScreenHeight(300) .setDensity(Density.XHIGH) .setNavigation(Navigation.NONAV); - parser = new LayoutPullParser(APP_TEST_RES + "/layout/" + - "expand_horz_layout.xml"); + parser = createLayoutPullParser("expand_horz_layout.xml"); params = getSessionParams(parser, customConfigGenerator, - layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, + layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false, RenderingMode.H_SCROLL, 22); renderAndVerify(params, "expand_horz_layout.png"); @@ -358,8 +396,7 @@ public class Main { @Test public void testVectorAnimation() throws ClassNotFoundException { // Create the layout pull parser. - LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" + - "indeterminate_progressbar.xml"); + LayoutPullParser parser = createLayoutPullParser("indeterminate_progressbar.xml"); // Create LayoutLibCallback. LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); layoutLibCallback.initResources(); @@ -370,8 +407,7 @@ public class Main { renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2)); - parser = new LayoutPullParser(APP_TEST_RES + "/layout/" + - "indeterminate_progressbar.xml"); + parser = createLayoutPullParser("indeterminate_progressbar.xml"); params = getSessionParams(parser, ConfigGenerator.NEXUS_5, layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, RenderingMode.V_SCROLL, 22); @@ -385,8 +421,7 @@ public class Main { @Test public void testVectorDrawable() throws ClassNotFoundException { // Create the layout pull parser. - LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" + - "vector_drawable.xml"); + LayoutPullParser parser = createLayoutPullParser("vector_drawable.xml"); // Create LayoutLibCallback. LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); layoutLibCallback.initResources(); @@ -398,6 +433,72 @@ public class Main { renderAndVerify(params, "vector_drawable.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()); + layoutLibCallback.initResources(); + + SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, + layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, + RenderingMode.V_SCROLL, 22); + params.setForceNoDecor(); + params.setExtendedViewInfoMode(true); + + RenderResult result = renderAndVerify(params, "scrolled.png"); + assertNotNull(result); + assertTrue(result.getResult().isSuccess()); + + ViewInfo rootLayout = result.getRootViews().get(0); + // Check the first box in the main LinearLayout + assertEquals(-90, rootLayout.getChildren().get(0).getTop()); + assertEquals(-30, rootLayout.getChildren().get(0).getLeft()); + assertEquals(90, rootLayout.getChildren().get(0).getBottom()); + assertEquals(150, rootLayout.getChildren().get(0).getRight()); + + // Check the first box within the nested LinearLayout + assertEquals(-450, rootLayout.getChildren().get(5).getChildren().get(0).getTop()); + assertEquals(90, rootLayout.getChildren().get(5).getChildren().get(0).getLeft()); + assertEquals(-270, rootLayout.getChildren().get(5).getChildren().get(0).getBottom()); + assertEquals(690, rootLayout.getChildren().get(5).getChildren().get(0).getRight()); + } + + @Test + public void testGetResourceNameVariants() throws Exception { + // Setup + SessionParams params = createSessionParams("", ConfigGenerator.NEXUS_4); + AssetManager assetManager = AssetManager.getSystem(); + DisplayMetrics metrics = new DisplayMetrics(); + Configuration configuration = RenderAction.getConfiguration(params); + Resources resources = new Resources(assetManager, metrics, configuration); + resources.mLayoutlibCallback = params.getLayoutlibCallback(); + resources.mContext = + new BridgeContext(params.getProjectKey(), metrics, params.getResources(), + params.getAssets(), params.getLayoutlibCallback(), configuration, + params.getTargetSdkVersion(), params.isRtlSupported()); + // Test + assertEquals("android:style/ButtonBar", + resources.getResourceName(android.R.style.ButtonBar)); + assertEquals("android", resources.getResourcePackageName(android.R.style.ButtonBar)); + assertEquals("ButtonBar", resources.getResourceEntryName(android.R.style.ButtonBar)); + assertEquals("style", resources.getResourceTypeName(android.R.style.ButtonBar)); + int id = resources.mLayoutlibCallback.getResourceId(ResourceType.STRING, "app_name"); + assertEquals("com.android.layoutlib.test.myapplication:string/app_name", + resources.getResourceName(id)); + assertEquals("com.android.layoutlib.test.myapplication", + resources.getResourcePackageName(id)); + assertEquals("string", resources.getResourceTypeName(id)); + assertEquals("app_name", resources.getResourceEntryName(id)); + } + + @NonNull + private LayoutPullParser createLayoutPullParser(String layoutPath) { + return new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutPath); + } + /** * Create a new rendering session and test that rendering the given layout doesn't throw any * exceptions and matches the provided image. @@ -405,7 +506,8 @@ public class Main { * If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time indicates * how far in the future is. */ - private void renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos) + @Nullable + private RenderResult renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos) throws ClassNotFoundException { // TODO: Set up action bar handler properly to test menu rendering. // Create session params. @@ -428,48 +530,60 @@ public class Main { try { String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName; ImageUtils.requireSimilar(goldenImagePath, session.getImage()); + + return RenderResult.getFromSession(session); } catch (IOException e) { getLogger().error(e, e.getMessage()); } finally { session.dispose(); } + + return null; } /** * Create a new rendering session and test that rendering the given layout doesn't throw any * exceptions and matches the provided image. */ - private void renderAndVerify(SessionParams params, String goldenFileName) + @Nullable + private RenderResult renderAndVerify(SessionParams params, String goldenFileName) throws ClassNotFoundException { - renderAndVerify(params, goldenFileName, -1); + return renderAndVerify(params, goldenFileName, -1); } /** * Create a new rendering session and test that rendering the given layout on nexus 5 * doesn't throw any exceptions and matches the provided image. */ - private void renderAndVerify(String layoutFileName, String goldenFileName) + @Nullable + private RenderResult renderAndVerify(String layoutFileName, String goldenFileName) throws ClassNotFoundException { - renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5); + return renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5); } /** * Create a new rendering session and test that rendering the given layout on given device * doesn't throw any exceptions and matches the provided image. */ - private void renderAndVerify(String layoutFileName, String goldenFileName, + @Nullable + private RenderResult renderAndVerify(String layoutFileName, String goldenFileName, ConfigGenerator deviceConfig) throws ClassNotFoundException { + SessionParams params = createSessionParams(layoutFileName, deviceConfig); + return renderAndVerify(params, goldenFileName); + } + + private SessionParams createSessionParams(String layoutFileName, ConfigGenerator deviceConfig) + throws ClassNotFoundException { // Create the layout pull parser. - LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutFileName); + LayoutPullParser parser = createLayoutPullParser(layoutFileName); // Create LayoutLibCallback. LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); layoutLibCallback.initResources(); // TODO: Set up action bar handler properly to test menu rendering. // Create session params. - SessionParams params = getSessionParams(parser, deviceConfig, + return getSessionParams(parser, deviceConfig, layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22); - renderAndVerify(params, goldenFileName); } /** @@ -484,7 +598,7 @@ public class Main { sFrameworkRepo.getConfiguredResources(config), themeName, isProjectTheme); - return new SessionParams( + SessionParams sessionParams = new SessionParams( layoutParser, renderingMode, null /*used for caching*/, @@ -494,6 +608,8 @@ public class Main { 0, targetSdk, getLayoutLog()); + sessionParams.setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true); + return sessionParams; } private static LayoutLog getLayoutLog() { @@ -566,6 +682,6 @@ public class Main { } private static void failWithMsg(@NonNull String msgFormat, Object... args) { - fail(args == null ? "" : String.format(msgFormat, args)); + sRenderMessages.add(args == null ? msgFormat : String.format(msgFormat, args)); } } diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java new file mode 100644 index 000000000000..17b20f76d2cd --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java @@ -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. + */ + +package com.android.layoutlib.bridge.intensive; + +import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.ViewInfo; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +class RenderResult { + private final List<ViewInfo> mRootViews; + private final List<ViewInfo> mSystemViews; + private final Result mRenderResult; + + private RenderResult(@Nullable Result result, @Nullable List<ViewInfo> systemViewInfoList, + @Nullable List<ViewInfo> rootViewInfoList) { + mSystemViews = systemViewInfoList == null ? Collections.emptyList() : systemViewInfoList; + mRootViews = rootViewInfoList == null ? Collections.emptyList() : rootViewInfoList; + mRenderResult = result; + } + + @NonNull + static RenderResult getFromSession(@NonNull RenderSession session) { + return new RenderResult(session.getResult(), + new ArrayList<>(session.getSystemRootViews()), + new ArrayList<>(session.getRootViews())); + } + + @Nullable + Result getResult() { + return mRenderResult; + } + + @NonNull + public List<ViewInfo> getRootViews() { + return mRootViews; + } + + @NonNull + public List<ViewInfo> getSystemViews() { + return mSystemViews; + } +} 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 6c16ed01ca54..96ae523006b3 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 @@ -24,7 +24,9 @@ import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.ParserFactory; import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.SessionParams.Key; import com.android.ide.common.resources.IntArrayWrapper; +import com.android.layoutlib.bridge.android.RenderParamsFlags; import com.android.resources.ResourceType; import com.android.util.Pair; import com.android.utils.ILogger; @@ -176,4 +178,12 @@ public class LayoutLibTestCallback extends LayoutlibCallback { } }; } + + @Override + public <T> T getFlag(Key<T> key) { + if (key.equals(RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE)) { + return (T) PACKAGE_NAME; + } + return null; + } } 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 c79b66281efc..111049474461 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 @@ -56,9 +56,7 @@ public class LayoutPullParser extends KXmlParser implements ILayoutPullParser{ public LayoutPullParser(File layoutFile) { try { init(new FileInputStream(layoutFile)); - } catch (XmlPullParserException e) { - throw new IOError(e); - } catch (FileNotFoundException e) { + } catch (XmlPullParserException | FileNotFoundException e) { throw new IOError(e); } } diff --git a/tools/layoutlib/create/Android.mk b/tools/layoutlib/create/Android.mk index e6f0bc306b07..c7f2c4137687 100644 --- a/tools/layoutlib/create/Android.mk +++ b/tools/layoutlib/create/Android.mk @@ -20,7 +20,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_JAR_MANIFEST := manifest.txt LOCAL_STATIC_JAVA_LIBRARIES := \ - asm-4.0 + asm-5.0 LOCAL_MODULE := layoutlib_create diff --git a/tools/layoutlib/create/create.iml b/tools/layoutlib/create/create.iml index 9b18e73aae90..368b46bc92dc 100644 --- a/tools/layoutlib/create/create.iml +++ b/tools/layoutlib/create/create.iml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <module type="JAVA_MODULE" version="4"> - <component name="NewModuleRootManager" inherit-compiler-output="true"> + <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="true"> <exclude-output /> <content url="file://$MODULE_DIR$"> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> @@ -9,12 +9,12 @@ <sourceFolder url="file://$MODULE_DIR$/tests/mock_data" type="java-test-resource" /> <excludeFolder url="file://$MODULE_DIR$/.settings" /> </content> - <orderEntry type="inheritedJdk" /> + <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" /> <orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="module-library"> - <library name="asm-4.0"> + <library name="asm-5.0"> <CLASSES> - <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/asm/asm-4.0.jar!/" /> + <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/asm/asm-5.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> @@ -22,6 +22,6 @@ </SOURCES> </library> </orderEntry> - <orderEntry type="library" scope="TEST" name="JUnit4" level="application" /> + <orderEntry type="library" scope="TEST" name="junit" level="project" /> </component> </module>
\ No newline at end of file diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java index a6902a40b1a3..01c940ad665c 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java @@ -21,7 +21,6 @@ import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; @@ -44,7 +43,7 @@ public abstract class AbstractClassAdapter extends ClassVisitor { abstract String renameInternalType(String name); public AbstractClassAdapter(ClassVisitor cv) { - super(Opcodes.ASM4, cv); + super(Main.ASM_VERSION, cv); } /** @@ -177,17 +176,6 @@ public abstract class AbstractClassAdapter extends ClassVisitor { } } - /* Java 7 verifies the StackMapTable of a class if its version number is greater than 50.0. - * However, the check is disabled if the class version number is 50.0 or less. Generation - * of the StackMapTable requires a rewrite using the tree API of ASM. As a workaround, - * we rewrite the version number of the class to be 50.0 - * - * http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6693236 - */ - if (version > 50) { - version = 50; - } - super.visit(version, access, name, signature, superName, interfaces); } @@ -239,7 +227,7 @@ public abstract class AbstractClassAdapter extends ClassVisitor { * The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass). */ public RenameMethodAdapter(MethodVisitor mv) { - super(Opcodes.ASM4, mv); + super(Main.ASM_VERSION, mv); } @Override @@ -276,7 +264,8 @@ public abstract class AbstractClassAdapter extends ClassVisitor { } @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc) { + public void visitMethodInsn(int opcode, String owner, String name, String desc, + boolean itf) { // The owner sometimes turns out to be a type descriptor. We try to detect it and fix. if (owner.indexOf(';') > 0) { owner = renameTypeDesc(owner); @@ -285,7 +274,7 @@ public abstract class AbstractClassAdapter extends ClassVisitor { } desc = renameMethodDesc(desc); - super.visitMethodInsn(opcode, owner, name, desc); + super.visitMethodInsn(opcode, owner, name, desc, itf); } @Override @@ -330,7 +319,7 @@ public abstract class AbstractClassAdapter extends ClassVisitor { private final SignatureVisitor mSv; public RenameSignatureAdapter(SignatureVisitor sv) { - super(Opcodes.ASM4); + super(Main.ASM_VERSION); mSv = sv; } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java index c8b2b8448e21..11d4c81c6dc7 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java @@ -23,7 +23,6 @@ import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; @@ -65,7 +64,7 @@ public class AsmAnalyzer { /** Glob patterns of files to keep as is. */ private final String[] mIncludeFileGlobs; /** Internal names of classes that contain method calls that need to be rewritten. */ - private final Set<String> mReplaceMethodCallClasses = new HashSet<String>(); + private final Set<String> mReplaceMethodCallClasses = new HashSet<>(); /** * Creates a new analyzer. @@ -97,8 +96,8 @@ public class AsmAnalyzer { */ public void analyze() throws IOException, LogAbortException { - TreeMap<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>(); - Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + TreeMap<String, ClassReader> zipClasses = new TreeMap<>(); + Map<String, InputStream> filesFound = new TreeMap<>(); parseZip(mOsSourceJar, zipClasses, filesFound); mLog.info("Found %d classes in input JAR%s.", zipClasses.size(), @@ -189,7 +188,7 @@ public class AsmAnalyzer { */ Map<String, ClassReader> findIncludes(Map<String, ClassReader> zipClasses) throws LogAbortException { - TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>(); + TreeMap<String, ClassReader> found = new TreeMap<>(); mLog.debug("Find classes to include."); @@ -318,10 +317,10 @@ public class AsmAnalyzer { Map<String, ClassReader> findDeps(Map<String, ClassReader> zipClasses, Map<String, ClassReader> inOutKeepClasses) { - TreeMap<String, ClassReader> deps = new TreeMap<String, ClassReader>(); - TreeMap<String, ClassReader> new_deps = new TreeMap<String, ClassReader>(); - TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>(); - TreeMap<String, ClassReader> temp = new TreeMap<String, ClassReader>(); + TreeMap<String, ClassReader> deps = new TreeMap<>(); + TreeMap<String, ClassReader> new_deps = new TreeMap<>(); + TreeMap<String, ClassReader> new_keep = new TreeMap<>(); + TreeMap<String, ClassReader> temp = new TreeMap<>(); DependencyVisitor visitor = getVisitor(zipClasses, inOutKeepClasses, new_keep, @@ -399,7 +398,7 @@ public class AsmAnalyzer { Map<String, ClassReader> outKeep, Map<String,ClassReader> inDeps, Map<String,ClassReader> outDeps) { - super(Opcodes.ASM4); + super(Main.ASM_VERSION); mZipClasses = zipClasses; mInKeep = inKeep; mOutKeep = outKeep; @@ -439,7 +438,8 @@ public class AsmAnalyzer { try { // exclude classes that are part of the default JRE (the one executing this program) - if (getClass().getClassLoader().loadClass(className) != null) { + if (className.startsWith("java.") || + getClass().getClassLoader().loadClass(className) != null) { return; } } catch (ClassNotFoundException e) { @@ -557,7 +557,7 @@ public class AsmAnalyzer { private class MyFieldVisitor extends FieldVisitor { public MyFieldVisitor() { - super(Opcodes.ASM4); + super(Main.ASM_VERSION); } @Override @@ -630,7 +630,7 @@ public class AsmAnalyzer { private String mOwnerClass; public MyMethodVisitor(String ownerClass) { - super(Opcodes.ASM4); + super(Main.ASM_VERSION); mOwnerClass = ownerClass; } @@ -719,7 +719,8 @@ public class AsmAnalyzer { // instruction that invokes a method @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc) { + public void visitMethodInsn(int opcode, String owner, String name, String desc, + boolean itf) { // owner is the internal name of the method's owner class considerName(owner); @@ -779,7 +780,7 @@ public class AsmAnalyzer { private class MySignatureVisitor extends SignatureVisitor { public MySignatureVisitor() { - super(Opcodes.ASM4); + super(Main.ASM_VERSION); } // --------------------------------------------------- @@ -878,7 +879,7 @@ public class AsmAnalyzer { private class MyAnnotationVisitor extends AnnotationVisitor { public MyAnnotationVisitor() { - super(Opcodes.ASM4); + super(Main.ASM_VERSION); } // Visits a primitive value of an annotation 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 414b255be0b6..3b376123daa4 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 @@ -91,7 +91,7 @@ public class AsmGenerator { mLog = log; mOsDestJar = osDestJar; ArrayList<Class<?>> injectedClasses = - new ArrayList<Class<?>>(Arrays.asList(createInfo.getInjectedClasses())); + new ArrayList<>(Arrays.asList(createInfo.getInjectedClasses())); // Search for and add anonymous inner classes also. ListIterator<Class<?>> iter = injectedClasses.listIterator(); while (iter.hasNext()) { @@ -107,25 +107,25 @@ public class AsmGenerator { } } mInjectClasses = injectedClasses.toArray(new Class<?>[0]); - mStubMethods = new HashSet<String>(Arrays.asList(createInfo.getOverriddenMethods())); + mStubMethods = new HashSet<>(Arrays.asList(createInfo.getOverriddenMethods())); // Create the map/set of methods to change to delegates - mDelegateMethods = new HashMap<String, Set<String>>(); + mDelegateMethods = new HashMap<>(); addToMap(createInfo.getDelegateMethods(), mDelegateMethods); for (String className : createInfo.getDelegateClassNatives()) { className = binaryToInternalClassName(className); Set<String> methods = mDelegateMethods.get(className); if (methods == null) { - methods = new HashSet<String>(); + methods = new HashSet<>(); mDelegateMethods.put(className, methods); } methods.add(DelegateClassAdapter.ALL_NATIVES); } // Create the map of classes to rename. - mRenameClasses = new HashMap<String, String>(); - mClassesNotRenamed = new HashSet<String>(); + mRenameClasses = new HashMap<>(); + mClassesNotRenamed = new HashSet<>(); String[] renameClasses = createInfo.getRenamedClasses(); int n = renameClasses.length; for (int i = 0; i < n; i += 2) { @@ -138,7 +138,7 @@ public class AsmGenerator { } // Create a map of classes to be refactored. - mRefactorClasses = new HashMap<String, String>(); + mRefactorClasses = new HashMap<>(); String[] refactorClasses = createInfo.getJavaPkgClasses(); n = refactorClasses.length; for (int i = 0; i < n; i += 2) { @@ -149,7 +149,7 @@ public class AsmGenerator { } // create the map of renamed class -> return type of method to delete. - mDeleteReturns = new HashMap<String, Set<String>>(); + mDeleteReturns = new HashMap<>(); String[] deleteReturns = createInfo.getDeleteReturns(); Set<String> returnTypes = null; String renamedClass = null; @@ -172,12 +172,12 @@ public class AsmGenerator { // just a standard return type, we add it to the list. if (returnTypes == null) { - returnTypes = new HashSet<String>(); + returnTypes = new HashSet<>(); } returnTypes.add(binaryToInternalClassName(className)); } - mPromotedFields = new HashMap<String, Set<String>>(); + mPromotedFields = new HashMap<>(); addToMap(createInfo.getPromotedFields(), mPromotedFields); mInjectedMethodsMap = createInfo.getInjectedMethodsMap(); @@ -197,7 +197,7 @@ public class AsmGenerator { String methodOrFieldName = entry.substring(pos + 1); Set<String> set = map.get(className); if (set == null) { - set = new HashSet<String>(); + set = new HashSet<>(); map.put(className, set); } set.add(methodOrFieldName); @@ -247,7 +247,7 @@ public class AsmGenerator { /** Generates the final JAR */ public void generate() throws IOException { - TreeMap<String, byte[]> all = new TreeMap<String, byte[]>(); + TreeMap<String, byte[]> all = new TreeMap<>(); for (Class<?> clazz : mInjectClasses) { String name = classToEntryPath(clazz); @@ -314,7 +314,7 @@ public class AsmGenerator { * e.g. for the input "android.view.View" it returns "android/view/View.class" */ String classNameToEntryPath(String className) { - return className.replaceAll("\\.", "/").concat(".class"); + return className.replace('.', '/').concat(".class"); } /** diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java index 2c955fd9d9bb..4748a7c7b2f1 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java @@ -31,7 +31,7 @@ import org.objectweb.asm.Opcodes; */ public class ClassHasNativeVisitor extends ClassVisitor { public ClassHasNativeVisitor() { - super(Opcodes.ASM4); + super(Main.ASM_VERSION); } private boolean mHasNativeMethods = false; 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 e17f1f891ddd..061bed7b7740 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 @@ -111,7 +111,7 @@ public final class CreateInfo implements ICreateInfo { public Set<String> getExcludedClasses() { String[] refactoredClasses = getJavaPkgClasses(); int count = refactoredClasses.length / 2 + EXCLUDED_CLASSES.length; - Set<String> excludedClasses = new HashSet<String>(count); + Set<String> excludedClasses = new HashSet<>(count); for (int i = 0; i < refactoredClasses.length; i+=2) { excludedClasses.add(refactoredClasses[i]); } @@ -144,11 +144,8 @@ public final class CreateInfo implements ICreateInfo { InjectMethodRunnable.class, InjectMethodRunnables.class, /* Java package classes */ - AutoCloseable.class, - Objects.class, IntegralToString.class, UnsafeByteSequence.class, - Charsets.class, System_Delegate.class, LinkedHashMap_Delegate.class, }; @@ -171,6 +168,7 @@ public final class CreateInfo implements ICreateInfo { "android.content.res.Resources#getLayout", "android.content.res.Resources#getResourceEntryName", "android.content.res.Resources#getResourceName", + "android.content.res.Resources#getResourcePackageName", "android.content.res.Resources#getResourceTypeName", "android.content.res.Resources#getString", "android.content.res.Resources#getStringArray", @@ -192,8 +190,11 @@ public final class CreateInfo implements ICreateInfo { "android.content.res.TypedArray#obtain", "android.graphics.BitmapFactory#finishDecode", "android.graphics.BitmapFactory#setDensityFromOptions", + "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#useLastSeenTarget", "android.graphics.drawable.GradientDrawable#buildRing", + "android.graphics.FontFamily#addFont", "android.graphics.Typeface#getSystemFontConfigLocation", + "android.graphics.Typeface#makeFamilyFromParsed", "android.os.Handler#sendMessageAtTime", "android.os.HandlerThread#run", "android.preference.Preference#getView", @@ -205,15 +206,16 @@ public final class CreateInfo implements ICreateInfo { "android.view.Choreographer#scheduleVsyncLocked", "android.view.Display#updateDisplayInfoLocked", "android.view.Display#getWindowManager", + "android.view.HandlerActionQueue#postDelayed", "android.view.LayoutInflater#rInflate", "android.view.LayoutInflater#parseInclude", "android.view.View#getWindowToken", "android.view.View#isInEditMode", "android.view.ViewRootImpl#isInTouchMode", - "android.view.ViewRootImpl$RunQueue#postDelayed", "android.view.WindowManagerGlobal#getWindowManagerService", "android.view.inputmethod.InputMethodManager#getInstance", "android.view.MenuInflater#registerMenu", + "android.view.RenderNode#getMatrix", "android.view.RenderNode#nCreate", "android.view.RenderNode#nDestroyRenderNode", "android.view.RenderNode#nSetElevation", @@ -226,7 +228,6 @@ public final class CreateInfo implements ICreateInfo { "android.view.RenderNode#nGetTranslationZ", "android.view.RenderNode#nSetRotation", "android.view.RenderNode#nGetRotation", - "android.view.RenderNode#getMatrix", "android.view.RenderNode#nSetLeft", "android.view.RenderNode#nSetTop", "android.view.RenderNode#nSetRight", @@ -242,7 +243,6 @@ public final class CreateInfo implements ICreateInfo { "android.view.RenderNode#nGetScaleY", "android.view.RenderNode#nIsPivotExplicitlySet", "android.view.ViewGroup#drawChild", - "android.widget.TimePickerClockDelegate#getAmOrPmKeyCode", "com.android.internal.view.menu.MenuBuilder#createNewMenuItem", "com.android.internal.util.XmlUtils#convertValueToInt", "com.android.internal.textservice.ITextServicesManager$Stub#asInterface", @@ -250,7 +250,7 @@ public final class CreateInfo implements ICreateInfo { "libcore.io.MemoryMappedFile#mmapRO", "libcore.io.MemoryMappedFile#close", "libcore.io.MemoryMappedFile#bigEndianIterator", - "libcore.util.ZoneInfo$WallTime#createGregorianCalendar", + "libcore.util.NativeAllocationRegistry#applyFreeFunction", }; /** @@ -258,7 +258,6 @@ public final class CreateInfo implements ICreateInfo { */ public final static String[] DELEGATE_CLASS_NATIVES = new String[] { "android.animation.PropertyValuesHolder", - "android.graphics.AvoidXfermode", "android.graphics.Bitmap", "android.graphics.BitmapFactory", "android.graphics.BitmapShader", @@ -286,7 +285,6 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.PathDashPathEffect", "android.graphics.PathEffect", "android.graphics.PathMeasure", - "android.graphics.PixelXorXfermode", "android.graphics.PorterDuffColorFilter", "android.graphics.PorterDuffXfermode", "android.graphics.RadialGradient", @@ -297,11 +295,16 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.SweepGradient", "android.graphics.Typeface", "android.graphics.Xfermode", + "android.graphics.drawable.AnimatedVectorDrawable", + "android.graphics.drawable.VectorDrawable", "android.os.SystemClock", "android.os.SystemProperties", "android.text.AndroidBidi", "android.text.StaticLayout", + "android.util.PathParser", "android.view.Display", + "com.android.internal.util.VirtualRefBasePtr", + "com.android.internal.view.animation.NativeInterpolatorFactoryHelper", "libcore.icu.ICU", }; @@ -333,12 +336,9 @@ public final class CreateInfo implements ICreateInfo { */ private final static String[] JAVA_PKG_CLASSES = new String[] { - "java.lang.AutoCloseable", "com.android.tools.layoutlib.java.AutoCloseable", - "java.util.Objects", "com.android.tools.layoutlib.java.Objects", - "java.nio.charset.Charsets", "com.android.tools.layoutlib.java.Charsets", + "java.nio.charset.Charsets", "java.nio.charset.StandardCharsets", "java.lang.IntegralToString", "com.android.tools.layoutlib.java.IntegralToString", "java.lang.UnsafeByteSequence", "com.android.tools.layoutlib.java.UnsafeByteSequence", - "java.nio.charset.StandardCharsets", "com.android.tools.layoutlib.java.Charsets", // Use android.icu.text versions of DateFormat and SimpleDateFormat since the // original ones do not match the Android implementation "java.text.DateFormat", "android.icu.text.DateFormat", @@ -351,11 +351,13 @@ public final class CreateInfo implements ICreateInfo { "org.kxml2.io.KXmlParser" }; + /** + * List of fields for which we will update the visibility to be public. This is sometimes + * needed when access from the delegate classes is needed. + */ private final static String[] PROMOTED_FIELDS = new String[] { - "android.view.Choreographer#mLastFrameTimeNanos", - "android.widget.SimpleMonthView#mTitle", - "android.widget.SimpleMonthView#mCalendar", - "android.widget.SimpleMonthView#mDayOfWeekLabelCalendar" + "android.graphics.drawable.VectorDrawable#mVectorState", + "android.view.Choreographer#mLastFrameTimeNanos" }; /** diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java index 7ef75662aad4..cbb3a8abd692 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java @@ -60,7 +60,7 @@ public class DelegateClassAdapter extends ClassVisitor { ClassVisitor cv, String className, Set<String> delegateMethods) { - super(Opcodes.ASM4, cv); + super(Main.ASM_VERSION, cv); mLog = log; mClassName = className; mDelegateMethods = delegateMethods; diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java index cca9e574b7ea..da8babcbca83 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java @@ -124,7 +124,7 @@ class DelegateMethodAdapter extends MethodVisitor { String desc, boolean isStatic, boolean isStaticClass) { - super(Opcodes.ASM4); + super(Main.ASM_VERSION); mLog = log; mOrgWriter = mvOriginal; mDelWriter = mvDelegate; @@ -188,7 +188,7 @@ class DelegateMethodAdapter extends MethodVisitor { mDelWriter.visitLineNumber((Integer) p[0], (Label) p[1]); } - ArrayList<Type> paramTypes = new ArrayList<Type>(); + ArrayList<Type> paramTypes = new ArrayList<>(); String delegateClassName = mClassName + DELEGATE_SUFFIX; boolean pushedArg0 = false; int maxStack = 0; @@ -253,7 +253,8 @@ class DelegateMethodAdapter extends MethodVisitor { mDelWriter.visitMethodInsn(Opcodes.INVOKESTATIC, delegateClassName, mMethodName, - desc); + desc, + false); Type returnType = Type.getReturnType(mDesc); mDelWriter.visitInsn(returnType.getOpcode(Opcodes.IRETURN)); @@ -371,9 +372,9 @@ class DelegateMethodAdapter extends MethodVisitor { } @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc) { + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { if (mOrgWriter != null) { - mOrgWriter.visitMethodInsn(opcode, owner, name, desc); + mOrgWriter.visitMethodInsn(opcode, owner, name, desc, itf); } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java index 61b64a2e8e38..aa68ea099844 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java @@ -26,7 +26,6 @@ import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; @@ -82,7 +81,7 @@ public class DependencyFinder { Map<String, Set<String>> missing = findMissingClasses(deps, zipClasses.keySet()); - List<Map<String, Set<String>>> result = new ArrayList<Map<String,Set<String>>>(2); + List<Map<String, Set<String>>> result = new ArrayList<>(2); result.add(deps); result.add(missing); return result; @@ -151,7 +150,7 @@ public class DependencyFinder { * class name => ASM ClassReader. Class names are in the form "android.view.View". */ Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException { - TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>(); + TreeMap<String, ClassReader> classes = new TreeMap<>(); for (String jarPath : jarPathList) { ZipFile zip = new ZipFile(jarPath); @@ -202,7 +201,7 @@ public class DependencyFinder { // The dependencies that we'll collect. // It's a map Class name => uses class names. - Map<String, Set<String>> dependencyMap = new TreeMap<String, Set<String>>(); + Map<String, Set<String>> dependencyMap = new TreeMap<>(); DependencyVisitor visitor = getVisitor(); @@ -211,7 +210,7 @@ public class DependencyFinder { for (Entry<String, ClassReader> entry : zipClasses.entrySet()) { String name = entry.getKey(); - TreeSet<String> set = new TreeSet<String>(); + TreeSet<String> set = new TreeSet<>(); dependencyMap.put(name, set); visitor.setDependencySet(set); @@ -240,7 +239,7 @@ public class DependencyFinder { private Map<String, Set<String>> findMissingClasses( Map<String, Set<String>> deps, Set<String> zipClasses) { - Map<String, Set<String>> missing = new TreeMap<String, Set<String>>(); + Map<String, Set<String>> missing = new TreeMap<>(); for (Entry<String, Set<String>> entry : deps.entrySet()) { String name = entry.getKey(); @@ -250,7 +249,7 @@ public class DependencyFinder { // This dependency doesn't exist in the zip classes. Set<String> set = missing.get(dep); if (set == null) { - set = new TreeSet<String>(); + set = new TreeSet<>(); missing.put(dep, set); } set.add(name); @@ -284,7 +283,7 @@ public class DependencyFinder { * Creates a new visitor that will find all the dependencies for the visited class. */ public DependencyVisitor() { - super(Opcodes.ASM4); + super(Main.ASM_VERSION); } /** @@ -435,7 +434,7 @@ public class DependencyFinder { private class MyFieldVisitor extends FieldVisitor { public MyFieldVisitor() { - super(Opcodes.ASM4); + super(Main.ASM_VERSION); } @Override @@ -510,7 +509,7 @@ public class DependencyFinder { private class MyMethodVisitor extends MethodVisitor { public MyMethodVisitor() { - super(Opcodes.ASM4); + super(Main.ASM_VERSION); } @@ -598,7 +597,8 @@ public class DependencyFinder { // instruction that invokes a method @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc) { + public void visitMethodInsn(int opcode, String owner, String name, String desc, + boolean itf) { // owner is the internal name of the method's owner class if (!considerDesc(owner) && owner.indexOf('/') != -1) { @@ -654,7 +654,7 @@ public class DependencyFinder { private class MySignatureVisitor extends SignatureVisitor { public MySignatureVisitor() { - super(Opcodes.ASM4); + super(Main.ASM_VERSION); } // --------------------------------------------------- @@ -753,7 +753,7 @@ public class DependencyFinder { private class MyAnnotationVisitor extends AnnotationVisitor { public MyAnnotationVisitor() { - super(Opcodes.ASM4); + super(Main.ASM_VERSION); } // Visits a primitive value of an annotation diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java index 37fc096acb04..1941ab4e78d8 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java @@ -42,9 +42,9 @@ public class InjectMethodRunnables { mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", - "()Ljava/lang/Class;"); + "()Ljava/lang/Class;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader", - "()Ljava/lang/ClassLoader;"); + "()Ljava/lang/ClassLoader;", false); mv.visitInsn(ARETURN); mv.visitMaxs(1, 1); mv.visitEnd(); diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java index ea2b9c900ad0..c834808d950d 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java @@ -19,7 +19,6 @@ package com.android.tools.layoutlib.create; import com.android.tools.layoutlib.create.ICreateInfo.InjectMethodRunnable; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Opcodes; /** * Injects methods into some classes. @@ -29,7 +28,7 @@ public class InjectMethodsAdapter extends ClassVisitor { private final ICreateInfo.InjectMethodRunnable mRunnable; public InjectMethodsAdapter(ClassVisitor cv, InjectMethodRunnable runnable) { - super(Opcodes.ASM4, cv); + super(Main.ASM_VERSION, cv); mRunnable = runnable; } 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 383168face86..9bb91e599d77 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 @@ -16,6 +16,8 @@ package com.android.tools.layoutlib.create; +import org.objectweb.asm.Opcodes; + import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -52,13 +54,15 @@ public class Main { public boolean listOnlyMissingDeps = false; } + public static final int ASM_VERSION = Opcodes.ASM5; + public static final Options sOptions = new Options(); public static void main(String[] args) { Log log = new Log(); - ArrayList<String> osJarPath = new ArrayList<String>(); + ArrayList<String> osJarPath = new ArrayList<>(); String[] osDestJar = { null }; if (!processArgs(log, args, osJarPath, osDestJar)) { diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java index 6fc2b240b84f..faba4d727a06 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java @@ -36,41 +36,40 @@ public interface MethodListener { * @param isNative True if the method was a native method. * @param caller The calling object. Null for static methods, "this" for instance methods. */ - public void onInvokeV(String signature, boolean isNative, Object caller); + void onInvokeV(String signature, boolean isNative, Object caller); /** * Same as {@link #onInvokeV(String, boolean, Object)} but returns an integer or similar. * @see #onInvokeV(String, boolean, Object) * @return an integer, or a boolean, or a short or a byte. */ - public int onInvokeI(String signature, boolean isNative, Object caller); + int onInvokeI(String signature, boolean isNative, Object caller); /** * Same as {@link #onInvokeV(String, boolean, Object)} but returns a long. * @see #onInvokeV(String, boolean, Object) * @return a long. */ - public long onInvokeL(String signature, boolean isNative, Object caller); + long onInvokeL(String signature, boolean isNative, Object caller); /** * Same as {@link #onInvokeV(String, boolean, Object)} but returns a float. * @see #onInvokeV(String, boolean, Object) * @return a float. */ - public float onInvokeF(String signature, boolean isNative, Object caller); + float onInvokeF(String signature, boolean isNative, Object caller); /** * Same as {@link #onInvokeV(String, boolean, Object)} but returns a double. * @see #onInvokeV(String, boolean, Object) * @return a double. */ - public double onInvokeD(String signature, boolean isNative, Object caller); + double onInvokeD(String signature, boolean isNative, Object caller); /** * Same as {@link #onInvokeV(String, boolean, Object)} but returns an object. * @see #onInvokeV(String, boolean, Object) * @return an object. */ - public Object onInvokeA(String signature, boolean isNative, Object caller); + Object onInvokeA(String signature, boolean isNative, Object caller); } - diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java index 4c87b3c3562d..7ccafc3867e7 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java @@ -28,7 +28,7 @@ import java.util.HashMap; public final class OverrideMethod { /** Map of method overridden. */ - private static HashMap<String, MethodListener> sMethods = new HashMap<String, MethodListener>(); + private static HashMap<String, MethodListener> sMethods = new HashMap<>(); /** Default listener for all method not listed in sMethods. Nothing if null. */ private static MethodListener sDefaultListener = null; diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java index e4b70da2504f..05af0337a397 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java @@ -24,7 +24,6 @@ import java.util.Set; import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PROTECTED; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; -import static org.objectweb.asm.Opcodes.ASM4; /** * Promotes given fields to public visibility. @@ -35,7 +34,7 @@ public class PromoteFieldClassAdapter extends ClassVisitor { private static final int ACC_NOT_PUBLIC = ~(ACC_PRIVATE | ACC_PROTECTED); public PromoteFieldClassAdapter(ClassVisitor cv, Set<String> fieldNames) { - super(ASM4, cv); + super(Main.ASM_VERSION, cv); mFieldNames = fieldNames; } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java index 91161f573b33..024e32f2ddbc 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java @@ -16,9 +16,11 @@ package com.android.tools.layoutlib.create; +import java.util.Arrays; import java.util.HashMap; import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; public class RefactorClassAdapter extends AbstractClassAdapter { @@ -30,6 +32,14 @@ public class RefactorClassAdapter extends AbstractClassAdapter { } @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, + String[] exceptions) { + MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions); + + return new RefactorStackMapAdapter(mw); + } + + @Override protected String renameInternalType(String oldClassName) { if (oldClassName != null) { String newName = mRefactorClasses.get(oldClassName); @@ -46,4 +56,49 @@ public class RefactorClassAdapter extends AbstractClassAdapter { } return oldClassName; } + + /** + * A method visitor that renames all references from an old class name to a new class name in + * the stackmap of the method. + */ + private class RefactorStackMapAdapter extends MethodVisitor { + + private RefactorStackMapAdapter(MethodVisitor mv) { + super(Main.ASM_VERSION, mv); + } + + + private Object[] renameFrame(Object[] elements) { + if (elements == null) { + return null; + } + + // The input array cannot be modified. We only copy the source array on write + boolean copied = false; + for (int i = 0; i < elements.length; i++) { + if (!(elements[i] instanceof String)) { + continue; + } + + if (!copied) { + elements = Arrays.copyOf(elements, elements.length); + copied = true; + } + + String type = (String)elements[i]; + if (type.indexOf(';') > 0) { + elements[i] = renameTypeDesc(type); + } else { + elements[i] = renameInternalType(type); + } + } + + return elements; + } + + @Override + public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { + super.visitFrame(type, nLocal, renameFrame(local), nStack, renameFrame(stack)); + } + } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java index 5e47261ea89c..bf94415a4c6f 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java @@ -43,11 +43,11 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { * Descriptors for specialized versions {@link System#arraycopy} that are not present on the * Desktop VM. */ - private static Set<String> ARRAYCOPY_DESCRIPTORS = new HashSet<String>(Arrays.asList( + private static Set<String> ARRAYCOPY_DESCRIPTORS = new HashSet<>(Arrays.asList( "([CI[CII)V", "([BI[BII)V", "([SI[SII)V", "([II[III)V", "([JI[JII)V", "([FI[FII)V", "([DI[DII)V", "([ZI[ZII)V")); - private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<MethodReplacer>(5); + private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<>(5); private static final String ANDROID_LOCALE_CLASS = "com/android/layoutlib/bridge/android/AndroidLocale"; @@ -232,7 +232,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { private final String mOriginalClassName; public ReplaceMethodCallsAdapter(ClassVisitor cv, String originalClassName) { - super(Opcodes.ASM4, cv); + super(Main.ASM_VERSION, cv); mOriginalClassName = originalClassName; } @@ -245,11 +245,12 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { private class MyMethodVisitor extends MethodVisitor { public MyMethodVisitor(MethodVisitor mv) { - super(Opcodes.ASM4, mv); + super(Main.ASM_VERSION, mv); } @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc) { + public void visitMethodInsn(int opcode, String owner, String name, String desc, + boolean itf) { for (MethodReplacer replacer : METHOD_REPLACERS) { if (replacer.isNeeded(owner, name, desc, mOriginalClassName)) { MethodInformation mi = new MethodInformation(opcode, owner, name, desc); @@ -261,7 +262,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { break; } } - super.visitMethodInsn(opcode, owner, name, desc); + super.visitMethodInsn(opcode, owner, name, desc, itf); } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java index 416b73a43c11..4ba7237d7991 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java @@ -50,7 +50,7 @@ class StubMethodAdapter extends MethodVisitor { public StubMethodAdapter(MethodVisitor mv, String methodName, Type returnType, String invokeSignature, boolean isStatic, boolean isNative) { - super(Opcodes.ASM4); + super(Main.ASM_VERSION); mParentVisitor = mv; mReturnType = returnType; mInvokeSignature = invokeSignature; @@ -82,7 +82,8 @@ class StubMethodAdapter extends MethodVisitor { mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "com/android/tools/layoutlib/create/OverrideMethod", "invokeV", - "(Ljava/lang/String;ZLjava/lang/Object;)V"); + "(Ljava/lang/String;ZLjava/lang/Object;)V", + false); mParentVisitor.visitInsn(Opcodes.RETURN); break; case Type.BOOLEAN: @@ -93,7 +94,8 @@ class StubMethodAdapter extends MethodVisitor { mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "com/android/tools/layoutlib/create/OverrideMethod", "invokeI", - "(Ljava/lang/String;ZLjava/lang/Object;)I"); + "(Ljava/lang/String;ZLjava/lang/Object;)I", + false); switch(sort) { case Type.BOOLEAN: Label l1 = new Label(); @@ -101,6 +103,7 @@ class StubMethodAdapter extends MethodVisitor { mParentVisitor.visitInsn(Opcodes.ICONST_1); mParentVisitor.visitInsn(Opcodes.IRETURN); mParentVisitor.visitLabel(l1); + mParentVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); mParentVisitor.visitInsn(Opcodes.ICONST_0); break; case Type.CHAR: @@ -119,21 +122,24 @@ class StubMethodAdapter extends MethodVisitor { mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "com/android/tools/layoutlib/create/OverrideMethod", "invokeL", - "(Ljava/lang/String;ZLjava/lang/Object;)J"); + "(Ljava/lang/String;ZLjava/lang/Object;)J", + false); mParentVisitor.visitInsn(Opcodes.LRETURN); break; case Type.FLOAT: mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "com/android/tools/layoutlib/create/OverrideMethod", "invokeF", - "(Ljava/lang/String;ZLjava/lang/Object;)F"); + "(Ljava/lang/String;ZLjava/lang/Object;)F", + false); mParentVisitor.visitInsn(Opcodes.FRETURN); break; case Type.DOUBLE: mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "com/android/tools/layoutlib/create/OverrideMethod", "invokeD", - "(Ljava/lang/String;ZLjava/lang/Object;)D"); + "(Ljava/lang/String;ZLjava/lang/Object;)D", + false); mParentVisitor.visitInsn(Opcodes.DRETURN); break; case Type.ARRAY: @@ -141,7 +147,8 @@ class StubMethodAdapter extends MethodVisitor { mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "com/android/tools/layoutlib/create/OverrideMethod", "invokeA", - "(Ljava/lang/String;ZLjava/lang/Object;)Ljava/lang/Object;"); + "(Ljava/lang/String;ZLjava/lang/Object;)Ljava/lang/Object;", + false); mParentVisitor.visitTypeInsn(Opcodes.CHECKCAST, mReturnType.getInternalName()); mParentVisitor.visitInsn(Opcodes.ARETURN); break; @@ -282,9 +289,9 @@ class StubMethodAdapter extends MethodVisitor { } @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc) { + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { if (mIsInitMethod) { - mParentVisitor.visitMethodInsn(opcode, owner, name, desc); + mParentVisitor.visitMethodInsn(opcode, owner, name, desc, itf); } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java index d9ecf980658c..a28ae694246d 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java @@ -49,7 +49,7 @@ class TransformClassAdapter extends ClassVisitor { public TransformClassAdapter(Log logger, Set<String> stubMethods, Set<String> deleteReturns, String className, ClassVisitor cv, boolean stubNativesOnly) { - super(Opcodes.ASM4, cv); + super(Main.ASM_VERSION, cv); mLog = logger; mStubMethods = stubMethods; mClassName = className; diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java index ed2c128e1900..7d6c4ec1ad9e 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java @@ -28,4 +28,5 @@ public interface AutoCloseable { /** * Closes the object and release any system resources it holds. */ - void close() throws Exception; } + void close() throws Exception; +} diff --git a/tools/layoutlib/create/tests/Android.mk b/tools/layoutlib/create/tests/Android.mk index c197d5733658..dafb9c6f9402 100644 --- a/tools/layoutlib/create/tests/Android.mk +++ b/tools/layoutlib/create/tests/Android.mk @@ -24,7 +24,7 @@ LOCAL_MODULE := layoutlib-create-tests LOCAL_MODULE_TAGS := optional LOCAL_JAVA_LIBRARIES := layoutlib_create junit -LOCAL_STATIC_JAVA_LIBRARIES := asm-4.0 +LOCAL_STATIC_JAVA_LIBRARIES := asm-5.0 include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java index 78e2c4899c1b..f86917a8139c 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java @@ -17,13 +17,8 @@ package com.android.tools.layoutlib.create; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.objectweb.asm.ClassReader; @@ -32,11 +27,15 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; -import java.util.HashSet; +import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + /** * Unit tests for some methods of {@link AsmAnalyzer}. */ @@ -51,26 +50,22 @@ public class AsmAnalyzerTest { mLog = new MockLog(); URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar"); - mOsJarPath = new ArrayList<String>(); + mOsJarPath = new ArrayList<>(); + //noinspection ConstantConditions mOsJarPath.add(url.getFile()); - Set<String> excludeClasses = new HashSet<String>(1); - excludeClasses.add("java.lang.JavaClass"); + Set<String> excludeClasses = Collections.singleton("java.lang.JavaClass"); String[] includeFiles = new String[]{"mock_android/data/data*"}; mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */, null /* deriveFrom */, null /* includeGlobs */, excludeClasses, includeFiles); } - @After - public void tearDown() throws Exception { - } - @Test public void testParseZip() throws IOException { - Map<String, ClassReader> map = new TreeMap<String, ClassReader>(); - Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + Map<String, ClassReader> map = new TreeMap<>(); + Map<String, InputStream> filesFound = new TreeMap<>(); mAa.parseZip(mOsJarPath, map, filesFound); @@ -101,11 +96,11 @@ public class AsmAnalyzerTest { @Test public void testFindClass() throws IOException, LogAbortException { - Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>(); - Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + Map<String, ClassReader> zipClasses = new TreeMap<>(); + Map<String, InputStream> filesFound = new TreeMap<>(); mAa.parseZip(mOsJarPath, zipClasses, filesFound); - TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>(); + TreeMap<String, ClassReader> found = new TreeMap<>(); ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams", zipClasses, found); @@ -120,11 +115,11 @@ public class AsmAnalyzerTest { @Test public void testFindGlobs() throws IOException, LogAbortException { - Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>(); - Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + Map<String, ClassReader> zipClasses = new TreeMap<>(); + Map<String, InputStream> filesFound = new TreeMap<>(); mAa.parseZip(mOsJarPath, zipClasses, filesFound); - TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>(); + TreeMap<String, ClassReader> found = new TreeMap<>(); // this matches classes, a package match returns nothing found.clear(); @@ -183,11 +178,11 @@ public class AsmAnalyzerTest { @Test public void testFindClassesDerivingFrom() throws LogAbortException, IOException { - Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>(); - Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + Map<String, ClassReader> zipClasses = new TreeMap<>(); + Map<String, InputStream> filesFound = new TreeMap<>(); mAa.parseZip(mOsJarPath, zipClasses, filesFound); - TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>(); + TreeMap<String, ClassReader> found = new TreeMap<>(); mAa.findClassesDerivingFrom("mock_android.view.View", zipClasses, found); @@ -209,14 +204,14 @@ public class AsmAnalyzerTest { @Test public void testDependencyVisitor() throws IOException, LogAbortException { - Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>(); - Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + Map<String, ClassReader> zipClasses = new TreeMap<>(); + Map<String, InputStream> filesFound = new TreeMap<>(); mAa.parseZip(mOsJarPath, zipClasses, filesFound); - TreeMap<String, ClassReader> keep = new TreeMap<String, ClassReader>(); - TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>(); - TreeMap<String, ClassReader> in_deps = new TreeMap<String, ClassReader>(); - TreeMap<String, ClassReader> out_deps = new TreeMap<String, ClassReader>(); + TreeMap<String, ClassReader> keep = new TreeMap<>(); + TreeMap<String, ClassReader> new_keep = new TreeMap<>(); + TreeMap<String, ClassReader> in_deps = new TreeMap<>(); + TreeMap<String, ClassReader> out_deps = new TreeMap<>(); ClassReader cr = mAa.findClass("mock_android.widget.LinearLayout", zipClasses, keep); DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps); 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 8a2235b8526c..c4dd7eeafbba 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 @@ -18,12 +18,6 @@ package com.android.tools.layoutlib.create; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -31,7 +25,6 @@ import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import java.io.ByteArrayOutputStream; @@ -44,7 +37,6 @@ import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; -import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -52,11 +44,18 @@ import java.util.TreeMap; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + /** * Unit tests for some methods of {@link AsmGenerator}. */ public class AsmGeneratorTest { + private static final String[] EMPTY_STRING_ARRAY = new String[0]; private MockLog mLog; private ArrayList<String> mOsJarPath; private String mOsDestJar; @@ -70,7 +69,8 @@ public class AsmGeneratorTest { mLog = new MockLog(); URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar"); - mOsJarPath = new ArrayList<String>(); + mOsJarPath = new ArrayList<>(); + //noinspection ConstantConditions mOsJarPath.add(url.getFile()); mTempFile = File.createTempFile("mock", ".jar"); @@ -98,18 +98,18 @@ public class AsmGeneratorTest { @Override public String[] getDelegateMethods() { - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getDelegateClassNatives() { - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getOverriddenMethods() { // methods to force override - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override @@ -123,7 +123,7 @@ public class AsmGeneratorTest { @Override public String[] getJavaPkgClasses() { - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override @@ -134,17 +134,17 @@ public class AsmGeneratorTest { @Override public String[] getDeleteReturns() { // methods deleted from their return type. - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getPromotedFields() { - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public Map<String, InjectMethodRunnable> getInjectedMethodsMap() { - return new HashMap<String, InjectMethodRunnable>(0); + return Collections.emptyMap(); } }; @@ -155,7 +155,7 @@ public class AsmGeneratorTest { new String[] { // include classes "**" }, - new HashSet<String>(0) /* excluded classes */, + Collections.<String>emptySet() /* excluded classes */, new String[]{} /* include files */); aa.analyze(); agen.generate(); @@ -178,24 +178,24 @@ public class AsmGeneratorTest { @Override public String[] getDelegateMethods() { - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getDelegateClassNatives() { - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getOverriddenMethods() { // methods to force override - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getRenamedClasses() { // classes to rename (so that we can replace them) - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override @@ -214,17 +214,17 @@ public class AsmGeneratorTest { @Override public String[] getDeleteReturns() { // methods deleted from their return type. - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getPromotedFields() { - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public Map<String, InjectMethodRunnable> getInjectedMethodsMap() { - return new HashMap<String, InjectMethodRunnable>(0); + return Collections.emptyMap(); } }; @@ -235,14 +235,14 @@ public class AsmGeneratorTest { new String[] { // include classes "**" }, - new HashSet<String>(1), + Collections.<String>emptySet(), new String[] { /* include files */ "mock_android/data/data*" }); aa.analyze(); agen.generate(); - Map<String, ClassReader> output = new TreeMap<String, ClassReader>(); - Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + Map<String, ClassReader> output = new TreeMap<>(); + Map<String, InputStream> filesFound = new TreeMap<>(); parseZip(mOsDestJar, output, filesFound); boolean injectedClassFound = false; for (ClassReader cr: output.values()) { @@ -265,35 +265,35 @@ public class AsmGeneratorTest { @Override public String[] getDelegateMethods() { - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getDelegateClassNatives() { - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getOverriddenMethods() { // methods to force override - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getRenamedClasses() { // classes to rename (so that we can replace them) - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getJavaPkgClasses() { // classes to refactor (so that we can replace them) - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public Set<String> getExcludedClasses() { - Set<String> set = new HashSet<String>(2); + Set<String> set = new HashSet<>(2); set.add("mock_android.dummy.InnerTest"); set.add("java.lang.JavaClass"); return set; @@ -302,17 +302,17 @@ public class AsmGeneratorTest { @Override public String[] getDeleteReturns() { // methods deleted from their return type. - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getPromotedFields() { - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public Map<String, InjectMethodRunnable> getInjectedMethodsMap() { - return new HashMap<String, InjectMethodRunnable>(0); + return Collections.emptyMap(); } }; @@ -329,8 +329,8 @@ public class AsmGeneratorTest { }); aa.analyze(); agen.generate(); - Map<String, ClassReader> output = new TreeMap<String, ClassReader>(); - Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + Map<String, ClassReader> output = new TreeMap<>(); + Map<String, InputStream> filesFound = new TreeMap<>(); parseZip(mOsDestJar, output, filesFound); for (String s : output.keySet()) { assertFalse(excludedClasses.contains(s)); @@ -351,55 +351,52 @@ public class AsmGeneratorTest { @Override public String[] getDelegateMethods() { - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getDelegateClassNatives() { - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getOverriddenMethods() { // methods to force override - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getRenamedClasses() { // classes to rename (so that we can replace them) - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getJavaPkgClasses() { // classes to refactor (so that we can replace them) - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public Set<String> getExcludedClasses() { - return new HashSet<String>(0); + return Collections.emptySet(); } @Override public String[] getDeleteReturns() { // methods deleted from their return type. - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public String[] getPromotedFields() { - return new String[0]; + return EMPTY_STRING_ARRAY; } @Override public Map<String, InjectMethodRunnable> getInjectedMethodsMap() { - HashMap<String, InjectMethodRunnable> map = - new HashMap<String, InjectMethodRunnable>(1); - map.put("mock_android.util.EmptyArray", + return Collections.singletonMap("mock_android.util.EmptyArray", InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER); - return map; } }; @@ -415,8 +412,8 @@ public class AsmGeneratorTest { }); aa.analyze(); agen.generate(); - Map<String, ClassReader> output = new TreeMap<String, ClassReader>(); - Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + Map<String, ClassReader> output = new TreeMap<>(); + Map<String, InputStream> filesFound = new TreeMap<>(); parseZip(mOsDestJar, output, filesFound); final String modifiedClass = "mock_android.util.EmptyArray"; final String modifiedClassPath = modifiedClass.replace('.', '/').concat(".class"); @@ -424,11 +421,8 @@ public class AsmGeneratorTest { ZipEntry entry = zipFile.getEntry(modifiedClassPath); assertNotNull(entry); final byte[] bytes; - final InputStream inputStream = zipFile.getInputStream(entry); - try { + try (InputStream inputStream = zipFile.getInputStream(entry)) { bytes = getByteArray(inputStream); - } finally { - inputStream.close(); } ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) { @Override @@ -489,7 +483,7 @@ public class AsmGeneratorTest { boolean mInjectedClassFound = false; TestClassVisitor() { - super(Opcodes.ASM4); + super(Main.ASM_VERSION); } @Override @@ -514,7 +508,7 @@ public class AsmGeneratorTest { public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); - return new MethodVisitor(Opcodes.ASM4, mv) { + return new MethodVisitor(Main.ASM_VERSION, mv) { @Override public void visitFieldInsn(int opcode, String owner, String name, @@ -540,10 +534,10 @@ public class AsmGeneratorTest { @Override public void visitMethodInsn(int opcode, String owner, String name, - String desc) { + String desc, boolean itf) { assertTrue(!getBase(owner).equals(JAVA_CLASS_NAME)); assertTrue(testType(Type.getType(desc))); - super.visitMethodInsn(opcode, owner, name, desc); + super.visitMethodInsn(opcode, owner, name, desc, itf); } }; diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java index 0135c40e71ab..0cdcdc0c1235 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java @@ -60,7 +60,7 @@ public class ClassHasNativeVisitorTest { * Overrides {@link ClassHasNativeVisitor} to collec the name of the native methods found. */ private static class MockClassHasNativeVisitor extends ClassHasNativeVisitor { - private ArrayList<String> mMethodsFound = new ArrayList<String>(); + private ArrayList<String> mMethodsFound = new ArrayList<>(); public String[] getMethodsFound() { return mMethodsFound.toArray(new String[mMethodsFound.size()]); diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java index e37a09b348b8..afaa3997adf7 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java @@ -53,12 +53,10 @@ public class DelegateClassAdapterTest { private MockLog mLog; - private static final String NATIVE_CLASS_NAME = ClassWithNative.class.getCanonicalName(); - private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName(); - private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" + - InnerClass.class.getSimpleName(); - private static final String STATIC_INNER_CLASS_NAME = - OuterClass.class.getCanonicalName() + "$" + StaticInnerClass.class.getSimpleName(); + private static final String NATIVE_CLASS_NAME = ClassWithNative.class.getName(); + private static final String OUTER_CLASS_NAME = OuterClass.class.getName(); + private static final String INNER_CLASS_NAME = InnerClass.class.getName(); + private static final String STATIC_INNER_CLASS_NAME = StaticInnerClass.class.getName(); @Before public void setUp() throws Exception { @@ -69,12 +67,12 @@ public class DelegateClassAdapterTest { /** * Tests that a class not being modified still works. */ - @SuppressWarnings("unchecked") @Test public void testNoOp() throws Throwable { // create an instance of the class that will be modified // (load the class in a distinct class loader so that we can trash its definition later) ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { }; + @SuppressWarnings("unchecked") Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(NATIVE_CLASS_NAME); ClassWithNative instance1 = clazz1.newInstance(); assertEquals(42, instance1.add(20, 22)); @@ -88,7 +86,7 @@ public class DelegateClassAdapterTest { // Now process it but tell the delegate to not modify any method ClassWriter cw = new ClassWriter(0 /*flags*/); - HashSet<String> delegateMethods = new HashSet<String>(); + HashSet<String> delegateMethods = new HashSet<>(); String internalClassName = NATIVE_CLASS_NAME.replace('.', '/'); DelegateClassAdapter cv = new DelegateClassAdapter( mLog, cw, internalClassName, delegateMethods); @@ -152,7 +150,7 @@ public class DelegateClassAdapterTest { String internalClassName = NATIVE_CLASS_NAME.replace('.', '/'); - HashSet<String> delegateMethods = new HashSet<String>(); + HashSet<String> delegateMethods = new HashSet<>(); delegateMethods.add("<init>"); DelegateClassAdapter cv = new DelegateClassAdapter( mLog, cw, internalClassName, delegateMethods); @@ -166,7 +164,7 @@ public class DelegateClassAdapterTest { ClassWriter cw = new ClassWriter(0 /*flags*/); String internalClassName = NATIVE_CLASS_NAME.replace('.', '/'); - HashSet<String> delegateMethods = new HashSet<String>(); + HashSet<String> delegateMethods = new HashSet<>(); delegateMethods.add(DelegateClassAdapter.ALL_NATIVES); DelegateClassAdapter cv = new DelegateClassAdapter( mLog, cw, internalClassName, delegateMethods); @@ -217,7 +215,7 @@ public class DelegateClassAdapterTest { @Test public void testDelegateInner() throws Throwable { // We'll delegate the "get" method of both the inner and outer class. - HashSet<String> delegateMethods = new HashSet<String>(); + HashSet<String> delegateMethods = new HashSet<>(); delegateMethods.add("get"); delegateMethods.add("privateMethod"); @@ -300,7 +298,7 @@ public class DelegateClassAdapterTest { @Test public void testDelegateStaticInner() throws Throwable { // We'll delegate the "get" method of both the inner and outer class. - HashSet<String> delegateMethods = new HashSet<String>(); + HashSet<String> delegateMethods = new HashSet<>(); delegateMethods.add("get"); // Generate the delegate for the outer class. @@ -367,7 +365,7 @@ public class DelegateClassAdapterTest { */ private abstract class ClassLoader2 extends ClassLoader { - private final Map<String, byte[]> mClassDefs = new HashMap<String, byte[]>(); + private final Map<String, byte[]> mClassDefs = new HashMap<>(); public ClassLoader2() { super(null); diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java new file mode 100644 index 000000000000..3db3e2364eec --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java @@ -0,0 +1,132 @@ +/* + * 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.tools.layoutlib.create; + +import com.android.tools.layoutlib.create.dataclass.StubClass; + +import org.junit.Assert; +import org.junit.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.lang.reflect.Method; +import java.util.function.BiPredicate; +import java.util.function.Consumer; + +import static org.junit.Assert.*; + +public class StubMethodAdapterTest { + + private static final String STUB_CLASS_NAME = StubClass.class.getName(); + + /** + * Load a dummy class, stub one of its method and ensure that the modified class works as + * intended. + */ + @Test + public void testBoolean() throws Exception { + final String methodName = "returnTrue"; + // First don't change the method and assert that it returns true + testBoolean((name, type) -> false, Assert::assertTrue, methodName); + // Change the method now and assert that it returns false. + testBoolean((name, type) -> methodName.equals(name) && + Type.BOOLEAN_TYPE.equals(type.getReturnType()), Assert::assertFalse, methodName); + } + + /** + * @param methodPredicate tests if the method should be replaced + */ + private void testBoolean(BiPredicate<String, Type> methodPredicate, Consumer<Boolean> assertion, + String methodName) throws Exception { + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + // Always rename the class to avoid conflict with the original class. + String newClassName = STUB_CLASS_NAME + '_'; + new ClassReader(STUB_CLASS_NAME).accept( + new ClassAdapter(newClassName, writer, methodPredicate), 0); + MyClassLoader myClassLoader = new MyClassLoader(newClassName, writer.toByteArray()); + Class<?> aClass = myClassLoader.loadClass(newClassName); + assertTrue("StubClass not loaded by the classloader. Likely a bug in the test.", + myClassLoader.findClassCalled); + Method method = aClass.getMethod(methodName); + Object o = aClass.newInstance(); + assertion.accept((Boolean) method.invoke(o)); + } + + private static class ClassAdapter extends ClassVisitor { + + private final String mClassName; + private final BiPredicate<String, Type> mMethodPredicate; + + private ClassAdapter(String className, ClassVisitor cv, + BiPredicate<String, Type> methodPredicate) { + super(Main.ASM_VERSION, cv); + mClassName = className.replace('.', '/'); + mMethodPredicate = methodPredicate; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { + super.visit(version, access, mClassName, signature, superName, + interfaces); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, + String[] exceptions) { + // Copied partly from + // com.android.tools.layoutlib.create.DelegateClassAdapter.visitMethod() + // but not generating the _Original method. + boolean isStatic = (access & Opcodes.ACC_STATIC) != 0; + boolean isNative = (access & Opcodes.ACC_NATIVE) != 0; + MethodVisitor originalMethod = + super.visitMethod(access, name, desc, signature, exceptions); + Type descriptor = Type.getMethodType(desc); + if (mMethodPredicate.test(name, descriptor)) { + String methodSignature = mClassName + "#" + name; + String invokeSignature = methodSignature + desc; + return new StubMethodAdapter(originalMethod, name, descriptor.getReturnType(), + invokeSignature, isStatic, isNative); + } + return originalMethod; + } + } + + private static class MyClassLoader extends ClassLoader { + private final String mName; + private final byte[] mBytes; + private boolean findClassCalled; + + private MyClassLoader(String name, byte[] bytes) { + mName = name; + mBytes = bytes; + } + + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + if (name.equals(mName)) { + findClassCalled = true; + return defineClass(name, mBytes, 0, mBytes.length); + } + return super.findClass(name); + } + } +} diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/StubClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/StubClass.java new file mode 100644 index 000000000000..3ae8e47de6e3 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/StubClass.java @@ -0,0 +1,30 @@ +/* + * 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.tools.layoutlib.create.dataclass; + +import com.android.tools.layoutlib.create.StubMethodAdapterTest; + +/** + * Used by {@link StubMethodAdapterTest} + */ +@SuppressWarnings("unused") +public class StubClass { + + public boolean returnTrue() { + return true; + } +} diff --git a/tools/localedata/extract_icu_data.py b/tools/localedata/extract_icu_data.py new file mode 100755 index 000000000000..b071093a5615 --- /dev/null +++ b/tools/localedata/extract_icu_data.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python +# +# Copyright 2016 The Android Open Source Project. All Rights Reserved. +# +# 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. +# + +"""Generate a C++ data table containing locale data.""" + +import collections +import glob +import os.path +import sys + + +def get_locale_parts(locale): + """Split a locale into three parts, for langauge, script, and region.""" + parts = locale.split('_') + if len(parts) == 1: + return (parts[0], None, None) + elif len(parts) == 2: + if len(parts[1]) == 4: # parts[1] is a script + return (parts[0], parts[1], None) + else: + return (parts[0], None, parts[1]) + else: + assert len(parts) == 3 + return tuple(parts) + + +def read_likely_subtags(input_file_name): + """Read and parse ICU's likelySubtags.txt.""" + with open(input_file_name) as input_file: + likely_script_dict = { + # Android's additions for pseudo-locales. These internal codes make + # sure that the pseudo-locales would not match other English or + # Arabic locales. (We can't use private-use ISO 15924 codes, since + # they may be used by apps for other purposes.) + "en_XA": "~~~A", + "ar_XB": "~~~B", + } + representative_locales = { + # Android's additions + "en_Latn_GB", # representative for en_Latn_001 + "es_Latn_MX", # representative for es_Latn_419 + "es_Latn_US", # representative for es_Latn_419 (not the best idea, + # but Android has been shipping with it for quite a + # while. Fortunately, MX < US, so if both exist, MX + # would be chosen.) + } + for line in input_file: + line = unicode(line, 'UTF-8').strip(u' \n\uFEFF').encode('UTF-8') + if line.startswith('//'): + continue + if '{' in line and '}' in line: + from_locale = line[:line.index('{')] + to_locale = line[line.index('"')+1:line.rindex('"')] + from_lang, from_scr, from_region = get_locale_parts(from_locale) + _, to_scr, to_region = get_locale_parts(to_locale) + if from_lang == 'und': + continue # not very useful for our purposes + if from_region is None and to_region != '001': + representative_locales.add(to_locale) + if from_scr is None: + likely_script_dict[from_locale] = to_scr + return likely_script_dict, frozenset(representative_locales) + + +# From packLanguageOrRegion() in ResourceTypes.cpp +def pack_language_or_region(inp, base): + """Pack langauge or region in a two-byte tuple.""" + if inp is None: + return (0, 0) + elif len(inp) == 2: + return ord(inp[0]), ord(inp[1]) + else: + assert len(inp) == 3 + base = ord(base) + first = ord(inp[0]) - base + second = ord(inp[1]) - base + third = ord(inp[2]) - base + + return (0x80 | (third << 2) | (second >>3), + ((second << 5) | first) & 0xFF) + + +# From packLanguage() in ResourceTypes.cpp +def pack_language(language): + """Pack language in a two-byte tuple.""" + return pack_language_or_region(language, 'a') + + +# From packRegion() in ResourceTypes.cpp +def pack_region(region): + """Pack region in a two-byte tuple.""" + return pack_language_or_region(region, '0') + + +def pack_to_uint32(locale): + """Pack language+region of locale into a 32-bit unsigned integer.""" + lang, _, region = get_locale_parts(locale) + plang = pack_language(lang) + pregion = pack_region(region) + return (plang[0] << 24) | (plang[1] << 16) | (pregion[0] << 8) | pregion[1] + + +def dump_script_codes(all_scripts): + """Dump the SCRIPT_CODES table.""" + print 'const char SCRIPT_CODES[][4] = {' + for index, script in enumerate(all_scripts): + print " /* %-2d */ {'%c', '%c', '%c', '%c'}," % ( + index, script[0], script[1], script[2], script[3]) + print '};' + print + + +def dump_script_data(likely_script_dict, all_scripts): + """Dump the script data.""" + print + print 'const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({' + for locale in sorted(likely_script_dict.keys()): + script = likely_script_dict[locale] + print ' {0x%08Xu, %2du}, // %s -> %s' % ( + pack_to_uint32(locale), + all_scripts.index(script), + locale.replace('_', '-'), + script) + print '});' + + +def pack_to_uint64(locale): + """Pack a full locale into a 64-bit unsigned integer.""" + _, script, _ = get_locale_parts(locale) + return ((pack_to_uint32(locale) << 32) | + (ord(script[0]) << 24) | + (ord(script[1]) << 16) | + (ord(script[2]) << 8) | + ord(script[3])) + + +def dump_representative_locales(representative_locales): + """Dump the set of representative locales.""" + print + print 'std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({' + for locale in sorted(representative_locales): + print ' 0x%08Xllu, // %s' % ( + pack_to_uint64(locale), + locale) + print '});' + + +def read_and_dump_likely_data(icu_data_dir): + """Read and dump the likely-script data.""" + likely_subtags_txt = os.path.join(icu_data_dir, 'misc', 'likelySubtags.txt') + likely_script_dict, representative_locales = read_likely_subtags( + likely_subtags_txt) + + all_scripts = list(set(likely_script_dict.values())) + assert len(all_scripts) <= 256 + all_scripts.sort() + + dump_script_codes(all_scripts) + dump_script_data(likely_script_dict, all_scripts) + dump_representative_locales(representative_locales) + return likely_script_dict + + +def read_parent_data(icu_data_dir): + """Read locale parent data from ICU data files.""" + all_icu_data_files = glob.glob(os.path.join(icu_data_dir, '*', '*.txt')) + parent_dict = {} + for data_file in all_icu_data_files: + locale = os.path.splitext(os.path.basename(data_file))[0] + with open(data_file) as input_file: + for line in input_file: + if '%%Parent' in line: + parent = line[line.index('"')+1:line.rindex('"')] + if locale in parent_dict: + # Different files shouldn't have different parent info + assert parent_dict[locale] == parent + else: + parent_dict[locale] = parent + elif locale.startswith('ar_') and 'default{"latn"}' in line: + # Arabic parent overrides for ASCII digits. Since + # Unicode extensions are not supported in ResourceTypes, + # we will use ar-015 (Arabic, Northern Africa) instead + # of the more correct ar-u-nu-latn. + parent_dict[locale] = 'ar_015' + return parent_dict + + +def get_likely_script(locale, likely_script_dict): + """Find the likely script for a locale, given the likely-script dictionary. + """ + if locale.count('_') == 2: + # it already has a script + return locale.split('_')[1] + elif locale in likely_script_dict: + return likely_script_dict[locale] + else: + language = locale.split('_')[0] + return likely_script_dict[language] + + +def dump_parent_data(script_organized_dict): + """Dump information for parents of locales.""" + sorted_scripts = sorted(script_organized_dict.keys()) + print + for script in sorted_scripts: + parent_dict = script_organized_dict[script] + print ('const std::unordered_map<uint32_t, uint32_t> %s_PARENTS({' + % script.upper()) + for locale in sorted(parent_dict.keys()): + parent = parent_dict[locale] + print ' {0x%08Xu, 0x%08Xu}, // %s -> %s' % ( + pack_to_uint32(locale), + pack_to_uint32(parent), + locale.replace('_', '-'), + parent.replace('_', '-')) + print '});' + print + + print 'const struct {' + print ' const char script[4];' + print ' const std::unordered_map<uint32_t, uint32_t>* map;' + print '} SCRIPT_PARENTS[] = {' + for script in sorted_scripts: + print " {{'%c', '%c', '%c', '%c'}, &%s_PARENTS}," % ( + script[0], script[1], script[2], script[3], + script.upper()) + print '};' + + +def dump_parent_tree_depth(parent_dict): + """Find and dump the depth of the parent tree.""" + max_depth = 1 + for locale, _ in parent_dict.items(): + depth = 1 + while locale in parent_dict: + locale = parent_dict[locale] + depth += 1 + max_depth = max(max_depth, depth) + assert max_depth < 5 # Our algorithms assume small max_depth + print + print 'const size_t MAX_PARENT_DEPTH = %d;' % max_depth + + +def read_and_dump_parent_data(icu_data_dir, likely_script_dict): + """Read parent data from ICU and dump it.""" + parent_dict = read_parent_data(icu_data_dir) + script_organized_dict = collections.defaultdict(dict) + for locale in parent_dict: + parent = parent_dict[locale] + if parent == 'root': + continue + script = get_likely_script(locale, likely_script_dict) + script_organized_dict[script][locale] = parent_dict[locale] + dump_parent_data(script_organized_dict) + dump_parent_tree_depth(parent_dict) + + +def main(): + """Read the data files from ICU and dump the output to a C++ file.""" + source_root = sys.argv[1] + icu_data_dir = os.path.join( + source_root, + 'external', 'icu', 'icu4c', 'source', 'data') + + print '// Auto-generated by %s' % sys.argv[0] + print + likely_script_dict = read_and_dump_likely_data(icu_data_dir) + read_and_dump_parent_data(icu_data_dir, likely_script_dict) + + +if __name__ == '__main__': + main() diff --git a/tools/preload2/Android.mk b/tools/preload2/Android.mk new file mode 100644 index 000000000000..35d28fbfeba8 --- /dev/null +++ b/tools/preload2/Android.mk @@ -0,0 +1,32 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk + +# To connect to devices (and take hprof dumps). +LOCAL_STATIC_JAVA_LIBRARIES := ddmlib-prebuilt + +# To process hprof dumps. +LOCAL_STATIC_JAVA_LIBRARIES += perflib-prebuilt trove-prebuilt guavalib + +# For JDWP access we use the framework in the JDWP tests from Apache Harmony, for +# convenience (and to not depend on internal JDK APIs). +LOCAL_STATIC_JAVA_LIBRARIES += apache-harmony-jdwp-tests-host junit + +LOCAL_MODULE:= preload2 + +include $(BUILD_HOST_JAVA_LIBRARY) + +# Copy the preload-tool shell script to the host's bin directory. +include $(CLEAR_VARS) +LOCAL_IS_HOST_MODULE := true +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MODULE := preload-tool +include $(BUILD_SYSTEM)/base_rules.mk +$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/preload-tool $(ACP) + @echo "Copy: $(PRIVATE_MODULE) ($@)" + $(copy-file-to-new-target) + $(hide) chmod 755 $@ diff --git a/tools/preload2/preload-tool b/tools/preload2/preload-tool new file mode 100644 index 000000000000..36dbc1c775b1 --- /dev/null +++ b/tools/preload2/preload-tool @@ -0,0 +1,37 @@ +# 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. + +# This script is used on the host only. It uses a common subset +# shell dialect that should work well. It is partially derived +# from art/tools/art. + +function follow_links() { + if [ z"$BASH_SOURCE" != z ]; then + file="$BASH_SOURCE" + else + file="$0" + fi + while [ -h "$file" ]; do + # On Mac OS, readlink -f doesn't work. + file="$(readlink "$file")" + done + echo "$file" +} + + +PROG_NAME="$(follow_links)" +PROG_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)" +ANDROID_ROOT=$PROG_DIR/.. + +java -cp $ANDROID_ROOT/framework/preload2.jar com.android.preload.Main diff --git a/tools/preload2/src/com/android/preload/ClientUtils.java b/tools/preload2/src/com/android/preload/ClientUtils.java new file mode 100644 index 000000000000..71ef025d6587 --- /dev/null +++ b/tools/preload2/src/com/android/preload/ClientUtils.java @@ -0,0 +1,224 @@ +/* + * 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.preload; + +import com.android.ddmlib.AndroidDebugBridge; +import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener; +import com.android.ddmlib.Client; +import com.android.ddmlib.IDevice; + +/** + * Helper class for common communication with a Client (the ddms name for a running application). + * + * Instances take a default timeout parameter that's applied to all functions without explicit + * timeout. Timeouts are in milliseconds. + */ +public class ClientUtils { + + private int defaultTimeout; + + public ClientUtils() { + this(10000); + } + + public ClientUtils(int defaultTimeout) { + this.defaultTimeout = defaultTimeout; + } + + /** + * Shortcut for findClient with default timeout. + */ + public Client findClient(IDevice device, String processName, int processPid) { + return findClient(device, processName, processPid, defaultTimeout); + } + + /** + * Find the client with the given process name or process id. The name takes precedence over + * the process id (if valid). Stop looking after the given timeout. + * + * @param device The device to communicate with. + * @param processName The name of the process. May be null. + * @param processPid The pid of the process. Values less than or equal to zero are ignored. + * @param timeout The amount of milliseconds to wait, at most. + * @return The client, if found. Otherwise null. + */ + public Client findClient(IDevice device, String processName, int processPid, int timeout) { + WaitForClient wfc = new WaitForClient(device, processName, processPid, timeout); + return wfc.get(); + } + + /** + * Shortcut for findAllClients with default timeout. + */ + public Client[] findAllClients(IDevice device) { + return findAllClients(device, defaultTimeout); + } + + /** + * Retrieve all clients known to the given device. Wait at most the given timeout. + * + * @param device The device to investigate. + * @param timeout The amount of milliseconds to wait, at most. + * @return An array of clients running on the given device. May be null depending on the + * device implementation. + */ + public Client[] findAllClients(IDevice device, int timeout) { + if (device.hasClients()) { + return device.getClients(); + } + WaitForClients wfc = new WaitForClients(device, timeout); + return wfc.get(); + } + + private static class WaitForClient implements IClientChangeListener { + + private IDevice device; + private String processName; + private int processPid; + private long timeout; + private Client result; + + public WaitForClient(IDevice device, String processName, int processPid, long timeout) { + this.device = device; + this.processName = processName; + this.processPid = processPid; + this.timeout = timeout; + this.result = null; + } + + public Client get() { + synchronized (this) { + AndroidDebugBridge.addClientChangeListener(this); + + // Maybe it's already there. + if (result == null) { + result = searchForClient(device); + } + + if (result == null) { + try { + wait(timeout); + } catch (InterruptedException e) { + // Note: doesn't guard for spurious wakeup. + } + } + } + + AndroidDebugBridge.removeClientChangeListener(this); + return result; + } + + private Client searchForClient(IDevice device) { + if (processName != null) { + Client tmp = device.getClient(processName); + if (tmp != null) { + return tmp; + } + } + if (processPid > 0) { + String name = device.getClientName(processPid); + if (name != null && !name.isEmpty()) { + Client tmp = device.getClient(name); + if (tmp != null) { + return tmp; + } + } + } + if (processPid > 0) { + // Try manual search. + for (Client cl : device.getClients()) { + if (cl.getClientData().getPid() == processPid + && cl.getClientData().getClientDescription() != null) { + return cl; + } + } + } + return null; + } + + private boolean isTargetClient(Client c) { + if (processPid > 0 && c.getClientData().getPid() == processPid) { + return true; + } + if (processName != null + && processName.equals(c.getClientData().getClientDescription())) { + return true; + } + return false; + } + + @Override + public void clientChanged(Client arg0, int arg1) { + synchronized (this) { + if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) { + if (isTargetClient(arg0)) { + result = arg0; + notifyAll(); + } + } + } + } + } + + private static class WaitForClients implements IClientChangeListener { + + private IDevice device; + private long timeout; + + public WaitForClients(IDevice device, long timeout) { + this.device = device; + this.timeout = timeout; + } + + public Client[] get() { + synchronized (this) { + AndroidDebugBridge.addClientChangeListener(this); + + if (device.hasClients()) { + return device.getClients(); + } + + try { + wait(timeout); // Note: doesn't guard for spurious wakeup. + } catch (InterruptedException exc) { + } + + // We will be woken up when the first client data arrives. Sleep a little longer + // to give (hopefully all of) the rest of the clients a chance to become available. + // Note: a loop with timeout is brittle as well and complicated, just accept this + // for now. + try { + Thread.sleep(500); + } catch (InterruptedException exc) { + } + } + + AndroidDebugBridge.removeClientChangeListener(this); + + return device.getClients(); + } + + @Override + public void clientChanged(Client arg0, int arg1) { + synchronized (this) { + if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) { + notifyAll(); + } + } + } + } +} diff --git a/tools/preload2/src/com/android/preload/DeviceUtils.java b/tools/preload2/src/com/android/preload/DeviceUtils.java new file mode 100644 index 000000000000..72de7b58b0a7 --- /dev/null +++ b/tools/preload2/src/com/android/preload/DeviceUtils.java @@ -0,0 +1,390 @@ +/* + * 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.preload; + +import com.android.ddmlib.AndroidDebugBridge; +import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; +import com.android.preload.classdataretrieval.hprof.Hprof; +import com.android.ddmlib.DdmPreferences; +import com.android.ddmlib.IDevice; +import com.android.ddmlib.IShellOutputReceiver; + +import java.util.Date; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * Helper class for some device routines. + */ +public class DeviceUtils { + + public static void init(int debugPort) { + DdmPreferences.setSelectedDebugPort(debugPort); + + Hprof.init(); + + AndroidDebugBridge.init(true); + + AndroidDebugBridge.createBridge(); + } + + /** + * Run a command in the shell on the device. + */ + public static void doShell(IDevice device, String cmdline, long timeout, TimeUnit unit) { + doShell(device, cmdline, new NullShellOutputReceiver(), timeout, unit); + } + + /** + * Run a command in the shell on the device. Collects and returns the console output. + */ + public static String doShellReturnString(IDevice device, String cmdline, long timeout, + TimeUnit unit) { + CollectStringShellOutputReceiver rec = new CollectStringShellOutputReceiver(); + doShell(device, cmdline, rec, timeout, unit); + return rec.toString(); + } + + /** + * Run a command in the shell on the device, directing all output to the given receiver. + */ + public static void doShell(IDevice device, String cmdline, IShellOutputReceiver receiver, + long timeout, TimeUnit unit) { + try { + device.executeShellCommand(cmdline, receiver, timeout, unit); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Run am start on the device. + */ + public static void doAMStart(IDevice device, String name, String activity) { + doShell(device, "am start -n " + name + " /." + activity, 30, TimeUnit.SECONDS); + } + + /** + * Find the device with the given serial. Give up after the given timeout (in milliseconds). + */ + public static IDevice findDevice(String serial, int timeout) { + WaitForDevice wfd = new WaitForDevice(serial, timeout); + return wfd.get(); + } + + /** + * Get all devices ddms knows about. Wait at most for the given timeout. + */ + public static IDevice[] findDevices(int timeout) { + WaitForDevice wfd = new WaitForDevice(null, timeout); + wfd.get(); + return AndroidDebugBridge.getBridge().getDevices(); + } + + /** + * Return the build type of the given device. This is the value of the "ro.build.type" + * system property. + */ + public static String getBuildType(IDevice device) { + try { + Future<String> buildType = device.getSystemProperty("ro.build.type"); + return buildType.get(500, TimeUnit.MILLISECONDS); + } catch (Exception e) { + } + return null; + } + + /** + * Check whether the given device has a pre-optimized boot image. More precisely, checks + * whether /system/framework/ * /boot.art exists. + */ + public static boolean hasPrebuiltBootImage(IDevice device) { + String ret = + doShellReturnString(device, "ls /system/framework/*/boot.art", 500, TimeUnit.MILLISECONDS); + + return !ret.contains("No such file or directory"); + } + + /** + * Remove files involved in a standard build that interfere with collecting data. This will + * remove /etc/preloaded-classes, which determines which classes are allocated already in the + * boot image. It also deletes any compiled boot image on the device. Then it restarts the + * device. + * + * This is a potentially long-running operation, as the boot after the deletion may take a while. + * The method will abort after the given timeout. + */ + public static boolean removePreloaded(IDevice device, long preloadedWaitTimeInSeconds) { + String oldContent = + DeviceUtils.doShellReturnString(device, "cat /etc/preloaded-classes", 1, TimeUnit.SECONDS); + if (oldContent.trim().equals("")) { + System.out.println("Preloaded-classes already empty."); + return true; + } + + // Stop the system server etc. + doShell(device, "stop", 100, TimeUnit.MILLISECONDS); + + // Remount /system, delete /etc/preloaded-classes. It would be nice to use "adb remount," + // but AndroidDebugBridge doesn't expose it. + doShell(device, "mount -o remount,rw /system", 500, TimeUnit.MILLISECONDS); + doShell(device, "rm /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS); + // We do need an empty file. + doShell(device, "touch /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS); + + // Delete the files in the dalvik cache. + doShell(device, "rm /data/dalvik-cache/*/*boot.art", 500, TimeUnit.MILLISECONDS); + + // We'll try to use dev.bootcomplete to know when the system server is back up. But stop + // doesn't reset it, so do it manually. + doShell(device, "setprop dev.bootcomplete \"0\"", 500, TimeUnit.MILLISECONDS); + + // Start the system server. + doShell(device, "start", 100, TimeUnit.MILLISECONDS); + + // Do a loop checking each second whether bootcomplete. Wait for at most the given + // threshold. + Date startDate = new Date(); + for (;;) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // Ignore spurious wakeup. + } + // Check whether bootcomplete. + String ret = + doShellReturnString(device, "getprop dev.bootcomplete", 500, TimeUnit.MILLISECONDS); + if (ret.trim().equals("1")) { + break; + } + System.out.println("Still not booted: " + ret); + + // Check whether we timed out. This is a simplistic check that doesn't take into account + // things like switches in time. + Date endDate = new Date(); + long seconds = + TimeUnit.SECONDS.convert(endDate.getTime() - startDate.getTime(), TimeUnit.MILLISECONDS); + if (seconds > preloadedWaitTimeInSeconds) { + return false; + } + } + + return true; + } + + /** + * Enable method-tracing on device. The system should be restarted after this. + */ + public static void enableTracing(IDevice device) { + // Disable selinux. + doShell(device, "setenforce 0", 100, TimeUnit.MILLISECONDS); + + // Make the profile directory world-writable. + doShell(device, "chmod 777 /data/dalvik-cache/profiles", 100, TimeUnit.MILLISECONDS); + + // Enable streaming method tracing with a small 1K buffer. + doShell(device, "setprop dalvik.vm.method-trace true", 100, TimeUnit.MILLISECONDS); + doShell(device, "setprop dalvik.vm.method-trace-file " + + "/data/dalvik-cache/profiles/zygote.trace.bin", 100, TimeUnit.MILLISECONDS); + doShell(device, "setprop dalvik.vm.method-trace-file-siz 1024", 100, TimeUnit.MILLISECONDS); + doShell(device, "setprop dalvik.vm.method-trace-stream true", 100, TimeUnit.MILLISECONDS); + } + + private static class NullShellOutputReceiver implements IShellOutputReceiver { + @Override + public boolean isCancelled() { + return false; + } + + @Override + public void flush() {} + + @Override + public void addOutput(byte[] arg0, int arg1, int arg2) {} + } + + private static class CollectStringShellOutputReceiver implements IShellOutputReceiver { + + private StringBuilder builder = new StringBuilder(); + + @Override + public String toString() { + String ret = builder.toString(); + // Strip trailing newlines. They are especially ugly because adb uses DOS line endings. + while (ret.endsWith("\r") || ret.endsWith("\n")) { + ret = ret.substring(0, ret.length() - 1); + } + return ret; + } + + @Override + public void addOutput(byte[] arg0, int arg1, int arg2) { + builder.append(new String(arg0, arg1, arg2)); + } + + @Override + public void flush() {} + + @Override + public boolean isCancelled() { + return false; + } + } + + private static class WaitForDevice { + + private String serial; + private long timeout; + private IDevice device; + + public WaitForDevice(String serial, long timeout) { + this.serial = serial; + this.timeout = timeout; + device = null; + } + + public IDevice get() { + if (device == null) { + WaitForDeviceListener wfdl = new WaitForDeviceListener(serial); + synchronized (wfdl) { + AndroidDebugBridge.addDeviceChangeListener(wfdl); + + // Check whether we already know about this device. + IDevice[] devices = AndroidDebugBridge.getBridge().getDevices(); + if (serial != null) { + for (IDevice d : devices) { + if (serial.equals(d.getSerialNumber())) { + // Only accept if there are clients already. Else wait for the callback informing + // us that we now have clients. + if (d.hasClients()) { + device = d; + } + + break; + } + } + } else { + if (devices.length > 0) { + device = devices[0]; + } + } + + if (device == null) { + try { + wait(timeout); + } catch (InterruptedException e) { + // Ignore spurious wakeups. + } + device = wfdl.getDevice(); + } + + AndroidDebugBridge.removeDeviceChangeListener(wfdl); + } + } + + if (device != null) { + // Wait for clients. + WaitForClientsListener wfcl = new WaitForClientsListener(device); + synchronized (wfcl) { + AndroidDebugBridge.addDeviceChangeListener(wfcl); + + if (!device.hasClients()) { + try { + wait(timeout); + } catch (InterruptedException e) { + // Ignore spurious wakeups. + } + } + + AndroidDebugBridge.removeDeviceChangeListener(wfcl); + } + } + + return device; + } + + private static class WaitForDeviceListener implements IDeviceChangeListener { + + private String serial; + private IDevice device; + + public WaitForDeviceListener(String serial) { + this.serial = serial; + } + + public IDevice getDevice() { + return device; + } + + @Override + public void deviceChanged(IDevice arg0, int arg1) { + // We may get a device changed instead of connected. Handle like a connection. + deviceConnected(arg0); + } + + @Override + public void deviceConnected(IDevice arg0) { + if (device != null) { + // Ignore updates. + return; + } + + if (serial == null || serial.equals(arg0.getSerialNumber())) { + device = arg0; + synchronized (this) { + notifyAll(); + } + } + } + + @Override + public void deviceDisconnected(IDevice arg0) { + // Ignore disconnects. + } + + } + + private static class WaitForClientsListener implements IDeviceChangeListener { + + private IDevice myDevice; + + public WaitForClientsListener(IDevice myDevice) { + this.myDevice = myDevice; + } + + @Override + public void deviceChanged(IDevice arg0, int arg1) { + if (arg0 == myDevice && (arg1 & IDevice.CHANGE_CLIENT_LIST) != 0) { + // Got a client list, done here. + synchronized (this) { + notifyAll(); + } + } + } + + @Override + public void deviceConnected(IDevice arg0) { + } + + @Override + public void deviceDisconnected(IDevice arg0) { + } + + } + } + +} diff --git a/tools/preload2/src/com/android/preload/DumpData.java b/tools/preload2/src/com/android/preload/DumpData.java new file mode 100644 index 000000000000..d99722416a1d --- /dev/null +++ b/tools/preload2/src/com/android/preload/DumpData.java @@ -0,0 +1,91 @@ +/* + * 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.preload; + +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Holds the collected data for a process. + */ +public class DumpData { + /** + * Name of the package (=application). + */ + String packageName; + + /** + * A map of class name to a string for the classloader. This may be a toString equivalent, + * or just a unique ID. + */ + Map<String, String> dumpData; + + /** + * The Date when this data was captured. Mostly for display purposes. + */ + Date date; + + /** + * A cached value for the number of boot classpath classes (classloader value in dumpData is + * null). + */ + int bcpClasses; + + public DumpData(String packageName, Map<String, String> dumpData, Date date) { + this.packageName = packageName; + this.dumpData = dumpData; + this.date = date; + + countBootClassPath(); + } + + public String getPackageName() { + return packageName; + } + + public Date getDate() { + return date; + } + + public Map<String, String> getDumpData() { + return dumpData; + } + + public void countBootClassPath() { + bcpClasses = 0; + for (Map.Entry<String, String> e : dumpData.entrySet()) { + if (e.getValue() == null) { + bcpClasses++; + } + } + } + + // Return an inverted mapping. + public Map<String, Set<String>> invertData() { + Map<String, Set<String>> ret = new HashMap<>(); + for (Map.Entry<String, String> e : dumpData.entrySet()) { + if (!ret.containsKey(e.getValue())) { + ret.put(e.getValue(), new HashSet<String>()); + } + ret.get(e.getValue()).add(e.getKey()); + } + return ret; + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/DumpDataIO.java b/tools/preload2/src/com/android/preload/DumpDataIO.java new file mode 100644 index 000000000000..28625c5531e4 --- /dev/null +++ b/tools/preload2/src/com/android/preload/DumpDataIO.java @@ -0,0 +1,141 @@ +/* + * 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.preload; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; + +import java.io.File; +import java.io.FileReader; +import java.text.DateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +/** + * Helper class for serialization and deserialization of a collection of DumpData objects to XML. + */ +public class DumpDataIO { + + /** + * Serialize the given collection to an XML document. Returns the produced string. + */ + public static String serialize(Collection<DumpData> data) { + // We'll do this by hand, constructing a DOM or similar is too complicated for our simple + // use case. + + StringBuilder sb = new StringBuilder(); + sb.append("<preloaded-classes-data>\n"); + + for (DumpData d : data) { + serialize(d, sb); + } + + sb.append("</preloaded-classes-data>\n"); + return sb.toString(); + } + + private static void serialize(DumpData d, StringBuilder sb) { + sb.append("<data package=\"" + d.packageName + "\" date=\"" + + DateFormat.getDateTimeInstance().format(d.date) +"\">\n"); + + for (Map.Entry<String, String> e : d.dumpData.entrySet()) { + sb.append("<class name=\"" + e.getKey() + "\" classloader=\"" + e.getValue() + "\"/>\n"); + } + + sb.append("</data>\n"); + } + + /** + * Load a collection of DumpData objects from the given file. + */ + public static Collection<DumpData> deserialize(File f) throws Exception { + // Use SAX parsing. Our format is very simple. Don't do any schema validation or such. + + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setNamespaceAware(false); + SAXParser saxParser = spf.newSAXParser(); + + XMLReader xmlReader = saxParser.getXMLReader(); + DumpDataContentHandler ddch = new DumpDataContentHandler(); + xmlReader.setContentHandler(ddch); + xmlReader.parse(new InputSource(new FileReader(f))); + + return ddch.data; + } + + private static class DumpDataContentHandler extends DefaultHandler { + Collection<DumpData> data = new LinkedList<DumpData>(); + DumpData openData = null; + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + if (qName.equals("data")) { + if (openData != null) { + throw new IllegalStateException(); + } + String pkg = attributes.getValue("package"); + String dateString = attributes.getValue("date"); + + if (pkg == null || dateString == null) { + throw new IllegalArgumentException(); + } + + try { + Date date = DateFormat.getDateTimeInstance().parse(dateString); + openData = new DumpData(pkg, new HashMap<String, String>(), date); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else if (qName.equals("class")) { + if (openData == null) { + throw new IllegalStateException(); + } + String className = attributes.getValue("name"); + String classLoader = attributes.getValue("classloader"); + + if (className == null || classLoader == null) { + throw new IllegalArgumentException(); + } + + openData.dumpData.put(className, classLoader.equals("null") ? null : classLoader); + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (qName.equals("data")) { + if (openData == null) { + throw new IllegalStateException(); + } + openData.countBootClassPath(); + + data.add(openData); + openData = null; + } + } + } +} diff --git a/tools/preload2/src/com/android/preload/DumpTableModel.java b/tools/preload2/src/com/android/preload/DumpTableModel.java new file mode 100644 index 000000000000..d97cbf0df5e5 --- /dev/null +++ b/tools/preload2/src/com/android/preload/DumpTableModel.java @@ -0,0 +1,93 @@ +/* + * 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.preload; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.table.AbstractTableModel; + +/** + * A table model for collected DumpData. This is both the internal storage as well as the model + * for display. + */ +public class DumpTableModel extends AbstractTableModel { + + private List<DumpData> data = new ArrayList<DumpData>(); + + public void addData(DumpData d) { + data.add(d); + fireTableRowsInserted(data.size() - 1, data.size() - 1); + } + + public void clear() { + int size = data.size(); + if (size > 0) { + data.clear(); + fireTableRowsDeleted(0, size - 1); + } + } + + public List<DumpData> getData() { + return data; + } + + @Override + public int getRowCount() { + return data.size(); + } + + @Override + public int getColumnCount() { + return 4; + } + + @Override + public String getColumnName(int column) { + switch (column) { + case 0: + return "Package"; + case 1: + return "Date"; + case 2: + return "# All Classes"; + case 3: + return "# Boot Classpath Classes"; + + default: + throw new IndexOutOfBoundsException(String.valueOf(column)); + } + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + DumpData d = data.get(rowIndex); + switch (columnIndex) { + case 0: + return d.packageName; + case 1: + return d.date; + case 2: + return d.dumpData.size(); + case 3: + return d.bcpClasses; + + default: + throw new IndexOutOfBoundsException(String.valueOf(columnIndex)); + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/Main.java b/tools/preload2/src/com/android/preload/Main.java new file mode 100644 index 000000000000..ca5b0e005a1d --- /dev/null +++ b/tools/preload2/src/com/android/preload/Main.java @@ -0,0 +1,242 @@ +/* + * 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.preload; + +import com.android.ddmlib.Client; +import com.android.ddmlib.IDevice; +import com.android.preload.actions.ClearTableAction; +import com.android.preload.actions.ComputeThresholdAction; +import com.android.preload.actions.ComputeThresholdXAction; +import com.android.preload.actions.DeviceSpecific; +import com.android.preload.actions.ExportAction; +import com.android.preload.actions.ImportAction; +import com.android.preload.actions.ReloadListAction; +import com.android.preload.actions.RunMonkeyAction; +import com.android.preload.actions.ScanAllPackagesAction; +import com.android.preload.actions.ScanPackageAction; +import com.android.preload.actions.ShowDataAction; +import com.android.preload.classdataretrieval.ClassDataRetriever; +import com.android.preload.classdataretrieval.hprof.Hprof; +import com.android.preload.classdataretrieval.jdwp.JDWPClassDataRetriever; +import com.android.preload.ui.UI; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.swing.Action; +import javax.swing.DefaultListModel; + +public class Main { + + /** + * Enable tracing mode. This is a work-in-progress to derive compiled-methods data, so it is + * off for now. + */ + public final static boolean ENABLE_TRACING = false; + + /** + * Ten-second timeout. + */ + public final static int DEFAULT_TIMEOUT_MILLIS = 10 * 1000; + + /** + * Hprof timeout. Two minutes. + */ + public final static int HPROF_TIMEOUT_MILLIS = 120 * 1000; + + private IDevice device; + private static ClientUtils clientUtils; + + private DumpTableModel dataTableModel; + private DefaultListModel<Client> clientListModel; + + private UI ui; + + // Actions that need to be updated once a device is selected. + private Collection<DeviceSpecific> deviceSpecificActions; + + // Current main instance. + private static Main top; + private static boolean useJdwpClassDataRetriever = false; + + public final static String CLASS_PRELOAD_BLACKLIST = "android.app.AlarmManager$" + "|" + + "android.app.SearchManager$" + "|" + "android.os.FileObserver$" + "|" + + "com.android.server.PackageManagerService\\$AppDirObserver$" + "|" + + + + // Threads + "android.os.AsyncTask$" + "|" + "android.pim.ContactsAsyncHelper$" + "|" + + "android.webkit.WebViewClassic\\$1$" + "|" + "java.lang.ProcessManager$" + "|" + + "(.*\\$NoPreloadHolder$)"; + + /** + * @param args + */ + public static void main(String[] args) { + Main m = new Main(); + top = m; + + m.startUp(); + } + + public Main() { + clientListModel = new DefaultListModel<Client>(); + dataTableModel = new DumpTableModel(); + + clientUtils = new ClientUtils(DEFAULT_TIMEOUT_MILLIS); // Client utils with 10s timeout. + + List<Action> actions = new ArrayList<Action>(); + actions.add(new ReloadListAction(clientUtils, null, clientListModel)); + actions.add(new ClearTableAction(dataTableModel)); + actions.add(new RunMonkeyAction(null, dataTableModel)); + actions.add(new ScanPackageAction(clientUtils, null, dataTableModel)); + actions.add(new ScanAllPackagesAction(clientUtils, null, dataTableModel)); + actions.add(new ComputeThresholdAction("Compute preloaded-classes", dataTableModel, 2, + CLASS_PRELOAD_BLACKLIST)); + actions.add(new ComputeThresholdAction("Compute compiled-classes", dataTableModel, 1, + null)); + actions.add(new ComputeThresholdXAction("Compute(X)", dataTableModel, + CLASS_PRELOAD_BLACKLIST)); + actions.add(new ShowDataAction(dataTableModel)); + actions.add(new ImportAction(dataTableModel)); + actions.add(new ExportAction(dataTableModel)); + + deviceSpecificActions = new ArrayList<DeviceSpecific>(); + for (Action a : actions) { + if (a instanceof DeviceSpecific) { + deviceSpecificActions.add((DeviceSpecific)a); + } + } + + ui = new UI(clientListModel, dataTableModel, actions); + ui.setVisible(true); + } + + public static UI getUI() { + return top.ui; + } + + public static ClassDataRetriever getClassDataRetriever() { + if (useJdwpClassDataRetriever) { + return new JDWPClassDataRetriever(); + } else { + return new Hprof(HPROF_TIMEOUT_MILLIS); + } + } + + public IDevice getDevice() { + return device; + } + + public void setDevice(IDevice device) { + this.device = device; + for (DeviceSpecific ds : deviceSpecificActions) { + ds.setDevice(device); + } + } + + public DefaultListModel<Client> getClientListModel() { + return clientListModel; + } + + static class DeviceWrapper { + IDevice device; + + public DeviceWrapper(IDevice d) { + device = d; + } + + @Override + public String toString() { + return device.getName() + " (#" + device.getSerialNumber() + ")"; + } + } + + private void startUp() { + getUI().showWaitDialog(); + initDevice(); + + // Load clients. + new ReloadListAction(clientUtils, getDevice(), clientListModel).run(); + + getUI().hideWaitDialog(); + } + + private void initDevice() { + DeviceUtils.init(DEFAULT_TIMEOUT_MILLIS); + + IDevice devices[] = DeviceUtils.findDevices(DEFAULT_TIMEOUT_MILLIS); + if (devices == null || devices.length == 0) { + throw new RuntimeException("Could not find any devices..."); + } + + getUI().hideWaitDialog(); + + DeviceWrapper deviceWrappers[] = new DeviceWrapper[devices.length]; + for (int i = 0; i < devices.length; i++) { + deviceWrappers[i] = new DeviceWrapper(devices[i]); + } + + DeviceWrapper ret = Main.getUI().showChoiceDialog("Choose a device", "Choose device", + deviceWrappers); + if (ret != null) { + setDevice(ret.device); + } else { + System.exit(0); + } + + boolean prepare = Main.getUI().showConfirmDialog("Prepare device?", + "Do you want to prepare the device? This is highly recommended."); + if (prepare) { + String buildType = DeviceUtils.getBuildType(device); + if (buildType == null || (!buildType.equals("userdebug") && !buildType.equals("eng"))) { + Main.getUI().showMessageDialog("Need a userdebug or eng build! (Found " + buildType + + ")"); + return; + } + if (DeviceUtils.hasPrebuiltBootImage(device)) { + Main.getUI().showMessageDialog("Cannot prepare a device with pre-optimized boot " + + "image!"); + return; + } + + if (ENABLE_TRACING) { + DeviceUtils.enableTracing(device); + } + + Main.getUI().showMessageDialog("The device will reboot. This will potentially take a " + + "long time. Please be patient."); + if (!DeviceUtils.removePreloaded(device, 15 * 60) /* 15m timeout */) { + Main.getUI().showMessageDialog("Removing preloaded-classes failed unexpectedly!"); + } + } + } + + public static Map<String, String> findAndGetClassData(IDevice device, String packageName) + throws Exception { + Client client = clientUtils.findClient(device, packageName, -1); + if (client == null) { + throw new RuntimeException("Could not find client..."); + } + System.out.println("Found client: " + client); + + return getClassDataRetriever().getClassData(client); + } + +} diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java new file mode 100644 index 000000000000..fbf83d2e2339 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java @@ -0,0 +1,34 @@ +/* + * 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.preload.actions; + +import java.awt.event.ActionEvent; + +import javax.swing.AbstractAction; + +public abstract class AbstractThreadedAction extends AbstractAction implements Runnable { + + protected AbstractThreadedAction(String title) { + super(title); + } + + @Override + public void actionPerformed(ActionEvent e) { + new Thread(this).start(); + } + +} diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java new file mode 100644 index 000000000000..7906417b7a8d --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java @@ -0,0 +1,45 @@ +/* + * 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.preload.actions; + +import com.android.ddmlib.IDevice; + +import java.awt.event.ActionEvent; + +public abstract class AbstractThreadedDeviceSpecificAction extends AbstractThreadedAction + implements DeviceSpecific { + + protected IDevice device; + + protected AbstractThreadedDeviceSpecificAction(String title, IDevice device) { + super(title); + this.device = device; + } + + @Override + public void setDevice(IDevice device) { + this.device = device; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (device == null) { + return; + } + super.actionPerformed(e); + } +} diff --git a/tools/preload2/src/com/android/preload/actions/ClearTableAction.java b/tools/preload2/src/com/android/preload/actions/ClearTableAction.java new file mode 100644 index 000000000000..c0e4795b6d90 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ClearTableAction.java @@ -0,0 +1,37 @@ +/* + * 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.preload.actions; + +import com.android.preload.DumpTableModel; + +import java.awt.event.ActionEvent; + +import javax.swing.AbstractAction; + +public class ClearTableAction extends AbstractAction { + private final DumpTableModel dataTableModel; + + public ClearTableAction(DumpTableModel dataTableModel) { + super("Clear"); + this.dataTableModel = dataTableModel; + } + + @Override + public void actionPerformed(ActionEvent e) { + dataTableModel.clear(); + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java new file mode 100644 index 000000000000..b524716fc2cb --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java @@ -0,0 +1,151 @@ +/* + * 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.preload.actions; + +import com.android.preload.DumpData; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Pattern; + +import javax.swing.AbstractAction; +import javax.swing.JFileChooser; + +/** + * Compute an intersection of classes from the given data. A class is in the intersection if it + * appears in at least the number of threshold given packages. An optional blacklist can be + * used to filter classes from the intersection. + */ +public class ComputeThresholdAction extends AbstractAction implements Runnable { + protected int threshold; + private Pattern blacklist; + private DumpTableModel dataTableModel; + + /** + * Create an action with the given parameters. The blacklist is a regular expression + * that filters classes. + */ + public ComputeThresholdAction(String name, DumpTableModel dataTableModel, int threshold, + String blacklist) { + super(name); + this.dataTableModel = dataTableModel; + this.threshold = threshold; + if (blacklist != null) { + this.blacklist = Pattern.compile(blacklist); + } + } + + @Override + public void actionPerformed(ActionEvent e) { + List<DumpData> data = dataTableModel.getData(); + if (data.size() == 0) { + Main.getUI().showMessageDialog("No data available, please scan packages or run " + + "monkeys."); + return; + } + if (data.size() == 1) { + Main.getUI().showMessageDialog("Cannot compute list from only one data set, please " + + "scan packages or run monkeys."); + return; + } + + new Thread(this).start(); + } + + @Override + public void run() { + Main.getUI().showWaitDialog(); + + Map<String, Set<String>> uses = new HashMap<String, Set<String>>(); + for (DumpData d : dataTableModel.getData()) { + Main.getUI().updateWaitDialog("Merging " + d.getPackageName()); + updateClassUse(d.getPackageName(), uses, getBootClassPathClasses(d.getDumpData())); + } + + Main.getUI().updateWaitDialog("Computing thresholded set"); + Set<String> result = fromThreshold(uses, blacklist, threshold); + Main.getUI().hideWaitDialog(); + + boolean ret = Main.getUI().showConfirmDialog("Computed a set with " + result.size() + + " classes, would you like to save to disk?", "Save?"); + if (ret) { + JFileChooser jfc = new JFileChooser(); + int ret2 = jfc.showSaveDialog(Main.getUI()); + if (ret2 == JFileChooser.APPROVE_OPTION) { + File f = jfc.getSelectedFile(); + saveSet(result, f); + } + } + } + + private Set<String> fromThreshold(Map<String, Set<String>> classUses, Pattern blacklist, + int threshold) { + TreeSet<String> ret = new TreeSet<>(); // TreeSet so it's nicely ordered by name. + + for (Map.Entry<String, Set<String>> e : classUses.entrySet()) { + if (e.getValue().size() >= threshold) { + if (blacklist == null || !blacklist.matcher(e.getKey()).matches()) { + ret.add(e.getKey()); + } + } + } + + return ret; + } + + private static void updateClassUse(String pkg, Map<String, Set<String>> classUses, + Set<String> classes) { + for (String className : classes) { + Set<String> old = classUses.get(className); + if (old == null) { + classUses.put(className, new HashSet<String>()); + } + classUses.get(className).add(pkg); + } + } + + private static Set<String> getBootClassPathClasses(Map<String, String> source) { + Set<String> ret = new HashSet<>(); + for (Map.Entry<String, String> e : source.entrySet()) { + if (e.getValue() == null) { + ret.add(e.getKey()); + } + } + return ret; + } + + private static void saveSet(Set<String> result, File f) { + try { + PrintWriter out = new PrintWriter(f); + for (String s : result) { + out.println(s); + } + out.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java new file mode 100644 index 000000000000..3ec0a4c18db1 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java @@ -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. + */ + +package com.android.preload.actions; + +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +public class ComputeThresholdXAction extends ComputeThresholdAction { + + public ComputeThresholdXAction(String name, DumpTableModel dataTableModel, + String blacklist) { + super(name, dataTableModel, 1, blacklist); + } + + @Override + public void run() { + String value = Main.getUI().showInputDialog("Threshold?"); + + if (value != null) { + try { + threshold = Integer.parseInt(value); + super.run(); + } catch (Exception exc) { + } + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java b/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java new file mode 100644 index 000000000000..35a8f26a99fe --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java @@ -0,0 +1,38 @@ +/* + * 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.preload.actions; + +import com.android.ddmlib.IDevice; + +/** + * Marks an action as being device-specific. The user must set the device through the specified + * method if the device selection changes. + * + * Implementors must tolerate a null device (for example, with a no-op). This includes calling + * any methods before setDevice has been called. + */ +public interface DeviceSpecific { + + /** + * Set the device that should be used. Note that there is no restriction on calling other + * methods of the implementor before a setDevice call. Neither is device guaranteed to be + * non-null. + * + * @param device The device to use going forward. + */ + public void setDevice(IDevice device); +} diff --git a/tools/preload2/src/com/android/preload/actions/ExportAction.java b/tools/preload2/src/com/android/preload/actions/ExportAction.java new file mode 100644 index 000000000000..cb8b3df75b18 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ExportAction.java @@ -0,0 +1,65 @@ +/* + * 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.preload.actions; + +import com.android.preload.DumpDataIO; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.PrintWriter; + +import javax.swing.AbstractAction; + +public class ExportAction extends AbstractAction implements Runnable { + private File lastSaveFile; + private DumpTableModel dataTableModel; + + public ExportAction(DumpTableModel dataTableModel) { + super("Export data"); + this.dataTableModel = dataTableModel; + } + + @Override + public void actionPerformed(ActionEvent e) { + lastSaveFile = Main.getUI().showSaveDialog(); + if (lastSaveFile != null) { + new Thread(this).start(); + } + } + + @Override + public void run() { + Main.getUI().showWaitDialog(); + + String serialized = DumpDataIO.serialize(dataTableModel.getData()); + + if (serialized != null) { + try { + PrintWriter out = new PrintWriter(lastSaveFile); + out.println(serialized); + out.close(); + + Main.getUI().hideWaitDialog(); + } catch (Exception e) { + Main.getUI().hideWaitDialog(); + Main.getUI().showMessageDialog("Failed writing: " + e.getMessage()); + } + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/ImportAction.java b/tools/preload2/src/com/android/preload/actions/ImportAction.java new file mode 100644 index 000000000000..5c1976580f94 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ImportAction.java @@ -0,0 +1,68 @@ +/* + * 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.preload.actions; + +import com.android.preload.DumpData; +import com.android.preload.DumpDataIO; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.util.Collection; + +import javax.swing.AbstractAction; + +public class ImportAction extends AbstractAction implements Runnable { + private File[] lastOpenFiles; + private DumpTableModel dataTableModel; + + public ImportAction(DumpTableModel dataTableModel) { + super("Import data"); + this.dataTableModel = dataTableModel; + } + + @Override + public void actionPerformed(ActionEvent e) { + lastOpenFiles = Main.getUI().showOpenDialog(true); + if (lastOpenFiles != null) { + new Thread(this).start(); + } + } + + @Override + public void run() { + Main.getUI().showWaitDialog(); + + try { + for (File f : lastOpenFiles) { + try { + Collection<DumpData> data = DumpDataIO.deserialize(f); + + for (DumpData d : data) { + dataTableModel.addData(d); + } + } catch (Exception e) { + Main.getUI().showMessageDialog("Failed reading: " + e.getMessage()); + } + } + } finally { + Main.getUI().hideWaitDialog(); + } + + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/ReloadListAction.java b/tools/preload2/src/com/android/preload/actions/ReloadListAction.java new file mode 100644 index 000000000000..29f055700dfa --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ReloadListAction.java @@ -0,0 +1,68 @@ +/* + * 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.preload.actions; + +import com.android.ddmlib.Client; +import com.android.ddmlib.IDevice; +import com.android.preload.ClientUtils; + +import java.util.Arrays; +import java.util.Comparator; + +import javax.swing.DefaultListModel; + +public class ReloadListAction extends AbstractThreadedDeviceSpecificAction { + + private ClientUtils clientUtils; + private final DefaultListModel<Client> clientListModel; + + public ReloadListAction(ClientUtils utils, IDevice device, + DefaultListModel<Client> clientListModel) { + super("Reload", device); + this.clientUtils = utils; + this.clientListModel = clientListModel; + } + + @Override + public void run() { + Client[] clients = clientUtils.findAllClients(device); + if (clients != null) { + Arrays.sort(clients, new ClientComparator()); + } + clientListModel.removeAllElements(); + for (Client c : clients) { + clientListModel.addElement(c); + } + } + + private static class ClientComparator implements Comparator<Client> { + + @Override + public int compare(Client o1, Client o2) { + String s1 = o1.getClientData().getClientDescription(); + String s2 = o2.getClientData().getClientDescription(); + + if (s1 == null || s2 == null) { + // Not good, didn't get all data? + return (s1 == null) ? -1 : 1; + } + + return s1.compareTo(s2); + } + + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java new file mode 100644 index 000000000000..385e8577b1c8 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java @@ -0,0 +1,119 @@ +/* + * 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.preload.actions; + +import com.android.ddmlib.IDevice; +import com.android.preload.DeviceUtils; +import com.android.preload.DumpData; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.awt.event.ActionEvent; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.swing.AbstractAction; + +public class RunMonkeyAction extends AbstractAction implements DeviceSpecific { + + private final static String DEFAULT_MONKEY_PACKAGES = + "com.android.calendar,com.android.gallery3d"; + + private IDevice device; + private DumpTableModel dataTableModel; + + public RunMonkeyAction(IDevice device, DumpTableModel dataTableModel) { + super("Run monkey"); + this.device = device; + this.dataTableModel = dataTableModel; + } + + @Override + public void setDevice(IDevice device) { + this.device = device; + } + + @Override + public void actionPerformed(ActionEvent e) { + String packages = Main.getUI().showInputDialog("Please enter packages name to run with" + + " the monkey, or leave empty for default."); + if (packages == null) { + return; + } + if (packages.isEmpty()) { + packages = DEFAULT_MONKEY_PACKAGES; + } + new Thread(new RunMonkeyRunnable(packages)).start(); + } + + private class RunMonkeyRunnable implements Runnable { + + private String packages; + private final static int ITERATIONS = 1000; + + public RunMonkeyRunnable(String packages) { + this.packages = packages; + } + + @Override + public void run() { + Main.getUI().showWaitDialog(); + + try { + String pkgs[] = packages.split(","); + + for (String pkg : pkgs) { + Main.getUI().updateWaitDialog("Running monkey on " + pkg); + + try { + // Stop running app. + forceStop(pkg); + + // Little bit of breather here. + try { + Thread.sleep(1000); + } catch (Exception e) { + } + + DeviceUtils.doShell(device, "monkey -p " + pkg + " " + ITERATIONS, 1, + TimeUnit.MINUTES); + + Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg); + Map<String, String> data = Main.findAndGetClassData(device, pkg); + DumpData dumpData = new DumpData(pkg, data, new Date()); + dataTableModel.addData(dumpData); + } catch (Exception e) { + e.printStackTrace(); + } finally { + // Stop running app. + forceStop(pkg); + } + } + } finally { + Main.getUI().hideWaitDialog(); + } + } + + private void forceStop(String packageName) { + // Stop running app. + DeviceUtils.doShell(device, "force-stop " + packageName, 5, TimeUnit.SECONDS); + DeviceUtils.doShell(device, "kill " + packageName, 5, TimeUnit.SECONDS); + DeviceUtils.doShell(device, "kill `pid " + packageName + "`", 5, TimeUnit.SECONDS); + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java b/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java new file mode 100644 index 000000000000..d74b8a3f6cfb --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java @@ -0,0 +1,63 @@ +/* + * 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.preload.actions; + +import com.android.ddmlib.Client; +import com.android.ddmlib.IDevice; +import com.android.preload.ClientUtils; +import com.android.preload.DumpData; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.util.Date; +import java.util.Map; + +public class ScanAllPackagesAction extends AbstractThreadedDeviceSpecificAction { + + private ClientUtils clientUtils; + private DumpTableModel dataTableModel; + + public ScanAllPackagesAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) { + super("Scan all packages", device); + this.clientUtils = utils; + this.dataTableModel = dataTableModel; + } + + @Override + public void run() { + Main.getUI().showWaitDialog(); + + try { + Client[] clients = clientUtils.findAllClients(device); + for (Client c : clients) { + String pkg = c.getClientData().getClientDescription(); + Main.getUI().showWaitDialog(); + Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg); + + try { + Map<String, String> data = Main.getClassDataRetriever().getClassData(c); + DumpData dumpData = new DumpData(pkg, data, new Date()); + dataTableModel.addData(dumpData); + } catch (Exception e) { + e.printStackTrace(); + } + } + } finally { + Main.getUI().hideWaitDialog(); + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java b/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java new file mode 100644 index 000000000000..98492bd951bf --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java @@ -0,0 +1,97 @@ +/* + * 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.preload.actions; + +import com.android.ddmlib.Client; +import com.android.ddmlib.IDevice; +import com.android.preload.ClientUtils; +import com.android.preload.DumpData; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.util.Date; +import java.util.Map; + +public class ScanPackageAction extends AbstractThreadedDeviceSpecificAction { + + private ClientUtils clientUtils; + private DumpTableModel dataTableModel; + + public ScanPackageAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) { + super("Scan package", device); + this.clientUtils = utils; + this.dataTableModel = dataTableModel; + } + + @Override + public void run() { + Main.getUI().showWaitDialog(); + + try { + Client client = Main.getUI().getSelectedClient(); + if (client != null) { + work(client); + } else { + Client[] clients = clientUtils.findAllClients(device); + if (clients.length > 0) { + ClientWrapper[] clientWrappers = new ClientWrapper[clients.length]; + for (int i = 0; i < clientWrappers.length; i++) { + clientWrappers[i] = new ClientWrapper(clients[i]); + } + Main.getUI().hideWaitDialog(); + + ClientWrapper ret = Main.getUI().showChoiceDialog("Choose a package to scan", + "Choose package", + clientWrappers); + if (ret != null) { + work(ret.client); + } + } + } + } finally { + Main.getUI().hideWaitDialog(); + } + } + + private void work(Client c) { + String pkg = c.getClientData().getClientDescription(); + Main.getUI().showWaitDialog(); + Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg); + + try { + Map<String, String> data = Main.findAndGetClassData(device, pkg); + DumpData dumpData = new DumpData(pkg, data, new Date()); + dataTableModel.addData(dumpData); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static class ClientWrapper { + private Client client; + + public ClientWrapper(Client c) { + client = c; + } + + @Override + public String toString() { + return client.getClientData().getClientDescription() + " (pid " + + client.getClientData().getPid() + ")"; + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/ShowDataAction.java b/tools/preload2/src/com/android/preload/actions/ShowDataAction.java new file mode 100644 index 000000000000..2bb175f60772 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ShowDataAction.java @@ -0,0 +1,94 @@ +/* + * 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.preload.actions; + +import com.android.preload.DumpData; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.AbstractAction; +import javax.swing.JFrame; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +public class ShowDataAction extends AbstractAction { + private DumpTableModel dataTableModel; + + public ShowDataAction(DumpTableModel dataTableModel) { + super("Show data"); + this.dataTableModel = dataTableModel; + } + + @Override + public void actionPerformed(ActionEvent e) { + // TODO(agampe): Auto-generated method stub + int selRow = Main.getUI().getSelectedDataTableRow(); + if (selRow != -1) { + DumpData data = dataTableModel.getData().get(selRow); + Map<String, Set<String>> inv = data.invertData(); + + StringBuilder builder = new StringBuilder(); + + // First bootclasspath. + add(builder, "Boot classpath:", inv.get(null)); + + // Now everything else. + for (String k : inv.keySet()) { + if (k != null) { + builder.append("==================\n\n"); + add(builder, k, inv.get(k)); + } + } + + JFrame newFrame = new JFrame(data.getPackageName() + " " + data.getDate()); + newFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); + newFrame.getContentPane().add(new JScrollPane(new JTextArea(builder.toString())), + BorderLayout.CENTER); + newFrame.setSize(800, 600); + newFrame.setLocationRelativeTo(null); + newFrame.setVisible(true); + } + } + + private void add(StringBuilder builder, String head, Set<String> set) { + builder.append(head); + builder.append('\n'); + addSet(builder, set); + builder.append('\n'); + } + + private void addSet(StringBuilder builder, Set<String> set) { + if (set == null) { + builder.append(" NONE\n"); + return; + } + List<String> sorted = new ArrayList<>(set); + Collections.sort(sorted); + for (String s : sorted) { + builder.append(s); + builder.append('\n'); + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java b/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java new file mode 100644 index 000000000000..f04360fc1942 --- /dev/null +++ b/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java @@ -0,0 +1,29 @@ +/* + * 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.preload.classdataretrieval; + +import com.android.ddmlib.Client; + +import java.util.Map; + +/** + * Retrieve a class-to-classloader map for loaded classes from the client. + */ +public interface ClassDataRetriever { + + public Map<String, String> getClassData(Client client); +} diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java new file mode 100644 index 000000000000..8d797ee00128 --- /dev/null +++ b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java @@ -0,0 +1,70 @@ +/* + * 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.preload.classdataretrieval.hprof; + +import com.android.ddmlib.Client; +import com.android.ddmlib.ClientData.IHprofDumpHandler; + +import java.util.ArrayList; +import java.util.List; + +public class GeneralHprofDumpHandler implements IHprofDumpHandler { + + private List<IHprofDumpHandler> handlers = new ArrayList<>(); + + public void addHandler(IHprofDumpHandler h) { + synchronized (handlers) { + handlers.add(h); + } + } + + public void removeHandler(IHprofDumpHandler h) { + synchronized (handlers) { + handlers.remove(h); + } + } + + private List<IHprofDumpHandler> getIterationList() { + synchronized (handlers) { + return new ArrayList<>(handlers); + } + } + + @Override + public void onEndFailure(Client arg0, String arg1) { + List<IHprofDumpHandler> iterList = getIterationList(); + for (IHprofDumpHandler h : iterList) { + h.onEndFailure(arg0, arg1); + } + } + + @Override + public void onSuccess(String arg0, Client arg1) { + List<IHprofDumpHandler> iterList = getIterationList(); + for (IHprofDumpHandler h : iterList) { + h.onSuccess(arg0, arg1); + } + } + + @Override + public void onSuccess(byte[] arg0, Client arg1) { + List<IHprofDumpHandler> iterList = getIterationList(); + for (IHprofDumpHandler h : iterList) { + h.onSuccess(arg0, arg1); + } + } + }
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java new file mode 100644 index 000000000000..21b7a04e07dc --- /dev/null +++ b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java @@ -0,0 +1,220 @@ +/* + * 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.preload.classdataretrieval.hprof; + +import com.android.ddmlib.Client; +import com.android.ddmlib.ClientData; +import com.android.ddmlib.ClientData.IHprofDumpHandler; +import com.android.preload.classdataretrieval.ClassDataRetriever; +import com.android.preload.ui.NullProgressMonitor; +import com.android.tools.perflib.captures.MemoryMappedFileBuffer; +import com.android.tools.perflib.heap.ClassObj; +import com.android.tools.perflib.heap.Queries; +import com.android.tools.perflib.heap.Snapshot; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class Hprof implements ClassDataRetriever { + + private static GeneralHprofDumpHandler hprofHandler; + + public static void init() { + synchronized(Hprof.class) { + if (hprofHandler == null) { + ClientData.setHprofDumpHandler(hprofHandler = new GeneralHprofDumpHandler()); + } + } + } + + public static File doHprof(Client client, int timeout) { + GetHprof gh = new GetHprof(client, timeout); + return gh.get(); + } + + /** + * Return a map of class names to class-loader names derived from the hprof dump. + * + * @param hprofLocalFile + */ + public static Map<String, String> analyzeHprof(File hprofLocalFile) throws Exception { + Snapshot snapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(hprofLocalFile)); + + Map<String, Set<ClassObj>> classes = Queries.classes(snapshot, null); + Map<String, String> retValue = new HashMap<String, String>(); + for (Map.Entry<String, Set<ClassObj>> e : classes.entrySet()) { + for (ClassObj c : e.getValue()) { + String cl = c.getClassLoader() == null ? null : c.getClassLoader().toString(); + String cName = c.getClassName(); + int aDepth = 0; + while (cName.endsWith("[]")) { + cName = cName.substring(0, cName.length()-2); + aDepth++; + } + String newName = transformPrimitiveClass(cName); + if (aDepth > 0) { + // Need to use kind-a descriptor syntax. If it was transformed, it is primitive. + if (newName.equals(cName)) { + newName = "L" + newName + ";"; + } + for (int i = 0; i < aDepth; i++) { + newName = "[" + newName; + } + } + retValue.put(newName, cl); + } + } + + // Free up memory. + snapshot.dispose(); + + return retValue; + } + + private static Map<String, String> primitiveMapping; + + static { + primitiveMapping = new HashMap<>(); + primitiveMapping.put("boolean", "Z"); + primitiveMapping.put("byte", "B"); + primitiveMapping.put("char", "C"); + primitiveMapping.put("double", "D"); + primitiveMapping.put("float", "F"); + primitiveMapping.put("int", "I"); + primitiveMapping.put("long", "J"); + primitiveMapping.put("short", "S"); + primitiveMapping.put("void", "V"); + } + + private static String transformPrimitiveClass(String name) { + String rep = primitiveMapping.get(name); + if (rep != null) { + return rep; + } + return name; + } + + private static class GetHprof implements IHprofDumpHandler { + + private File target; + private long timeout; + private Client client; + + public GetHprof(Client client, long timeout) { + this.client = client; + this.timeout = timeout; + } + + public File get() { + synchronized (this) { + hprofHandler.addHandler(this); + client.dumpHprof(); + if (target == null) { + try { + wait(timeout); + } catch (Exception e) { + System.out.println(e); + } + } + } + + hprofHandler.removeHandler(this); + return target; + } + + private void wakeUp() { + synchronized (this) { + notifyAll(); + } + } + + @Override + public void onEndFailure(Client arg0, String arg1) { + System.out.println("GetHprof.onEndFailure"); + if (client == arg0) { + wakeUp(); + } + } + + private static File createTargetFile() { + try { + return File.createTempFile("ddms", ".hprof"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void onSuccess(String arg0, Client arg1) { + System.out.println("GetHprof.onSuccess"); + if (client == arg1) { + try { + target = createTargetFile(); + arg1.getDevice().getSyncService().pullFile(arg0, + target.getAbsoluteFile().toString(), new NullProgressMonitor()); + } catch (Exception e) { + e.printStackTrace(); + target = null; + } + wakeUp(); + } + } + + @Override + public void onSuccess(byte[] arg0, Client arg1) { + System.out.println("GetHprof.onSuccess"); + if (client == arg1) { + try { + target = createTargetFile(); + BufferedOutputStream out = + new BufferedOutputStream(new FileOutputStream(target)); + out.write(arg0); + out.close(); + } catch (Exception e) { + e.printStackTrace(); + target = null; + } + wakeUp(); + } + } + } + + private int timeout; + + public Hprof(int timeout) { + this.timeout = timeout; + } + + @Override + public Map<String, String> getClassData(Client client) { + File hprofLocalFile = Hprof.doHprof(client, timeout); + if (hprofLocalFile == null) { + throw new RuntimeException("Failed getting dump..."); + } + System.out.println("Dump file is " + hprofLocalFile); + + try { + return analyzeHprof(hprofLocalFile); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java new file mode 100644 index 000000000000..dbd4c89b27cf --- /dev/null +++ b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java @@ -0,0 +1,221 @@ +/* + * 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.preload.classdataretrieval.jdwp; + +import com.android.ddmlib.Client; +import com.android.preload.classdataretrieval.ClassDataRetriever; + +import org.apache.harmony.jpda.tests.framework.jdwp.CommandPacket; +import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands; +import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants; +import org.apache.harmony.jpda.tests.framework.jdwp.ReplyPacket; +import org.apache.harmony.jpda.tests.jdwp.share.JDWPTestCase; +import org.apache.harmony.jpda.tests.jdwp.share.JDWPUnitDebuggeeWrapper; +import org.apache.harmony.jpda.tests.share.JPDALogWriter; +import org.apache.harmony.jpda.tests.share.JPDATestOptions; + +import java.util.HashMap; +import java.util.Map; + +public class JDWPClassDataRetriever extends JDWPTestCase implements ClassDataRetriever { + + private final Client client; + + public JDWPClassDataRetriever() { + this(null); + } + + public JDWPClassDataRetriever(Client client) { + this.client = client; + } + + + @Override + protected String getDebuggeeClassName() { + return "<unset>"; + } + + @Override + public Map<String, String> getClassData(Client client) { + return new JDWPClassDataRetriever(client).retrieve(); + } + + private Map<String, String> retrieve() { + if (client == null) { + throw new IllegalStateException(); + } + + settings = createTestOptions("localhost:" + String.valueOf(client.getDebuggerListenPort())); + settings.setDebuggeeSuspend("n"); + + logWriter = new JPDALogWriter(System.out, "", false); + + try { + internalSetUp(); + + return retrieveImpl(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } finally { + internalTearDown(); + } + } + + private Map<String, String> retrieveImpl() { + try { + // Suspend the app. + { + CommandPacket packet = new CommandPacket( + JDWPCommands.VirtualMachineCommandSet.CommandSetID, + JDWPCommands.VirtualMachineCommandSet.SuspendCommand); + ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet); + if (reply.getErrorCode() != JDWPConstants.Error.NONE) { + return null; + } + } + + // List all classes. + CommandPacket packet = new CommandPacket( + JDWPCommands.VirtualMachineCommandSet.CommandSetID, + JDWPCommands.VirtualMachineCommandSet.AllClassesCommand); + ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet); + + if (reply.getErrorCode() != JDWPConstants.Error.NONE) { + return null; + } + + int classCount = reply.getNextValueAsInt(); + System.out.println("Runtime reported " + classCount + " classes."); + + Map<Long, String> classes = new HashMap<Long, String>(); + Map<Long, String> arrayClasses = new HashMap<Long, String>(); + + for (int i = 0; i < classCount; i++) { + byte refTypeTag = reply.getNextValueAsByte(); + long typeID = reply.getNextValueAsReferenceTypeID(); + String signature = reply.getNextValueAsString(); + /* int status = */ reply.getNextValueAsInt(); + + switch (refTypeTag) { + case JDWPConstants.TypeTag.CLASS: + case JDWPConstants.TypeTag.INTERFACE: + classes.put(typeID, signature); + break; + + case JDWPConstants.TypeTag.ARRAY: + arrayClasses.put(typeID, signature); + break; + } + } + + Map<String, String> result = new HashMap<String, String>(); + + // Parse all classes. + for (Map.Entry<Long, String> entry : classes.entrySet()) { + long typeID = entry.getKey(); + String signature = entry.getValue(); + + if (!checkClass(typeID, signature, result)) { + System.err.println("Issue investigating " + signature); + } + } + + // For arrays, look at the leaf component type. + for (Map.Entry<Long, String> entry : arrayClasses.entrySet()) { + long typeID = entry.getKey(); + String signature = entry.getValue(); + + if (!checkArrayClass(typeID, signature, result)) { + System.err.println("Issue investigating " + signature); + } + } + + return result; + } finally { + // Resume the app. + { + CommandPacket packet = new CommandPacket( + JDWPCommands.VirtualMachineCommandSet.CommandSetID, + JDWPCommands.VirtualMachineCommandSet.ResumeCommand); + /* ReplyPacket reply = */ debuggeeWrapper.vmMirror.performCommand(packet); + } + } + } + + private boolean checkClass(long typeID, String signature, Map<String, String> result) { + CommandPacket packet = new CommandPacket( + JDWPCommands.ReferenceTypeCommandSet.CommandSetID, + JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand); + packet.setNextValueAsReferenceTypeID(typeID); + ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet); + if (reply.getErrorCode() != JDWPConstants.Error.NONE) { + return false; + } + + long classLoaderID = reply.getNextValueAsObjectID(); + + // TODO: Investigate the classloader to have a better string? + String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID); + + result.put(getClassName(signature), classLoaderString); + + return true; + } + + private boolean checkArrayClass(long typeID, String signature, Map<String, String> result) { + // Classloaders of array classes are the same as the component class'. + CommandPacket packet = new CommandPacket( + JDWPCommands.ReferenceTypeCommandSet.CommandSetID, + JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand); + packet.setNextValueAsReferenceTypeID(typeID); + ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet); + if (reply.getErrorCode() != JDWPConstants.Error.NONE) { + return false; + } + + long classLoaderID = reply.getNextValueAsObjectID(); + + // TODO: Investigate the classloader to have a better string? + String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID); + + // For array classes, we *need* the signature directly. + result.put(signature, classLoaderString); + + return true; + } + + private static String getClassName(String signature) { + String withoutLAndSemicolon = signature.substring(1, signature.length() - 1); + return withoutLAndSemicolon.replace('/', '.'); + } + + + private static JPDATestOptions createTestOptions(String address) { + JPDATestOptions options = new JPDATestOptions(); + options.setAttachConnectorKind(); + options.setTimeout(1000); + options.setWaitingTime(1000); + options.setTransportAddress(address); + return options; + } + + @Override + protected JDWPUnitDebuggeeWrapper createDebuggeeWrapper() { + return new PreloadDebugeeWrapper(settings, logWriter); + } +} diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java new file mode 100644 index 000000000000..b9df6d0aeb93 --- /dev/null +++ b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java @@ -0,0 +1,40 @@ +/* + * 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.preload.classdataretrieval.jdwp; + +import org.apache.harmony.jpda.tests.framework.LogWriter; +import org.apache.harmony.jpda.tests.jdwp.share.JDWPManualDebuggeeWrapper; +import org.apache.harmony.jpda.tests.share.JPDATestOptions; + +import java.io.IOException; + +public class PreloadDebugeeWrapper extends JDWPManualDebuggeeWrapper { + + public PreloadDebugeeWrapper(JPDATestOptions options, LogWriter writer) { + super(options, writer); + } + + @Override + protected Process launchProcess(String cmdLine) throws IOException { + return null; + } + + @Override + protected void WaitForProcessExit(Process process) { + } + +} diff --git a/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java b/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java new file mode 100644 index 000000000000..f45aad06ac6b --- /dev/null +++ b/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java @@ -0,0 +1,39 @@ +/* + * 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.preload.ui; + +import com.android.ddmlib.SyncService.ISyncProgressMonitor; + +public class NullProgressMonitor implements ISyncProgressMonitor { + + @Override + public void advance(int arg0) {} + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public void start(int arg0) {} + + @Override + public void startSubTask(String arg0) {} + + @Override + public void stop() {} +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/ui/UI.java b/tools/preload2/src/com/android/preload/ui/UI.java new file mode 100644 index 000000000000..47174ddd0e07 --- /dev/null +++ b/tools/preload2/src/com/android/preload/ui/UI.java @@ -0,0 +1,267 @@ +/* + * 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.preload.ui; + +import com.android.ddmlib.Client; +import com.android.ddmlib.ClientData; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.io.File; +import java.util.List; + +import javax.swing.Action; +import javax.swing.DefaultListCellRenderer; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JProgressBar; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JToolBar; +import javax.swing.ListModel; +import javax.swing.SwingUtilities; +import javax.swing.table.TableModel; + +public class UI extends JFrame { + + private JList<Client> clientList; + private JTable dataTable; + + // Shared file chooser, means the directory is retained. + private JFileChooser jfc; + + public UI(ListModel<Client> clientListModel, + TableModel dataTableModel, + List<Action> actions) { + super("Preloaded-classes computation"); + + getContentPane().add(new JScrollPane(clientList = new JList<Client>(clientListModel)), + BorderLayout.WEST); + clientList.setCellRenderer(new ClientListCellRenderer()); + // clientList.addListSelectionListener(listener); + + dataTable = new JTable(dataTableModel); + getContentPane().add(new JScrollPane(dataTable), BorderLayout.CENTER); + + JToolBar toolbar = new JToolBar(JToolBar.HORIZONTAL); + for (Action a : actions) { + if (a == null) { + toolbar.addSeparator(); + } else { + toolbar.add(a); + } + } + getContentPane().add(toolbar, BorderLayout.PAGE_START); + + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setBounds(100, 100, 800, 600); + } + + public Client getSelectedClient() { + return clientList.getSelectedValue(); + } + + public int getSelectedDataTableRow() { + return dataTable.getSelectedRow(); + } + + private JDialog currentWaitDialog = null; + + public void showWaitDialog() { + if (currentWaitDialog == null) { + currentWaitDialog = new JDialog(this, "Please wait...", true); + currentWaitDialog.getContentPane().add(new JLabel("Please be patient."), + BorderLayout.CENTER); + JProgressBar progress = new JProgressBar(JProgressBar.HORIZONTAL); + progress.setIndeterminate(true); + currentWaitDialog.getContentPane().add(progress, BorderLayout.SOUTH); + currentWaitDialog.setSize(200, 100); + currentWaitDialog.setLocationRelativeTo(null); + showWaitDialogLater(); + } + } + + private void showWaitDialogLater() { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(true); // This is blocking. + } + } + }); + } + + public void updateWaitDialog(String s) { + if (currentWaitDialog != null) { + ((JLabel) currentWaitDialog.getContentPane().getComponent(0)).setText(s); + Dimension prefSize = currentWaitDialog.getPreferredSize(); + Dimension curSize = currentWaitDialog.getSize(); + if (prefSize.width > curSize.width || prefSize.height > curSize.height) { + currentWaitDialog.setSize(Math.max(prefSize.width, curSize.width), + Math.max(prefSize.height, curSize.height)); + currentWaitDialog.invalidate(); + } + } + } + + public void hideWaitDialog() { + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(false); + currentWaitDialog = null; + } + } + + public void showMessageDialog(String s) { + // Hide the wait dialog... + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(false); + } + + try { + JOptionPane.showMessageDialog(this, s); + } finally { + // And reshow it afterwards... + if (currentWaitDialog != null) { + showWaitDialogLater(); + } + } + } + + public boolean showConfirmDialog(String title, String message) { + // Hide the wait dialog... + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(false); + } + + try { + return JOptionPane.showConfirmDialog(this, title, message, JOptionPane.YES_NO_OPTION) + == JOptionPane.YES_OPTION; + } finally { + // And reshow it afterwards... + if (currentWaitDialog != null) { + showWaitDialogLater(); + } + } + } + + public String showInputDialog(String message) { + // Hide the wait dialog... + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(false); + } + + try { + return JOptionPane.showInputDialog(message); + } finally { + // And reshow it afterwards... + if (currentWaitDialog != null) { + showWaitDialogLater(); + } + } + } + + @SuppressWarnings("unchecked") + public <T> T showChoiceDialog(String title, String message, T[] choices) { + // Hide the wait dialog... + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(false); + } + + try{ + return (T)JOptionPane.showInputDialog(this, + title, + message, + JOptionPane.QUESTION_MESSAGE, + null, + choices, + choices[0]); + } finally { + // And reshow it afterwards... + if (currentWaitDialog != null) { + showWaitDialogLater(); + } + } + } + + public File showSaveDialog() { + // Hide the wait dialog... + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(false); + } + + try{ + if (jfc == null) { + jfc = new JFileChooser(); + } + + int ret = jfc.showSaveDialog(this); + if (ret == JFileChooser.APPROVE_OPTION) { + return jfc.getSelectedFile(); + } else { + return null; + } + } finally { + // And reshow it afterwards... + if (currentWaitDialog != null) { + showWaitDialogLater(); + } + } + } + + public File[] showOpenDialog(boolean multi) { + // Hide the wait dialog... + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(false); + } + + try{ + if (jfc == null) { + jfc = new JFileChooser(); + } + + jfc.setMultiSelectionEnabled(multi); + int ret = jfc.showOpenDialog(this); + if (ret == JFileChooser.APPROVE_OPTION) { + return jfc.getSelectedFiles(); + } else { + return null; + } + } finally { + // And reshow it afterwards... + if (currentWaitDialog != null) { + showWaitDialogLater(); + } + } + } + + private class ClientListCellRenderer extends DefaultListCellRenderer { + + @Override + public Component getListCellRendererComponent(JList<?> list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + ClientData cd = ((Client) value).getClientData(); + String s = cd.getClientDescription() + " (pid " + cd.getPid() + ")"; + return super.getListCellRendererComponent(list, s, index, isSelected, cellHasFocus); + } + } +} diff --git a/tools/split-select/Android.mk b/tools/split-select/Android.mk index d9ddf08862b3..863abae1e0fb 100644 --- a/tools/split-select/Android.mk +++ b/tools/split-select/Android.mk @@ -43,13 +43,12 @@ cIncludes := \ external/zlib \ frameworks/base/tools -hostLdLibs := hostStaticLibs := \ libaapt \ libandroidfw \ libpng \ - liblog \ libutils \ + liblog \ libcutils \ libexpat \ libziparchive-host \ @@ -57,17 +56,13 @@ hostStaticLibs := \ cFlags := -Wall -Werror -ifeq ($(HOST_OS),linux) - hostLdLibs += -lrt -ldl -lpthread -endif +hostLdLibs_linux := -lrt -ldl -lpthread # Statically link libz for MinGW (Win SDK under Linux), # and dynamically link for all others. -ifneq ($(strip $(USE_MINGW)),) - hostStaticLibs += libz -else - hostLdLibs += -lz -endif +hostStaticLibs_windows := libz +hostLdLibs_darwin := -lz +hostLdLibs_linux += -lz # ========================================================== @@ -75,11 +70,12 @@ endif # ========================================================== include $(CLEAR_VARS) LOCAL_MODULE := libsplit-select +LOCAL_MODULE_HOST_OS := darwin linux windows LOCAL_SRC_FILES := $(sources) -LOCAL_C_INCLUDES += $(cIncludes) -LOCAL_CFLAGS += $(cFlags) -D_DARWIN_UNLIMITED_STREAMS +LOCAL_C_INCLUDES := $(cIncludes) +LOCAL_CFLAGS := $(cFlags) -D_DARWIN_UNLIMITED_STREAMS include $(BUILD_HOST_STATIC_LIBRARY) @@ -93,10 +89,12 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(testSources) -LOCAL_C_INCLUDES += $(cIncludes) -LOCAL_STATIC_LIBRARIES += libsplit-select $(hostStaticLibs) -LOCAL_LDLIBS += $(hostLdLibs) -LOCAL_CFLAGS += $(cFlags) +LOCAL_C_INCLUDES := $(cIncludes) +LOCAL_STATIC_LIBRARIES := libsplit-select $(hostStaticLibs) +LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows) +LOCAL_LDLIBS_darwin := $(hostLdLibs_darwin) +LOCAL_LDLIBS_linux := $(hostLdLibs_linux) +LOCAL_CFLAGS := $(cFlags) include $(BUILD_HOST_NATIVE_TEST) @@ -105,13 +103,16 @@ include $(BUILD_HOST_NATIVE_TEST) # ========================================================== include $(CLEAR_VARS) LOCAL_MODULE := split-select +LOCAL_MODULE_HOST_OS := darwin linux windows LOCAL_SRC_FILES := $(main) -LOCAL_C_INCLUDES += $(cIncludes) -LOCAL_STATIC_LIBRARIES += libsplit-select $(hostStaticLibs) -LOCAL_LDLIBS += $(hostLdLibs) -LOCAL_CFLAGS += $(cFlags) +LOCAL_C_INCLUDES := $(cIncludes) +LOCAL_STATIC_LIBRARIES := libsplit-select $(hostStaticLibs) +LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows) +LOCAL_LDLIBS_darwin := $(hostLdLibs_darwin) +LOCAL_LDLIBS_linux := $(hostLdLibs_linux) +LOCAL_CFLAGS := $(cFlags) include $(BUILD_HOST_EXECUTABLE) |